Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change admin container logic to use public keys from user-data #19

Merged
merged 4 commits into from
Feb 24, 2021

Conversation

jpculp
Copy link
Member

@jpculp jpculp commented Feb 18, 2021

Issue number:

N/A

Description of changes:

Adds jq to the container image.

Replaces IMDS logic with fetching public keys from user-data.

This change allows the admin container to be platform agnostic as it
retrieves its .ssh/authorized_keys from a base64-encoded
JSON-structured block in user-data rather than relying on the AWS
instance metadata service (IMDS) directly.

Example JSON: {"ssh":{"authorized_keys":[]}}

Also adds support for TrustedUserCAKeys.

This allows users who want to set TrustedUserCAKeys to do so by adding
them to the base64-encoded JSON-structured user-data block.

Example JSON: {"ssh":{"trusted_user_ca_keys":[]}}

Bumps version to v0.6.0.

Testing done:
Launched the new admin container on a bottlerocket instance containing shibaken and verified that SSH access was still available.

After that, I went through several test cases of disabling the admin container, posting a new base64-encoded block to host-containers.admin.user-data, re-enabling the admin container, and checking the connection.

  • ✅ Empty JSON Structure (expected fail)
  • ✅ Custom authorized key
  • ✅ Several keys, some good, some bad (only the strings that could be parsed by ssh-keygen were present in authorized_keys)
  • ✅ Authorized key AND trusted user CA key (.ssh/authorized_keys, /etc/ssh/trusted_user_ca_keys.pub, and /etc/ssh/sshd_config looked as expected)
  • ✅ Removed authorized key so that only trusted user CA key was available

Terms of contribution:

By submitting this pull request, I agree that this contribution is dual-licensed under the terms of both the Apache License, version 2.0, and the MIT license.

start_admin_sshd.sh Outdated Show resolved Hide resolved
@jpculp jpculp force-pushed the userdata-keys branch 3 times, most recently from aadfd0a to 50bc482 Compare February 19, 2021 20:39
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Show resolved Hide resolved
README.md Show resolved Hide resolved
@jahkeup
Copy link
Member

jahkeup commented Feb 19, 2021

Related to changes in PR bottlerocket-os/bottlerocket#1331

Copy link
Member

@jahkeup jahkeup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small scripty changes - thanks for rolling this up with bottlerocket-os/bottlerocket#1331 👍🏽

README.md Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
start_admin_sshd.sh Outdated Show resolved Hide resolved
start_admin_sshd.sh Outdated Show resolved Hide resolved
start_admin_sshd.sh Outdated Show resolved Hide resolved
if ! grep -q "^TrustedUserCAKeys: ${ssh_trusted_user_ca_keys}$" ${sshd_config}; then
echo "Failed to write TrustedUserCAKeys to sshd_config" >&2
else
((++available_auth_methods))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically ++n is used in very specific use cases and tickles my spideysenses - can we switch to n++?

Suggested change
((++available_auth_methods))
((available_auth_methods++))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave this a shot, but the container seemed to stop immediately after the first instance:

+ [[ ! -s /home/ec2-user/.ssh/authorized_keys ]]
+ (( available_auth_methods++ ))

I think this is due to the set -e at the top of the script.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah this would have to written as : (( available_auth_methods++ )) or let available_auth_methods++.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems those also are causing an early exit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems those also are causing an early exit.

TIL:

from bash -c 'help let'

Exit Status:
If the last ARG evaluates to 0, let returns 1; let returns 0 otherwise.

The : ((available_auth_methods++)) expression should be okay no matter what (because of : noop).

( x=0; set -x; : x++; echo $?; x=0; let x++; echo $?; x=0; let ++x; echo $?)
# + : x++
# + echo 0
# 0
# + x=0
# + let x++
# + echo 1
# 1
# + x=0
# + let ++x
# + echo 0
# 0

start_admin_sshd.sh Outdated Show resolved Hide resolved
start_admin_sshd.sh Outdated Show resolved Hide resolved
start_admin_sshd.sh Outdated Show resolved Hide resolved
Copy link
Contributor

@zmrow zmrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good - can you update the testing section in the PR description with the testing you've done?

🏐

start_admin_sshd.sh Outdated Show resolved Hide resolved
start_admin_sshd.sh Outdated Show resolved Hide resolved
start_admin_sshd.sh Outdated Show resolved Hide resolved
start_admin_sshd.sh Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
@jpculp jpculp force-pushed the userdata-keys branch 2 times, most recently from 687ac66 to 29dca43 Compare February 23, 2021 04:29
start_admin_sshd.sh Outdated Show resolved Hide resolved
echo "${trusted_user_ca_keys}" > "${ssh_trusted_user_ca_keys}"

cat <<EOF >> "${sshd_config}"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This extra newline seems unneeded - the comment adds context if the intent is to break up the appended config from the prior. This isn't a sticking point though, if you want to keep this: feel free to leave it.

