DEV Community

Cover image for Mastering Multiple AWS Accounts with AWS CLI: Navigating Role Delegation, MFA, and Automated Login Scripts
Frankovskyi Bogdan
Frankovskyi Bogdan

Posted on • Edited on

Mastering Multiple AWS Accounts with AWS CLI: Navigating Role Delegation, MFA, and Automated Login Scripts

In my role as a platform engineer, I often work with multiple AWS accounts. This can get tricky, especially when setting up Terraform or writing scripts for different account resources. I've learned some helpful tips and tricks along the way, which may be helpful to you.

For multiple accounts access role delegation is usually used. This is especially helpful, because it is easy to manage, it adds required granularity of access to the different resources and allows user to have only one pair of secrets on computer. It is also easy to configure for AWS CLI and it is supported by Terraform.

It is a bit more difficult to configure when Multi-Factor Authentication (MFA) is enabled for the additional security. MFA is great for security - it makes sure this is you who are trying to use credentials, not anyone else uses stolen creds. On practice, it means you need to enter some One-Time Password (OTP) code generated for you by some program (like Google Authenicator) or by separate hardware (like Yubikey or Protectimus). There are other solutions on a market, but I have experience with these only.

For OTP I usually use Google Authenicator but using it with some short-lived session is very annoying - you have to type code from phone every time. For these cases I use Yubikey to generate OTP codes, or I use 1Password when I can't use Yubikey. 1Password is a bit less secure option for this case - because it can be used if someone get access to your computer and have your password, while with GA or hardware token attacker must have access to them too.

Once you have it configured, let's start from configuring AWS CLI and login script. Login script will generate temporary credentials which may be used to access all other account. The flow of it is simple:

  • Retrieve new set of temporary credentials using sts get-session-token AWS CLI command using credentials from account where user is created
  • Set temporary credentials to ~/.aws/credentials profile
  • use this profile as source_profile with multiple AWS profiles

Image description

First of all, you need to add profile where your user was created, and credentials to it:

~/.aws/config

[profile account-where-user-congured]
region = us-east-1
Enter fullscreen mode Exit fullscreen mode

~/.aws/credentials

[account-where-user-configured]
aws_access_key_id        = *******************
aws_secret_access_key    = *********************
Enter fullscreen mode Exit fullscreen mode

Add empty profile where generated credentials will be stored.
~/.aws/credentials

[account-where-user-configured]
aws_access_key_id        = *******************
aws_secret_access_key    = *********************

[root-mfa]
Enter fullscreen mode Exit fullscreen mode

and, of course, add other accounts where user have access with assumed role:
~/.aws/config

[profile account-where-user-configured]
region = us-east-1
output = json

[profile some-account-you-can-assume-role-in]
region = us-east-1
source_profile = root-mfa
role_arn = arn:aws:iam::0123456789:role/my-awesome-role

[profile some-other-account-you-can-assume-role-in]
region = us-east-1
source_profile = root-mfa
role_arn = arn:aws:iam::0123456789:role/my-awesome-role
....
Enter fullscreen mode Exit fullscreen mode

Now, let's make script to retrieve temporary credentials (based on script from here Script to generate AWS STS token · GitHub).

awslogin.sh

#!/bin/bash
#
# Sample for getting temp session token from AWS STS
#
# aws --profile youriamuser sts get-session-token --duration 3600 \
# --serial-number arn:aws:iam::012345678901:mfa/user --token-code 012345
#
# Based on : https://github.com/EvidentSecurity/MFAonCLI/blob/master/aws-temp-token.sh
#

set -e
AWS_CLI=`which aws`

if [ $? -ne 0 ]; then
  echo "AWS CLI is not installed; exiting"
  exit 1
else
  echo "Using AWS CLI found at $AWS_CLI"
fi

AWS_USER_PROFILE=account-where-user-configured
AWS_2AUTH_PROFILE=root-mfa
ARN_OF_MFA=arn:aws:iam::account-where-user-configured-id:mfa/yubikey-mfa-arn-name
DURATION=129600

read AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN <<< \
$( aws --profile $AWS_USER_PROFILE sts get-session-token \
  --duration $DURATION  \
  --serial-number $ARN_OF_MFA \
  --token-code $(ykman oath code -s $ARN_OF_MFA) \
  --output text  | awk '{ print $2, $4, $5 }')

