Hello lad
Hello lad

Reputation: 18790

How to use MFA with AWS CLI?

How do I type in the MFA code when using the AWS CLI? I have checked the documentation page of IAM http://docs.aws.amazon.com/cli/latest/reference/iam/index.html.

I have the MFA-Devices already enabled under my username.

aws iam list-mfa-devices --user-name X

returns

{
"MFADevices": [
    {
        "UserName": "X", 
        "SerialNumber": "arn:aws:iam::+++:mfa/X", 
        "EnableDate": "2016-01-13T23:15:43Z"
    }
]
}

Upvotes: 80

Views: 98782

Answers (20)

Gert van den Berg
Gert van den Berg

Reputation: 2776

A Bourne-like shell function (only tested on bash) that will set up STS for you:

aws_clear_vars ()
{
    unset AWS_SESSION_TOKEN AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_EXPIRY AWS_EC2_METADATA_DISABLED aws_response
}

aws_2fa ()
{
    aws_clear_vars;
    export AWS_EC2_METADATA_DISABLED=true;
    mfacode="$1";
    if [ -z "$mfacode" ]; then
        echo "Error: Please pass MFA code as parameter" 1>&2;
        return 1;
    fi;
    profile=${AWS_PROFILE:-default};
    role=$(aws configure get role_arn);
    mfa_serial=$(aws configure get mfa_serial);
    duration_seconds=$(aws configure get duration_seconds);
    if [ -n "$duration_seconds" ]; then
        extra_params=(--duration-seconds $duration_seconds);
    fi;
    if [ -n "$mfa_serial" ]; then
        if [ -n "$role" ]; then
            aws_response=$(env -u AWS_PROFILE aws sts assume-role --role-arn "$role" "${extra_params[@]}" --role-session-name aws-cli --serial-number "$mfa_serial" --token-code "$mfacode");
        else
            aws_response="$(aws sts get-session-token "${extra_params[@]}" --token-code "$mfacode" --serial-number "$mfa_serial")";
        fi;
        if [ $? -eq 0 ]; then
            export AWS_ACCESS_KEY_ID="$(echo "$aws_response" | jq .Credentials.AccessKeyId -r)";
            export AWS_SECRET_ACCESS_KEY="$(echo "$aws_response" | jq .Credentials.SecretAccessKey -r)";
            export AWS_SESSION_TOKEN="$(echo "$aws_response" | jq .Credentials.SessionToken -r)";
            export AWS_SESSION_EXPIRY="$(echo "$aws_response" | jq .Credentials.Expiration -r)";
            echo "Got a session. Valid until $AWS_SESSION_EXPIRY";
        else
            echo "Error getting session!" 1>&2;
            echo "$aws_response" 1>&2;
            return 2;
        fi;
    else
        echo "Error: Please ensure that mfa_serial is present in your ~/.aws/config for the relevant profile" 1>&2;
        return 1;
    fi
}

aws_clear_vars clears the environment variables.

