Skip to content

Commit

Permalink
Merge pull request #19 from jpculp/userdata-keys
Browse files Browse the repository at this point in the history
Change admin container logic to use public keys from user-data
  • Loading branch information
jpculp authored Feb 24, 2021
2 parents eec7430 + 14064c0 commit d90644f
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 50 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.6.0

* Use user-data file rather than IMDS directly to set public keys. ([#19])

[#19]: https://github.com/bottlerocket-os/bottlerocket-admin-container/pull/19

# 0.5.0

* Use /proc to find the bash binary in sheltie. ([#8])
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ RUN test -n "$IMAGE_VERSION"
LABEL "org.opencontainers.image.version"="$IMAGE_VERSION"

RUN yum update -y \
&& yum install -y openssh-server sudo util-linux procps-ng \
&& yum install -y openssh-server sudo util-linux procps-ng jq \
&& yum clean all

COPY --from=builder /opt/bash /opt/bin/
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,33 @@ For more information about how the admin container fits into the Bottlerocket op

You'll need Docker 17.06.2 or later, for multi-stage build support.
Then run `make`!

## Authenticating with the Admin Container

Starting from v0.6.0, users have the option to pass in their own ssh keys rather than the admin container relying on the AWS instance metadata service (IMDS).

Users can add their own keys by populating the admin container's user-data with a base64-encoded JSON block.
If user-data is populated then Bottlerocket will not fetch from IMDS at all, but if user-data is not set then Bottlerocket will continue to use the keys from IMDS.

To use custom public keys for `.ssh/authorized_keys` and/or custom CA keys for `/etc/ssh/trusted_user_ca_keys.pub` you will want to generate a JSON-structure like this:

```
{
"ssh":{
"authorized_keys":[
"ssh-rsa EXAMPLEAUTHORIZEDPUBLICKEYHERE my-key-pair"
],
"trusted_user_ca_keys":[
"ssh-rsa EXAMPLETRUSTEDCAPUBLICKEYHERE [email protected]"
]
}
}
```

Once you've created your JSON, you'll need to base64-encode it and set it as the value of the admin host container's user-data setting in your [instance user data toml](https://github.com/bottlerocket-os/bottlerocket#using-user-data).

```
[settings.host-containers.admin]
# ex: echo '{"ssh":{"authorized_keys":[]}}' | base64
user-data = "eyJzc2giOnsiYXV0aG9yaXplZF9rZXlzIjpbXX19"
```
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.5.2
v0.6.0
3 changes: 3 additions & 0 deletions sshd_config
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE

# SFTP is enabled by default in Amazon Linux 2; keeping that behavior here.
Subsystem sftp /usr/libexec/openssh/sftp-server

# Configured by user data
TrustedUserCAKeys /etc/ssh/trusted_user_ca_keys.pub
122 changes: 74 additions & 48 deletions start_admin_sshd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,64 +4,90 @@
# SPDX-License-Identifier: Apache-2.0 OR MIT
set -e

mkdir -p /home/ec2-user/.ssh/
chmod 700 /home/ec2-user/.ssh/
ssh_host_key_dir="/.bottlerocket/host-containers/admin/etc/ssh"
ssh_config_dir="/home/ec2-user/.ssh"
log() {
echo "$*" >&2
}

# Populate authorized_keys with all the public keys found in instance meta-data
# The URLs for keys include an index and the keypair name, e.g.
# http://169.254.169.254/latest/meta-data/public-keys/0=mykeypair/openssh-key
ssh_authorized_keys="${ssh_config_dir}/authorized_keys"
touch ${ssh_authorized_keys}
chmod 600 ${ssh_authorized_keys}
public_key_base_url="http://169.254.169.254/latest/meta-data/public-keys/"
imds_session_token=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60")
imds_request_add_header="X-aws-ec2-metadata-token: ${imds_session_token}"
public_key_indexes=($(curl -H "${imds_request_add_header}" -sf "${public_key_base_url}" \
| cut -d= -f1 \
| xargs))
declare -r local_user="ec2-user"
declare -r ssh_host_key_dir="/.bottlerocket/host-containers/admin/etc/ssh"
declare -r user_data="/.bottlerocket/host-containers/admin/user-data"
declare -r user_ssh_dir="/home/${local_user}/.ssh"
available_auth_methods=0

for public_key_index in "${public_key_indexes[@]}"; do
public_key_data="$(curl -H "${imds_request_add_header}" -sf "${public_key_base_url}/${public_key_index}/openssh-key")"
if [[ ! "${public_key_data}" =~ ^"ssh" ]]; then
echo "Key ${public_key_data} with index ${public_key_index} looks invalid" >&2
continue
fi
echo "${public_key_data}" >> "${ssh_authorized_keys}"
if ! grep -q "${public_key_data}" "${ssh_authorized_keys}"; then
echo "Failed to write key with index ${public_key_index} to authorized_keys" >&2
continue
fi
done
mkdir -p "${user_ssh_dir}"
chmod 700 "${user_ssh_dir}"

get_user_data_keys() {
# Extract the keys from user-data json
local raw_keys
local key_type="${1:?}"
if ! raw_keys=$(jq --arg key_type "${key_type}" -e -r '.["ssh"][$key_type][]' "${user_data}" 2>/dev/null); then
log "Failed to parse ${key_type} from ${user_data}"
return 1
fi

# Map the keys to avoid improper splitting
local mapped_keys
mapfile -t mapped_keys <<< "${raw_keys}"

# Verify the keys are valid
local key
local -a valid_keys
for key in "${mapped_keys[@]}"; do
if ! echo "${key}" | ssh-keygen -lf - &>/dev/null; then
log "Failed to validate ${key}"
continue
fi
valid_keys+=( "${key}" )
done

( IFS=$'\n'; echo "${valid_keys[*]}" )
}

# If we didn't write any keys at all, there's not much point in continuing
if [ ! -s "${ssh_authorized_keys}" ]; then
echo "Failed to write any valid public keys to authorized_keys" >&2
exit 1
# Populate authorized_keys with all the authorized keys found in user-data
if authorized_keys=$(get_user_data_keys "authorized_keys"); then
ssh_authorized_keys="${user_ssh_dir}/authorized_keys"
touch "${ssh_authorized_keys}"
chmod 600 "${ssh_authorized_keys}"
echo "${authorized_keys}" > "${ssh_authorized_keys}"
((++available_auth_methods))
fi

chown ec2-user -R "${ssh_config_dir}"
# Populate trusted_user_ca_keys with all the authorized keys found in user-data
if trusted_user_ca_keys=$(get_user_data_keys "trusted_user_ca_keys"); then
ssh_trusted_user_ca_keys="/etc/ssh/trusted_user_ca_keys.pub"
touch "${ssh_trusted_user_ca_keys}"
chmod 600 "${ssh_trusted_user_ca_keys}"
echo "${trusted_user_ca_keys}" > "${ssh_trusted_user_ca_keys}"
((++available_auth_methods))
fi

chown "${local_user}" -R "${user_ssh_dir}"

# If there were no successful auth methods, then users cannot authenticate
if [[ "${available_auth_methods}" -eq 0 ]]; then
log "Failed to configure ssh authentication"
fi

# Generate the server keys
mkdir -p "${ssh_host_key_dir}"
for key in rsa ecdsa ed25519; do
# If both of the keys exist, don't overwrite them
if [ -s "${ssh_host_key_dir}/ssh_host_${key}_key" ] && [ -s "${ssh_host_key_dir}/ssh_host_${key}_key.pub" ]; then
echo "${key} key already exists, will use existing key." >&2
continue
fi
# If both of the keys exist, don't overwrite them
if [ -s "${ssh_host_key_dir}/ssh_host_${key}_key" ] &&
[ -s "${ssh_host_key_dir}/ssh_host_${key}_key.pub" ]; then
log "${key} key already exists, will use existing key."
continue
fi

rm -rf \
"${ssh_host_key_dir}/ssh_host_${key}_key" \
"${ssh_host_key_dir}/ssh_host_${key}_key.pub"
if ssh-keygen -t "${key}" -f "${ssh_host_key_dir}/ssh_host_${key}_key" -q -N ""; then
chmod 600 "${ssh_host_key_dir}/ssh_host_${key}_key"
chmod 644 "${ssh_host_key_dir}/ssh_host_${key}_key.pub"
else
echo "Failure to generate host ${key} ssh keys" >&2
exit 1
fi
rm -rf \
"${ssh_host_key_dir}/ssh_host_${key}_key" \
"${ssh_host_key_dir}/ssh_host_${key}_key.pub"
if ssh-keygen -t "${key}" -f "${ssh_host_key_dir}/ssh_host_${key}_key" -q -N ""; then
chmod 600 "${ssh_host_key_dir}/ssh_host_${key}_key"
chmod 644 "${ssh_host_key_dir}/ssh_host_${key}_key.pub"
else
log "Failure to generate host ${key} ssh keys"
fi
done

# Start a single sshd process in the foreground
Expand Down

0 comments on commit d90644f

Please sign in to comment.