Vegard's blog

Getting started with AWS and CLI tools

AWS IAM

In my last blog post I touched briefly on the topic of enforcing MFA for all users and how to still be able to work effectively with the CLI using aws-mfa. I thought a more detailed explanation of how this is set up might be of interest to others, so I decided to write up a short guide of how I did this.

Registering and initial setup of AWS account

The first thing to do is simply to register an account with AWS if you have not already done so. When you first register your account you will register a root user that will be the superuser of the account. When you log in after registering, this is the user you will be logged in with.

The root user has full admin access in addition to having access to the billing information for the account. It is not recommended to use the root user for anything other than for the few tasks that other users will not have access to. In practice it should only be necessary to use the root user very seldomly once everything is set up. It is also recommended to protect the root user through MFA.

To enable this you click the account name in the top right corner and choose "Security Credentials". This leads to the page below, where you can see that I have already created a virtual MFA device. To add this for the first time you simply click add and follow the instructions for setting up the device. AWS also offers several other options such as physical MFA devices, e.g. Yubikey, but for my purposes it is sufficient to use a virtual MFA device on my phone. For more information about available MFA options see the AWS documentation.

AWS Security Credentials

Creating a personal admin user with enforced MFA

With the root user account secure we can move on to create a personal user account that will be used for AWS access through the CLI. To do this we search for and enter the IAM service.

Here we click "Users" in the left hand navigation menu which shows a list of the users registered for the account. For a new AWS account there should not yet be any IAM users.

AWS IAM Users

In my account I currently have a single user, which is the user I use from my local machine. The next step is to add a new user. To be able to generate credentials for the user and access the CLI check the box for programmatic access. If you are simply creating a single user for all-round use like me then you also need to allow AWS console access as well.

AWS IAM User Registration

Click next and proceed to the page where permissions for the user are set. In a production account at a company it is recommended to have groups for different access levels, with the necessary policies attached to the groups. For a personal AWS account where you only plan to create a single user we will instead attach policies directly to the created user.

In my case I wanted to create a user with adminstrator access and chose "Attach existing policies" and chose the managed AWS policy AdministratorAccess.

AWS IAM Attach Policies

With the policy selected we can continue. The next page is used to add tags to the user, which I did not do, and on the final page we can click "Create user". The following page shows information about the created user, including the access key ID, secret access key ID and the password generated for the user. Copy these values and store them securely for later use.

AWS IAM User Created

You can now log out from the root user and log in using the newly created IAM user. To do this it is necessary to specify the account ID during the login process. To view the ID of your account simply click in the top right corner and the account ID is shown. Please note that the dashes (-) in the account ID are not used during the login process.

You can now log out and go to the login page and select "IAM user" as the signin option. Enter the account ID and proceed to log in with the username and password for the newly created user.

AWS Login Page

Once you are logged in the first step is to add an MFA device. This is done just as described for the root user by clicking in the top right corner, then "Security credentials" and adding an MFA device.

Enforcing MFA for IAM users

At this point the root user is secured with MFA and we have a new user with admin access, access credentials and an attached MFA device. When logging in to the AWS web console it is now necessary to provide a code from the MFA device in addition to the username and password for both users.

This might seem to be sufficient, however it is now possible to take the generated access credentials and use the AWS CLI without having to input an MFA code. If the credentials were to get lost an attacker would be able to use the credentials to take down any existing resources and run up large bills by provisioning resources on the account.

To solve this we need to add an additional policy to our newly created user. The way AWS evaluates policies and permissions includes several steps. A simplified explanation is that AWS checks for any Allow or Deny statements for the requested action on the given resource. By default the request is denied, but if there is an explicit Allow statement it will be allowed as long as there is no Deny statement for the action. However, if there is a Deny statement present this will always supersede any Allow statement.

We can enforce MFA by introducing a Deny statement whenever the user has not used MFA to authenticate. This can be done by creating and attaching a policy to the user that denies the necessary actions when MFA is not present. We still want to allow the user to change their password and to create an MFA device in the first place so these will have to be exempted from the policy. The policy looks like this:

