-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathrotate-ec2-key.sh
executable file
·287 lines (235 loc) · 10.6 KB
/
rotate-ec2-key.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#!/usr/bin/env bash
# ================================================================
# === DESCRIPTION
# ================================================================
#
# Summary: This script rotates the root ssh key on an EC2 instance
#
# Version: 1.0.0
#
# Tested platforms:
# - CoreOS (ignition must be used instead of cloud-init to persist key rotation. See: https://coreos.com/ignition/docs/latest)
# - Ubuntu, Amazon Linux
#
# Command-line arguments:
# - See the 'PrintHelp' function below for command-line arguments. Or run this script with the --help option.
#
# Legal stuff:
# - Copyright (c) 2015 Jake Bennett
# - Licensed under the MIT License - https://opensource.org/licenses/MIT
# ================================================================
# === FUNCTIONS
# ================================================================
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."
}
# ================================================================
# === INITIALIZATION
# ================================================================
# 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)"
# ======================================================
# === PARSE COMMAND-LINE OPTIONS
# ======================================================
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
# ======================================================
# === MAIN SCRIPT
# ======================================================
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."