Enforce MFA for AWS IAM users.

Enforce MFA for AWS IAM users.

AWS Multi-Factor Authentication (MFA) adds an extra layer of protection on top of your user name and password. With MFA enabled, when a user signs in to an AWS website, they will be prompted for their user name and password (the first factor—what they know), as well as for an authentication response from their AWS MFA device (the second factor—what they have). Taken together, these multiple factors provide increased security for your AWS account settings and resources.

In this article, we are going to explain how to enforce the usage of MFA for your IAM users (in the Console and in awscli) by using a custom policy that requires that MFA is enabled to perform any action. We are also going to provide a custom script to automate session token generation (required to use awscli).

Policy to enforce MFA for AWS IAM users

We are going to create a policy that allows IAM users to self-manage an MFA device. This policy grants the permissions necessary to complete this action from the AWS API or AWS CLI only.

{
  'Version': '2012-10-17',
  'Statement':
    [
      {
        'Sid': 'AllowViewAccountInfo',
        'Effect': 'Allow',
        'Action':
          [
            'iam:GetAccountPasswordPolicy',
            'iam:GetAccountSummary',
            'iam:ListVirtualMFADevices',
          ],
        'Resource': '*',
      },
      {
        'Sid': 'AllowManageOwnPasswords',
        'Effect': 'Allow',
        'Action': ['iam:ChangePassword', 'iam:GetUser'],
        'Resource': 'arn:aws:iam::*:user/${aws:username}',
      },
      {
        'Sid': 'AllowManageOwnAccessKeys',
        'Effect': 'Allow',
        'Action':
          [
            'iam:CreateAccessKey',
            'iam:DeleteAccessKey',
            'iam:ListAccessKeys',
            'iam:UpdateAccessKey',
          ],
        'Resource': 'arn:aws:iam::*:user/${aws:username}',
      },
      {
        'Sid': 'AllowManageOwnSigningCertificates',
        'Effect': 'Allow',
        'Action':
          [
            'iam:DeleteSigningCertificate',
            'iam:ListSigningCertificates',
            'iam:UpdateSigningCertificate',
            'iam:UploadSigningCertificate',
          ],
        'Resource': 'arn:aws:iam::*:user/${aws:username}',
      },
      {
        'Sid': 'AllowManageOwnSSHPublicKeys',
        'Effect': 'Allow',
        'Action':
          [
            'iam:DeleteSSHPublicKey',
            'iam:GetSSHPublicKey',
            'iam:ListSSHPublicKeys',
            'iam:UpdateSSHPublicKey',
            'iam:UploadSSHPublicKey',
          ],
        'Resource': 'arn:aws:iam::*:user/${aws:username}',
      },
      {
        'Sid': 'AllowManageOwnGitCredentials',
        'Effect': 'Allow',
        'Action':
          [
            'iam:CreateServiceSpecificCredential',
            'iam:DeleteServiceSpecificCredential',
            'iam:ListServiceSpecificCredentials',
            'iam:ResetServiceSpecificCredential',
            'iam:UpdateServiceSpecificCredential',
          ],
        'Resource': 'arn:aws:iam::*:user/${aws:username}',
      },
      {
        'Sid': 'AllowManageOwnVirtualMFADevice',
        'Effect': 'Allow',
        'Action': ['iam:CreateVirtualMFADevice', 'iam:DeleteVirtualMFADevice'],
        'Resource': 'arn:aws:iam::*:mfa/${aws:username}',
      },
      {
        'Sid': 'AllowManageOwnUserMFA',
        'Effect': 'Allow',
        'Action':
          [
            'iam:DeactivateMFADevice',
            'iam:EnableMFADevice',
            'iam:ListMFADevices',
            'iam:ResyncMFADevice',
          ],
        'Resource': 'arn:aws:iam::*:user/${aws:username}',
      },
      {
        'Sid': 'DenyAllExceptListedIfNoMFA',
        'Effect': 'Deny',
        'NotAction':
          [
            'iam:CreateVirtualMFADevice',
            'iam:EnableMFADevice',
            'iam:GetUser',
            'iam:ListMFADevices',
            'iam:ListVirtualMFADevices',
            'iam:ResyncMFADevice',
            'sts:GetSessionToken',
          ],
        'Resource': '*',
        'Condition':
          { 'BoolIfExists': { 'aws:MultiFactorAuthPresent': 'false' } },
      },
    ],
}

Now, assign this policy to your users or groups along with the other policies you desire and they will be forced to use MFA to perform any action.

Automate temporary AWS keys generation using a custom script

With the former policy, in order to be able to use awscli, you must create a temporary session token instead. To do this, you have to run the sts get-session-token AWS CLI command, replacing the variables with information from your account, resources, and MFA device:

$ aws sts get-session-token --serial-number arn-of-the-mfa-device --token-code code-from-token

You'll receive output with temporary credentials and an expiration time for those temporary credentials (by default, 12 hours). However, it's quite painful having to do this, at maximum, every 36 hours. We created a custom script to automate this process. When invoked, it will call sts get-session-token command and populate awscli config profile with this temporary token. To invoke it, use the following command. Remember to replace the placeholder values on the script with your profile names and AWS account ID.

./aws-token.sh <username> <mfa_token_code>
#!/bin/bash

AWS_CLI=`which aws`

if [ $? -ne 0 ]; then
  echo "AWS CLI is not installed; exiting"
  exit 1
else
  echo "Using AWS CLI found at $AWS_CLI"
fi

if [ $# -ne 2 ]; then
  echo "Usage: $0  <IAM_USER> <MFA_TOKEN_CODE>"
  echo "Where:"
  echo "   <IAM_USER> = IAM user name"
  echo "   <MFA_TOKEN_CODE> = Code from virtual MFA device"
  exit 2
fi

AWS_USER_PROFILE=<AWS_ORIGINAL_PROFILE_NAME>
AWS_2AUTH_PROFILE=<AWS_mfa_PROFILE_NAME>
ARN_OF_MFA=arn:aws:iam::<AWS_ACCOUNT_ID>:mfa/$1
MFA_TOKEN_CODE=$2
DURATION=129600

echo "AWS-CLI Profile: $AWS_CLI_PROFILE"
echo "MFA ARN: $ARN_OF_MFA"
echo "MFA Token Code: $MFA_TOKEN_CODE"
set -x

read AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN <<< \
$( aws --profile $AWS_USER_PROFILE sts get-session-token \
  --duration $DURATION  \
  --serial-number $ARN_OF_MFA \
  --token-code $MFA_TOKEN_CODE \
  --output text  | awk '{ print $2, $4, $5 }')

echo "AWS_ACCESS_KEY_ID: " $AWS_ACCESS_KEY_ID
echo "AWS_SECRET_ACCESS_KEY: " $AWS_SECRET_ACCESS_KEY
echo "AWS_SESSION_TOKEN: " $AWS_SESSION_TOKEN

if [ -z "$AWS_ACCESS_KEY_ID" ]
then
  exit 1
fi

`aws --profile $AWS_2AUTH_PROFILE configure set aws_access_key_id "$AWS_ACCESS_KEY_ID"`
`aws --profile $AWS_2AUTH_PROFILE configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"`
`aws --profile $AWS_2AUTH_PROFILE configure set aws_session_token "$AWS_SESSION_TOKEN"`
Jose López
Jose López
2019-07-29 | 4 min read
Share article

More articles