Tony Wolski

Secure your AWS Credentials

2021-02-01

You 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.