Skip to content

Instantly share code, notes, and snippets.

@dbazile
Last active December 12, 2019 22:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dbazile/87110f80de086726f50ac2ca0e75e409 to your computer and use it in GitHub Desktop.
Save dbazile/87110f80de086726f50ac2ca0e75e409 to your computer and use it in GitHub Desktop.
aws-mfa -- less painful MFA auth across multiple profiles
#!/usr/bin/env python3
import argparse
import configparser
import json
import subprocess
import os
import arrow
CREDENTIALS_FILEPATH = os.path.expanduser('~/.aws/credentials')
ap = argparse.ArgumentParser()
ap.add_argument('profile')
opts = ap.parse_args()
profile = opts.profile
credentials = configparser.ConfigParser()
credentials.read(CREDENTIALS_FILEPATH)
try:
mfa_serial = credentials[profile]['__mfa__']
except KeyError:
print(f"error: could not read '{profile}.__mfa__' (file={CREDENTIALS_FILEPATH})")
exit(1)
try:
response = subprocess.check_output([
'aws',
'sts',
'get-session-token',
'--profile',
profile,
'--serial-number',
mfa_serial,
'--token-code',
input('\nEnter MFA token: ').strip(),
])
except subprocess.CalledProcessError as err:
print('error: auth failed: {}'.format(err))
exit(1)
except KeyboardInterrupt:
print()
exit(1)
try:
parsed = json.loads(response)
except json.JSONDecodeError as err:
print('error: could not decode auth response: {}'.format(err))
print('---\n{}\n---'.format(response))
exit(1)
session_start = arrow.now().to('US/Eastern')
session_expiration = arrow.get(parsed['Credentials']['Expiration']).to('US/Eastern')
credentials.remove_section('default')
credentials.add_section('default')
credentials.set('default', '__profile__', profile)
credentials.set('default', '__started__', session_start.format())
credentials.set('default', '__expires__', session_expiration.format())
credentials.set('default', 'aws_access_key_id', parsed['Credentials']['AccessKeyId'])
credentials.set('default', 'aws_secret_access_key', parsed['Credentials']['SecretAccessKey'])
credentials.set('default', 'aws_session_token', parsed['Credentials']['SessionToken'])
with open(CREDENTIALS_FILEPATH, 'w') as f:
credentials.write(f)
print('Session started; expires {}'.format(session_expiration.humanize()))

Usage

$ cat ~/.aws/credentials

[foo]
__mfa__ = arn:aws:iam:12:34:56:mfa/bob
aws_access_key_id = ZZZZZ
aws_secret_access_key ZZZZZ


$ ./aws-login.py foo

Enter MFA token: •••••••••••
Session started; expires in 2 hours


$ aws s3 ls

bucket1
bucket2
...
bucketN

Configuring IAM to enforce multi-factor auth

aws iam create-policy \
    --policy-name 'AdministratorAccess-RequireMFA' \
    --policy-document '{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": ["*"],  // probably lock this down more
                "Resource": "*",
                "Condition": {
                    "Bool": {
                        "aws:MultiFactorAuthPresent": "true"
                    }
                }
            }
        ]
    }'

aws iam create-group \
    --group-name AdministratorAccess-RequireMFA

aws iam attach-group-policy \
    --group-name AdministratorAccess-RequireMFA \
    --policy-arn POLICY_ARN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment