#!/usr/bin/env python3
import argparse
import subprocess
import time
import os
from os.path import expanduser, exists
import boto3
import configparser
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("action", choices=["configure", "session", "otp"])
args = parser.parse_args()
return args
"""
Finds the name of a preconfigured MFA device
"""
def get_mfa_device():
home = expanduser("~")
mfa_device_file = home+"/.aws/.mfa_device"
if not exists(mfa_device_file):
print("You must configure an mfa device prior to using this function")
exit(3)
f = open(mfa_device_file, "r")
mfa_device = f.read().strip("\n")
f.close()
return mfa_device
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
"""
Checks if a binary is in the PATH
and returns the full path if it is
"""
def which(program):
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
def read_aws_config(file):
config = configparser.ConfigParser()
config.read(file)
return config
def get_session_token():
home = expanduser("~")
credentials_file = home+"/.aws/credentials"
orig_credentials_file = home+"/.aws/credentials.orig"
if not exists(orig_credentials_file):
print("Backing up your original credentials file")
config = read_aws_config(credentials_file)
with open(orig_credentials_file, 'w') as configfile:
config.write(configfile)
proc = subprocess.Popen(
['ykman', 'oath', 'accounts', 'code', get_mfa_device()], stdout=subprocess.PIPE)
token = str(proc.communicate()[0].decode('utf-8').split()[-1])
config = read_aws_config(credentials_file)
orig_config = read_aws_config(orig_credentials_file)
client = boto3.client('sts', aws_access_key_id=orig_config['default']['aws_access_key_id'],
aws_secret_access_key=orig_config['default']['aws_secret_access_key'])
response = client.get_session_token(
DurationSeconds=28800, SerialNumber=get_mfa_device(), TokenCode=token)
config['default']['aws_access_key_id'] = response['Credentials']['AccessKeyId']
config['default']['aws_secret_access_key'] = response['Credentials']['SecretAccessKey']
config['default']['aws_session_token'] = response['Credentials']['SessionToken']
with open(credentials_file, 'w') as configfile:
config.write(configfile)
return
"""
Used to configure a new yubikey as a virtual MFA
device for AWS. Produces an error if more than one yubikey is present
or if the yubikey doesn't support oath!
"""
def configure_new_device():
f = filter(None, subprocess.run(
['ykman', 'list'], capture_output=True).stdout.decode('utf-8').split("\n"))
keys = list(f)
if len(keys) != 1:
print("You must have exactly one yubikey inserted to configure a new virtual mfa device")
exit(2)
account_number = input("Enter your AWS account number: ")
iam_username = input("Enter your IAM username: ")
secret_key = input("Enter the secret key provided by AWS: ")
oath_account_string = "arn:aws:iam::{account_number}:mfa/{iam_username}".format(
account_number=account_number, iam_username=iam_username)
subprocess.run(['ykman', 'oath', 'accounts', 'add',
'-t', oath_account_string, secret_key])
print()
print("We're going to generate a few OTPs now to pass back to AWS, please press your YubiKey when prompted")
x = 0
while x < 3:
print()
subprocess.run(['ykman', 'oath', 'accounts',
'code', oath_account_string])
time.sleep(10)
x = x + 1
print()
print("You should have at least two subsequent time based codes, go enter them in the AWS management console")
print("To get an OTP on a one-off basis run 'ykman oath accounts code {oath_account_string}'".format(
oath_account_string=oath_account_string))
home = expanduser("~")
f = open(home+"/.aws/.mfa_device", "w")
f.write(oath_account_string)
f.close()
return
def get_otp():
proc = subprocess.run(
['ykman', 'oath', 'accounts', 'code', get_mfa_device()])
return
def main():
args = parse_args()
ykman = which('ykman')
if ykman is None:
print("You must have 'ykman' in your path prior to using this script")
exit(1)
if args.action == "configure":
configure_new_device()
elif args.action == "otp":
get_otp()
elif args.action == "session":
get_session_token()
return
if __name__ == '__main__':
main()