Skip to content

Instantly share code, notes, and snippets.

@gjyoung1974
Last active January 22, 2020 18:29
Show Gist options
  • Save gjyoung1974/1cce5b1fc45a271691bc45204c1f7098 to your computer and use it in GitHub Desktop.
Save gjyoung1974/1cce5b1fc45a271691bc45204c1f7098 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
#
# Summary: Use this script to rotat the root ssh key on an EC2 instance
function PrintHelp() {
echo "usage: $__base.sh [options...] "
echo "options:"
echo " -s --ssh-key-file Path to EC2 private ssh key file for the key to be replaced. Required."
echo " -h --host IP address or DNS name for the EC2 instance. Required."
echo " -a --aws-key-file The file for the .csv access key file for an AWS administrator. Optional. The AWS administrator"
echo " must have the rights to create tags for EC2 instances. The script expects the .csv format "
echo " used when you dowload the key from IAM in the AWS console. If you don't specify a key file,"
echo " the default credentials in ~/.aws/credentials will be used."
echo " -u --user Root/admin user for the EC2 instance. Optional. The default value is 'core' (for the CoreOS distro)."
echo " -j --json A file to send JSON output to. Optional."
echo " --help Prints this help message"
}
function ConfigureAwsCli() {
# Configure the AWS command-line tool with the proper credentials
if [[ ! -z "$AWS_KEY_FILE" ]] ; then
echo "Using the AWS administrator key file specified."
AWS_ACCESS_KEY_ID=$(awk -F ',' 'NR==2 {print $2}' "$AWS_KEY_FILE")
AWS_SECRET_ACCESS_KEY=$(awk -F ',' 'NR==2 {print $3}' "$AWS_KEY_FILE")
# Configure temp profile
aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID"
aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"
fi
}
function VerifySSHKey() {
# Test the old key first to make sure it works
echo "Testing the current private EC2 key passed in the command line..."
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -q -i "$OLD_KEY_FILE" "$EC2_USER@$EC2_HOST" who && RETURN_CODE=$? || RETURN_CODE=$?
if [ "$RETURN_CODE" -ne 0 ] ; then
>&2 echo "Unable to connect via SSH using the EC2 key '$OLD_KEY_FILE'"
>&2 echo "Stopping. No keys were rotated."
exit 5
else
echo "EC2 key '$OLD_KEY_FILE' works."
fi
}
function VerifyAWSPermissions() {
# Verify that the credentials used for the AWS CLI have the rights to update tags on the EC2 instance.
# Get instance-id from the AWS EC2 meta-data.
echo "Getting EC2 meta-data..."
INSTANCE_ID=$(ssh -o StrictHostKeyChecking=no -q -i "$OLD_KEY_FILE" "$EC2_USER@$EC2_HOST" "curl -s http://169.254.169.254/latest/meta-data/instance-id")
echo "EC2 instance-id: $INSTANCE_ID"
# Test to make sure we have rights to update the tags for this instance. Otherwise, stop.
echo "Verifying that the AWS CLI credentials are allowed to update tags on the EC2 instance..."
# Get the current tag value for the
aws ec2 describe-tags --filters "Name=resource-id,Values=$INSTANCE_ID" > ec2_tags
TAG_LINE=$(grep -n "EC2KeyName" "ec2_tags" | cut -d':' -f1)
# Check if we found a tag specifying EC2 SSH key
if [[ -z "$TAG_LINE" ]]; then
# The ssh key tag doesn't existing yet on this instance
TAG_VALUE=""
else
TAG_LINE=$((TAG_LINE-1))
TAG_VALUE=$(awk -F ':' 'FNR==MY_LINE {print substr($2,3) }' MY_LINE=$TAG_LINE ec2_tags)
TAG_VALUE=$(echo "$TAG_VALUE" | cut -d "\"" -f1)
fi
rm ec2_tags
# Make small update to tag for testing purposes, and then revert it back
aws ec2 create-tags --resources "$INSTANCE_ID" --tags Key=EC2KeyName,Value="$TAG_VALUE "
aws ec2 create-tags --resources "$INSTANCE_ID" --tags Key=EC2KeyName,Value="$TAG_VALUE"
echo "Verified. The AWS credentials have permission to update tags."
}
# Exit if there is an error in the script. Get last error for piped commands
set -o errexit
set -o pipefail
#set -o xtrace
# Set magic variables
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__root="$(cd "$(dirname "${__dir}")" && pwd)" # <-- change this
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename ${__file} .sh)"
OLD_KEY_FILE=
EC2_HOST=
EC2_USER=core
AWS_KEY_FILE=
JSON_OUTPUT_FILE=
# Check if any arguments were passed. If not, print an error
if [ $# -eq 0 ]; then
echo "error: too few arguments"
PrintHelp
exit 2
fi
# Loop through the command-line options
while [ "$1" != "" ]; do
case "$1" in
-s | --ssh-key-file) shift
OLD_KEY_FILE="$1"
;;
-h | --host) shift
EC2_HOST="$1"
;;
-u | --user) shift
EC2_USER="$1"
;;
-a | --aws-key-file) shift
AWS_KEY_FILE="$1"
;;
-j | --json) shift
JSON_OUTPUT_FILE="$1"
;;
--help) PrintHelp
exit 0
;;
*) >&2 echo "error: invalid option: '$1'"
PrintHelp
exit 3
esac
# Shift all the parameters down by one
shift
done
# Make sure that all the required arguments were passed into the script
if [ -z "$OLD_KEY_FILE" ] || [ -z "$EC2_HOST" ] ; then
>&2 echo "error: too few arguments"
PrintHelp
exit 0
fi
echo ""
echo "Starting key rotation process..."
# Do initial housekeeping and verification that we have the proper rights to rotate keys
ConfigureAwsCli
VerifySSHKey
VerifyAWSPermissions
# Check the Linux distro. If it's CoreOS, we need to do some special processing.
PLATFORM=$(ssh -o StrictHostKeyChecking=no -q -i "$OLD_KEY_FILE" "$EC2_USER@$EC2_HOST" "uname -a")
echo "Platform: $PLATFORM"
if [[ $PLATFORM =~ "coreos" ]]; then
PLATFORM=coreos
else
PLATFORM=other
fi
echo "Platform: $PLATFORM"
# Create a new private key via ssh-keygen
echo ""
echo "Generating new keys..."
cd "$__dir"
NEW_KEY_LABEL=EC2-Key
NEW_KEY_NAME="$NEW_KEY_LABEL"-$(date +"%Y-%m-%d-%H%M%S")
NEW_PRIVATE_KEY_FILE="$NEW_KEY_NAME.pem"
NEW_PUBLIC_KEY_FILE="$NEW_KEY_NAME.pub"
ssh-keygen -t rsa -f "$NEW_KEY_NAME.pem" -q -N "" -C "$NEW_KEY_NAME"
mv "$NEW_KEY_NAME.pem.pub" "$NEW_PUBLIC_KEY_FILE"
NEW_PUBLIC_KEY=$(cat "$NEW_PUBLIC_KEY_FILE")
# Display new key info
echo "---------------------------------------"
echo "New key name: $NEW_KEY_NAME"
echo "New private key file: $NEW_KEY_NAME.pem"
echo "New public key file: $NEW_KEY_NAME.pub"
echo "Files are located in directory: $__dir"
echo "---------------------------------------"
echo ""
# Test the new key: Add the new key to the authorized keys on the instance, update a test file, and re-log in
# with the new key to retrieve the test value
echo "Testing new key..."
echo $(cat "$NEW_PUBLIC_KEY_FILE") | ssh -o StrictHostKeyChecking=no -q -i "$OLD_KEY_FILE" "$EC2_USER@$EC2_HOST" \
"cat >> ~/.ssh/authorized_keys"
TEST_VALUE="Testing 123"
echo "$TEST_VALUE" | ssh -o StrictHostKeyChecking=no -q -i "$OLD_KEY_FILE" "$EC2_USER@$EC2_HOST" "cat > ~/.rotation_test_file"
NEW_TEST_VALUE=$(ssh -o StrictHostKeyChecking=no -q -i "$NEW_PRIVATE_KEY_FILE" "$EC2_USER@$EC2_HOST" "cat ~/.rotation_test_file")
if [ "$NEW_TEST_VALUE" != "$TEST_VALUE" ] ; then
>&2 echo "Test with the new key failed. Stopping."
exit 4
fi
echo "Test successful. Removing old key..."
if [ "$PLATFORM" == "coreos" ]; then
# First, replace the public ssh key mananged by the CoreOS boot manager, ignition. This is stored in:
# ~/.ssh/authorized_keys.d/coreos-ignition.
# Ignition is a replacement for cloud-init. You can't run cloud-init if you want to rotate keys on CoreOS, because
# cloud-init will ignore your changes and pull the EC2 key from the EC2 meta-data on every reboot.
ssh -o StrictHostKeyChecking=no -q -i "$OLD_KEY_FILE" "$EC2_USER@$EC2_HOST" \
"echo $NEW_PUBLIC_KEY > ~/.ssh/authorized_keys.d/coreos-ignition && update-ssh-keys"
else
# Get a sed-search-safe version of the public key that escapes forward slashes contained in the key
OLD_PUBLIC_KEY=$(ssh-keygen -y -f $OLD_KEY_FILE)
OLD_PUBLIC_KEY=$(echo "$OLD_PUBLIC_KEY" | sed 's/\//\\\//g')
# Remove the old key from ~/.ssh/authorized_keys
ssh -o StrictHostKeyChecking=no -q -i "$NEW_PRIVATE_KEY_FILE" "$EC2_USER@$EC2_HOST" \
"sed -i \"/$OLD_PUBLIC_KEY/d\" ~/.ssh/authorized_keys"
fi
# Test again with new key
echo "Re-testing new key..."
NEW_TEST_VALUE=$(ssh -o StrictHostKeyChecking=no -q -i "$NEW_KEY_NAME.pem" "$EC2_USER@$EC2_HOST" "cat ~/.rotation_test_file")
if [ "$NEW_TEST_VALUE" != "$TEST_VALUE" ] ; then
>&2 echo "WARNING: Second test with the new key failed. Try accessing EC2 instance immediately."
exit 4
fi
echo "Second test successful. Keys have been rotated. Please keep your new key files in a secure location."
# Cleanup the temp file used for testing
ssh -o StrictHostKeyChecking=no -q -i "$NEW_KEY_NAME.pem" "$EC2_USER@$EC2_HOST" "rm -f ~/.rotation_test_file"
# Update the EC2 instance to include a tag with the key name
echo "Updating the instance tag to include the key name..."
aws ec2 create-tags --resources "$INSTANCE_ID" --tags Key=EC2KeyName,Value="$NEW_KEY_NAME"
# Print the JSON file if requested
if [ ! "$JSON_OUTPUT_FILE" == "" ]; then
echo "Outputing JSON to $JSON_OUTPUT_FILE..."
printf '
{
"KeyName":"%s",
"Host": "%s",
"InstanceId":"%s",
"PrivateKeyFile":"%s",
"PublicKeyFile": "%s"
}\n' "$NEW_KEY_NAME" "$EC2_HOST" "$INSTANCE_ID" "$__dir/$NEW_PRIVATE_KEY_FILE" "$__dir/$NEW_PUBLIC_KEY_FILE" > "$JSON_OUTPUT_FILE"
fi
echo "Rotation complete."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment