Getting and Using Session Tokens with MFA in the AWS CLI


Posted by Steven

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:

  1. $ aws eks list-clusters
  2. 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:

  1. $ aws sts get-session-token --serial-number arn:aws:iam::000000000012:mfa/yourusername --token-code 123456
  2. {
  3. "Credentials": {
  4. "AccessKeyId": "A...2",
  5. "SecretAccessKey": "x...F",
  6. "SessionToken": "IQ....DZS",
  7. "Expiration": "2022-02-27T22:10:04+00:00"
  8. }
  9. }

To make following commands in the AWS CLI use this token, it can be exported in the environment variables like this:

  1. $ export AWS_ACCESS_KEY_ID=A...2
  2. $ export AWS_SECRET_ACCESS_KEY=x...F
  3. $ 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:

  1. $ aws eks list-clusters
  2. {
  3. "clusters": [
  4. "my-cluster-name-here"
  5. ]
  6. }

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:

  1. #!/bin/bash
  2.  
  3. if [ ${BASH_SOURCE[0]} == ${0} ]
  4. then
  5. SOURCED=false
  6. else
  7. SOURCED=true
  8. fi
  9.  
  10. while getopts ":h" option; do
  11. case $option in
  12. h) # Display help
  13. echo "This will unset the following environment variables:"
  14. echo
  15. echo " AWS_ACCESS_KEY_ID"
  16. echo " AWS_SECRET_ACCESS_KEY"
  17. echo " AWS_SESSION_TOKEN"
  18. echo
  19. echo "To be able to change environment variables, this script has to be executed with 'source' like this:"
  20. echo "source ./aws-unset-mfa-access-token.sh"
  21. if [ $SOURCED = true ]
  22. then
  23. return
  24. else
  25. exit
  26. fi
  27. ;;
  28. esac
  29. done
  30.  
  31. if [ $SOURCED = false ]
  32. then
  33. echo "You are not running this script as source."
  34. echo "To make these changes permanent, call this script source'd like this:"
  35. echo "source ./aws-unset-mfa-access-token.sh"
  36. exit
  37. fi
  38.  
  39. echo "Removing AWS_ACCESS_KEY_ID from environment variables ..."
  40. unset AWS_ACCESS_KEY_ID
  41. echo "Removing AWS_SECRET_ACCESS_KEY from environment variables ..."
  42. unset AWS_SECRET_ACCESS_KEY
  43. echo "Removing AWS_SESSION_TOKEN from environment variables ..."
  44. 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:

  1. #!/bin/bash
  2.  
  3. if [ ${BASH_SOURCE[0]} == ${0} ]
  4. then
  5. SOURCED=false
  6. else
  7. SOURCED=true
  8. fi
  9.  
  10. while getopts ":h" option; do
  11. case $option in
  12. h) # Display help
  13. echo "This will call AWS STS with your current credentials, get a temporary access token and set this token with the following environment variables:"
  14. echo
  15. echo " AWS_ACCESS_KEY_ID"
  16. echo " AWS_SECRET_ACCESS_KEY"
  17. echo " AWS_SESSION_TOKEN"
  18. echo
  19. echo "Usage:"
  20. echo
  21. echo "aws-set-mfa-access-token MFA_SERIAL_NUMBER TOKEN_CODE"
  22. echo
  23. echo "Example:"
  24. echo
  25. echo "aws-set-mfa-access-token arn:aws:iam::000000000042:mfa/youruser 424242"
  26. echo
  27. echo "This will cause every request to be send with this access token."
  28. echo
  29. echo "To be able to change environment variables, this script has to be executed with 'source' like this:"
  30. echo "source ./aws-set-mfa-access-token.sh"
  31. if [ $SOURCED = true ]
  32. then
  33. return
  34. else
  35. exit
  36. fi
  37. ;;
  38. esac
  39. done
  40.  
  41. if [ $SOURCED = false ]
  42. then
  43. echo "You are not running this script as source."
  44. echo "To make these changes permanent, call this script source'd like this:"
  45. echo "source ./aws-set-mfa-access-token.sh"
  46. exit
  47. fi
  48.  
  49. if [ $# -lt 2 ]
  50. then
  51. echo "Please provide two arguments:"
  52. echo "1. MFA_SERIAL_NUMBER"
  53. echo "2. TOKEN_CODE"
  54. return
  55. fi
  56.  
  57. echo "Checking if environment variables are set ..."
  58. echo
  59. if [ -z ${AWS_ACCESS_KEY_ID+x} ]; then
  60. echo "AWS_ACCESS_KEY_ID is unset";
  61. AWS_ACCESS_KEY_ID_ALREADY_SET=false
  62. else
  63. echo "AWS_ACCESS_KEY_ID is set to '$AWS_ACCESS_KEY_ID'";
  64. AWS_ACCESS_KEY_ID_ALREADY_SET=true
  65. fi
  66.  
  67. if [ -z ${AWS_SECRET_ACCESS_KEY+x} ]; then
  68. echo "AWS_SECRET_ACCESS_KEY is unset";
  69. AWS_SECRET_ACCESS_KEY_ALREADY_SET=false
  70. else
  71. echo "AWS_SECRET_ACCESS_KEY is set to '$AWS_SECRET_ACCESS_KEY'";
  72. AWS_SECRET_ACCESS_KEY_ALREADY_SET=true
  73. fi
  74.  
  75. if [ -z ${AWS_SESSION_TOKEN+x} ]; then
  76. echo "AWS_SESSION_TOKEN is unset";
  77. AWS_SESSION_TOKEN_ALREADY_SET=false
  78. else
  79. echo "AWS_SESSION_TOKEN is set to '$AWS_SESSION_TOKEN'";
  80. AWS_SESSION_TOKEN_ALREADY_SET=true
  81. fi
  82.  
  83. echo
  84.  
  85. if [ $AWS_ACCESS_KEY_ID_ALREADY_SET = true ] || [ $AWS_SECRET_ACCESS_KEY_ALREADY_SET = true ] || [ $AWS_SESSION_TOKEN_ALREADY_SET = true ]; then
  86. echo "The already existing variables will be overriden. Continue?"
  87. read -p "Continue? (Y/N): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || return
  88. else
  89. echo "No existing variables set. Will continue ..."
  90. fi
  91.  
  92.  
  93. echo "Getting new MFA access token ..."
  94. CREDJSON="$(aws sts get-session-token --serial-number $1 --token-code $2)"
  95.  
  96. ACCESSKEY="$(echo $CREDJSON | jq '.Credentials.AccessKeyId' | sed 's/"//g')"
  97. SECRETACESSKEY="$(echo $CREDJSON | jq '.Credentials.SecretAccessKey' | sed 's/"//g')"
  98. SESSIONTOKEN="$(echo $CREDJSON | jq '.Credentials.SessionToken' | sed 's/"//g')"
  99.  
  100. echo
  101. echo "AccessKeyId:"
  102. echo $ACCESSKEY
  103. echo "SECRETACESSKEY:"
  104. echo $SECRETACESSKEY
  105. echo "SESSIONTOKEN:"
  106. echo $SESSIONTOKEN
  107.  
  108. echo
  109. echo "Setting values as environment variables ..."
  110. export AWS_ACCESS_KEY_ID=$ACCESSKEY
  111. export AWS_SECRET_ACCESS_KEY=$SECRETACESSKEY
  112. export AWS_SESSION_TOKEN=$SESSIONTOKEN
  113. 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. 

Share: