Using MFA With the AWS API
The awesome and informative Last week in AWS newsletter by Corey Quinn has been around for a few weeks now, with curated AWS announcements, tips, tools and blog posts. If you are responsible for making services run using AWS, you should definitely subscribe. At the bottom of the very first issue there is a quick tip about requiring MFA for API usage, which solves a long standing issue we have had with AWS, namely that while you could require an MFA code to log into the web console, there didn’t seem to be a similar limitation for API access, meaning that having API keys on your laptop was a potential security risk. The tip links to the AWS docs on MFA, with instructions on preventing access to the API unless you entered an MFA code, but there was little on that page about how to actually go about entering said code when using the AWS command line tools (or other tools that use the API, such as terraform). This blog post explains what I discovered and set up in order to make using MFA with the AWS command line pretty delightful.
Restricting API access
First, I’ll go through the setting for restricting access to the API without having first typed an MFA code.
I’m assuming here that you have an IAM user, and that you have already
set up an MFA token (you will be prompted for your MFA code on login to
the web console if this is the case). I’m also assuming that you have set up
access keys and can access the API via the AWS CLI tools (in other words, you
can run something like aws s3 ls
on the command line and it will list your
S3 buckets).
When setting up your IAM user, you will have assigned a policy to it granting it permissions. The policy will look something like this (the policy here is the AdministratorAccess policy, which grants access to everything):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
in order to require MFA, you just add a condition to this policy (if you’re using a predefined IAM policy such as AdministratorAccess then you’ll have to make a copy of it in order to change it) requiring that aws:MultiFactorAuthPresent be true. The AdministratorAccess policy shown above would be changed to look like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
Now, if you apply that policy to a user, and they have no other policies applied to them giving them access, then when they try to access the API they will get an AccessDenied error, unless they type in the MFA code first.
If you wish, you can allow access to certain APIs without MFA by adding additional policies to the user or group, such as ReadOnlyAccess. In my experience however, while this may seem like a good idea on the surface, it’s actually more convenient to just enter your MFA key at the start of the day and use it than it is to have to think about whether your actions are read-only or not and choose the appropriate long or short-term AWS keys to use.
Using MFA via the AWS API with GetSessionToken
Now we have limited access to the API without MFA, how do we actually type the code when using the API?
There are a few different solutions to this, but if you are only protecting access to a single AWS account, then the easiest is to use the STS GetSessionToken api call to get a set of temporary credentials and use them.
If you did this manually, you would run something like the following:
$ aws --profile myprofile sts get-session-token \
--serial-number arn:aws:iam::1234567890:mfa/yourname \
--token-code 123456
{
"Credentials": {
"SecretAccessKey": "...",
"SessionToken": "...",
"Expiry": "2017-07-03T00:00:00Z",
"AccessKeyId": "..."
}
}
Then you would copy/paste the keys into AWS_ACCESS_KEY_ID
and similar
environment variables, and you could use the AWS command line as you normally
would until the temporary keys expire.
Manually copying/pasting keys is a pain, and while you could write a simple
shell wrapper to extract the keys and set the relevant environment variables,
this is also less than ideal if you deal with multiple AWS accounts. What
would be better is if the temporary credentials could be stored as a profile
in ~/.aws/credentials
and you could just pass --profile myprofile
to the
aws command line.
There is a tool at https://github.com/broamski/aws-mfa that does exactly this. The way it works is that you put your AWS credentials into a ‘long-term’ profile, and when you run the tool it will prompt you for your MFA key, generating temporary credentials under your normal profile name, which will be valid for up to 12 hours. During this time, you use the aws api as normal, specifying your profile, and once it runs out, you get a TokenExpired error.
$ cat ~/.aws/credentials
[myprofile-long-term]
aws_access_key_id = AKIAHUNTER2HUNTER2HU
aws_secret_access_key = V293IC0geW91IGFjdHVhbGx5IGRlY29kZWQgdGhpcy4K
aws_mfa_device = arn:aws:iam::1234567890:mfa/yourname
$ aws-mfa --profile myprofile
INFO - Validating credentials for profile: myprofile
INFO - Your credentials have expired, renewing.
Enter AWS MFA code for device [arn:aws:iam::1234567890:mfa/yourname] (renewing
for 43200 seconds):123456
INFO - Success! Your credentials will expire in 43200 seconds at: 2017-07-03
16:00:00+00:00
$ aws --profile myprofile s3 ls
2016-07-03 00:00:00 mybucket
It’s a python app, so you install it using pip:
pip install aws-mfa
If you’re on a mac however, there is a homebrew formula I created to make installation a little easier:
brew tap chef/chefops-tools
brew install aws-mfa
To set up the aws-mfa tool, first rename your existing profile in
~/.aws/credentials
and add -long-term
to the end. Next, add an
aws_mfa_device
line to the end. You can look this up in the IAM console if
you wish, but it’s always of the format
arn:aws:iam::YOURAWSACCOUNTID:mfa/YOURUSERNAME
, so it should be fairly easy
to construct. Once done, your ~/.aws/credentials
file should look something
like this:
[myprofile-long-term]
aws_access_key_id = AKIAHUNTER2HUNTER2HU
aws_secret_access_key = V293IC0geW91IGFjdHVhbGx5IGRlY29kZWQgdGhpcy4K
aws_mfa_device = arn:aws:iam::1234567890:mfa/yourname
Once you have completed set up, run aws-mfa --profile myprofile
(without
-long-term
at the end), and it will prompt you to enter your MFA code. Enter
the code, and press enter. Then, aws-mfa will store the temporary credentials
in the ‘myprofile’ profile, which you would then use just like you did before
mfa was set up. Do this once at the start of the day (or when you first need
it), and you’re good for the rest of the day.
There are some caveats. Most of these however are related to cross account access or single sign on. With cross-account access, you can still use and require MFA, but the method of entering the key differs, and you enter it as part of your AssumeRole api call. The other caveat is if you use SAML. Because you don’t have a single IAM user when using SAML, you can’t register an MFA token, and you’re at the mercy of whatever MFA facilities your SAML provider provides. Sadly, I haven’t yet worked out a good way to use MFA with the AWS API if you use something like Okta for sign in. Maybe that’s a topic for a future blog post.