Skip to content

Instantly share code, notes, and snippets.

@shalomb
Last active January 10, 2022 20:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shalomb/cae5b4a0c8563cae032bdcebbf3d5b16 to your computer and use it in GitHub Desktop.
Save shalomb/cae5b4a0c8563cae032bdcebbf3d5b16 to your computer and use it in GitHub Desktop.
Transparent MFA support for AWS CLI

Setup

Install/Configure AWS CLI as usual (without MFA)

$ pip install awscli
...

$ aws configure  # and setup the default profile
...

$ head -n4 ~/.aws/credentials 
[default]
aws_access_key_id = AKIAX2J7XXXX7TCRYB
aws_secret_access_key = 82B6XXXXF9haINGLbM3XXXNzL

Setup for MFA

$ source /path/to/aws-sts-mfa-session.sh

$ alias aws=aws-sts-mfa-session

$ aws sts get-caller-identity 
Enter the MFA token for arn:aws:iam::537540003341:mfa/john.doe:  719274
{
    "UserId": "AIDAXXXXXKRBB2",
    "Account": "12309834586",
    "Arn": "arn:aws:iam::537540003341:user/john.doe"
}

# From here on, you will not be prompted for the MFA token
# unless it expires or is invalidated.

$ aws s3 ls
2021-10-28 12:32:42 prod-5y5xxxxyux-eu-west-1
2021-10-28 12:33:16 stage-5y5txxxxux-us-east-1
...

$ terraform plan
...

$ cdk deploy
...
#!/bin/bash
# This function sets up the
# AWS_ACCESS_KEY_ID,
# AWS_SECRET_ACCESS_KEY and
# AWS_SESSION_TOKEN
# environment variables necessary for the AWS CLI (awscli)
# command to authenticate with the AWS Control Plane APIs.
# Therefore, this function is meant to be sourced directly
# into the bash shell and so should be placed into ~/.bashrc or similar.
#
# The aws command can be wrapped like so
# alias aws=aws-sts-mfa-session
#
# If a valid STS token is not present or is not cached, a new STS session
# is setup and you will be prompted for an MFA token from the virtual
# device you registered in IAM
function aws-sts-mfa-session {
local AWS_MFA_AUTH_CODE
: ${AWS_MFA_AUTH_CODE:=''}
: ${AWS_MFA_PROFILE:=default}
: ${STS_SESSION_CACHE:=~/.aws/sts-mfa-session.json}
: ${STS_SESSION_DURATION:=86400} #24 hours
function _aws { command aws "$@" --profile "$AWS_MFA_PROFILE"; }
function pluck { jq -cer "$@" "$STS_SESSION_CACHE"; }
function load-sts-cached-session {
export AWS_ACCESS_KEY_ID=$(pluck '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(pluck '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(pluck '.Credentials.SessionToken')
export AWS_SESSION_TOKEN_EXPIRY_TIME=$(
date '+%s' -d $(pluck '.Credentials.Expiration') || true
)
}
function new-sts-session {
local mfa_device
if ! mfa_device=$(
_aws iam list-mfa-devices | jq -cer .MFADevices[0].SerialNumber
); then
echo >&2 "ERROR: No MFA devices listed. Is \$AWS_MFA_PROFILE ($AWS_MFA_PROFILE) set?"
return 1
fi
if [[ -z $AWS_MFA_AUTH_CODE ]]; then
read -p "Enter the MFA token for $mfa_device: " -r AWS_MFA_AUTH_CODE
fi
if session_object=$(_aws sts get-session-token \
--duration-seconds "$STS_SESSION_DURATION" \
--serial-number "$mfa_device" \
--token-code "$AWS_MFA_AUTH_CODE"); then
echo "$session_object" > "$STS_SESSION_CACHE"
else
echo >&2 "ERROR: Unable to get an STS token. Check MFA token ($AWS_MFA_AUTH_CODE) for correctness."
return 1
fi
load-sts-cached-session
}
if [[ $1 == test ]]; then command aws sts get-caller-identity; return $?; fi
if [[ $1 == @(refresh|login) ]]; then new-sts-session; ec=$?; command aws sts get-caller-identity; return $?; fi
if [[ -z $AWS_ACCESS_KEY_ID ||
-z $AWS_SECRET_ACCESS_KEY ||
-z $AWS_SESSION_TOKEN ||
-z $AWS_SESSION_TOKEN_EXPIRY_TIME
]]; then
load-sts-cached-session
fi
local now=$(date +%s)
(( now > AWS_SESSION_TOKEN_EXPIRY_TIME )) && { new-sts-session || return $?; }
command aws "$@"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment