Store AWS CLI and MFA Credentials In The Mac OS Keychain App

July - 2021

I lock my AWS accounts down with MFA. Using the command line tools and SDKs require generating new credentials every 36 hours. To get them, I run the following command:

mfa 012345

The command itself is a function in my private .zshrc file. It looks like this:

######################################################################
# AWS MFA Updater
function mfa () {
    if [ "$1" ]
    then
        if [ "$2" ]
        then
            echo "You passed too many arguments"
        else
            export AWS_TOKEN=[CURRENT_TOKEN] && security add-generic-password -a [LOCAL_USERNAME] -s [NAME_FOR_MFA_STORAGE] -U -w $(aws sts --profile [AWS_CREDENTIALS_PROFILE] get-session-token --duration-seconds 129600 --serial-number [MFA_SERIAL_NUMBER] --token-code $AWS_TOKEN | jq -c '. | {Version: 1,AccessKeyId: .Credentials.AccessKeyId, SecretAccessKey: .Credentials.SecretAccessKey, SessionToken: .Credentials.SessionToken }')
        fi
    else
        echo "You need to pass an argument"
    fi
}
export AWS_TOKEN=[CURRENT_TOKEN] && security add-generic-password -a [LOCAL_USERNAME] -s [NAME_FOR_MFA_STORAGE] -U -w $(aws sts --profile [AWS_CREDENTIALS_PROFILE] get-session-token --duration-seconds 129600 --serial-number [MFA_SERIAL_NUMBER] --token-code $AWS_TOKEN | jq -c '. | {Version: 1,AccessKeyId: .Credentials.AccessKeyId, SecretAccessKey: .Credentials.SecretAccessKey, SessionToken: .Credentials.SessionToken }')

The only value I change each time is CURRENT_TOKEN with the current six digit token from my MFA device.

To use the code

The values I change out are:

  • CURRENT_TOKEN with the current six digit token
  • LOCAL_USERNAME my machine username (e.g. alan)
  • NAME_FOR_MFA_STORAGE a key name to use to store the credentials under
  • AWS_CREDENTIALS_PROFILE
  • MFA_SERIAL_NUMBER you'll get this from your IAM user page where you setup the MFA device. Looks something like: "arn:aws:iam::0123456789:mfa/AccountName"

One Time Initial Setup

Add Profiles To AWS Credentials

Add these to your ~/.aws/credentials file

[default]
credential_process = security find-generic-password -w -a MAC_USERNAME -s AWS-ACCOUNT-MFA

[AWS-ACCOUNT]
credential_process = security find-generic-password -w -a MAC_USERNAME -s AWS-ACCOUNT-NON-MFA

Store Non-MFA Credentails

Make a json file called credentails.json with the format with your non-mfa credentails

{ 
    "Version": 1, 
    "AccessKeyId": "YOUR NON-MFA ACCESS KEY", 
    "SecretAccessKey": "YOUR NON-MFA SECRET KEY" 
}

Push those credentials to Keychain Access with:

security add-generic-password -a MAC_USERNAME -s AWS-ACCOUNT-NON-MFA -U -w "$(jq -c . credentials.json)"

Generating Tokens

Every time you need to update the tokens, use this with your current AWS_TOKEN number:

That updates the credentials that are setup for your default aws profile in the credentials file. Test it with:

aws s3 ls

Notes:

  • You can totally put this in a command line function in your bash/zsh profile. (I just haven't done that yet)
  • I use the [default] profile for the mfa credentials since that's what I use for everything except updating the mfa token. You can, of course, use whatever profile you'd like.
  • The reason to put the initial credentials inside a json file instead of entering them directly on the command line is to prevent them from showing up in your command line history
  • The AWS-ACCOUNT-MFA and AWS-ACCOUNT-NON-MFA are key names you can change to whatever you want.
  • You'll need to update arn:aws:iam::123456789:mfa/AWS-ACCOUNT with your account MFA token ID (which is found on your IAM page for setting up MFA devices)
  • The AWS-ACCOUNT in the profile and the command to get the token can be changed as long as you do it in both places.

Draft stuff

This assumes you have an account setup that lets you use the default access/secret credentials along with an MFA token to pull a set of temporary mfa credentials.

You'll setup two sets of credentials in they Keychain Access app. The first is for the main crednetials that you'll use to get the MFA. The second is the MFA credentials that will be refreshed by the command

Setup a basic account and store the credentials like this. This is for the account that can use generate MFA credentails for itself. TKTKTK Get the permission needed to allow for this.

TKTKTKTK. Show how to do this without putting the text on the command line so it's not in the command line history.

security add-generic-password -a USERNAME -s KEYNAME -U -w '{ "Version": 1, "AccessKeyId": "your AWS secret access key", "SecretAccessKey": "your AWS secret access key" }'

Then update your ~/.aws/credentails file with:

[default]
credential_process = security find-generic-password -w -a USERNAME -s KEYNAME

You need an account setup with basic access keys that can get the mfa keys.

Get your MFA credentials with:

TKTKTK

Store your temporary credentials in Keychain Access with

security add-generic-password -a USERNAME -s KEYNAME -U -w '{ "Version": 1, "AccessKeyId": "an AWS access key", "SecretAccessKey": "your AWS secret access key", "SessionToken": "the AWS session token for temporary credentials" }'

Then, add this to your ~/.aws/credentials file:

[mfa_access]
credential_process = security find-generic-password -w -a USERNAME -s KEYNAME

NOTE: you can't use -w without the password on the command line to prompt for it because the prompt apparently doesn't allow you to input a long enough line to accommodate the JSON.

So, in order to do this from the command line, you'll need to input the data from the command itself. That ends up putting the credentials in your command line history which isn't great. Should work on getting another process to set it.


Tried this, but it didn't work, there's probably a way to do it though where you read from a file instead of putting the credentials on the command line

security add-generic-password -a alans -s KEYNAME -U -w '`cat ~/Desktop/_work.json`'

TODO: Named profile

[mfa] aws_access_key_id = example-access-key-as-in-returned-output aws_secret_access_key = example-secret-access-key-as-in-returned-output aws_session_token = example-session-Token-as-in-returned-output