Secure your AWS Credentials
2021-02-01You shouldn’t keep credentials in plain-text, right? (Hint: the answer is no, you shouldn’t). So why is keeping AWS command line credentials in ~/.aws/credentials
so common? Probably because setting up anything more secure adds a disproportionate level of complexity, when what you really want is get on with the fun stuff, developing.
I wanted to something a little more secure than plain text aws_access_key_id
and aws_secret_access_key
in ~/.aws/credentials
. What I came up with, I think, provides a good level of security without too much overhead. My solution uses pass to store credentials encrypted, physical access to a Yubikey to decrypt them, a tiny custom bash script, and the aws cli
external process utility.
First up, I use pass
to store my passwords and secrets (and other stuff). It’s great. Simple and secure, storing each secret in a gpg
encrypted file. For my AWS credentials solution my personal/aws
entry looks like this:
$ pass personal/aws
<web-console-password>
url: https://<aws-account-number>.signin.aws.amazon.com/console
user: <username>
access_key_id: <access-key-id>
secret_access_key: <secret-access-key>
The great thing about this is all of my AWS details are in one place.
I keep my GPG subkeys, including the encryption key, on a Yubikey. So to decrypt anything in the password store you need a) physical access to the Yubikey, and b) the passphrase to unlock it. Three wrong guesses and it’s locked.
Next, I wrote a script to call pass
and extract the access_key_id
and secret_access_key
values into a format that aws cli’s credential_process
requires (visit AWS docs for more details on sourcing credentials with an external process).
#!/usr/bin/env bash
access_key_id=$(pass "$1" | grep access_key_id | sed -r 's/^(.*: )(.*)/\2/g')
secret_access_key=$(pass "$1" | grep secret_access_key | sed -r 's/^(.*: )(.*)/\2/g')
echo '{ "Version": 1, "AccessKeyId": "'"${access_key_id}"'", "SecretAccessKey": "'"${secret_access_key}"'" }'
The script takes the password store credential name as an argument (ensure it is executable and on the PATH
).
$ aws_login personal/aws
The card reader kicks in and asks to unlock the Yubikey:
┌──────────────────────────────────────────────┐
│ Please unlock the card │
│ │
│ Number: 0002 19533393 │
│ Holder: Your Name │
│ │
│ PIN ________________________________________ │
│ │
│ <OK> <Cancel> │
└──────────────────────────────────────────────┘
Output looks like this:
{ "Version": 1, "AccessKeyId": "<access-key-id>", "SecretAccessKey": "<secret-access-key>" }
Finally, I removed my ~.aws/credentials
file and added the credential_process
line to your ~/.aws/config
file:
[default]
region = eu-west-2
output = json
credential_process = aws_login personal/aws
That’s it, no more plain text credentials lying around! Now when I execute an aws
command the cli runs my script to extract the encrypted credentials, prompting me for Yubikey passphrase if I haven’t already entered it in the session.
Your thoughts? I'd love to hear them. Please get in contact.