# Extract the keys from user-data json
local raw_keys
if ! raw_keys=$(jq -e -r '.["ssh"]["authorized_keys"][]' "${user_data}" 2>/dev/null); then
echo "Failed to parse authorized_keys from ${user_data}" >&2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The echo msg >&2 is repeated throughout, instead of relying on the convention of redirection a function would make the intent clear and safer overall. It would reduce the chances that extra output is sent to subshell substitutes that are expecting data in stdout.

log() {
  echo "$*" >&2
}
Suggested change
echo "Failed to parse authorized_keys from ${user_data}" >&2
log "Failed to parse authorized_keys from ${user_data}"
Or, alternatively (using my go to log definition)
# log with date prefix. 
# 
# Suggested use: `log $LEVEL: $msg`
log() {
  echo "[$(date --iso-8601=s --utc)] $*" >&2
}

# with use as:

log "INFO: installed ssh authorized keys"
log "WARN: unable to parse trusted user CA certificates"
log "ERROR: unable to setup ssh with provided user data"

echo "Failed to write key with index ${public_key_index} to authorized_keys" >&2
continue
declare -r ssh_host_key_dir="/.bottlerocket/host-containers/admin/etc/ssh"
declare -r ssh_config_dir="/home/ec2-user/.ssh"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put this above the mkdir and use it there to make sure we're being consistent throughout with directory creation and their early setup.

I'd also like to call this something else.. this isn't /etc/ssh/ssh_config (which would have been my guess once I lost this off my mental stack). May I suggest: user_ssh_dir. To me, this is far clearer in contexts like "$user_ssh_dir/authorized_keys" (vs. "$ssh_config_dir/authorized_keys", which could have been a system level ssh config directory).

Comment on lines 87 to 89
if [[ -f "${ssh_authorized_keys}" ]]; then
echo "AuthorizedKeysFile ${ssh_authorized_keys}" >> "${sshd_config}"
fi
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be configuring an absolute path to the authorized keys. I haven't tried this myself, but I'd suspect that an absolute path would authorize all users with the given file, meaning any user could login as any user. Even so, sshd_config(5) has this to say:

AuthorizedKeysFile

  Specifies the file that contains the public keys used for user authentication.
  The format is described in the AUTHORIZED_KEYS FILE FORMAT section of sshd(8).
  Arguments to AuthorizedKeysFile accept the tokens described in the TOKENS
  section. After expansion, AuthorizedKeysFile is taken to be an absolute path
  or one relative to the user's home direc‐ tory. Multiple files may be listed,
  separated by whitespace. Alternately this option may be set to none to skip
  checking for user keys in files. 

  The default is ".ssh/authorized_keys .ssh/authorized_keys2".

I'm of a mind that we do not try to set or modify the option. The location we're installing the keys will work without adding the explicit path.


# If there were no successful auth methods, then users cannot authenticate
if [[ "${available_auth_methods}" -eq 0 ]]; then
echo "Failed to configure ssh authentication" >&2
exit 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ${ssh_config_dir} was created by the script, so we should make sure its chown'd before exiting. If users are doing anything to customize their image (and still using this script) then the directory may be inaccessible to them.

