You need more than one AWS account: AWS bastions and assume-role

Graham Jenson
The Coinbase Blog
Published in
4 min readOct 17, 2017
Bastion at Castel Sant’Angelo

You need more than one AWS account. This is to isolate production resources, manage limits (especially API rate limiting), handle costs, simplify compliance and security concerns, and restrict user access.

However, managing multiple AWS accounts can be difficult. To help with this AWS has built many useful features like organizations, consolidated billing, VPC peering, and descriptive IAM policies. Using these features effectively to structure and connect AWS accounts, a variety of patterns have been developed. One such pattern used by Coinbase is the AWS Bastion account.

AWS Bastion

A bastion account stores only IAM resources providing a central, isolated account. Users in the bastion account can access the resources in other accounts by assuming IAM roles into those accounts. These roles are setup to trust the bastion account to manage who is allowed to assume them and under what conditions they can be assumed, e.g. using temporary credentials with MFA.

Here is a basic example of how to set up a bastion account with an id 0987654321098 and a “production” account with the id 123456789012.

Bastion account users assume read role into production and development accounts

In the production account create a role called read with the trust relationship:

{
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::0987654321098:root" },
"Action": "sts:AssumeRole”,
"Condition": {
"Bool": {
"aws:SecureTransport": "true",
"aws:MultiFactorAuthPresent": "true"
},
"NumericLessThan": {
"aws:MultiFactorAuthAge": "43200"
}
}
}
]
}

The conditions aws:MultiFactorAuthPresent and aws:MultiFactorAuthAge force the use of a recent (within 12 hours) MFA token. This policy can also include other conditions like IpAddress to limit the IP addresses the role can be assumed from.

In the bastion account, create a IAM group called assume-read with the policy:

{
"Statement": [
{
"Effect": "Allow",
"Action": [ "sts:AssumeRole" ],
"Resource": [ "arn:aws:iam::123456789012:role/read" ],
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true",
"aws:SecureTransport": "true"
},
"NumericLessThan": {
"aws:MultiFactorAuthAge": "43200"
}
}
}
]
}

Bastion users that have the assume-read group attached can now assume the read role into the production account. You can test this by selecting “Switch Role” in the AWS management console, as shown below:

assume-role

Switching roles is pretty easy in the AWS management console, but the command line is a different story. Assuming a role with the AWS CLI requires a few steps. First, an API call with the MFA token is made to the bastion account to create temporary credentials. Then, an API call using the new credentials is made to the other account to assume the role. This can get tedious quickly as it has be redone every time the temporary credentials expire.

At Coinbase we try to use the CLI as much as possible because it makes tasks easier to automate across our many AWS accounts. To simplify assuming roles through our bastion account using temporary credentials and MFA, we built and open-sourced the assume-role tool. assume-role can be used like this:

This GIF uses a prompt to make sure you are on the right account in the correct role:

# AWS ACCOUNT NAME
function aws_account_info {
[ "$AWS_ACCOUNT_NAME" ] && [ "$AWS_ACCOUNT_ROLE" ] && echo "%F{blue}aws:(%f%F{red}$AWS_ACCOUNT_NAME:$AWS_ACCOUNT_ROLE%f%F{blue})%F$reset_color"
}
# )ofni_tnuocca_swa($ is $(aws_account_info) backwards
PROMPT=`echo $PROMPT | rev | sed 's/ / )ofni_tnuocca_swa($ /'| rev`

assume-role can be installed with brew:

brew tap coinbase/assume-role
brew install assume-role

or curl:

curl https://raw.githubusercontent.com/coinbase/assume-role/master/install-assume-role | bash

Pull requests and issues that make assume-role better and more secure are always welcome.

Summary

AWS is moving in a direction where you must have multiple accounts and this is increasing the surface area for security issues as access to them must be managed. Using an AWS bastion account to manage access and forcing users to have temporary credentials with MFA is recommended but can be difficult to use. Tools like assume-role can make it easier to use the bastion while being secure.

Thanks to Jack, Shane, Luke, Yuliya, Rob for help in crafting this post.

Links

--

--

Graham Jenson
The Coinbase Blog

New Zealand Programmer, Dad, DevOps, Data, Scale Everything