Using Short Lived Credentials with AWS CLI

04 January, 2023 |  Vladimir Djurovic 
img/credentials-key.png

When working with AWS (or any other cloud provider), one of the most important aspects to consider is security. If attackers gains access to your credentials, they can wreak havoc to your account.

One of the most effective measures to protect your AWS account, is to use short-lived credentials used for AWS CLI. In this post, we’ll see how to implement this technique in the simplest way possible.

Table Of Contents

Implementing short lived credentials

Usual way to implement short lived AWS credentials is to use identity federation services to let users login and then use the obtained token to assume required role. This works well on large scale, ie. for organizations which have large amount of users. For small organizations, it can be an overkill and add complexity for managing it.

Fortunately, we can implement short lived credentials in simple way using only AWS CLI , roles and permissions. Let’s see how it works.

Prerequisites

You will need the following to implement this flow:

  • Access to AWS account with permissions to create users, roles and IAM policies

  • AWS CLI

  • jq (for JSON manipulation)

Create execution role

First thing we need to do is to create a role to be assumed. We can do this in AWS management console. Navigate to IAM > Roles > New Role. Paste the following JSON into the editor:

 1{
 2    "Version": "2012-10-17",
 3    "Statement": [
 4        {
 5            "Sid": "Statement1",
 6            "Effect": "Allow",
 7            "Principal": {
 8                "AWS": "arn:aws:iam::123456789012:user/some-user"
 9            },
10            "Action": "sts:AssumeRole"
11        }
12    ]
13}

This creates a trust policy which allows a principal to assume a role. In this case, principal is an AWS user with ARN arn:aws:iam::123456789012:user/some-user. You can have multiple principals here, so you can have multiple users assume a role.

Once the role is created, make a note of it’s ARN. You will need it to assume a role.

Attach a permissions policy to role

The role has no permissions at the moment, so it can’t really do anything useful. Let’s create a permissions policy so the role has access to S3.

Navigate to IAM > Policies > Create policy. Paste the following JSON in the editor:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": "*"
        }
    ]
}

Assume role from CLI

You need to have access keys for the user specified as principal in a policy. You can verify the current user identity using the following command:

aws sts get-caller-identity
{
    "UserId": "XXXXXXYYYYYYYZZZZZ",
    "Account": "123451234677",
    "Arn": "arn:aws:iam::123451234677:user/some-user"
}

This output says that current user is some-user. This is indeed the principal listed in the role trust policy.

In order to assume the role, run the following command:

aws sts assume-role --role-arn arn:aws:iam::538219348660:role/dev-access-role --role-session-name test-role --duration 7200
{
    "Credentials": {
        "AccessKeyId": "ASIAX2UC7QK2M6LGBLFU",
        "SecretAccessKey": "OiYbKUQf8h4T9Hiq5Enpis16QSvPpa05DYrC1v7Q",
        "SessionToken": "IQoJb3JpZ2luX2VjEL3//////////wEaDGV1LWNlbnRyYWwtMSJHMEUCIAPUELN3SC5NYLJ/CtBNyGOsk9yMhIemGgDMaUvXsXgEAiEAk0u9twHbYfqaPNaE7SWtH+v0zGkLJxDZglNDeXzjLMcqlgIIFhADGgw1MzgyMTkzNDg2NjAiDKV9XkCmre12+29xtirzAd8HkFsGLOKVEU07bVD1eReWm9yWDH1s3Bs7loD+7ZkBGMvdV4Ri8XtaWoQ6m3vLzmPctaOAzprKEIwhxOQmtSoPLVoDYuIeqHHCONW/yZTxClC5heI4PIt4iQ7wP2eYV0GFa47C25rkaCgQEvHm9nmF65L2g8YnVzYnlzwfAi4TLoVREXSo0QdFtgpzJD5yRiTNDQTnDHb7RSMuV5XskZlsZLiKDcHncKNpDK7k5gclNCXjlJ5RQzRl+TJ9pMJVWygDdmUZ/DHJhQwliXOP1UC1f3KIFlrqhRMO00WysGzn/XWlRAj1rWnuY4nJLkivhOCtyDCtmtudBjqdAbR2HKJ0yQ07iihzBG8X81BQWE6M4sid0WjAuXYYVwIld/OBTMD62zpoOfAPnFbI0rCttZyZIG0lBi3pj50mY0tzSvQG9x+4QzKdv7jEgzx7wyJMbYzhSn1RogiZTcPG2lusruP9VWvibQvYnJvPiVKByn+YAV/GAHx7YovLHE2QpvYgLVnqtij0ljC62jiv2ojJRn8a6dey8RhqOQw=",
        "Expiration": "2023-01-05T14:14:21+00:00"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROAX2UC7QK2ERFCCTSOZ:test-role",
        "Arn": "arn:aws:sts::123456123456:assumed-role/test-role/test-role"
    }
}

In this command, we specify the following parameters:

  • --role-arn - ARN of the role we created in the first step

  • --role-session-name - descriptive name of the current role

  • --duration - specifies how long the credentials are valid. By default, duration is 1 hour

The output of this command is the JSON with credentials for assumed role.

In order to use new credentials, the simplest way is to export them as environment variables.

export AWS_ACCESS_KEY_ID=ASIAX2UC7QK2M6LGBLFU
export AWS_SESCRET_ACCESS_KEY=xxxxxxxxxxxxxx...
export AWS_SESSION_TOKEN=xxxxxxxxxxxx....