To use run aws_2fa 987655 (where "987655" is a MFA code - it will then get a session in the default profile / the one specified in AWS_PROFILE and set the environment variables that the most AWS SDK-based tools use. (including the AWS CLI)

It will try to follow the session length from duration_seconds in the ~/.aws/config.

It is necessary to ensure the presence of mfa_serial entries on the profile being used (including default)

How it works:

  • Clears the env vars that it will set up (and which might confuse the STS command)
  • Disables fetching credentials from metadata (you're probably not getting a MFA session if you are using metadata to auth...)
  • Checks input
  • Gets the settings from the config using aws configure
  • Check that we have a mfa_serial setting in the config
  • Run a get-session-token if there is no role specified or a assume-role if a role is specified to get a token, etc out of STS
  • If no errors, set the env variables to the STS output

Usage examples:

# Basic, default profile
aws_2fa 434235
aws ec2 describe_instances

For other profiles (mostly not needed, since the CLI will prompt for a code, but can be useful for python stuff that does not support the credential cache)

AWS_PROFILE=otheraccount aws_2fa 426536
aws ec2 describe_instances  # This will now be against that profile, since env vars are set
ansible-playbook -i inventories/ansible-managed-region_aws_ec2.yml test.yml # will not prompt for MFA and operate on that profile
aws_clear_vars
aws ec2 describe_instances # Back to as if aws_2fa was not run

Upvotes: 0

rmharrison
rmharrison

Reputation: 5338

Step-by-step manual solution:

  1. Request a session token with MFA
aws sts get-session-token --serial-number arn-of-the-mfa-device --token-code code-from-token

arn-of-the-mfa-device: visible from your user IAM

  • Option: Use CLI to retrieve: aws iam list-mfa-devices --user-name ryan
  • Option: View in IAM console: IAM --> Users --> <YOU> --> Security Credentials

code-from-token: 6 digit code from your configured MFA device

  1. Create a profile with the returned credentials
aws configure --profile cli

aws configure set --profile cli aws_session_token <SESSION_TOKEN_HERE>

aws_session_token is not included in aws configure

  1. Test command
aws s3 ls --profile cli

Upvotes: 21

Chris Halcrow
Chris Halcrow

Reputation: 31980

Here's a PowerShell module that lets you set up AWS profiles that authenticate using MFA. It will manage the MFA session token, which will last up to 36 hours without needing to re-enter MFA credentials:

https://github.com/CurtisLusmore/AwsCredentialsManager/tree/161783a4c89a3f694b8010778dd34029bc153921

Upvotes: 0

Tom Harrison
Tom Harrison

Reputation: 14068

Using 1Password CLI with AWS plugin

Note: this solution requires 1Password, which requires a paid subscription.

1Password can securely store credentials, and automatically create short-term credentials even in cases where you are working in a basic setup with AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY configuration.

MFA can be enabled using biometrics (e.g. faceid, touchid, apple watch in the Apple world, others as well) -- 1Password will supply the required MFA token automatically.

Set up the 1Password CLI (op) using these instructions: https://developer.1password.com/docs/cli

Set up the AWS plugin, including steps to enable MFA using these instructions: https://developer.1password.com/docs/cli/shell-plugins/aws

Upvotes: 0

Joe Harris
Joe Harris

Reputation: 14045

The CLI can manage a lot of this for you if you're using roles. Described here: http://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html

In my credentials file I have:

[my_iam_user]
aws_access_key_id = AKIABLAHBLAHBLAHBLAH
aws_secret_access_key = <blah>
region = us-east-1

[my_admin_role]
role_arn = arn:aws:iam::123456789123:role/my_admin_role
source_profile = my_iam_user
mfa_serial = arn:aws:iam::123456789123:mfa/my_iam_user
region = us-east-1

Note the mfa_serial entry. You can get this value from your user details in the AWS IAM console. This entry tells the CLI that MFA is required for that role.

When I call aws s3 ls --profile my_admin_role it says Enter MFA code:, after I paste in the code it returns the listing.

Note: I haven't found a way to get the CLI to ask for MFA when calling a user profile (--profile my_iam_user) only calling a role profile triggers the MFA request.

The MFA token is then carried forward and the user profile can be used as well:

aws sts get-caller-identity --profile my_iam_user
 # {
 # "Account": "123456789123",
 # "UserId": "AIDABLAHBLAHBLAHBLAH",
 # "Arn": "arn:aws:iam::123456789123:user/my_iam_user"
 # }

aws sts get-caller-identity --profile my_admin_role
 # {
 # "Account": "123456789123",
 # "UserId": "AROABLAHBLAHBLAHBLAH:AWS-CLI-session-1234567890",
 # "Arn": "arn:aws:sts::123456789123:assumed-role/my_admin_role/AWS-CLI-session-1234567890"
 # }

Upvotes: 115

Fabr&#237;cio Ceolin
Fabr&#237;cio Ceolin

Reputation: 574

Snippet easy to copy and paste.

cat <<EOF >aws-credentials.sh
export AWS_ACCESS_KEY_ID=<key>
export AWS_SECRET_ACCESS_KEY=<secret>
export AWS_DEFAULT_REGION=us-east-1
export AWS_USER=<aws_user>
export AWS_ACCOUNT=<aws_account>
EOF

# Almost one liner:
source aws-credentials.sh
source <(paste -d= <(echo -ne "export AWS_ACCESS_KEY_ID\nexport AWS_SECRET_ACCESS_KEY\nexport AWS_SESSION_TOKEN\nexport EXPIRATION\n") <(aws sts get-session-token  --duration-seconds 129600 --serial-number arn:aws:iam::$AWS_ACCOUNT:mfa/$AWS_USER --token-code $(read -p "Token:" TOKEN; echo $TOKEN)  | jq  '.Credentials | .[]'))

# Example
aws s3 ls s3://my-bucket/

Upvotes: 0

neoakris
neoakris

Reputation: 5145

I think my answer will be useful for people trying to follow the official docs, which sadly leave out a lot of details. I discovered a few nuances when trying to figure out how to do this according to the official docs so I wrote this up to help.

I originally had a non-MFA CLI user configured in my ~/.aws/credentials file
(this will be important later)

I then created a 2nd user "mfa-test" to which I attached a custom policy named Force_MFA. I sourced the policy from this aws doc

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowListActions",
            "Effect": "Allow",
            "Action": [
                "iam:ListUsers",
                "iam:ListVirtualMFADevices"
            ],
            "Resource": "*"
        },
        {
            "Sid": "AllowIndividualUserToManageTheirOwnMFA",
            "Effect": "Allow",
            "Action": [
                "iam:CreateVirtualMFADevice",
                "iam:DeleteVirtualMFADevice",
                "iam:ListMFADevices",
                "iam:EnableMFADevice",
                "iam:ResyncMFADevice"
            ],
            "Resource": [
                "arn:aws:iam::*:mfa/${aws:username}",
                "arn:aws:iam::*:user/${aws:username}"
            ]
        },
        {
            "Sid": "AllowIndividualUserToDeactivateOnlyTheirOwnMFAOnlyWhenUsingMFA",
            "Effect": "Allow",
            "Action": [
                "iam:DeactivateMFADevice"
            ],
            "Resource": [
                "arn:aws:iam::*:mfa/${aws:username}",
                "arn:aws:iam::*:user/${aws:username}"
            ],
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        },
        {
            "Sid": "BlockMostAccessUnlessSignedInWithMFA",
            "Effect": "Deny",
            "NotAction": [
                "iam:CreateVirtualMFADevice",
                "iam:EnableMFADevice",
                "iam:ListMFADevices",
                "iam:ListUsers",
                "iam:ListVirtualMFADevices",
                "iam:ResyncMFADevice"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        }
    ]
}

