Using Multi-Factor Authentication (MFA) with AWS

Some of our team members are required to use multi-factor authentication (MFA) with AWS while others are not. This is due to some administrative quirks and changes in the onboarding process over time. Everybody uses MFA to sign in to the console, but certain groups of users have an IAM policy that explicitly denies all actions without MFA except for a handful of user details. This discrepancy between users caused confusion, so recently I set out to improve that experience.

My ideal solution looks something like this:

Minimal developer interaction

  • ✅ Developers are prompted for an MFA token when it’s needed.
  • ❌ Developers must remember to authenticate at the start of a session.

Minimal developer knowledge of the system

  • ✅ Developers can run any script or operation and be confident their credentials are valid.
  • ❌ Developers need to know which scripts and tools interact with AWS systems.

Works for both the command-line interface and locally running SDKs.

  • ✅ After initial authentication, everything works seamlessly.
  • ❌ CLI-only solutions that require extra bloat for running AWS SDKs locally.

AWS offers a few approaches to this problem. Spoiler alert: none of AWS’ methods fit my ideal solution. In the remainder of this post, I’ll briefly describe those options and where they fall short. Then I’ll show what I’ve settled on that feels good enough for now.

AWS Option 1: Temporary Session Profile

This authentication strategy requires setting up your IAM user’s access key in the credentials file (~/.aws/credentials). Then you can write a script that uses aws sts get-session-token to retrieve temporary credentials with your MFA token and either store them in AWS credentials’ environment variables or configure a secondary profile in the credentials file. Configuring a secondary credential file can be done with commands like aws configure set aws_secret_access_key .... This AWS knowledge center post describes more details of this approach.

This method has several drawbacks in my eyes. First, it requires a lot of developer interaction and overhead to remember when and how to authenticate. The environment variable route is especially disappointing because it only exports the credentials for the current terminal session. I’ve also been advised to avoid storing temporary credentials by reading this very informative post.

AWS Option 2: Assuming a Role with MFA

Another strategy that avoids permanently storing temporary credentials is to allow the AWS CLI to cache a role session for you as inspired by these docs. The AWS CLI can configure profile credentials that automatically assume a role. If this role requires MFA it will automatically prompt for a token! This is accomplished by setting up a role that requires MFA and shares the same permissions as your IAM user or user group. Then simply set up your credentials file as shown below. Now, when you try to use the CLI, if you aren’t authenticated, you’ll be prompted for an MFA token.

[my-iam-user]
aws_access_key_id = MY_KEY_ID
aws_secret_access_key = MY_SECRET_KEY

[my-role]
source_profile = my-iam-user
role_arn = arn:aws:iam::acctid123:role/my-new-role
mfa_serial = arn:aws:iam::acctid123:mfa/my-mfa-arn

This approach feels perfect for AWS CLI interaction but left me a little uncomfortable with the SDK requirements. We’re using several IAM-authorized services. This means signing requests with AWS Signature V4 with a default credential provider capable of pulling AWS credentials from the current environment when running locally. For the SDK running locally to pull these role-based credentials, we’d need to update this mechanism, giving it the ability to assume a role. This felt like substantial bloat in production code just to support our auth strategy when running locally.

AWS Option 3: Automated Credential Processes

I was optimistic when I ran across the documentation for the credential_process. This credentials file property allows you to configure a script that sets the credentials environment anytime the profile is invoked. A transparent script that automatically sets the credentials sounds exactly like what I want! I wrote a script for retrieving a user’s credentials and prompts for the MFA token and that then uses STS to fetch and set credentials. I tried it out and. . . nothing.

[my-profile]
credential_process = aws_mfa_script.sh

Why aren’t I being asked for an MFA token? It turns out, a seemingly undocumented detail about credential_process is that it doesn’t allow running interactively, so no stdout or stdin. Maybe this could still work out if everybody on the team used something like the 1Password CLI for their MFA tokens. But then again, that approach would require developers to remember to log in to that service first, right back where we started…

My Good Enough Solution

After all this research, I realized my ideal solution might be wishful thinking. I’ll need to roll my own creative strategy and potentially relax some of my criteria. I think the easiest part to ease is making the process more developer-interactive.

I used everything I learned throughout this research to write a script that prompts a user for an MFA token if it’s needed and set a temporary user session in the credentials file. This essentially boils down to a refined version of Option 1 above.

Then, to improve the developer ergonomics, I hooked into the direnv scripts all our repositories have. This process checks if a developer’s session is valid and prompts them with how to run the script if they need to at the start of every session. I check the validity of the session with aws sts get-caller-identity ....

This strategy isn’t perfect, but it has the benefit of informing users of their current status for every session. The next step toward a better solution would be convincing the team to adopt a tool like AWS Vault or AWSume.

Conversation

Join the conversation

Your email address will not be published. Required fields are marked *