Simplified Multi-Factor Authentication for AWS

Cyberattacks thrive and Multi-Factor Authentication (MFA) is a best practice that protects all kinds of online accounts from hacking attempts. MFA requires a second device that will output a confirmation code required to enable a sensitive action such as login. The key reasoning behind MFA is that one of your devices may be hacked, but all of them at once is less probable. Amazon Web Services (AWS) accounts are among the most valuable that can be: You don’t want to give control to your most precious IT assets just because your main laptop was hacked, do you?

Enabling and using MFA within the AWS Management Console is relatively easy and requires 8 simple steps, according to AWS documentation. However, setting up MFA that way is limited to the AWS Console accesses. If you use any Command-Line Interface (CLI) or APIs, the access keys will not be covered by MFA. Sadly, securing those accesses is much more complex. In this article, we explain how to use awless to simplify the usage of MFA for AWS users within an organization. awless is a powerful open-source CLI for AWS; The project was released in February 2017 and quickly gained traction, with more than 3.7k stars on GitHub and praise from key AWS evangelists.

Enabling MFA in just 2 steps

With awless, it is very simple to enable MFA with a Virtual MFA device: A software application generating a time-based unique password, generally running on a smartphone. For example: Google Authenticator [iOS, Android], Authy 2-Factor Authentication [iOS, Android], or the open source FreeOTP [iOS, Android, F-droid].

Let’s start by creating a device:

> awless create mfadevice name=my-user-name

The command above will generate a QR code nicely displayed in the CLI that you can directly scan in your Virtual MFA app as shown in the following example:

Scan the QR-Code directly from your terminal!

Now that we have a MFA device, we need to attach it to a given user, by running the following command and entering two consecutive codes generated by the Virtual MFA:

> awless attach mfadevice
Please specify (Ctrl+C to quit, Tab for completion):
mfadevice.id? [TAB]
mfadevice.id? arn:aws:iam::0123456789:mfa/my-user-name
mfadevice.mfa-code-1? 180492
mfadevice.mfa-code-2? 240185
mfadevice.user? my-user-name
...
Do you want to create a profile for this MFA device in /home/me/.aws/config? [y/N] n

Thanks to awless, it took us just two steps to activate the MFA for the AWS Console. But beware: CLI/API calls are still performed without MFA and since you use awless, you’d rather use the CLI.

Note two things: (1) You need a few permissions to run the command above: iam:CreateVirtualMFADevice and iam:EnableMFADevice. Don’t worry if you don’t have them, we will cover this later. (2) AWS recommends to use the same name for the user and the virtual device, as a user can only be attached to a single MFA device.

In the rest of this article, we will expand the protection to API calls and CLI use. To achieve that, we will create roles with high privileges that can be assumed only when being MFA-authenticated, then demote the privileges given to the actual users authenticating with AWS credentials.

Creating groups, roles and policies without editing a single line of JSON

Imagine we are a super administrator (in group superAdmin) of a large AWS account and we want to enforce MFA for sensitive actions performed through AWS API calls. Not all actions are equal: We want to allow read-only access (for instance listing users with awless list users) without MFA but we want to enforce the use of MFA to create a new user (awless create user).

Technically, we will introduce the policies in the following figure to distinguish between two roles:

  • For a request without MFA, the CLI uses the user’s credentials directly and rely on the IAMReadOnlyAccess policy attached to members of thesuperAdmin group.
  • For a request with MFA, the CLI requires a valid MFA authentication code. Then it uses the CanAssumeSuperAdminMFA policy from the superAdmin group to assume the superAdminWithMFA role. On AWS, the trust policy of the superAdminWithMFA role checks the user is MFA-authenticated before giving temporary credentials. With these new credentials, assuming the superAdminWithMFA role, the user has IAMFullAccess permissions.

The following figure shows an overview of the policies attached to the superAdmin group and the corresponding role (superAdminWithMFA) and how they are used with (green) or without (orange) MFA.

Policies used by janedoe to make requests without (orange) or with (green) MFA

Policy for your own virtual MFA device

With the following awless template, let’s create the policy recommended by AWS to allow each user to manage its own MFA device (create a device, attaching the device, etc.).

Policy allowing a user to create/enable only its MFA device, and to disable/delete it only using the MFA device

Run this template directly from github or store it locally (ManageOwnMFA.aws) and run it with:

> awless run ManageOwnMFA.aws account.id=$(awless whoami --account-only)

(Note that we inline here the use of awless whoami --account-only to get the AWS account ID.)

Groups and corresponding roles

Following AWS best practices, we will use groups and roles to give permissions to users. As presented before, we have a superAdmin group:

> awless create group name=superAdmin

and a role that admins will assume when using MFA:

> awless create role name=superAdminWithMFA conditions=\"aws:MultiFactorAuthPresent==true\" principal-account=$(awless whoami --account-only)

