Assume IAM roles through an AWS Bastion account with MFA via the command line.
AWS Bastion accounts store only IAM users providing a central, isolated account to manage their credentials and access. Trusting AWS accounts create IAM roles that the Bastion users can assume, to allow a single user access to multiple accounts resources. Under this setup, assume-role makes it easier to follow the standard security practices of MFA and short lived credentials.
assume-role requires jq and aws CLI tools to be installed.
brew tap arvatoaws/assume-role
brew install assume-roleYou can then upgrade at any time by running:
brew upgrade assume-roleYou can install/upgrade assume-role with this command:
curl https://raw.githubusercontent.com/arvatoaws-labs/assume-role/master/install-assume-role -O
cat install-assume-role # inspect the script for security
bash ./install-assume-role # install assume-roleIt will ask for your sudo password if necessary.
Make sure that credentials for your AWS bastion account are stored in ~/.aws/credentials.
Out of the box you can call assume-role like:
eval $(assume-role account-id role mfa-token)If your shell supports bash functions (e.g. zsh) then you can add source $(which assume-role) to your rc file (e.g. ~/.zshrc), then you can call assume-role like:
assume-role [account-id] [role] [mfa-token]assume-role this method can be used with arguments or interactively like:
You can define aliases to account ids in ~/.aws/accounts which assume-role can use, e.g.
{
"default": "123456789012",
"staging": "123456789012",
"production": "123456789012"
}With this file, to assume the read role in the production account:
assume-role production read
# OR
assume-role 123456789012 readAlso, by setting $AWS_PROFILE_ASSUME_ROLE, you can define a default profile for assume-role if you want to separate concerns between
default accounts for assume-role and vanilla awscli or simply to have better names than default:
$ export AWS_PROFILE_ASSUME_ROLE="bastion"
$ assume-role production readMoreover, if you are in the need of longer client-side assume-role sessions and don't want to enter your MFA authentication every hour (default) this one is for you:
$ export AWS_ROLE_SESSION_TIMEOUT=43200However, be aware that for chained roles there's currently a forced 1 hour limit from AWS. You'll get the following error if you exceed that specific limit:
DurationSeconds exceeds the 1 hour session limit for roles assumed by role chaining.
Here is a simple example of how to set up a Bastion AWS account with an id 0987654321098 and a Production account with the id 123456789012.
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": "54000"
}
}
}
]
}The conditions aws:MultiFactorAuthPresent and aws:MultiFactorAuthAge forces the use of temporary credentials secured with MFA.
In the Bastion account, create a 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": "54000"
}
}
}
]
}Attach this group to Bastion users that should be able use read's policies in the Production account.
You can assume the read role in Production by running:
assume-role 123456789012 read
Then entering a MFA token on request.
If you are using zsh you can get a sweet prompt by adding to your .zshrc file:
source $(which assume-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`If you want to have a autocompleter for the accounts from your aws-config add the following at the beginning of your .zshrc file:
fpath=(~/zsh_functions $fpath)
autoload -U compinit
compinit
If you are using oh-my-zsh, a nice way to integrate this into the powerline segments (the relevant one being the custom_assume_role, the other segmenst are merely an example) would be to do the following:
- Follow general assume-role instructions
- Setup oh-my-zsh normally
- git clone https://github.com/bhilburn/powerlevel9k.git ~/.oh-my-zsh/custom/themes/powerlevel9k
- Add the following to your .zshrc
source $(which assume-role)
export POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(root_indicator context dir dir_writable rbenv chruby nodeenv pyenv aws custom_assume_role vcs)
export POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status command_execution_time background_jobs detect_virt disk_usage load ram time)
export POWERLEVEL9K_CUSTOM_ASSUME_ROLE="echo \$AWS_ACCOUNT_NAME"
export POWERLEVEL9K_CUSTOM_ASSUME_ROLE_FOREGROUND="black"
export POWERLEVEL9K_CUSTOM_ASSUME_ROLE_BACKGROUND="yellow"
ZSH_THEME="powerlevel9k/powerlevel9k"For bash you could put the following in your .bash_profile file:
source $(which assume-role)
function aws_account_info {
[ "$AWS_ACCOUNT_NAME" ] && [ "$AWS_ACCOUNT_ROLE" ] && echo -n "aws:($AWS_ACCOUNT_NAME:$AWS_ACCOUNT_ROLE) "
}
PROMPT_COMMAND='aws_account_info'You have to install ykman for your distribution
If you want to use your YubiKey as MFA, there is the feature to use the oath Feature of Yubikey:
You have to add your MFA Hash to oath:
ykman oath add -t NameOfYourChoice <YOUR_BASE_32_KEY>After that you can add the following ENV Variable to your profile:
export YUBIKEY_MFA="NameOfYourChoice"Now, when assume-role needs a MFA it will ask you to Touch your YubiKey
assume-role is tested with BATS (Bash Automated Testing System). To run the tests first you will need bats, jq and shellcheck installed. On macOS this can be accomplished with brew:
brew install bats
brew install jq
brew install shellcheckThen run bats test/assume-role.bats;

