Access the AWS console through access and secret keys

Posted: | Tags: cloud aws til

Granting a user permission to access resources across AWS accounts is a common task, typically the account with the resources contains an IAM role with the appropriate policy defining the actions the user can perform. In addition to this a trust policy is created that specifies which principal can assume that role in order to perform the allowed actions. Sometimes an external ID is added to the trust policy which verifies the user wanting to assume that role. There are good reasons to use this, which I will refer to the AWS documentation for.

Switching the roles itself is a simple task and can be done via the CLI, API and the AWS console. The AWS Security Token Service (STS) is used to exchange the role for temporary credentials. If you choose to use an external ID you cannot switch roles on the console since it does not support this. You can still assume the role by passing in the external ID through the CLI or API and get the access key, secret access key and session token. These values can be used to then federate and request a sign-in token from AWS, enabling the user to access the console. In effect, allowing you to switch roles with an external ID and use the console.

This post will cover how to switch roles with an external ID and use the console, there’s bits and pieces already on the internet outlining how to do this and the code provided in the documentation didn’t work out-of-the-box so this warrants a write-up. If you do not use an external ID you can instead follow the guide in the AWS documentation.

Create roles and establish trust

Architecture diagram showing the process of requesting and using temporary credentials across AWS accounts.

Architecture diagram showing the process of requesting and using temporary credentials across AWS accounts.

First let’s setup our environment, we have two AWS accounts, account A has resources in our case S3 that a user in account B would like to access. A Role is created called S3FullAccessExampleCorp with the AmazonS3FullAccess permissions policy and the following trust relationship.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "allow_users_roles",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<account-id>:root"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "sts:ExternalId": "abc123"
                }
            }
        }
    ]
}

Keep in mind using :root in a trust policy doesn’t represent the root user but a larger set of principles, refer to the AWS documentation on ways to narrow down a trust policy to your specific principle. The trust policy here also has an external ID which you can use to enter a randomised string.

Run federation script

Now our user from Account B can use the following script to generate a sign-in URL to the console. This is a modified version of the script provided within the AWS documentation to fit this scenario. The full script can be found at the bottom of this post.

First we use AWS STS to assume the role we created in Account A, pass in the role arn, external ID and a session name.

assumed_role_object = sts_connection.assume_role(
    RoleArn="arn:aws:iam::<account-id>:role/S3FullAccessExampleCorp",
    RoleSessionName="AssumeRoleSession",
    ExternalId="abc123"
)

From here we take the temporary credentials and request a sign-in token from the AWS federation endpoint. I’ve modified the calls slightly to dynamically calculate the session duration based on the temporary credentials returned. If a duration longer that what is permitted is entered you’ll encounter a HTML response, instead of the expected JSON, that says “You may have typed the address incorrectly or you may have used an outdated link.”

If no errors are encountered, the sign-in token is returned and the last part of the script creates the sign-in url which is printed to the terminal. This can be opened in the browser and if all has gone smoothly you are now signed in to the AWS account using the assumed role.

Python script

# Adapted from "Enabling custom identity broker access to the AWS console"
# https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html

import json
import datetime
import requests # 'pip install requests'
import boto3

sts_connection = boto3.client('sts')

# Assume role to get temporary credentials
assumed_role_object = sts_connection.assume_role(
    RoleArn="arn:aws:iam::<account-id>:role/S3FullAccessExampleCorp",
    RoleSessionName="AssumeRoleSession",
    ExternalId="abc123"
)

url_credentials = {
    "sessionId": assumed_role_object.get('Credentials').get('AccessKeyId'),
    "sessionKey": assumed_role_object.get('Credentials').get('SecretAccessKey'),
    "sessionToken": assumed_role_object.get('Credentials').get('SessionToken')
}

# Make request to the AWS federation endpoint to get sign-in token.
params = {
    "Action": "getSigninToken",
    "SessionDuration": int(
        (assumed_role_object["Credentials"]["Expiration"] - datetime.datetime.now(datetime.timezone.utc)).total_seconds()
    ),
    "Session": url_credentials
}
request_url = "https://signin.aws.amazon.com/federation"
r = requests.get(request_url, params=params)
# If no error is encountered, returns a JSON document with a single element named SigninToken
signin_token = json.loads(r.text)

# Create URL where users can use the sign-in token to sign in to the console.
params = {
    "Action": "login",
    "Issuer": "Example.org",
    "Destination": "https://console.aws.amazon.com/",
    "SigninToken": signin_token["SigninToken"]
}
r = requests.Request('get', request_url, params=params)
print (r.prepare().url)

Related ramblings