(Note that the trust policy of the role requires the users to be MFA-authenticated in order to assume the superAdminWithMFA role thanks to the aws:MultiFactorAuthPresent==true condition.)

We create the policy allowing to assume the role:

> awless create policy name=CanAssumeSuperAdminMFA action=sts:AssumeRole effect=allow resource=arn:aws:iam::$(awless whoami --account-only):role/superAdminWithMFA

Super administrator’s permissions

The last step is to attach the policies to the appropriate groups or role.

We want users in the superAdmin group to be able to:

  • manage their own MFA devices
> awless attach policy group=superAdmin arn=arn:aws:iam::$(awless whoami --account-only):policy/ManageOwnMFADevice
  • assume the superAdminWithMFA role, if they are MFA-authenticated
> awless attach policy group=superAdmin arn=arn:aws:iam::$(awless whoami --account-only):policy/CanAssumeSuperAdminMFA
  • have a read-only access to IAM, even without MFA
> awless attach policy group=superAdmin service=iam access=readonly

We want the superAdmin users being MFA-authenticated, assuming the superAdminWithMFA role to be able to:

  • have full access to IAM

> awless attach policy role=superAdminWithMFA service=iam access=full

Registering a new employee in a flash

Now that we have built the policies, groups and roles for our users, it is very easy to register a new user, while enforcing MFA. For example, to create a superAdmin named janedoe, run:

> awless create user name=janedoe
> awless attach user name=janedoe group=superAdmin

Generate access keys for CLI/API accesses with

> awless create accesskey user=janedoe

Transmit (ex: using a flash drive) the sensitive credentials to the new user.

If you also want to give access to the AWS Web console, run:

> awless create loginprofile username=janedoe password=PutHereATemporaryPassword password-reset=true

(Note that if you want to automate this process even more, you can create easily create an awless template for user creation.)

For now, the only permissions granted to the newly created user janedoe is to make read-only operations on IAM and create and attach a MFA device for herself. Thus, as detailed at the beginning of this article, janedoe can run herself:

> awless create mfadevice name=janedoe
> awless attach mfadevice
Please specify (Ctrl+C to quit, Tab for completion):
mfadevice.id? [TAB]
mfadevice.id? arn:aws:iam::0123456789:mfa/janedoe
mfadevice.mfa-code-1? 180492
mfadevice.mfa-code-2? 240185
mfadevice.user? my-user-name
...
Do you want to create a profile for this MFA device in /home/me/.aws/config? [y/N] y
Role name or ARN to assume with this MFA device ?
> [TAB]superAdminWithMFA
Enter source profile used to assume role: (default) [Enter]
Enter new MFA profile name: (mfa) [Enter]
...
append to '/Users/jane/.aws/config'? [y/N] y

Now, janedoe can use the mfa profile to assume the superAdminWithMFA role and be authorized to make read-write API calls to IAM, for example:

> awless create user name=otheruser -p mfa

And that’s it! janedoe has sufficient permissions to make read-only requests without MFA. After creating her MFA device, she can use it for full-access to APIs.

Installing awless

Ready to try it yourself?

awless is available through:

You can also easily install completion for bash or zsh.

Check that awless is properly installed by running:

> awless version

Securing your existing account

Now, to make your existing AWS account more secure using awless, follow this steps:

  1. Enable MFA for your account
  2. Create/update the relevant policies/groups/roles (with MFA conditions if necessary) for your use case
  3. Move existing users and the current user into the appropriate groups
  4. Test that permissions are working as expected with MFA before removing permissions from the accounts without MFA

Conclusion

Implementing Multi-factor authentication of Amazon Web Services’s users for CLI/API access can be cumbersome. awless eases the process with powerful scriptable commands while enforcing AWS best practices.

Have a look at the previous article on simplified user management for AWS with awless and many more features on the awless repository!

Limitation to detach the MFA device

The AWS ManageOwnMFADevice policy makes it impossible to securely allow a user assuming a role to use MFA to detach only its MFA device. This policy requires the user to use MFA to detach its MFA device (iam:DeactivateMFADevice). This is good security as it prevents an attacker controlling the user’s credentials — but not the MFA device — to replace the user MFA device and escalade privileges. Through our article, as the user has assumed a role for MFA, AWS could not resolve the ${aws:username} variable in the policy.

You can circumvent that by using the AWS console signed in as the user with MFA and deactivate the MFA device.

As an alternative, we could imagine in awless using sts:GetSessionToken to retrieve MFA temporary credentials for the user, instead of sts:AssumeRole. Not implemented yet in the AWS SDKs, there is also no standard way to specify such a profile in the ~/.aws/config configuration file (c.f. this opened issue).

Written by François-Xavier Aguessy, Henri Binsztok and Simon Caplette.