11#! /bin/bash
2+ # shellcheck disable=SC2155
3+ # Above directive suppresses ShellCheck SC2155: Declare and assign separately to avoid masking return values.
4+ # In this script, we do not care about return values, as problems are detected by the resulting empty value.
25
36export AWS_REGION_ABBREVIATION_TYPE=${AWS_REGION_ABBREVIATION_TYPE:- fixed}
47export AWS_DEFAULT_SHORT_REGION=${AWS_DEFAULT_SHORT_REGION:- $(aws-region --${AWS_REGION_ABBREVIATION_TYPE} ${AWS_DEFAULT_REGION:- us-west-2} )}
6265
6366function aws_choose_role() {
6467 _preview=" ${FZF_PREVIEW:- crudini --format=ini --get " $AWS_CONFIG_FILE " ' profile {}' } "
65- cat " ${AWS_SHARED_CREDENTIALS_FILE:- ~ / .aws / credentials} " " ${AWS_CONFIG_FILE:- ~ / .aws / config} " 2> /dev/null |
68+ cat " ${AWS_SHARED_CREDENTIALS_FILE:- ${GEODESIC_AWS_HOME} / credentials} " " ${AWS_CONFIG_FILE:- ${GEODESIC_AWS_HOME} / config} " 2> /dev/null |
6669 crudini --get - | sed ' s/^ *profile *//' |
6770 fzf \
6871 --height 30% \
@@ -107,74 +110,140 @@ function aws_sdk_assume_role() {
107110# Asks AWS what the currently active identity is and
108111# sets environment variables accordingly
109112function export_current_aws_role() {
110- local role_name
113+ local role_name role_names
111114 # Could be a primary or assumed role. If we have assumed a role, cut off the session name.
112115 local current_role=$( aws sts get-caller-identity --output text --query ' Arn' 2> /dev/null | cut -d/ -f1-2)
113116 if [[ -z $current_role ]]; then
114117 unset ASSUME_ROLE
115118 return 0
116119 fi
117120
118- # Quick check, are we who we say we are?
121+ # If AWS_VAULT is not enabled, clear any setting from it.
122+ [[ " ${AWS_VAULT_ENABLED:- false} " == " true" ]] || unset AWS_VAULT
123+
124+ # Quick check, are we who we say we are? Does the current role match the profile?
119125 local profile_arn
120126 local profile_target=${AWS_PROFILE:- ${AWS_VAULT:- default} }
121- if [[ -n $profile_target ]]; then
122- profile_arn=$( aws --profile " ${profile_target} " sts get-caller-identity --output text --query ' Arn' 2> /dev/null | cut -d/ -f1-2)
123- if [[ $profile_arn == $current_role ]]; then
124- # Extract profile name from config file:
125- # 1. For default profile, look for a better name
126- # 2. Skip identity profiles (ending with -identity), as they are too generic
127- # 3. Use the first non-default, non-identity profile found
128- if [[ $profile_target == " default" ]] || [[ $profile_target =~ -identity$ ]]; then
129- # Make some effort to find a better name for the role, but only check the config file, not credentials.
130- local config_file=" ${AWS_CONFIG_FILE:- \~ / .aws/ config} "
131- if [[ -r $config_file ]]; then
132- # Assumed roles in AWS config file use the role ARN, not the assumed role ARN, so adjust accordingly.
133- local role_arn=$( printf " %s" " $current_role " | sed ' s/:sts:/:iam:/g' | sed ' s,:assumed-role/,:role/,' )
134- role_name=($( crudini --get --format=lines " $config_file " | grep " $role_arn " | cut -d' ' -f 3) )
135- for rn in " ${role_name[@]} " ; do
136- if [[ $rn == " default" ]] || [[ $rn =~ -identity$ ]]; then
127+ # Remove the session name from the profile target role, if present
128+ profile_arn=$( aws --profile " ${profile_target} " sts get-caller-identity --output text --query ' Arn' 2> /dev/null | cut -d/ -f1-2)
129+ # The main way there would be a mismatch is if AWS_VAULT is set or there are API keys in the environment
130+ if [[ " $profile_arn " == " $current_role " ]]; then
131+ # If we are here, then the current role matches the assigned profile. That is a good thing.
132+ # However, the profile name may not be the best name for the role. If it is too generic, try to find a better name.
133+ # Extract profile name from config file:
134+ # 1. For default profile, look for a better name
135+ # 2. Skip identity profiles (ending with -identity), as they are too generic
136+ # 3. Use the first non-default, non-identity profile found
137+ if [[ $profile_target == " default" ]] || [[ $profile_target =~ -identity$ ]]; then
138+ local backup_name=" $profile_target "
139+ # Make some effort to find a better name for the role, but only check the config file, not credentials.
140+ local config_file=" ${AWS_CONFIG_FILE:- ${GEODESIC_AWS_HOME} / config} "
141+ if [[ -r $config_file ]]; then
142+ # Is this a normal IAM role or an Identity Center permissions set role?
143+ if [[ $current_role =~ AWSReservedSSO_[^_]+_[0-9a-f]+$ ]]; then
144+ # This is an Identity Center permissions set role
145+ # current_role is "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_IdentityAdminRoleAccess_5c90026c17fbd1c2"
146+
147+ # Extract account ID using cut
148+ local account_id=$( echo " $current_role " | cut -d' :' -f5)
149+
150+ # Extract the full role part
151+ local role_part=$( echo " $current_role " | cut -d' :' -f6) # This gets everything after the 5th colon
152+
153+ # Extract the role name by isolating it from boilerplate
154+ local sso_role_name=$( echo " $role_part " | cut -d' _' -f2) # This selects the second field delimited by '_'
155+
156+ # Find all profiles that have matching role names
157+ local profile_names=($( crudini --get --format=lines " $config_file " | grep " $sso_role_name " | cut -d' ' -f 3) )
158+ local profile_name
159+ for profile_name in " ${profile_names[@]} " ; do
160+ # Skip the generic profiles
161+ if [[ " $profile_name " == " default" ]] || [[ " $profile_name " =~ -identity$ ]]; then
137162 continue
138- else
139- export ASSUME_ROLE=$rn
163+ fi
164+ if [[ " $account_id " == " $( crudini --get " $config_file " " profile $profile_name " sso_account_id) " ]]; then
165+ export ASSUME_ROLE=" $profile_name "
140166 return
141167 fi
142168 done
169+ export ASSUME_ROLE=" $backup_name "
170+ return
143171 fi
144- else
145- export ASSUME_ROLE=" $profile_target "
146- return
172+
173+ # Normal IAM role
174+ # Assumed roles in AWS config file use the role ARN, not the assumed role ARN, so adjust accordingly.
175+ local role_arn=$( printf " %s" " $current_role " | sed ' s/:sts:/:iam:/g' | sed ' s,:assumed-role/,:role/,' )
176+ role_names=($( crudini --get --format=lines " $config_file " | grep " $role_arn " | cut -d' ' -f 3) )
177+ for rn in " ${role_names[@]} " ; do
178+ if [[ $rn == " default" ]] || [[ $rn =~ -identity$ ]]; then
179+ continue
180+ else
181+ export ASSUME_ROLE=$rn
182+ return
183+ fi
184+ done
147185 fi
148186 fi
149- echo " * $( red Profile is set to $profile_target but current role does not match:) "
150- echo " * $( red $current_role ) "
187+ # could not find a better match, so just use the generic profile name
188+ export ASSUME_ROLE=" $profile_target "
189+ return
190+ fi
191+
192+ # If we are here, then the current role is not what we would expect from the AWS_PROFILE setting.
193+ # If AWS_PROFILE is unset, then we forgive the current role not being the default role.
194+ # Otherwise, we warn about a mismatch.
195+ if [[ -n $AWS_PROFILE ]]; then
196+ red " * AWS Credentials Mismatch! AWS_PROFILE is set to $AWS_PROFILE "
197+ red " * That profile selects role $profile_arn "
198+ red " * But STS reports current role is $current_role "
199+ export ASSUME_ROLE=$( red-n ' !mixed!' )
200+ return
201+ elif [[ -n $AWS_VAULT ]]; then
202+ red " * AWS Credentials Mismatch! AWS_VAULT claims to have set role to profile $AWS_VAULT "
203+ red " * That profile selects role $profile_arn "
204+ red " * But STS reports current role is $current_role "
205+ red " * "
151206 export ASSUME_ROLE=$( red-n ' !mixed!' )
152207 return
153208 fi
154209
210+ # If we are here, then we are not using AWS_VAULT or AWS_PROFILE, and the current role does not match the default profile.
211+ # This is likely because we are using API keys directly in the environment or credentials file.
212+ # Try to figure out a better name for the role.
213+
155214 # saml2aws will store the assumed role from sign-in as x_principal_arn in credentials file
156215 # Default values from https://awscli.amazonaws.com/v2/documentation/api/latest/topic/config-vars.html
157- local creds_file=" ${AWS_SHARED_CREDENTIALS_FILE:- \~ / .aws / credentials} "
216+ local creds_file=" ${AWS_SHARED_CREDENTIALS_FILE:- ${GEODESIC_AWS_HOME} / credentials} "
158217 if [[ -r $creds_file ]]; then
159218 role_name=$( crudini --get --format=lines " ${creds_file} " | grep " $current_role " | head -1 | cut -d' ' -f 2)
160219 fi
161220
162221 # Assumed roles are normally found in AWS config file, but using the role ARN,
163222 # not the assumed role ARN. google2aws also puts login role in this file.
164- local config_file=" ${AWS_CONFIG_FILE:- \~ / .aws / config} "
223+ local config_file=" ${AWS_CONFIG_FILE:- ${GEODESIC_AWS_HOME} / config} "
165224 if [[ -z $role_name ]] && [[ -r $config_file ]]; then
166225 local role_arn=$( printf " %s" " $current_role " | sed ' s/:sts:/:iam:/g' | sed ' s,:assumed-role/,:role/,' )
167226 role_name=$( crudini --get --format=lines " $config_file " | grep " $role_arn " | head -1 | cut -d' ' -f 3)
168227 fi
169228
229+ # If we still don't have a profile name, make one up.
170230 if [[ -z $role_name ]]; then
171231 if [[ " $role_arn " =~ " role/OrganizationAccountAccessRole" ]]; then
172232 role_name=" $( printf " %s" " $role_arn " | cut -d: -f 5) :OrgAccess"
173- echo " * $( red " Could not find profile name for ${role_arn} ; calling it \" ${role_name} \" " ) " >&2
233+ elif [[ $current_role =~ AWSReservedSSO_[^_]+_[0-9a-f]+$ ]]; then
234+ # This is an Identity Center permissions set role
235+ # current_role is "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_IdentityAdminRoleAccess_5c90026c17fbd1c2"
236+ # Extract account ID using cut
237+ local account_id=$( echo " $current_role " | cut -d' :' -f5)
238+ # Extract the full role part
239+ local role_part=$( echo " $current_role " | cut -d' :' -f6) # This gets everything after the 5th colon
240+ # Extract the role name by isolating it from boilerplate
241+ local sso_role_name=$( echo " $role_part " | cut -d' _' -f2) # This selects the second field delimited by '_'
242+ role_name=" ${account_id} :${sso_role_name} "
174243 else
175244 role_name=" $( printf " %s" " $role_arn " | cut -d/ -f 2) "
176- echo " * $( green " Could not find profile name for ${role_arn} ; calling it \" ${role_name} \" " ) " >&2
177245 fi
246+ echo " * $( green " Could not find profile name for ${role_arn} ; calling it \" ${role_name} \" " ) " >&2
178247 fi
179248 export ASSUME_ROLE=" $role_name "
180249}
@@ -186,7 +255,7 @@ function refresh_current_aws_role_if_needed() {
186255 local is_exported=" ^declare -[^ x]*x[^ x]* "
187256 local aws_profile=$( declare -p AWS_PROFILE 2> /dev/null)
188257 [[ $aws_profile =~ $is_exported ]] || aws_profile=" "
189- local credentials_mtime=$( stat -c " %Y" ${AWS_SHARED_CREDENTIALS_FILE:- " ~/.aws/ credentials" } 2> /dev/null)
258+ local credentials_mtime=$( stat -c " %Y" " ${AWS_SHARED_CREDENTIALS_FILE:- ${GEODESIC_AWS_HOME} / credentials} " 2> /dev/null)
190259 local role_fingerprint=" ${aws_profile} /${credentials_mtime} /${AWS_ACCESS_KEY_ID} "
191260 if [[ $role_fingerprint != $GEODESIC_AWS_ROLE_CACHE ]]; then
192261 export_current_aws_role
0 commit comments