DEV Community

oscarrobert-star
oscarrobert-star

Posted on • Edited on

How to automate using MFA in AWS CLI

Problem Statement

I am a frequent AWS CLI user, and this has never been a challenge until my organisation decided to enforce AWS MFA for all users - a recommended practice. This significantly affected how I use the AWS CLI.

Look at the hustle you have to go through when using the CLI. To add to that, imagine if you had 4 AWS environments. Will you be copying these temporary credentials into the .aws/credentials file all the time, or updating the environment variables each time?

Solution

In the DevOps spirit, I had to find a way to automate this. My solution has two parts:

  1. A Python script that handles the authentication and updates .aws/credentials with the new session keys and token.
  2. Creating shell aliases to run the Python script seamlessly.

Part 1: The Python Script

The script uses argparse to define required command-line arguments: your MFA device ARN, the MFA token from your virtual device, and the AWS CLI profile you wish to authenticate.

import argparse
import boto3
import re
import os
import fileinput

description = "Authenticate and update AWS credentials with MFA tokens"
parser = argparse.ArgumentParser(description=description)
parser.add_argument('--mfa_serial_number', type=str, dest='mfa_serial_number', help='Enter your mfa device arn')
parser.add_argument('--profile_name', dest='profile_name', type=str, help='use default if you dont have any unique profiles')
parser.add_argument('--mfa_token_code', dest='mfa_token_code', type=str, help='MFA code from your virtual device')

# Parse the command-line arguments
args = parser.parse_args()

# Assign arguments to variables
mfa_serial_number = args.mfa_serial_number
profile_name = args.profile_name
mfa_token_code = args.mfa_token_code

# Check that all inputs are present
if not all([mfa_serial_number, mfa_token_code, profile_name]):
    parser.error('Required: --mfa_token_code, --mfa_serial_number and --profile_name are required.')
Enter fullscreen mode Exit fullscreen mode

The script then uses the boto3 STS client to authenticate and retrieve temporary session credentials. The session duration is configurable.

##################### Authenticate MFA ##########################
session = boto3.Session(profile_name=profile_name) # enter profile 
sts_client = session.client('sts')
response = sts_client.get_session_token(
    DurationSeconds=3600, # Value changes between 900 (15 mins) - 129600 (36 hours)
    SerialNumber=mfa_serial_number,
    TokenCode=mfa_token_code
)
access_key = response['Credentials']['AccessKeyId']
secret_key = response['Credentials']['SecretAccessKey']
session_token = response['Credentials']['SessionToken']
Enter fullscreen mode Exit fullscreen mode

Finally, the script updates the .aws/credentials file in-place. It searches for a specific profile block (e.g., [mfa]) and replaces the subsequent three lines with the new credentials.

Prerequisite: You must pre-create the profile block in ~/.aws/credentials:

[mfa]
wertyio
asdfgh
zxcvbn
Enter fullscreen mode Exit fullscreen mode
#################### Update .aws/credential file with the temporary mfa credentials #####################

# Define the pattern to search for
pattern = re.compile(r'\[mfa\]')

# Define the replacement content for the three lines
replacement_lines = [f'aws_access_key_id = {access_key}\n', f'aws_secret_access_key = {secret_key}\n', f'aws_session_token = {session_token}\n']

home = os.path.expanduser("~")
# Open the file in inplace mode using fileinput
with fileinput.input(f'{home}/.aws/credentials', inplace=True) as file:
    # Iterate over the lines in the file
    for line in file:
        # If the line matches the pattern, print it and then modify the next three lines
        if pattern.match(line):
            print(line, end='')
            # Print the new replacement lines
            for replacement_line in replacement_lines:
                print(replacement_line, end='')
            # Skip the next three lines in the original file
            next(file)
            next(file)
            next(file)
        # Otherwise, just print the line
        else:
            print(line, end='')
Enter fullscreen mode Exit fullscreen mode

Part 2: Creating Aliases

Aliases are shortcuts in your shell to execute longer commands. This is a personal preference; feel free to modify them.

First, create an alias to source your Python virtual environment (if you use one).

# Add to ~/.zshrc or ~/.bashrc
alias source_env='source /Users/devops/Desktop/python_lab/env/bin/activate'
Enter fullscreen mode Exit fullscreen mode

Next, create aliases for each of your AWS environments. The number of aliases depends on the number of environments you have (e.g., test, staging, prod).

# Add to ~/.zshrc or ~/.bashrc
alias mfaTest='source_env && python /Users/devops/Desktop/python_lab/enableMFA/newCred.py --mfa_serial_number $TEST_DEVICE --profile_name test --mfa_token_code '
alias mfaStaging='source_env && python /Users/devops/Desktop/python_lab/enableMFA/newCred.py --mfa_serial_number $STAGING_DEVICE --profile_name staging --mfa_token_code '
alias mfaProd='source_env && python /Users/devops/Desktop/python_lab/enableMFA/newCred.py --mfa_serial_number $PROD_DEVICE --profile_name prod --mfa_token_code '
Enter fullscreen mode Exit fullscreen mode

Important: The mfa_serial_number argument is passed from an environment variable. You must define these variables in your shell configuration file:

# Add to ~/.zshrc or ~/.bashrc
export TEST_DEVICE='arn:aws:iam::123456789012:mfa/my-device'
export STAGING_DEVICE='arn:aws:iam::210987654321:mfa/my-device'
export PROD_DEVICE='arn:aws:iam::098765432109:mfa/my-device'
Enter fullscreen mode Exit fullscreen mode

Usage: To authenticate, call the alias followed by the current MFA code from your authenticator app.

mfaTest 123456
Enter fullscreen mode Exit fullscreen mode

After running, you can verify your credentials by calling any AWS CLI command using the --profile mfa flag.


Alternative Approach

If you are an admin and have IAM permissions across your AWS accounts, you can create an IAM role with cross-account permissions. This allows you to assume a role directly for the account you need, which can often be a more scalable and secure solution than managing long-term credentials and MFA tokens for the CLI.

Top comments (0)