README.md Outdated
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClKsfkNkuSevGj3eYhCe53pcjqP3maAhDFcvBS7O6Vhz2ItxCih+PnDSUaw+WNQn/mZphTk/a/gU8jEzoOWbkM4yxyb/wB96xbiFveSFJuOp/d6RJhJOI0iBXrlsLnBItntckiJ7FbtxJMXLvvwJryDUilBMTjYtwB+QhYXUMOzce5Pjz5/i8SeJtjnV3iAoG/cQk+0FzZqaeJAAHco+CY/5WrUBkrHmFJr6HcXkvJdWPkYQS3xqC0+FmUZofz221CBt5IMucxXPkX4rWi+z7wB3RbBQoQzd8v7yeb7OzlPnWOyN0qFU0XA246RA8QFYiCNYwI3f05p6KLxEXAMPLE my-key-pair"
],
"trusted_user_ca_keys":[
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClKsfkNkuSevGj3eYhCe53pcjqP3maAhDFcvBS7O6Vhz2ItxCih+PnDSUaw+WNQn/mZphTk/a/gU8jEzoOWbkM4yxyb/wB96xbiFveSFJuOp/d6RJhJOI0iBXrlsLnBItntckiJ7FbtxJMXLvvwJryDUilBMTjYtwB+QhYXUMOzce5Pjz5/i8SeJtjnV3iAoG/cQk+0FzZaeJAAHco+CY/5WrUBkrHmFJr6HcXkvJdWPkYQS3xqC0+FmUZofz221CBt5IMucxXPkX4rWi+z7wB3RbBQoQzd8v7yeb7OzlPnWOyN0qFU0XA246RA8QFYiCNYwI3f05p6KLxEXAMPLE my-key-pair"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see a distinct and (maybe) valid example here instead of duplicated ssh public key (its the same as authorized_keys. At the very least, I think having a dummy value like ssh-rsa example-ssh-ca-public-key-formatted-here authority@ssh-ca.example.com is preferred.

@jpculp
Copy link
Member Author

jpculp commented Feb 24, 2021

  • Switched echos "blah" >&2 to log()
  • Renamed ssh_config_dir to user_ssh_dir
  • Replaced hardcoded /home/ec2-user/.ssh with user_ssh_dir
  • Tweaked cat/EOF block
  • Removed echo "AuthorizedKeysFile ${ssh_authorized_keys}" >> "${sshd_config}"
  • Moved chown ec2-user -R "${user_ssh_dir}" up to run before the possible exit due to authentication configuration failure

README.md Outdated
{
"ssh":{
"authorized_keys":[
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClKsfkNkuSevGj3eYhCe53pcjqP3maAhDFcvBS7O6Vhz2ItxCih+PnDSUaw+WNQn/mZphTk/a/gU8jEzoOWbkM4yxyb/wB96xbiFveSFJuOp/d6RJhJOI0iBXrlsLnBItntckiJ7FbtxJMXLvvwJryDUilBMTjYtwB+QhYXUMOzce5Pjz5/i8SeJtjnV3iAoG/cQk+0FzZqaeJAAHco+CY/5WrUBkrHmFJr6HcXkvJdWPkYQS3xqC0+FmUZofz221CBt5IMucxXPkX4rWi+z7wB3RbBQoQzd8v7yeb7OzlPnWOyN0qFU0XA246RA8QFYiCNYwI3f05p6KLxEXAMPLE my-key-pair"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: might be good to give this the same EXAMPLESSHPUBLICKEYFORMATTEDHERE treatment since the line is very long, and we want to be clear that this isn't a valid key that will give us access to the instance of anyone who copy/pastes this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it does look a little silly now. I'll change it to be more consistent.

Fun fact, the example key is from the IMDS documentation. (for anyone who is curious)

# Populate authorized_keys with all the authorized keys found in user-data
if authorized_keys=$(parse_authorized_keys); then
ssh_authorized_keys="${user_ssh_dir}/authorized_keys"
install -D -m 0600 /dev/null "${ssh_authorized_keys}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This results in ~/.ssh getting created with the default umask, but the recommended default is 0700.

Comment on lines +27 to +44
# 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[*]}" )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this common block be factored out into its own function?

Comment on lines 53 to 56
parse_trusted_user_ca_keys() {
# Extract the keys from user-data json
local raw_keys
if ! raw_keys=$(jq -e -r '.["ssh"]["trusted_user_ca_keys"][]' "${user_data}" 2>/dev/null); then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd move this up by parse_authorized_keys so it doesn't interrupt the flow here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the other feedback, I'll create a single function to take care of both 😃

Comment on lines 88 to 89
# Configured by user data
TrustedUserCAKeys ${ssh_trusted_user_ca_keys}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be set unconditionally and have sshd not complain too much if the file doesn't exist?

fi

chown ec2-user -R "${ssh_config_dir}"
chown ec2-user -R "${user_ssh_dir}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ec2-user should be in a variable so it's easier to override and matches how user_ssh_dir is defined

Comment on lines 107 to 108
if [[ -s "${ssh_host_key_dir}/ssh_host_${key}_key" ]] &&
[[ -s "${ssh_host_key_dir}/ssh_host_${key}_key.pub" ]]; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to use [[ here; seems like an unnecessary change. I prefer to use [ unless we're taking advantage of specific features that only [[ can provide.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just log a warning and start sshd anyhow, so the container doesn't continually exit and restart - there's no chance that a simple restart will clear up a transient failure.

This change allows the admin container to be platform agnostic as it
retrieves its .ssh/authorized_keys from a base64-encoded JSON block in
user-data rather than relying on the AWS instance metadata service
(IMDS) directly.

Example JSON: {"ssh":{"authorized_keys":[]}}
This allows users who want to set TrustedUserCAKeys to do so by adding
them as a base64-encoded JSON block in user-data.

Example JSON: {"ssh":{"trusted_user_ca_keys":[]}}
This commit bumps the version from v0.5.2 to v0.6.0 and adds a changelog
for the admin container.
@jpculp
Copy link
Member Author

jpculp commented Feb 24, 2021

  • Consolidated parse_authorized_keys/parse_trusted_user_ca_keys into a single get_user_data_keys function.
  • Reduced example authorized_key to something shorter.
  • Added TrustedUserCAKeys directly to sshd_config.
  • Replaced hardcoded ec2-user with variable local_user
  • Replaced install with the original touch/chmod to keep things simple.
  • Removed extraneous brackets.

Copy link
Contributor

@zmrow zmrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎮

@jpculp jpculp merged commit d90644f into bottlerocket-os:develop Feb 24, 2021
@jpculp jpculp deleted the userdata-keys branch February 26, 2021 02:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants