How to automate using MFA in AWS CLI
Published: 2023-05-03 10:05:19

## 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 affected how I use AWS CLI. Check out how to enable and use MFA [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_enable_virtual.html). 

If you are a cli user, [here's](https://repost.aws/knowledge-center/authenticate-mfa-cli) how to use MFA in the cli.

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

## Solution

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

1. a python script that does the authentication for us and updates `.aws/credentials`  with the new session access keys and token.

2. creating aliases to run the python script each time we are authenticating.

### Part 1 - The python script
The first part of this script is to define the command-line arguments needed when using the script. These include your mfa device ARN, the mfa token from the virtual device and the AWS cli profile you wish to authenticate mfa in. If you only have one profile, set the profile as `default`. 

```
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()

# Check that both 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.')
```

The second part of the script does the authentication for you and stores the credentials in variables. You have the liberty to set how long you want your session to last. In this script, it's set to one hour. You can set that time between 15 minutes and 36 hours (set in seconds).
```
##################### 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']
```
The final part of this script updates `.aws/credentials` for you. For it to work, you need to edit the credentials file and add a new profile block. You can add the following gibberish as the script will update it each time it's run with the correct details.

```
[mfa]
wertyio
asdfgh
zxcvbn
```
This script will edit the file inplace by looking for the pattern `[mfa]` and edit 3 subsequent lines by adding access key, secret key and session token.

```
#################### 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']
# print(replacement_lines)

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 to the console 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 to the console
        else:
            print(line, end='')

```
### Part 2 - creating aliases
Aliases are shortcuts that you can create in your command line interface to execute a longer command with a shorter keyword. In this case, I have created aliases for the Python script that automates MFA. Here's how to create aliases in your command line interface.

This is just a personal preference. Feel free to modify these as you please.
At the time of writing I'm on a macbook hence i'll be using `.zshrc` to create my aliases.

The first alias I need is sourcing my python environment varible
```
alias source_env='source /Users/devops/Desktop/python_lab/env/bin/activate'
```
The number of aliases you need depends on the number of environments you have. For example, if you have three environments say `test`, `staging` and `prod` the this is how to do it.
```
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 '
```
Notice my script takes in 3 arguments we defined earlier in the python script:

1. `mfa_serial_number` - This is your AWS MFA device arn. (The assumption here is that you have set up MFA in the console). Also note that it's being passed from an environment variable which you can set up in the same file (`.zshrc`) using this line `export PROD_DEVICE='arn:aws:iam::123456789012:mfa/my-device'`

2. `profile_name` - working with different aws account may require you to setup different aws cli profiles. Pass the correct profile name here.

3. `mfa_token_code` - this is the MFA token code from your authenticator app. We'll pass this each time we are calling the aliases.

Finally, to authenticate call the alias with the authentication code following it as follows:
```
mfaTest 123456
```
Finally, depending on the permissions you have you can describe any service to make sure you are in the right AWS account.

##Alternatively:
If you are an admin and have IAM permissions in all the AWS accounts, you can create an IAM role with cross account permissions. This way you can assume an IAM role for whatever account you need.