├── LICENSE.txt ├── README.md ├── assume_role.sh └── sts_assume_role.sh /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Civis Analytics 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Civis Analytics nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iam-role-injector 2 | 3 | The IAM Role Injector is a tool for easily assuming an IAM Role with 4 | Multi-Factor Authentication (MFA). It manipulates environment variables 5 | to allow codebases already using AWS credentials to use IAM roles with minimal to no 6 | refactoring. In the same vein, the Role Injector can also be used to help users using the 7 | command line tools to assume a role. 8 | 9 | ## Assumptions 10 | - AWS CLI configured correctly, storing 'aws_access_key_id' and 11 | 'aws_secret_access_key' in either environment variables -OR- in 12 | ~/.aws/credentials 13 | - One of the following Scenarios apply: 14 | 15 | ### Scenario One: Federated AWS Accounts 16 | - At least two AWS Accounts: 17 | - AWS Account 1 must have a policy that includes sts:AssumeRole to AWS Account 2 18 | - AWS Account 2 must have a Trust Relationship on a role that references AWS Account 1 19 | - AWS Account 1 may now assume the role in AWS Account 2 that has the Trust Relationship 20 | 21 | ### Scenario Two: Single AWS Account 22 | - An IAM User Account with a policy that include sts:Assume on an IAM 23 | Role. 24 | - The IAM Role has a trust relationship that allows entities in the 25 | account to assume it 26 | - In this case, AWS Account 1 and AWS Account 2 are the same. 27 | 28 | ## Installation 29 | 30 | 1. [Install AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) 31 | 2. [Configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) with required credentials, either as Environment 32 | Variables or by running 'aws configure' 33 | 3. Clone this repo 34 | 35 | ## Increase AWS IAM Role max-session-duration 36 | 1 hour is used by default (following the AWS assume-role default), if no timeout time is specified. 37 | To update an AWS IAM role beyond 1 hour using the sts assume-role call, you must initially update the IAM role with a max-session-duration (in seconds). 38 | e.g. (for 12 hours): 39 | 40 | `aws iam update-role --role-name administrator --max-session-duration 43200` 41 | 42 | ## Command Line Usage 43 | 44 | **IMPORTANT NOTE: The command below starts with "source"! If you run the script without sourcing it, the environment variables will go out of scope when the script ends and you'll have a bad time.** 45 | 46 | *source /path/to/sts_assume_role.sh -d [destination_account__number] -r [rolename]* 47 | 48 | e.g. 49 | `source /path/to/sts_assume_role.sh -d 1234567890 -r administrator` 50 | 51 | e.g. 52 | `source /path/to/sts_assume_role.sh --source 9876543210 --user iam-user --destination 1234567890 --role administrator --timeout 6h --mfa 552255` 53 | 54 | ### Optional features: 55 | #### Help menu 56 | ``` 57 | /path/to/sts_assume_role.sh -h 58 | [Help Menu Options] 59 | Specify at least a role (-r) and destination account (-d) 60 | -d|--destination to which AWS account you will assume-role 61 | -h|--help (this) help menu 62 | -i|--info output aws Info 63 | -m|--mfa multi-factor (2fa/mfa) authentication (default is NONE) 64 | -r|--role aws role you wish be become 65 | -s|--source source account id (not needed if you can 'aws iam list-account-aliases') 66 | -t|--timeout duration in which assume-role will be functional 67 | (values in (s)econds,(m)inutes,(h)ours - 60m up to 12h. Default is 3600s) 68 | -u|--user iam user name (not needed if you can 'aws sts get-caller-identity') 69 | -x|--unset unset assumed role vars 70 | ``` 71 | 72 | #### Show current iam user or role info 73 | `/path/to/sts_assume_role.sh -i` 74 | #### Revert to iam user from role 75 | `/path/to/sts_assume_role.sh -x` 76 | #### Specify expiration (aws sts now supports from 1 hour, up to 12 hours) 77 | `source /path/to/sts_assume_role.sh -d [destination_account__number] -r [rolename] -t 4h` 78 | *works with seconds, minutes, or hours. e.g. `-t 2h` `-t 120m` `-t 7200s` `-t 7200`* 79 | ### Specify nothing, and you will be prompted for necessary information 80 | ``` 81 | source /path/to/sts_assume_role.sh 82 | [No values set, please enter at least the destination account number and role name to assume a role] 83 | Source Account (Default is NONE): 9876543210 84 | Destination Account: 1234567890 85 | IAM User Name (Default is NONE): iam-user 86 | Role: administrator 87 | Timeout (Default is 1h): 88 | Multifactor Authentication? (default is NONE): 89 | ``` 90 | 91 | ### Variables exported 92 | ``` 93 | AWS_ACCOUNT_NAME 94 | AWS_ACCESS_KEY_ID 95 | AWS_ENV_VARS 96 | AWS_SECRET_ACCESS_KEY 97 | AWS_SESSION_TOKEN 98 | AWS_STS_EXPIRATION 99 | AWS_STS_TIMEOUT 100 | AWS_USER 101 | OG_AWS_ACCESS_KEY_ID 102 | OG_AWS_SECRET_ACCESS_KEY 103 | ``` 104 | 105 | ### Expiration/Timeout use case 106 | `AWS_STS_EXPIRATION` and `AWS_STS_TIMEOUT` are helpful to create a basic functions to determine how much time until the assume-role has 107 | expired. 108 | 109 | ``` 110 | function timeToSTSExpiration(){ 111 | echo $(( $(( ${AWS_STS_TIMEOUT} - $(date -u +%s) )) / 60 )) 112 | } 113 | ``` 114 | 115 | It can even be added to your prompt. e.g. 116 | ``` 117 | function checkSTS() { 118 | if [[ -n $AWS_STS_EXPIRATION && $AWS_STS_TIMEOUT -ge $(date +%s) ]]; then 119 | AWS_STS_MINUTES_REMAINING=$(( $(( AWS_STS_TIMEOUT - $(date -u +%s) )) / 60 )) 120 | echo "$AWS_ACCOUNT_NAME|$AWS_STS_MINUTES_REMAINING" 121 | elif [[ $AWS_STS_TIMEOUT -le $(date +%s) ]]; then 122 | unset AWS_STS_EXPIRATION 123 | fi 124 | STS_PROMPT_="$STS_COLOR"'$(checkSTS)' 125 | PROMPT="$STS_PROMPT >" 126 | ``` 127 | 128 | # Legacy script 129 | ``` 130 | source ~/assume_role.sh {sourceAccountNumber} {username} {destinationAccountNumber} {rolename} 131 | ``` 132 | 133 | - `sourceAccountNumber`: AWS Account Number of AWS Account 1 134 | - `username`: AWS Account 1 username 135 | - `destinationAccountNumber`: AWS Account Number of AWS Account 2 136 | - `rolename`: the name of the role to assume in AWS Account 2 that has the Trust Relationship to AWS Account 1 137 | 138 | Calling the script with 'source' is required for the 139 | environment variables to persist past the runtime of the script. 140 | 141 | The script will also protect your original credentials if you chose to 142 | store *them* as environment variables. 143 | 144 | ## Bugs 145 | 146 | Please report any bugs to: 147 | https://github.com/civisanalytics/iam-role-injector/issues 148 | 149 | ## Contributing 150 | 151 | Open an issue or a pull request if you see how we can improve the 152 | script! 153 | 154 | 155 | ## License 156 | 157 | iam-role-injector is released under the [BSD 3-Clause License](LICENSE.txt). 158 | -------------------------------------------------------------------------------- /assume_role.sh: -------------------------------------------------------------------------------- 1 | # USAGE: 2 | # requires 4 args and optionally a 5th, needs to be run with source to get exported variables to stick 3 | # source assume_role.sh [durationSeconds] 4 | 5 | echo "WARNING: This script is deprecated. Please use sts_assume_role.sh instead!" 6 | 7 | sourceAccountNumber=$1 8 | username=$2 9 | destinationAccountNumber=$3 10 | rolename=$4 11 | durationSeconds=${5:-3600} 12 | # Get current shell even if it is not the default shell: https://unix.stackexchange.com/a/227138 13 | defaultShell=$(ps -p $$ | awk '$1 != "PID" {print $(NF)}') 14 | 15 | roleArn="arn:aws:iam::${destinationAccountNumber}:role/${rolename}" 16 | serialArn="arn:aws:iam::${sourceAccountNumber}:mfa/${username}" 17 | 18 | clear_env_vars () { 19 | unset AWS_SECURITY_TOKEN 20 | unset AWS_SESSION_TOKEN 21 | if [ -z "$AWS_ENV_VARS" ]; then 22 | if [ -z "$AWS_SECRET_ACCESS_KEY" ]; then 23 | export AWS_ENV_VARS="True" 24 | elif [ -z "$OG_AWS_SECRET_ACCESS_KEY" ]; then 25 | export OG_AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY 26 | export OG_AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID 27 | else 28 | export AWS_SECRET_ACCESS_KEY=$OG_AWS_SECRET_ACCESS_KEY 29 | export AWS_ACCESS_KEY_ID=$OG_AWS_ACCESS_KEY_ID 30 | fi 31 | else 32 | unset AWS_SECRET_ACCESS_KEY 33 | unset AWS_ACCESS_KEY_ID 34 | fi 35 | } 36 | 37 | get_sts () { 38 | # allow a blank tokenCode for orgs that don't use an MFA 39 | echo "Enter MFA token code:" 40 | read tokenCode 41 | # zsh requires -A in order to read in an array 42 | readFlag="-a" 43 | if [[ "$defaultShell" == *"zsh"* ]]; then 44 | readFlag="-A" 45 | fi 46 | 47 | if [ -z "$tokenCode" ]; then 48 | read $readFlag commandResult <<< $(aws sts assume-role --output text\ 49 | --role-arn $roleArn \ 50 | --role-session-name iam-role-injector \ 51 | --query 'Credentials.[SecretAccessKey, SessionToken, AccessKeyId]' \ 52 | --duration-seconds $durationSeconds) 53 | else 54 | read $readFlag commandResult <<< $(aws sts assume-role --output text \ 55 | --role-arn $roleArn \ 56 | --role-session-name iam-role-injector \ 57 | --serial-number $serialArn \ 58 | --query 'Credentials.[SecretAccessKey, SessionToken, AccessKeyId]' \ 59 | --duration-seconds $durationSeconds \ 60 | --token-code $tokenCode) 61 | fi 62 | 63 | exitCode=$? 64 | } 65 | 66 | set_env_vars () { 67 | if (( ${#commandResult[@]} == 3 )); then 68 | if [[ "$defaultShell" == *"bash"* ]]; then 69 | echo "You have assumed the $rolename role successfully." 70 | export AWS_SECRET_ACCESS_KEY=${commandResult[0]} 71 | # Set AWS_SESSION_TOKEN and AWS_SECURITY_TOKEN for backwards compatibility 72 | # See: http://boto3.readthedocs.org/en/latest/guide/configuration.html 73 | export AWS_SECURITY_TOKEN=${commandResult[1]} 74 | export AWS_SESSION_TOKEN=${commandResult[1]} 75 | export AWS_ACCESS_KEY_ID=${commandResult[2]} 76 | elif [[ "$defaultShell" == *"zsh"* ]]; then 77 | echo "You have assumed the $rolename role successfully." 78 | # zsh arrays are numbered from one by default 79 | # see: https://stackoverflow.com/questions/36453146/why-does-read-a-fail-in-zsh 80 | export AWS_SECRET_ACCESS_KEY=${commandResult[1]} 81 | # Set AWS_SESSION_TOKEN and AWS_SECURITY_TOKEN for backwards compatibility 82 | # See: http://boto3.readthedocs.org/en/latest/guide/configuration.html 83 | export AWS_SECURITY_TOKEN=${commandResult[2]} 84 | export AWS_SESSION_TOKEN=${commandResult[2]} 85 | export AWS_ACCESS_KEY_ID=${commandResult[3]} 86 | fi 87 | else 88 | echo "Unable to assume role" 89 | exitCode=1 90 | fi 91 | } 92 | 93 | main () { 94 | if [ -n "$destinationAccountNumber" ] && [ -n "$sourceAccountNumber" ] && [ -n "$rolename" ] && [ -n "$username" ]; then 95 | clear_env_vars 96 | get_sts 97 | set_env_vars 98 | else 99 | echo "Usage: source assume_role.sh [durationSeconds]" 100 | exitCode=1 101 | fi 102 | 103 | } 104 | 105 | main 106 | # This runs in a subshell, so it will not exit your shell when you are sourcing, 107 | # but it still gives you the correct exit code if you read from $? 108 | (exit $exitCode) 109 | -------------------------------------------------------------------------------- /sts_assume_role.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PURPLE="\033[35m" 4 | WHITE="\033[0m" 5 | 6 | 7 | arg_vars(){ 8 | # Set aws args 9 | case "$1" in 10 | -d|--destination ) 11 | DESTINATION_ACCOUNT="$2" ;; 12 | -m|--mfa ) 13 | get_mfa_token "$2" && \ 14 | [ -z "$MFA_TOKEN" ] && MFA_TOKEN=NONE ;; 15 | -r|--role ) 16 | ROLE_NAME="$2" ;; 17 | -s|--source ) 18 | AWS_ACCOUNT_NUMBER="$2" ;; 19 | -t|--timeout ) 20 | timeout_time "$2" ;; 21 | -u|--user ) 22 | AWS_USER="$2" ;; 23 | esac 24 | } 25 | 26 | assume_role(){ 27 | header "AWS STS assume-role" 28 | roleArn="arn:aws:iam::" 29 | roleArn+="$DESTINATION_ACCOUNT" 30 | roleArn+=":role/" 31 | roleArn+="$ROLE_NAME" 32 | 33 | serialArn="arn:aws:iam::" 34 | serialArn+="$AWS_ACCOUNT_NUMBER" 35 | serialArn+=":mfa/" 36 | serialArn+="$AWS_USER" 37 | 38 | roleCommand="aws sts assume-role --role-arn $roleArn " 39 | roleCommand+="--role-session-name iam-role-injector " 40 | roleCommand+="--duration-seconds $TIMEOUT " 41 | roleCommand+="--serial-number $serialArn " 42 | roleCommand+="--query 'Credentials.[SecretAccessKey, SessionToken, AccessKeyId, Expiration]' " 43 | [ "$MFA_TOKEN" != NONE ] && \ 44 | roleCommand+="--token-code $MFA_TOKEN" 45 | 46 | commandResult=$(eval "$roleCommand") 47 | exitCode=$? 48 | if [[ "$commandResult" && ${#commandResult} -gt 6 ]]; then 49 | arg1=$(echo "$commandResult" | awk -F\" 'NR==2 {print $2}') 50 | arg2=$(echo "$commandResult" | awk -F\" 'NR==3 {print $2}') 51 | arg3=$(echo "$commandResult" | awk -F\" 'NR==4 {print $2}') 52 | arg4=$(echo "$commandResult" | awk -F\" 'NR==5 {print $2}') 53 | export AWS_SECRET_ACCESS_KEY="$arg1" 54 | export AWS_SESSION_TOKEN="$arg2" 55 | export AWS_ACCESS_KEY_ID="$arg3" 56 | export AWS_STS_EXPIRATION="$arg4" 57 | print_aws_info 58 | echo "expiration: $AWS_STS_EXPIRATION UTC" 59 | else 60 | echo 61 | exitCode=1 62 | main -h 63 | fi 64 | } 65 | 66 | exit_code(){ 67 | (exit $exitCode) 68 | } 69 | 70 | get_aws_info(){ 71 | AWS_INFO=$(aws sts get-caller-identity --output text --query '[Account, Arn]' 2>&1) 72 | if grep -q 'error.*GetCallerIdentity'<<< "$AWS_INFO"; then 73 | printf "$AWS_INFO\\n" 74 | exitCode=255 75 | exit_code 76 | else 77 | AWS_ACCOUNT_NUMBER=$(awk '{print $1}' <<< "$AWS_INFO") 78 | AWS_USER=$(awk -F"/" '{print $2}' <<< "$AWS_INFO") 79 | if [ ! "$AWS_ACCESS_KEY_ID" ]; then 80 | AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id) 81 | AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key) 82 | fi 83 | fi 84 | } 85 | 86 | get_mfa_token(){ 87 | if [ -z "$*" ]; then 88 | printf "Please enter your multifactor token code (default is NONE): " 89 | read -r MFA_TOKEN 90 | else 91 | MFA_TOKEN="$*" 92 | fi 93 | } 94 | 95 | header(){ 96 | echo -e "[${PURPLE}${1}${WHITE}]" 97 | } 98 | 99 | print_aws_info(){ 100 | get_aws_info && \ 101 | echo "$AWS_ACCOUNT_NUMBER:$AWS_USER $AWS_ACCESS_KEY_ID" 102 | } 103 | 104 | parse_args(){ 105 | if [ $# -eq 0 ]; then 106 | prompt_args 107 | else 108 | TIMEOUT=3600 109 | while [ $# -ne 0 ]; do 110 | arg_vars "$@" 111 | shift 112 | done 113 | fi 114 | } 115 | 116 | prompt_args(){ 117 | # Prompt user if no args specified 118 | header "No values set, please enter at least the destination account number and role name to assume" 119 | printf "Source Account (Default is NONE): " 120 | read -r AWS_ACCOUNT_NUMBER 121 | printf "Destination Account: " 122 | read -r DESTINATION_ACCOUNT 123 | printf "IAM User Name (Default is NONE): " 124 | read -r AWS_USER 125 | printf "Role: " 126 | read -r ROLE_NAME 127 | printf "Timeout (Default is 1h): " 128 | read -r TIMEOUT 129 | printf "Multifactor Authentication? (default is NONE): " 130 | read -r MFA_TOKEN 131 | main -s "$AWS_ACCOUNT_NUMBER" -u "$AWS_USER" -d "$DESTINATION_ACCOUNT" -r "$ROLE_NAME" -t "$TIMEOUT" -m "$MFA_TOKEN" 132 | } 133 | 134 | print_help(){ 135 | header "Help Menu Options" 136 | echo \ 137 | "Specify at least a role (-r) and destination account (-d) 138 | -d|--destination to which AWS account you will assume-role 139 | -h|--help (this) help menu 140 | -i|--info output aws Info 141 | -m|--mfa multi-factor (2fa/mfa) authentication (default is NONE) 142 | -r|--role aws role you wish to assume 143 | -s|--source source account id (not needed if you can 'aws sts get-caller-identity') 144 | -t|--timeout duration in which assume-role will be functional 145 | (values in (s)econds,(m)inutes,(h)ours - 60m up to 12h. Default is 3600s) 146 | -u|--user iam user name (not needed if you can 'aws sts get-caller-identity') 147 | -x|--unset unset assumed role vars" 148 | } 149 | 150 | rotate_keys(){ 151 | unset AWS_SECURITY_TOKEN 152 | unset AWS_SESSION_TOKEN 153 | 154 | if [ -z "$AWS_ENV_VARS" ]; then 155 | if [ -z "$AWS_SECRET_ACCESS_KEY" ]; then 156 | export AWS_ENV_VARS="True" 157 | elif [ -z "$OG_AWS_SECRET_ACCESS_KEY" ]; then 158 | export OG_AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY 159 | export OG_AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID 160 | else 161 | export AWS_SECRET_ACCESS_KEY=$OG_AWS_SECRET_ACCESS_KEY 162 | export AWS_ACCESS_KEY_ID=$OG_AWS_ACCESS_KEY_ID 163 | unset AWS_ENV_VARS 164 | fi 165 | else 166 | unset AWS_SECRET_ACCESS_KEY 167 | unset AWS_ACCESS_KEY_ID 168 | fi 169 | } 170 | 171 | timeout_time(){ 172 | if [ "$1" ]; then 173 | if [[ "$1" =~ [hH]$ ]]; then 174 | TIMEOUT="$(( ${1%?} * 60 * 60))" 175 | elif [[ "$1" =~ [mM]$ ]]; then 176 | TIMEOUT="$(( ${1%?} * 60))" 177 | elif [[ "$1" =~ [sS]$ ]]; then 178 | TIMEOUT="${1%?}" 179 | elif [[ "$1" =~ [0-9]$ ]]; then 180 | TIMEOUT="$1" 181 | fi 182 | else 183 | TIMEOUT=3600 184 | fi 185 | } 186 | 187 | unset_vars(){ 188 | header "Reverting assume-role vars back to IAM user"; 189 | unset AWS_INFO \ 190 | AWS_ACCOUNT_NUMBER \ 191 | AWS_SECURITY_TOKEN \ 192 | AWS_SESSION_TOKEN \ 193 | AWS_STS_EXPIRATION \ 194 | AWS_USER \ 195 | TIMEOUT 196 | print_aws_info 197 | } 198 | 199 | main(){ 200 | case "$1" in 201 | -h|--help ) 202 | print_help ;; 203 | -i|--info ) 204 | header "Current AWS Info" 205 | print_aws_info ;; 206 | -x|--unset ) 207 | rotate_keys 208 | unset_vars ;; 209 | "" ) 210 | prompt_args ;; 211 | * ) 212 | parse_args "$@" && \ 213 | rotate_keys && \ 214 | get_aws_info && \ 215 | assume_role 216 | ;; 217 | esac 218 | } 219 | 220 | main "$@" 221 | # This runs in a subshell, so it will not exit your shell when you are sourcing, 222 | # but it still gives you the correct exit code if you read from $? 223 | exit_code 224 | --------------------------------------------------------------------------------