AWS - Forced MFA and the CLI

2 minute read

Let’s say you want to force every user to enable MFA for AWS IAM accounts. Sure, that is possible.

Now there’s just one problem: This does not work on the CLI. The credentials in $HOME/.aws/credentials are “static”, they never have a aws:MultiFactorAuthPresent flag attached.

That would mean that CLI users are either not forced to use MFA, or they just can’t use the CLI any more. Which would - for example - break terraform.

There’s a solution though. Just get a token using aws sts get-session-token --serial-number ... --token-code ....

To ease this process (cause it’s really annoying) I created this function, which you can add to your .bashrc or .zshrc (or equivalent shell startup script).

Enjoy.

# gat = (G)et (A)ws session(T)oken
gat() {
  local TOKEN_DURATION=28800 # 8h

  # save existing AWS_* env vars
  local APRO_BACKUP
  local AKID_BACKUP
  local ASAK_BACKUP
  local ASET_BACKUP
  APRO_BACKUP=$AWS_PROFILE
  AKID_BACKUP=$AWS_ACCESS_KEY_ID
  ASAK_BACKUP=$AWS_SECRET_ACCESS_KEY
  ASET_BACKUP=$AWS_SESSION_TOKEN

  # check if we already have a session token active
  if [ -n "$AWS_SESSION_TOKEN" ] ; then
    if [ "$1" != "-f" ] ; then
      echo "Seems we are already using a session token, use -f to override."
      return
    else
      echo "Session token found, but '-f' set; proceeding."
    fi
  fi

  echo -n "AWS profile to use (ENTER for none): "
  read AWS_PROFILE
  if [ -z "$AWS_PROFILE" ] ; then
    echo "Not using specific AWS profile."
    unset AWS_PROFILE
  else
    echo "Using AWS profile '$AWS_PROFILE'"
    export AWS_PROFILE
  fi

  echo -n "Enter MFA token value (ENTER to abort): "
  read MFA_TOKEN
  if [ -z "$MFA_TOKEN" ] ; then
    echo "Abort."
    return
  fi

  # clean env variables, but only if a session token is active
  if [ -n "$AWS_SESSION_TOKEN" ] ; then
    echo "Cleaning existing ENV vars (already have a session token)"
    unset AWS_SESSION_TOKEN
    unset AWS_ACCESS_KEY_ID
    unset AWS_SECRET_ACCESS_KEY
  fi

  echo -n "Getting MFA ARN ... "
  MFA_ARN=$(aws iam list-mfa-devices | jq -r '.MFADevices[0].SerialNumber')
  if [ "$?" != "0" ] ; then
    echo "ERROR: something failed getting the session token."
    export AWS_PROFILE=$APRO_BACKUP
    export AWS_ACCESS_KEY_ID=$AKID_BACKUP
    export AWS_SECRET_ACCESS_KEY=$ASAK_BACKUP
    export AWS_SESSION_TOKEN=$ASET_BACKUP
    return
  fi
  echo $MFA_ARN

  echo -n "Getting session token ... "
  SESSION_TOKEN_JSON=$(aws sts get-session-token --serial-number $MFA_ARN --token-code $MFA_TOKEN --duration-seconds $TOKEN_DURATION)
  if [ "$?" != "0" ] ; then
    echo "ERROR: something failed getting the session token."
    export AWS_PROFILE=$APRO_BACKUP
    export AWS_ACCESS_KEY_ID=$AKID_BACKUP
    export AWS_SECRET_ACCESS_KEY=$ASAK_BACKUP
    export AWS_SESSION_TOKEN=$ASET_BACKUP
    return
  else
    echo "done."
  fi

  echo "Setting env variables:"
  export AWS_ACCESS_KEY_ID=$(echo $SESSION_TOKEN_JSON | jq -r '.Credentials.AccessKeyId')
  echo "  * (set) AWS_ACCESS_KEY_ID"
  export AWS_SECRET_ACCESS_KEY=$(echo $SESSION_TOKEN_JSON | jq -r '.Credentials.SecretAccessKey')
  echo "  * (set) AWS_SECRET_ACCESS_KEY"
  export AWS_SESSION_TOKEN=$(echo $SESSION_TOKEN_JSON | jq -r '.Credentials.SessionToken')
  echo "  * (set) AWS_ACCESS_KEY_ID"
  # just to be sure
  unset AWS_PROFILE
  echo "  * (*un*set) AWS_PROFILE"
  echo "... done.\n\nYou should be all set now."
}

(Stripped down version available here)