Securing your AWS IAM user with multi factor authentication (MFA) is a good idea. However, when working on the command line interface (CLI), the need to enter changing token codes creates some overhead. This article shows how to use MFA in the CLI.
Enabling MFA
This documentation shows how to enable MFA. With these settings, logins to the web console via the browser will only work with a code from your MFA device.
To also force IAM users to having to enter an MFA token code when using the CLI, set the IAM policy as shown here.
With this setup, any command given to the AWS CLI will fail without a proper token code:
$ aws eks list-clusters
An error occurred (AccessDeniedException) when calling the ListClusters operation: User: arn:aws:iam::000000000012:user/yourusername is not authorized to perform: eks:ListClusters on resource: arn:aws:eks:eu-central-1:000000000012:cluster/* with an explicit deny
Requesting and Setting Credentials Manually
Here is how to get a token and use it in every request. This process is also shown here on YouTube.
First, find the amazon resource name (ARN) of your MFA device. Go to the user table in IAM service and select your user. The tab “Security credentials” shows the ARN at “Assigned MFA device”.
With that, a token can be requested from the Security Token Service (STS), using one of the codes from your MFA device:
$ aws sts get-session-token --serial-number arn:aws:iam::000000000012:mfa/yourusername --token-code 123456
{
"Credentials": {
"AccessKeyId": "A...2",
"SecretAccessKey": "x...F",
"SessionToken": "IQ....DZS",
"Expiration": "2022-02-27T22:10:04+00:00"
}
}
To make following commands in the AWS CLI use this token, it can be exported in the environment variables like this:
$ export AWS_ACCESS_KEY_ID=A...2
$ export AWS_SECRET_ACCESS_KEY=x...F
$ export AWS_SESSION_TOKEN=IQ...DZS
Here is a good article about environment variables in Linux.
A request made within the lifetime of that token will now succeed:
$ aws eks list-clusters
{
"clusters": [
"my-cluster-name-here"
]
}
Requesting and Setting Credentials By Script
To ease the process demonstrated above, I wrote two scripts.
The first script, aws-unset-mfa-access-token.sh, removes the three variables from the environment:
#!/bin/bash
if [ ${BASH_SOURCE[0]} == ${0} ]
then
SOURCED=false
else
SOURCED=true
fi
while getopts ":h" option; do
case $option in
h) # Display help
echo "This will unset the following environment variables:"
echo
echo " AWS_ACCESS_KEY_ID"
echo " AWS_SECRET_ACCESS_KEY"
echo " AWS_SESSION_TOKEN"
echo
echo "To be able to change environment variables, this script has to be executed with 'source' like this:"
echo "source ./aws-unset-mfa-access-token.sh"
if [ $SOURCED = true ]
then
return
else
exit
fi
;;
esac
done
if [ $SOURCED = false ]
then
echo "You are not running this script as source."
echo "To make these changes permanent, call this script source'd like this:"
echo "source ./aws-unset-mfa-access-token.sh"
exit
fi
echo "Removing AWS_ACCESS_KEY_ID from environment variables ..."
unset AWS_ACCESS_KEY_ID
echo "Removing AWS_SECRET_ACCESS_KEY from environment variables ..."
unset AWS_SECRET_ACCESS_KEY
echo "Removing AWS_SESSION_TOKEN from environment variables ..."
unset AWS_SESSION_TOKEN
The second script, aws-set-mfa-access-token.sh, requests new credentials from the STS as shown above and sets them in the environment variables:
#!/bin/bash
if [ ${BASH_SOURCE[0]} == ${0} ]
then
SOURCED=false
else
SOURCED=true
fi
while getopts ":h" option; do
case $option in
h) # Display help
echo "This will call AWS STS with your current credentials, get a temporary access token and set this token with the following environment variables:"
echo
echo " AWS_ACCESS_KEY_ID"
echo " AWS_SECRET_ACCESS_KEY"
echo " AWS_SESSION_TOKEN"
echo
echo "Usage:"
echo
echo "aws-set-mfa-access-token MFA_SERIAL_NUMBER TOKEN_CODE"
echo
echo "Example:"
echo
echo "aws-set-mfa-access-token arn:aws:iam::000000000042:mfa/youruser 424242"
echo
echo "This will cause every request to be send with this access token."
echo
echo "To be able to change environment variables, this script has to be executed with 'source' like this:"
echo "source ./aws-set-mfa-access-token.sh"
if [ $SOURCED = true ]
then
return
else
exit
fi
;;
esac
done
if [ $SOURCED = false ]
then
echo "You are not running this script as source."
echo "To make these changes permanent, call this script source'd like this:"
echo "source ./aws-set-mfa-access-token.sh"
exit
fi
if [ $# -lt 2 ]
then
echo "Please provide two arguments:"
echo "1. MFA_SERIAL_NUMBER"
echo "2. TOKEN_CODE"
return
fi
echo "Checking if environment variables are set ..."
echo
if [ -z ${AWS_ACCESS_KEY_ID+x} ]; then
echo "AWS_ACCESS_KEY_ID is unset";
AWS_ACCESS_KEY_ID_ALREADY_SET=false
else
echo "AWS_ACCESS_KEY_ID is set to '$AWS_ACCESS_KEY_ID'";
AWS_ACCESS_KEY_ID_ALREADY_SET=true
fi
if [ -z ${AWS_SECRET_ACCESS_KEY+x} ]; then
echo "AWS_SECRET_ACCESS_KEY is unset";
AWS_SECRET_ACCESS_KEY_ALREADY_SET=false
else
echo "AWS_SECRET_ACCESS_KEY is set to '$AWS_SECRET_ACCESS_KEY'";
AWS_SECRET_ACCESS_KEY_ALREADY_SET=true
fi
if [ -z ${AWS_SESSION_TOKEN+x} ]; then
echo "AWS_SESSION_TOKEN is unset";
AWS_SESSION_TOKEN_ALREADY_SET=false
else
echo "AWS_SESSION_TOKEN is set to '$AWS_SESSION_TOKEN'";
AWS_SESSION_TOKEN_ALREADY_SET=true
fi
echo
if [ $AWS_ACCESS_KEY_ID_ALREADY_SET = true ] || [ $AWS_SECRET_ACCESS_KEY_ALREADY_SET = true ] || [ $AWS_SESSION_TOKEN_ALREADY_SET = true ]; then
echo "The already existing variables will be overriden. Continue?"
read -p "Continue? (Y/N): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || return
else
echo "No existing variables set. Will continue ..."
fi
echo "Getting new MFA access token ..."
CREDJSON="$(aws sts get-session-token --serial-number $1 --token-code $2)"
ACCESSKEY="$(echo $CREDJSON | jq '.Credentials.AccessKeyId' | sed 's/"//g')"
SECRETACESSKEY="$(echo $CREDJSON | jq '.Credentials.SecretAccessKey' | sed 's/"//g')"
SESSIONTOKEN="$(echo $CREDJSON | jq '.Credentials.SessionToken' | sed 's/"//g')"
echo
echo "AccessKeyId:"
echo $ACCESSKEY
echo "SECRETACESSKEY:"
echo $SECRETACESSKEY
echo "SESSIONTOKEN:"
echo $SESSIONTOKEN
echo
echo "Setting values as environment variables ..."
export AWS_ACCESS_KEY_ID=$ACCESSKEY
export AWS_SECRET_ACCESS_KEY=$SECRETACESSKEY
export AWS_SESSION_TOKEN=$SESSIONTOKEN
echo "Done."
Both scripts come with a help page and some user friendliness such as input validation and informative output. They can be downloaded at this GitHub repository.
Note: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN could also be added to the credentials file stored in the .aws folder. However, Amazon recommends using the environment variables. Also, there is no easy way adding the values to the credentials file via the AWS CLI.
(Image Public Domain, https://pxhere.com/en/photo/730639)