AWS Security - Securing Your Use of the AWS CLI and Automation Tools

Fact: Hackers would love to get hold of your username and password or access key and secret access key and run up a big bill running a crypto mining operation on EC2. In this post we'll look at two ways to protect yourself when running commands from AWS CLI or automation tools from your personal computer.

We will not cover security best practices on EC2 itself but good practices on your own pc.

We'll be using the scenario that:

  1. You are not using your root account user
  2. You are using an IAM user with an access key and access secret key

Strategy 1 - Assume Role + MFA

This is a nice strategy for when you tend to use the AWS CLI a lot from your pc. It basically consists of creating an IAM user with MFA enabled, with a single permision: to assume a role that has the permissions you want. The role can only be assumed when an MFA code is provided.

When you assume the role you are granted temporary credentials that give you access as that role. This limits your exposure greatly.

Step 1 - Create a role

This role will have the permissions that you need to get your work done. That might be full administrator access or a hand-crafted policy with only the permissions you need.

Go to Roles and click "Create a role". Select "Another AWS account. Belonging to you or 3rd party". A good practice is to have a separate account for your users and another for your infrastructure. But if you just have a single account just enter your account number. Check the Require MFA checkbox.

Next up you need to attach a policy with the permissions you need. Name your role.

Step 2 - Create a Policy

This policy will have only one permission: sts:Assume role where the resource is the role you just created.

Give it a name.

Step 3 - Create an IAM User

Create yourself an IAM user and attach the policy you just created. Also enable MFA on your user and add it to your Google Authenticator.

Step 4 - Configure AWS Profile

In the .aws directory, we'll need to add the access key and secret access key and the assume role information to your to the credentials file.

Create two profiles. One for your IAM user with their access keys. The second for your role. Make sure you add your mfa arn!

[myuser]
aws_access_key_id = <my access key>
aws_secret_access_key = <my secret access key>

[myrole]
role_arn = arn:aws:iam::123456789101:role/my-role
source_profile = myuser
mfa_serial = arn:aws:iam::123456789101:mfa/my-user
region = eu-west-1

Step 5 - Test It Out!

First let's make sure that we can't access anything with our IAM user alone.

$ aws s3 ls --profile myuser
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied

Great! Now let's check that when we use a role profile it will ask for our MFA code!

$ aws s3 ls --profile myrole
Enter MFA code for arn:aws:iam::123456789101:mfa/myuser: 
<list of buckets here>

Temporary credentials are now cached by the AWS CLI at ~/.aws/cli/cache in a json document. The credentials have a validity of one hour. Ensure that you protect the .aws directory from access by other user accounts.

The benefit of this approach is that you have no long-lived credentials that give the bearer full programmatic access to your AWS account. The long-lived credentials only allow you to assume a role when providing an MFA code, which an attacker will not have. Your vulnerability is that for one hour you have cached temporary credentials, but the exposure level of short-lived credentials is much lower.

Strategy 2 - IAM User with Enforced MFA (Tool Friendly)

Some automation tools (I am thinking of Terraform right now) do not play well with MFA. So if you use automation tools from your local PC then this strategy is a good one.

This strategy consists of creating an IAM user and attaching a policy with the permissions you need. This policy however will also have a condition that says to perform any action you must be MFA authenticated.

When it comes time to use the user, you request a temporary credential, using an MFA code, and then use that temporary credential to do your work.

Step 1 - Create a Policy

The below policy gives full admin access under the condition that the user be authenticated with MFA. The user cannot perform any action without MFA.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        }
    ]
}

Step 2 - Create a User

Create a new user with programmatic access and MFA enabled. Attach the policy.

Step 3 - Usage - GetSessionToken

The idea is to get temporary credentials, passing an MFA code. Those credentials contain the MFA context and you can use them without further need for MFA. The credentials are only valid for a short period.

One way you can do that is to run a script that loads the temporary credentials into environment variables that your tool needs.

Create the following script and call it get-creds.sh.

```
#!/bin/bash

if [ $# -eq 1 ]; then
    CREDS=$(aws sts get-session-token --duration-seconds 3600 --serial-number arn:aws:iam::123456789101:mfa/my-user --token-code $1 --profile my-user)
    export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r .Credentials.AccessKeyId)
    export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r .Credentials.SecretAccessKey)
    export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r .Credentials.SessionToken)
    echo "Temporary credentials attained for 1 hour"
else
    echo "Pass your mfa token as an argument"
fi
```

When you want to run a tool like Terraform, first you run the script, then the tool.

source ~/path/to/get-creds.sh 123456
terraform apply

Replace the 123456 code with your real MFA code.

Conclusion

Don't store long-lived access keys that provide full access to your account without the need for MFA. Always use MFA and try to perform all actions via temporary credentials. You don't want a $30000 AWS bill because a hacker managed to start up a few thousand EC2 instances for running a Bitcoin miner!