If you take a look at the variable names, you can see that they are capitalized, snake-case versions of the JSON fields.

If you now try to get caller identity, you will notice that it is now role identity.

aws sts get-caller-identity
{
    "UserId": "AROAX2UC7QK2ERFCCTSOZ:test-role",
    "Account": "1223451222",
    "Arn": "arn:aws:sts::1223451222:assumed-role/test-role/test-role"
}

Each command you run will be executed with role identity.

Protect credentials with MFA

Now we have short lived credentials, but it’s not much of an improvements, as you may have noticed. Who ever gains access to our credentials will be able to assume a role and run any command in our behalf.

What we can do is require that principals who assume the role must use MFA (Multi Factor Authentications). This adds another layer of security.

To require a MFA, we need to alter our role:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Statement1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::538219348660:user/dev-cli-user"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": [
                        "true"
                    ]
                }
            }
        }
    ]
}

Adding this condition, only principals who use MFA will be allowed to assume role.

This requires us to enable MFA for our principal. I won’t get into details on how to enable MFA, but you can find detailed guide in AWS documentation.

Now, in order to assume the role, we need to specify MFA information in the call:

aws sts assume-role \
 --role-arn arn:aws:iam::123456789012:role/test-role \
 --role-session-name ExampleSession \
 --serial-number arn:aws:iam::123456789012:mfa/some-user \
 --token-code 123456

We have 2 new parameters here:

  • --serial-number - the ARN of MFA device we created for user

  • --token-code - one-time code generated by the application

Now you can assume the role without problems.

Allow groups to assume a role

As the number of users you manage grows, adding a principal for each user can get cumbersome. A way to fix this is to add users to a group, and then allow a group to access the role. Since we can’t use group as a principal, we need to add this permissio to a group.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::123456789012:role/ExampleRole",
      "Effect": "Allow"
    }
  ]
}

Now all users who belong to a group can assume the role.

Automating process with a script

Typing commands and exporting variables can get tedious and error prone. Fortunately, we can automate things with a simple script.

This is the simple script that assumes the role and exports a script to setup environment variables:

#! /bin/bash

ROLE_ARN=arn:aws:iam::538219348660:role/dev-access-role
MFA_SERIAL=arn:aws:iam::538219348660:mfa/google-auth
DURATION=14400
RESPONSE=/tmp/assume-role-response
ROLE_CREDS_FILE=./role-creds.sh

if [ -z $1 ]; then
  echo "Usage: assume-dev-role token-code [duration (optional)]"
  exit
fi

if [ ! -z $2 ]; then
    DURATION=$2
fi

TOKEN_CODE=$1

unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN

$(aws sts assume-role --role-arn $ROLE_ARN --role-session-name test-role --duration $DURATION --serial-number $MFA_SERIAL  --token-code $TOKEN_CODE > $RESPONSE)

ACCESS_KEY_ID=$(jq -r .Credentials.AccessKeyId $RESPONSE)
SECRET_KEY=$(jq -r .Credentials.SecretAccessKey $RESPONSE)
SESSION_TOKEN=$(jq -r .Credentials.SessionToken $RESPONSE)

cat << EOF > $ROLE_CREDS_FILE
export AWS_ACCESS_KEY_ID=$ACCESS_KEY_ID
export AWS_SECRET_ACCESS_KEY=$SECRET_KEY
export AWS_SESSION_TOKEN=$SESSION_TOKEN
EOF

rm $RESPONSE

You call the script with token code generated by MFA as argument:

./assume-role-script 123456

Script will generate a file called role-creds.sh. You need to source this file to set the required environment variables

source role-creds.sh

Now you have everything setup with assumed role.

Pros and cons of using short lived AWS credentials

Short lived credentials are certainly useful in most situations, but they also have drawbacks. Lets see some pros and cons of using them.

The pros include

  1. Improved security: Short-lived credentials reduce the risk of unauthorized access to resources, as they expire after a short period of time and cannot be reused once they have expired. This can help prevent the unauthorized use of stolen credentials, as well as accidental data leaks due to the use of forgotten or stale credentials.

  2. Reduced attack surface: By using short-lived credentials, you can limit the time frame in which an attacker could potentially use stolen or compromised credentials to access resources. This can help reduce the overall attack surface of your AWS environment.

  3. Easier to manage: Short-lived credentials can be easier to manage than long-lived credentials, as they do not need to be rotated or managed as frequently. This can help save time and effort in the credential management process.

The cons include:

  1. Additional complexity: Implementing short-lived credentials may require additional steps and complexity, such as integrating with the AWS STS API or writing code to assume a role and generate the temporary credentials.

  2. Reduced convenience: Short-lived credentials may not be as convenient as long-lived credentials, as they require more frequent rotation and may need to be re-assumed more frequently. This can add additional overhead to the credential management process.

  3. Potential for disruption: If the process for generating and using short-lived credentials fails or is interrupted, it could disrupt access to resources and impact the availability of your applications. This is especially true if the credentials are required for critical tasks or are used frequently.

Conclusion

In this post, we’ve seen how to use short lived credentials. This can significantly improve security of your AWS account, and you should seriously consider using it.

As always, feel free to leave any questions or comments in the comments section.