Skip to content

Instantly share code, notes, and snippets.

@chattr
Last active April 25, 2025 17:16
Show Gist options
  • Save chattr/5ab07ebb3b8defc1bb422710eef60a82 to your computer and use it in GitHub Desktop.
Save chattr/5ab07ebb3b8defc1bb422710eef60a82 to your computer and use it in GitHub Desktop.
Auto-configure AWS CLI ~/.aws/config with SSO roles
#!/usr/bin/env bash
#
# Auto-configure AWS CLI ~/.aws/config with SSO roles
#
#
set -o nounset -o pipefail
# defaults if unset or null
: "${default_region:=eu-west-1}"
: "${region_dev:=eu-west-1}"
: "${region_qa:=eu-west-1}"
: "${region_staging:=eu-central-1}"
: "${region_prod:=eu-central-1}"
: "${sso_region:=us-east-1}"
: "${sso_start_url:=https://d-0123456789.awsapps.com/start}"
: "${config_backup_suffix:=$(date +%Y%m%d-%H%M)}"
_die() {
# restore AWS config from backup
trap 'cp ~/.aws/config{."${config_backup_suffix}",}' SIGINT SIGTERM EXIT
# print error to stderr and exit
printf "\nERROR: %s\n" "$1" >&2
exit "${2:-1}"
}
# check version of bash and exit if not using at least version 4
[[ ${BASH_VERSINFO[0]} -lt 4 ]] && _die 'This script requires bash 4. Aborting'
_cleanup() {
rm -f "${tempfile-}"
}
trap _cleanup EXIT
trap _cleanup SIGINT
_check_cmd() {
command -v "$1" > /dev/null 2>&1 || _die "${1} not installed. Aborting."
}
_check_cmd aws
_check_cmd jq
_print_guidance() {
local use_env_region=${1:-false}
# ANSI escape codes
bright_green='\033[1;32m'
reset='\033[0m'
printf '%b\n' \
"" \
"๐ŸŽ‰ AWS config created! ๐ŸŽ‰" \
""
if [ "$use_env_region" != "true" ]; then
printf '%b\n' \
"Each profile has been configured with the default region ${bright_green}\"${default_region}\"${reset}" \
"" \
"You may wish to update the value assigned to \"region\" for each profile where appropriate in \"~/.aws/config\"" \
""
fi
}
_pre_flight() {
# unset session variable if it's set
unset AWS_DEFAULT_PROFILE
# backup existing AWS config
if find ~/.aws/config > /dev/null; then
mv ~/.aws/config{,."${config_backup_suffix}"}
fi
# empty sso cache to mitigate conflicts with stale data
rm -rf ~/.aws/sso/
# seed boilerplate AWS config
printf '%s\n' \
"[default]" \
"region = ${default_region}" \
"output = json" \
"" \
"[sso-session login]" \
"sso_start_url = ${sso_start_url}" \
"sso_region = ${sso_region}" \
"sso_registration_scopes = sso:account:access" > ~/.aws/config
}
_spinner() {
local pid=$1
local delay=0.1
local spinstr="|/-\\"
tput civis # hide cursor
while kill -0 "$pid" 2>/dev/null; do
for i in $(seq 0 3); do
printf "\r[%c] Processing..." "${spinstr:i:1}"
sleep "$delay"
done
done
printf "\r \r" # clear spinner line
tput cnorm # show cursor
}
_pre_flight || _die 'Pre-flight tasks failed. Aborting.'
# login to AWS with SSO
"aws" sso login --sso-session login || _die 'AWS SSO login failed. Aborting.'
sso_cache="$(jq -s '.[0] * .[1]' ~/.aws/sso/cache/*)"
access_token="$(jq -r '.accessToken' <<< "$sso_cache")" || _die 'No access token found. You may need to aws sso login first. Aborting.'
start_url="$(jq -r '.startUrl' <<< "$sso_cache")"
region_sso=$(jq -r '.region // "us-east-1"' <<< "$sso_cache")
# default to "$region_sso" if unset or null
: "${assume_role_region:="$region_sso"}"
accounts_data="$("aws" sso list-accounts --region "$region_sso" --access-token "$access_token")" || _die 'You may need to aws sso login first. Aborting.'
accounts_list="$(jq -r '.accountList[].accountId' <<< "$accounts_data")"
tempfile="$(mktemp /tmp/aws_sso.XXXXXX)"
# present an option to set the region based on the profile name having dev, qa, staging and prod
read -r -p "Do you want to set the region for each profile based on environment (e.g. dev, prod) inset in the profile name? [y/n] " yn
# set a flag based on user input
use_env_based_region=false
case "$yn" in
[Yy]*) use_env_based_region=true ;;
esac
(
# collect profiles for sorting
profile_entries=()
while IFS= read -r account_id; do
account_data="$(jq -r ".accountList | .[] | select( .accountId == \"$account_id\" )" <<< "$accounts_data")"
account_name="$(jq -r '.accountName // .accountId' <<< "$account_data")"
account_roles="$("aws" sso list-account-roles --region "$region_sso" --access-token "$access_token" --account-id "$account_id")"
role_names="$(jq -r '.roleList | .[] | .roleName' <<< "$account_roles")"
while read -r role_name; do
config_profile_name="${account_name}-${role_name}"
profile_entries+=("${config_profile_name}|${account_id}|${role_name}")
done <<< "$role_names"
done <<< "$accounts_list"
# sort profile entries alphabetically
IFS=$'\n' mapfile -t sorted_entries < <(printf '%s\n' "${profile_entries[@]}" | sort)
# process sorted entries
for entry in "${sorted_entries[@]}"; do
IFS='|' read -r config_profile_name account_id role_name <<< "$entry"
if ! grep -q "$config_profile_name" ~/.aws/config; then
# default region (can override below)
region_value="${default_region}"
# override region based on environment if user opted in
if [ "$use_env_based_region" = true ]; then
case "$config_profile_name" in
*dev*) region_value="${region_dev}" ;;
*qa*) region_value="${region_qa}" ;;
*staging*) region_value="${region_staging}" ;;
*prod*) region_value="${region_prod}" ;;
*) region_value="${default_region}" ;;
esac
fi
printf '%s\n' \
"" \
"[profile ${config_profile_name}]" \
"sso_start_url = ${start_url}" \
"sso_region = ${region_sso}" \
"sso_account_id = ${account_id}" \
"sso_role_name = ${role_name}" \
"sts_regional_endpoints = regional" \
"region = ${region_value}" \
"sso_registration_scopes = sso:account:access" >> "$tempfile"
fi
done
) & _spinner $!
# check if there's config to append to ~/.aws/config
if [[ -s "$tempfile" ]]; then
new_profile_count="$(grep -c '^\[profile' "$tempfile")"
cat "$tempfile"
printf "\nINFO: %s\n" "$new_profile_count profile(s) will be added to ~/.aws/config:"
read -r -p "Do you want to proceed? [y/n] " yn
case $yn in
[Yy]* ) cat "$tempfile" >> ~/.aws/config; _print_guidance "$use_env_based_region"; ;;
* ) echo "aborted!";;
esac
else
printf "\nINFO: %s\n" "There is no config to append to ~/.aws/config. Exiting."
exit 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment