Simplifying using the AWS CLI with MFA
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.
- The script requires that you have
ykman
andjq
installed. Use your favorite package manager to make sure you have them. - Setup Virtual MFA Token in AWS (If you’re using a Yubikey, follow the instructions in this article)
- 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. - Edit the script and put in the ARN of your Virtual MFA Token from the IAM console.
- 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. - 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.