The Challenge

Security best practices tell us that a password or secret key alone do not provide us with a significant level of protection.  Passwords can be compromised in innumerable ways, and if someone steals a secret key it can quite literally give them the keys to the kingdom.

MFA has helped add an additional layer of security on top of the password/key and while it also has some challenges (MFA through SMS should not be a thing but it appears almost everywhere) when done right it can increase the difficulty of someone compromising an account by orders of magnitude.

Unfortunately there are scenarios where using MFA is a pain in the ass.  If I have to use MFA every time I log into my banking website, that seems like a fair tradeoff.  It is probably I do at most a few times a week, the additional burden of using MFA is measured in seconds, and my bank account is definitely not something I want unauthorized people to get access to.

When it comes to software development and using services that exist in the cloud, things get a little more problematic.  Cloud services are definitely something I don't want unauthorized users to get access to (it would exposes sensitive data and potentially let them run up hundreds of thousands of dollars in bills applied to my account), but if I was a full time developer I might access services hundreds of times a day.  Having to stop and deal with an MFA token that many times just isn't going to cut it.  That being said, I really shouldn't be comfortable just using a set of keys to access those services.

Further the AWS Foundational Security Best Practices recommend enforcing that all users that have Console access have MFA turned on and those users would certainly also be using the CLI.

All of this became frustrating for me one weekend as I was hacking together a prototype feature for Narrative's Data Streaming Platform and so I developed a script that made my life a little bit easier.  I'm sharing it here in the hopes that someone else finds it valuable.

Before I show the code, a couple of notes:

  • I'm using a Yubikey to generate the MFA codes.  As such the code below uses the Yubikey command line utility ykman.
  • It should be possible to use any MFA token tool.  To do so generate the token and then pass it in as the first parameter.

So the goal was to create a bash script that I could run periodically that would create a temporary session for me.  The session would require I enter my MFA one-time code and for a period of time at least I'd be able to go on with my life calling the various AWS services from the command line.

The Code

#!/bin/bash
#############
# Help                                                                   
#############
Help()
{
   # Display Help
   echo "Creates a session token based on MFA for the AWS CLI"
   echo
   echo "Syntax: mfa [-h] [mfa_token] [aws_session_proflie] [aws_secret_key_profile] [mfa_arn] [session_duration] [yubikey_proflie]"
   echo "options:"
   echo "-h 			Print this Help."
   echo "mfa_token 		The current token from your MFA device"
   echo "aws_session_proflie 	The AWS profile that will be assigned the session token"
   echo "aws_secret_key_profile  The AWS profile that will request the session token (can not be the same as aws_session_profile)"
   echo "mfa_arn 		The ARN of the MFA device in AWS (available in the IAM console)"
   echo "session_duration 	The duration the session token will be valid"
   echo "yubikey_proflie 	If you're using a yubikey, the profile from the Yubikey Profile Manager"
   echo
}

while getopts ":h" option; do
   case $option in
      h) # display Help
         Help
         exit;;
   esac
done

MFAARN=arn:aws:iam::1234567890123:mfa/foo@bar.com
AWSPROFILE="${2:-default}"
SESSION_PROFILE="${3:-session_creds}"
YKPROFILE="${6:-AWS}"
TOKEN_CODE=${1:-$(ykman oath code -s $YKPROFILE)}
SERIAL_ARN=${4:-$MFAARN}
DURATION="${5:-129600}"
SESSION_RESULTS="$(aws sts get-session-token --profile $SESSION_PROFILE --serial-number $SERIAL_ARN --token-code $TOKEN_CODE)"
aws configure set aws_secret_access_key $(jq -r '.Credentials.SecretAccessKey' <<< "$SESSION_RESULTS") --profile $AWSPROFILE
aws configure set aws_access_key_id $(jq -r '.Credentials.AccessKeyId' <<< "$SESSION_RESULTS") --profile $AWSPROFILE
aws configure set aws_session_token $(jq -r '.Credentials.SessionToken' <<< "$SESSION_RESULTS") --profile $AWSPROFILE

Instructions

These instructions are someone tailored to if you're using a Yubikey, but using the help from the script above it should be fairly apparent as to what to do if you're using an non Yubikey MFA.

  1. The script requires that you have ykman and jq installed.  Use your favorite package manager to make sure you have them.
  2. Setup Virtual MFA Token in AWS (If you’re using a Yubikey, follow the instructions in this article)
  3. Take your existing key_id/secret_key and put it into a new AWS PROFILE (not default).  They key/secret will be used for getting a session token for your default profile.  This is the SESSION_PROFILE variable in the script.
  4. Edit the script and put in the ARN of your Virtual MFA Token from the IAM console.
  5. Run mfa.sh <token code> where the token code is the current token from your MFA device.  If you’re using a Yubikey you can omit the token and you’ll be prompted to touch the key.
  6. You’re session token will be good for 36 hours before you’ll have to re-authenticate.  This mean you should now be able to call all of the services you normally have access until the token expires.

Results

My MFA experience when using the AWS CLI is now much more pleasant.  I usually run mfa.sh first thing in the morning, when prompted I touch my Yubikey and then I'm good to go for the day.

Reference Material