if [ -z "$AWS_ACCESS_KEY_ID" ]
then
  exit 1
fi

`aws --profile $AWS_2AUTH_PROFILE configure set aws_access_key_id "$AWS_ACCESS_KEY_ID"`
`aws --profile $AWS_2AUTH_PROFILE configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"`
`aws --profile $AWS_2AUTH_PROFILE configure set aws_session_token "$AWS_SESSION_TOKEN"`
Enter fullscreen mode Exit fullscreen mode

Note $(ykman oath code -s $ARN_OF_MFA) line - this is where ykman will ask you to touch Yubikey. That pretty much it. Now you can login just once and use different AWS profiles without need to re-login.

⋊>./awslogin.sh 
Using AWS CLI found at /home/linuxbrew/.linuxbrew/bin/aws
Touch your YubiKey...
⋊>aws s3 ls --profile some-account-you-can-assume-role-in
# some buckets will be shown
# you can simultaneously use it for all other accounts you can assume role in
# without need to switch to it or use env variables
⋊>aws s3 ls --profile some-other-account-you-can-assume-role-in
# some buckets will be shown
Enter fullscreen mode Exit fullscreen mode

Configuration with 1Password OTP generation is similar — but instead of ykman op should be used to retrieve a OTP code. If we are going to use 1Password for auth anyways, it is reasonable to get rid of the local secret for account-where-user-configured stored in plain text in ~/.aws/credentials.

Set up credentials in 1Password and remove [profile account-where-user-configured] from ~/.aws/credentials and ~/.aws/config.
Credentials for accounts where user is set up will be taken from 1Password and used in script instead of ones in ~/.aws/credentials. We also will use OTP generated by 1Password

#!/bin/bash
#
# Sample for getting temp session token from AWS STS
#
# aws --profile youriamuser sts get-session-token --duration 3600 \
# --serial-number arn:aws:iam::012345678901:mfa/user --token-code 012345
#
# Based on : https://github.com/EvidentSecurity/MFAonCLI/blob/master/aws-temp-token.sh
#

set -e
AWS_CLI=`which aws`

if [ $? -ne 0 ]; then
  echo "AWS CLI is not installed; exiting"
  exit 1
else
  echo "Using AWS CLI found at $AWS_CLI"
fi

AWS_USER_PROFILE=account-where-user-configured
AWS_2AUTH_PROFILE=root-mfa
ARN_OF_MFA=arn:aws:iam::account-where-user-configured-id:mfa/yubikey-mfa-arn-name
DURATION=129600

read AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN <<< \
$(env AWS_ACCESS_KEY_ID=$(op read "op://Private/<your-account-secret>/access key id") \
  AWS_SECRET_ACCESS_KEY=$(op read "op://Private/<your-account-secret>/secret access key") \
  aws sts get-session-token \
  --duration $DURATION  \
  --serial-number $ARN_OF_MFA \
  --token-code $(--token-code $(op read "op://Private/<your-account-secret>/one-time password")) \
  --output text  | awk '{ print $2, $4, $5 }')

if [ -z "$AWS_ACCESS_KEY_ID" ]
then
  exit 1
fi

`aws --profile $AWS_2AUTH_PROFILE configure set aws_access_key_id "$AWS_ACCESS_KEY_ID"`
`aws --profile $AWS_2AUTH_PROFILE configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"`
`aws --profile $AWS_2AUTH_PROFILE configure set aws_session_token "$AWS_SESSION_TOKEN"`
Enter fullscreen mode Exit fullscreen mode

TIP: You can use it with Terraform/Terragrun by setting environment variable temporary, just for the command:

env AWS_PROFILE=your-profile-name terraform init
Enter fullscreen mode Exit fullscreen mode

TIP: You can export generated credentials to environment variable (for cases when aws profiles is not supported) by the following command:

eval "$(aws configure export-credentials --profile your-profile-name --format env)"
Enter fullscreen mode Exit fullscreen mode

TIP: you also can pass credentials to docker container on run without storing them in separate file or export to your environment:

docker run --env-file <(aws configure export-credentials --profile your-profile-name --format env-no-export) ...
Enter fullscreen mode Exit fullscreen mode

Top comments (0)