{
    "Version": "2012-10-17",
    "Statement": [    
        {
            "Sid": "BlockMostAccessUnlessSignedInWithMFA",
            "Effect": "Deny",
            "NotAction": [
                "iam:CreateVirtualMFADevice",
                "iam:DeleteVirtualMFADevice",
                "iam:ListVirtualMFADevices",
                "iam:EnableMFADevice",
                "iam:ResyncMFADevice",
                "iam:ListAccountAliases",
                "iam:ListUsers",
                "iam:ListSSHPublicKeys",
                "iam:ListAccessKeys",
                "iam:ListServiceSpecificCredentials",
                "iam:ListMFADevices",
                "iam:GetAccountSummary",
                "sts:GetSessionToken"
            ],
            "Resource": "*",
            "Condition": {
                "Bool": {
                "aws:MultiFactorAuthPresent": "false",
                "aws:ViaAWSService": "false"
                }
            }
        }
    ]
}

To add this policy we once again enter the IAM service in the AWS web console. We first choose "Policies" and then "Create Policy". We select "JSON" and paste the policy from above. We continue without adding any tags and name the policy before creating it.

With the policy created we can go back to "Users", click on our newly created user and then press "Add permissions". We add the newly created policy and the user will now have to use MFA for CLI commands as well.

AWS Attach MFA Policy

Installing and testing the AWS CLI

Everything is now set up on the AWS account and we can proceed with the installation of the AWS CLI. This is best described for all platforms on the offical AWS documentation.

Once the CLI is installed we can configure it with the credentials generated for the user we just created. This can be done by running the command aws configure and entering the necessary information as instructed.

Once this is done we can verify the credentials by running the command:

aws sts get-caller-identity

If everything has been done correctly this should output information about the newly created user. With the MFA policy added to the user the credentials added to the AWS CLI should not allow any access beyond changing the password and adding an MFA device. To verify that this is in fact the case we can try to run any command that performs an action the user should not have access to. For example describing all running EC2 instances on the account:

aws ec2 describe-instances

This should result in an error (UnaothorizedOperation) since the user does not have access to perform this action, in which case the MFA policy works as intended.

Installing and configuring aws-mfa

The final step is figuring out how to generate credentials, using MFA, that will allow all actions permitted for the user from the CLI. By default the AWS CLI stores all the necessary configuration files in the ".aws" folder, which is placed in the user's home directory. This includes the "credentials" file, which includes the credentials for different "profiles".

The purpose of this is to make it possible to use the AWS CLI using different access tokens from the same computer, in some cases for different AWS accounts entirely. After installing and configuring the AWS CLI for the first time this should only include the "default" profile with the newly added credentials, which will be used for all AWS CLI calls when the profile is not explicitly specified for the command.

What we want to achieve is to generate and add MFA credentials to a profile in the credentials file. This can be done by getting the ARN of the MFA device configured for the user (under MFA on the Security Credentials page) and running the command:

aws sts get-session-token --serial-number arn-of-the-mfa-device --token-code code-from-token

This returns credentials that can be used to perform all allowed actions for the user. See the official AWS documentation for more information. We could now take the newly generated credentials and add them to a separate profile, which would look something like this:

AWS CLI Credentials

Having done this we could perform any allowed actions, before the MFA token expires, by running AWS CLI commands and adding --profile mfa-profile to the command. Although this works, the maximum session duration for tokens generated through get-session-token last 36 hours, with the default set to only 12 hours. At best this means that we might have to perform this manual step every other day.

The act of generating and adding credentials to the AWS CLI credentials file is fairly simple and my first thought when I came across this problem was to create a script to automate this process. In fact, I made a Python script at work both for generating tokens for MFA sessions and for roles that the user is allowed to assume. This has worked well and is still used in my place of work, but when I looked into it further for my personal projects I discovered an open source command line tool that performs the same job.

The tool is aws-mfa and is written in Python. To use aws-mfa it is necessary to install Python and the Python package manager. We can then install aws-mfa by running pip install aws-mfa. The linked page for aws-mfa provides more information about how it works, both for authenticating with MFA and assuming roles, but for my own personal use I have only needed to authenticate with MFA, which is what I will describe.

The aws-mfa tool distinguishes between long term tokens and short term tokens, where the tokens we have for the newly created user falls in the category of long term tokens. The way aws-mfa is used to generate MFA session tokens is by specifying the long term tokens and MFA device we want to generate short term tokens for. This can be done by altering the AWS credentials file and defining our tokens as long term tokens for aws-mfa to use. We can also specify the MFA device directly in the credentials file, which looks something like this:

AWS MFA Credentials File

This is the setup I currently have running on my local computer and I can simply run aws-mfa to generate a new MFA session. The credentials are added to the default profile in the credentials file, which allows me to use the AWS CLI for my personal user without explicitly specifying the profile. There are plenty of additional possibilities for different use cases that are supported by aws-mfa, which are well documented on the aws-mfa Github page.