Nuances I learned about this command:
aws sts get-session-token --serial-number arn:aws:iam::853680132675:mfa/mfa-test --token-code 630011

  1. You should update to the latest version of the AWS CLI before running the command.
    The docs mention it might fail if your aws cli isn't updated, but it's easy to miss that suggestion.

  2. The TOTP(time-based one-time password) token code 630011 comes from your virtual MFA device

  3. arn:aws:iam::853680132675:mfa/mfa-test can be looked up by viewing the Security credentials tab of the IAM user. screen snip

  4. The command will fail (without a useful error message) if ~/.aws/credentials corresponds to another account (like non-MFA CLI user).

    [default]
    aws_access_key_id = lalala
    aws_secret_access_key = lalala
    

    In other words, there's a hard requirement that the credentials used when running the aws sts get-session-token --serial-number arn:aws:iam::853680132675:mfa/mfa-test --token-code 630011 command, map to the user referenced in that command (username = mfa-test). (The only exception is if you have additional profiles defined in ~/.aws/credentials & ~/.aws/config and reference them using --profile additional_profile)

  5. So I'd recommend running aws sts get-caller-identity and making sure you see the user (mfa-test) in the results (.../user/mfa-test) matches the user mentioned in the ARN of the mfa device (.../mfa/mfa-test)

    "Arn": "arn:aws:iam::853680132675:user/mfa-test"
    
  6. Also if you install jq (json query) cli tool then unix users can use the following trick to streamline the trade-off of non-MFA CLI creds to MFA CLI creds that expire after 12 hours by default (note the | tr -d '"' in the command is read as translate delete ", it strips out the double quotes)

export MFA_CLI_SESSION=$(aws sts get-session-token --serial-number arn:aws:iam::853680132675:mfa/mfa-test --token-code 630011)

export AWS_ACCESS_KEY_ID=$( echo $MFA_CLI_SESSION  | jq .Credentials.AccessKeyId | tr -d '"' )
export AWS_SECRET_ACCESS_KEY=$( echo $MFA_CLI_SESSION  | jq .Credentials.SecretAccessKey | tr -d '"' )
export AWS_SESSION_TOKEN=$( echo $MFA_CLI_SESSION  | jq .Credentials.SessionToken | tr -d '"' )

You can run these commands as a smoke test to verify the variables aren't empty.

echo $MFA_CLI_SESSION 
echo $AWS_ACCESS_KEY_ID
echo $AWS_SECRET_ACCESS_KEY
echo $AWS_SESSION_TOKEN

From this point on your AWS CLI commands will work for the next 12 hours within that terminal session (since they're implemented as env vars). Also they'll work without having to add --profile additional_profile flag to your commands.

Upvotes: 1

Rob Pulsipher
Rob Pulsipher

Reputation: 11

Summary

Authenticating with MFA is done with the aws sts command. The basic process is:

  1. Set up a "permanent" profile in ~/.aws/credentials.
  2. Call the aws sts command with the "permanent" profile.
  3. Create a "temporary" profile in ~/.aws/credentials with the result from #2
  4. Use the "temporary" profile with your aws commands.

Details

#1:

[myaccountPerm]
aws_access_key_id = REDACTED
aws_secret_access_key = REDACTED

#2:

$ aws sts get-session-token --token-code 123456 --profile myaccountPerm --serial-number arn:aws:iam::REDACTED:mfa/REDACTED
{
    "Credentials": {
        "AccessKeyId": "REDACTED",
        "SecretAccessKey": "REDACTED",
        "SessionToken": "REDACTED",
        "Expiration": "2021-03-13T09:37:10Z"
    }
}

#3:

[myaccount]
aws_access_key_id = REDACTED
aws_secret_access_key = REDACTED
aws_session_token = REDACTED

Solution

You'll notice that this is kind of an excessive process for logging in. I recommend using a utility/script for this purpose. Several exist, including some mentioned in other answers.

I have also written such a utility, as well as an in-depth article on how to authenticate with MFA using the AWS CLI (includes a couple of other details, like how to require MFA).

My utility is just a short script, and doesn't use a pip installer, which makes it easy to inspect.

Overview of process

Utility for automation

Upvotes: 0

rcode
rcode

Reputation: 1898

A one-liner to authorize and append an MFA user to your credentials file using jq:

SERIAL_NUMBER=arn:aws:iam::000000000000:mfa/john TOKEN_CODE=123123 PROFILE_NAME=my_aws_2fa; \
aws sts get-session-token --serial-number $SERIAL_NUMBER --token-code $TOKEN_CODE \
   | jq -r '.Credentials | ("aws_access_key_id = " + .AccessKeyId), ("aws_secret_access_key = " + .SecretAccessKey), ("aws_session_token = " + .SessionToken)' \
   | (echo "\n[$PROFILE_NAME]" && cat) >> ~/.aws/credentials

Upvotes: 0

Mark B
Mark B

Reputation: 200998

Call aws sts get-session-token --serial-number <serial> --token-code <code> documented here. This will give you a temporary security token. Documentation on using the temporary security token can be found here.

Upvotes: 23

mellifluous
mellifluous

Reputation: 2975

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

https://aws.amazon.com/premiumsupport/knowledge-center/authenticate-mfa-cli/

Upvotes: 1

tongueroo
tongueroo

Reputation: 1197

Wrote a tool to add MFA support for standard IAM user profiles until @outcoldman PR gets merged: https://github.com/tongueroo/aws-mfa-secure

Setup for those in a hurry

  1. Install gem
gem install aws-mfa-secure
  1. Setup your ~/.aws/credentials with mfa_serial

~/.aws/credentials:

[mfa]
aws_access_key_id = BKCAXZ6ODJLQ1EXAMPLE
aws_secret_access_key = ABCDl4hXikfOHTvNqFAnb2Ea62bUuu/eUEXAMPLE
mfa_serial = arn:aws:iam::112233445566:mfa/MFAUser
  1. Add the alias to your ~/.bash_profile
alias aws="aws-mfa-secure session"

Restart your terminal.

Example with Output

$ export AWS_PROFILE=mfa
$ aws s3 ls
Please provide your MFA code: 751888
2019-09-21 15:53:34 my-example-test-bucket
$ aws s3 ls
2019-09-21 15:53:34 my-example-test-bucket
$

Assume Role Profiles

Assume role profiles work already for the AWS CLI, here's an example:

~/.aws/credentials:

[mfa]
aws_access_key_id = BKCAXZ6ODJLQ1EXAMPLE
aws_secret_access_key = ABCDl4hXikfOHTvNqFAnb2Ea62bUuu/eUEXAMPLE
mfa_serial = arn:aws:iam::112233445566:mfa/MFAUser

[assumed-role]
role_arn = arn:aws:iam::112233445566:role/Admin
source_profile = mfa
role_session_name = MFAUser
mfa_serial = arn:aws:iam::112233445566:mfa/MFAUser

Upvotes: 3

Ryan Shillington
Ryan Shillington

Reputation: 25167

On Windows

I'm on windows and I created a batch file to pass in my MFA code and have it automatically set up my credentials. First, you need to set up your production credentials in AWS:

aws configure --profile prod

Answer the questions appropriately with your key and secret. Then, I run my script like this:

C:\> mfa-getCreds.bat 229168

Your credentials are set up, and will expire on 2019-05-12T04:04:13Z

Now you should be able to run aws commands like this: aws s3 ls

Here are the contents of my mfa-getCreds.bat:

@echo off

set TOKEN=%1
if not defined TOKEN goto showUsage   

@call aws sts get-session-token --profile prod --serial-number "arn:aws:iam::109627855994:mfa/ryan.shillington" --token-code %* > c:\temp\mfa-getCreds.json

FOR /F "tokens=* USEBACKQ" %%g IN (`jq -r ".Credentials.AccessKeyId" c:\temp\mfa-getCreds.json`) do (SET AWS_ACCESS_KEY=%%g)
FOR /F "tokens=*" %%g IN ('jq -r ".Credentials.SecretAccessKey" c:\temp\mfa-getCreds.json') do (SET "AWS_SECRET_KEY=%%g")
FOR /F "tokens=*" %%g IN ('jq -r ".Credentials.SessionToken" c:\temp\mfa-getCreds.json') do (SET "AWS_SESSION_TOKEN=%%g")
FOR /F "tokens=*" %%g IN ('jq -r ".Credentials.Expiration" c:\temp\mfa-getCreds.json') do (SET "EXPIRATION=%%g")

set AWS_ACCESS_KEY_ID=%AWS_ACCESS_KEY%
set "AWS_SECRET_ACCESS_KEY=%AWS_SECRET_KEY%"

echo.
echo Your credentials are set up, but will expire on %EXPIRATION%
echo.
echo Now you should be able to run aws commands like this: aws s3 ls

goto :EOF

:showUsage
echo Usage: %0 [MFA Token]
goto :EOF

For this to run, you'll need the excellent jq package in your path.

Upvotes: 2

Ville
Ville

Reputation: 4346

AWS MFA use on the command line can be rather unpleasant and cumbersome, especially if you have multiple profiles and roles.

I have released awscli-mfa.sh script that makes MFA/role session management on the command line a lot easier. A companion script enable-disable-vmfa-device.sh similarly makes it easy to enable or disable a virtual MFA device on an IAM user account.

awscli-mfa.sh persists a started session in ~/.aws/credentials (with some info in ~/.aws/config), or allows you to start an in-env session only so that its details don't get persisted. When executed in Windows Subsystem for Linux, the script also provides session activation strings for PowerShell and Windows command line. However, the script itself only runs in bash (written for macOS, Linux, and WSL bash with Ubuntu).

You can find the scripts and the example MFA policies in my GitHub repo at https://github.com/vwal/awscli-mfa

Upvotes: 0

ryantuck
ryantuck

Reputation: 6674

aws-mfa acts as a wrapper around sts and works really well: https://github.com/broamski/aws-mfa

Upvotes: 6

kibble
kibble

Reputation: 46

I have forked Chinmay's gist and updated it to pull the device serial from aws instead of hardcoding it. I have also updated the exits to return a status of 1 instead of just exiting.

Available here: https://gist.github.com/jpribyl/e44021ae5cbf7fd1b4549598e85b5341

I am using it in deploy scripts like this (I renamed the script to awsMfaCli.sh):

. awsMfaCli.sh
script_status=$?

if [[ $script_status -ne 1 ]]; then
    echo "Building production"
    if npm run build ; then
       echo "Build Successful"
    else
      echo "Error building, exiting.."
      return 1
    fi


    echo "Removing all files on bucket.."
    aws s3 rm --recursive s3://mybucket

    echo "Uploading site.."
    aws s3 sync build/ s3://mybucket
    echo "S3 Upload complete.."
    echo "Deployment complete."
else
    return 1
fi

Upvotes: 0

yawn
yawn

Reputation: 8214

We documented a few considerations for AWS API multifactor in general (where to add the conditions, what are the implications etc.) in the documentation for some custom tooling (https://github.com/kreuzwerker/awsu) we developed for using Yubikeys as source for the TOTP tokens. This makes working with roles and long-term credentials + session tokens pretty easy.

Upvotes: 0

unexpectedGuest
unexpectedGuest

Reputation: 11

My use-case is I have a root account where all IAM users are created and assigned to IAM groups which in turn have the capability to assume roles on a different account with varying degree of access depending on the group they are on. I have a few house rules in place;

  1. No one is allowed to do anything on the root account except to manage their own IAM Users account.
  2. Required password reset.
  3. Required MFA.
  4. You cannot switch accounts without logging in with MFA.

This has been set up using AWS Shared Organizations.

Previously, I've been using a python script I wrote to let my users to login via cli with MFA and switch accounts. This is done by manipulating the ~/.aws/credentials.

I've since migrated to using this project https://gitlab.com/severity1/aws-auth, which is written in Go and allows me to do the same without much setup and it works on windows, macosx and linux.

This effectively gives all my users the ability to do local testing while developing Apps for AWS without having to hardcode AWS Credentials into their code.

Upvotes: 1

Chinmay B
Chinmay B

Reputation: 497

I wrote a small bash script to get over this annoying problem. You can find it here: https://gist.github.com/geekgunda/db4c9c8d850c08a48d1d60f119628032

Assumptions:

  1. Your original AWS Creds should be stored at ~/.aws/credentials
  2. You've corrected ARN for MFA device (search for FIXME)
  3. You've given correct MFA Code as cli argument
  4. You have jq installed. Ref: https://stedolan.github.io/jq/

Upvotes: 1

outcoldman
outcoldman

Reputation: 11832

I have published a PR for aws-cli, which will allow to use mfa_serial in the credentials, that will force you to enter the token before making request to AWS (and it will be cached while token is valid)

Feel free to vote, if you want to get it in.

Upvotes: 9

Related Questions