Skip to content

Commit

Permalink
Fix Role Prompt, bypass buggy secrets check, add lsb_release (#971)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nuru authored Feb 25, 2025
1 parent cdf5e15 commit 4f2eac5
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 33 deletions.
11 changes: 9 additions & 2 deletions os/debian/Dockerfile.debian
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,7 @@ RUN helm3 plugin install https://github.com/databus23/helm-diff.git --version v$
# AWS_DATA_PATH is a PATH-like variable for configuring the AWS botocore library to
# load additional modules. Do not set it.
ARG GEODESIC_AWS_HOME=${HOME}/.aws
ENV AWS_CONFIG_FILE=${GEODESIC_AWS_HOME}/config
ENV AWS_SHARED_CREDENTIALS_FILE=${GEODESIC_AWS_HOME}/credentials

# Region abbreviation types are "fixed" (always 3 chars), "short" (4-5 chars), or "long" (the full AWS string)
# See https://github.com/cloudposse/terraform-aws-utils#introduction
ENV AWS_REGION_ABBREVIATION_TYPE=short
Expand Down Expand Up @@ -320,6 +319,14 @@ RUN if [ "$TARGETARCH" = "amd64" ]; then \
sudo dpkg -i /tmp/session-manager-plugin.deb && \
rm -f /tmp/session-manager-plugin.deb

# This is a workaround for https://github.com/moby/buildkit/issues/5775
# CHAMBER_KMS_KEY_ALIAS is used by the `chamber` CLI, but it is incorrectly
# flagged as a secret by the SecretsUsedInArgOrEnv check. This is a false positive.
# So, as a workaround, we allow you to set `CHAMBER_KMS_ALIAS` instead,
# and at runtime we copy the value to `CHAMBER_KMS_KEY_ALIAS` for you.
ENV CHAMBER_KMS_ALIAS=aws/ssm


# Install documentation
COPY docs/ /usr/share/docs/

Expand Down
1 change: 1 addition & 0 deletions packages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ kubectl@cloudposse
kubectx@cloudposse
kubens@cloudposse
less
lsb-release
make
man-db
openssh-client
Expand Down
22 changes: 22 additions & 0 deletions rootfs/etc/profile.d/_10-colors.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@
# The main change is that it uses the terminal's default colors for foreground and background,
# whereas the previous version "reset" the color by setting it to black, which fails in dark mode.

# These utilities use basic ANSI color codes (0-7) to colorize text in the terminal.
# Besides being the most widely supported method, it also has the advantage
# that terminals that support colored text and backgrounds as themes often
# take responsibility for the ANSI colors being legible regardless of the theme.
# For example, on a completely red background, the ANSI red color will be changed
# as part of the theme to a lighter or darker shade of red to ensure it is legible.
# This would not be the case if we tried to set the color directly using RGB values,
# or even using extended ANSI colors.

# To test a terminal manually, the following script displays all 256 ANSI colors.
# Our color functions only use 1 through 6.
# We indirectly support 0 and 7 (black and white), reversing them in dark mode,
# but we do not provide direct functions for them.
# We support a "bold" modifier, which some terminals equate to colors 8 through 15.
#
# for i in {0..255}; do
# printf "\e[38;5;${i}mcolor%-5i\e[0m" $i
# if [ $((($i + 1) % 8)) == 0 ]; then
# echo
# fi
# done

function update-terminal-theme() {
local new_mode="$1"
local quiet=false
Expand Down
131 changes: 100 additions & 31 deletions rootfs/etc/profile.d/aws.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#!/bin/bash
# shellcheck disable=SC2155
# Above directive suppresses ShellCheck SC2155: Declare and assign separately to avoid masking return values.
# In this script, we do not care about return values, as problems are detected by the resulting empty value.

export AWS_REGION_ABBREVIATION_TYPE=${AWS_REGION_ABBREVIATION_TYPE:-fixed}
export AWS_DEFAULT_SHORT_REGION=${AWS_DEFAULT_SHORT_REGION:-$(aws-region --${AWS_REGION_ABBREVIATION_TYPE} ${AWS_DEFAULT_REGION:-us-west-2})}
Expand Down Expand Up @@ -62,7 +65,7 @@ fi

function aws_choose_role() {
_preview="${FZF_PREVIEW:-crudini --format=ini --get "$AWS_CONFIG_FILE" 'profile {}'}"
cat "${AWS_SHARED_CREDENTIALS_FILE:-~/.aws/credentials}" "${AWS_CONFIG_FILE:-~/.aws/config}" 2>/dev/null |
cat "${AWS_SHARED_CREDENTIALS_FILE:-${GEODESIC_AWS_HOME}/credentials}" "${AWS_CONFIG_FILE:-${GEODESIC_AWS_HOME}/config}" 2>/dev/null |
crudini --get - | sed 's/^ *profile *//' |
fzf \
--height 30% \
Expand Down Expand Up @@ -107,74 +110,140 @@ function aws_sdk_assume_role() {
# Asks AWS what the currently active identity is and
# sets environment variables accordingly
function export_current_aws_role() {
local role_name
local role_name role_names
# Could be a primary or assumed role. If we have assumed a role, cut off the session name.
local current_role=$(aws sts get-caller-identity --output text --query 'Arn' 2>/dev/null | cut -d/ -f1-2)
if [[ -z $current_role ]]; then
unset ASSUME_ROLE
return 0
fi

# Quick check, are we who we say we are?
# If AWS_VAULT is not enabled, clear any setting from it.
[[ "${AWS_VAULT_ENABLED:-false}" == "true" ]] || unset AWS_VAULT

# Quick check, are we who we say we are? Does the current role match the profile?
local profile_arn
local profile_target=${AWS_PROFILE:-${AWS_VAULT:-default}}
if [[ -n $profile_target ]]; then
profile_arn=$(aws --profile "${profile_target}" sts get-caller-identity --output text --query 'Arn' 2>/dev/null | cut -d/ -f1-2)
if [[ $profile_arn == $current_role ]]; then
# Extract profile name from config file:
# 1. For default profile, look for a better name
# 2. Skip identity profiles (ending with -identity), as they are too generic
# 3. Use the first non-default, non-identity profile found
if [[ $profile_target == "default" ]] || [[ $profile_target =~ -identity$ ]]; then
# Make some effort to find a better name for the role, but only check the config file, not credentials.
local config_file="${AWS_CONFIG_FILE:-\~/.aws/config}"
if [[ -r $config_file ]]; then
# Assumed roles in AWS config file use the role ARN, not the assumed role ARN, so adjust accordingly.
local role_arn=$(printf "%s" "$current_role" | sed 's/:sts:/:iam:/g' | sed 's,:assumed-role/,:role/,')
role_name=($(crudini --get --format=lines "$config_file" | grep "$role_arn" | cut -d' ' -f 3))
for rn in "${role_name[@]}"; do
if [[ $rn == "default" ]] || [[ $rn =~ -identity$ ]]; then
# Remove the session name from the profile target role, if present
profile_arn=$(aws --profile "${profile_target}" sts get-caller-identity --output text --query 'Arn' 2>/dev/null | cut -d/ -f1-2)
# The main way there would be a mismatch is if AWS_VAULT is set or there are API keys in the environment
if [[ "$profile_arn" == "$current_role" ]]; then
# If we are here, then the current role matches the assigned profile. That is a good thing.
# However, the profile name may not be the best name for the role. If it is too generic, try to find a better name.
# Extract profile name from config file:
# 1. For default profile, look for a better name
# 2. Skip identity profiles (ending with -identity), as they are too generic
# 3. Use the first non-default, non-identity profile found
if [[ $profile_target == "default" ]] || [[ $profile_target =~ -identity$ ]]; then
local backup_name="$profile_target"
# Make some effort to find a better name for the role, but only check the config file, not credentials.
local config_file="${AWS_CONFIG_FILE:-${GEODESIC_AWS_HOME}/config}"
if [[ -r $config_file ]]; then
# Is this a normal IAM role or an Identity Center permissions set role?
if [[ $current_role =~ AWSReservedSSO_[^_]+_[0-9a-f]+$ ]]; then
# This is an Identity Center permissions set role
# current_role is "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_IdentityAdminRoleAccess_5c90026c17fbd1c2"

# Extract account ID using cut
local account_id=$(echo "$current_role" | cut -d':' -f5)

# Extract the full role part
local role_part=$(echo "$current_role" | cut -d':' -f6) # This gets everything after the 5th colon

# Extract the role name by isolating it from boilerplate
local sso_role_name=$(echo "$role_part" | cut -d'_' -f2) # This selects the second field delimited by '_'

# Find all profiles that have matching role names
local profile_names=($(crudini --get --format=lines "$config_file" | grep "$sso_role_name" | cut -d' ' -f 3))
local profile_name
for profile_name in "${profile_names[@]}"; do
# Skip the generic profiles
if [[ "$profile_name" == "default" ]] || [[ "$profile_name" =~ -identity$ ]]; then
continue
else
export ASSUME_ROLE=$rn
fi
if [[ "$account_id" == "$(crudini --get "$config_file" "profile $profile_name" sso_account_id)" ]]; then
export ASSUME_ROLE="$profile_name"
return
fi
done
export ASSUME_ROLE="$backup_name"
return
fi
else
export ASSUME_ROLE="$profile_target"
return

# Normal IAM role
# Assumed roles in AWS config file use the role ARN, not the assumed role ARN, so adjust accordingly.
local role_arn=$(printf "%s" "$current_role" | sed 's/:sts:/:iam:/g' | sed 's,:assumed-role/,:role/,')
role_names=($(crudini --get --format=lines "$config_file" | grep "$role_arn" | cut -d' ' -f 3))
for rn in "${role_names[@]}"; do
if [[ $rn == "default" ]] || [[ $rn =~ -identity$ ]]; then
continue
else
export ASSUME_ROLE=$rn
return
fi
done
fi
fi
echo "* $(red Profile is set to $profile_target but current role does not match:)"
echo "* $(red $current_role)"
# could not find a better match, so just use the generic profile name
export ASSUME_ROLE="$profile_target"
return
fi

# If we are here, then the current role is not what we would expect from the AWS_PROFILE setting.
# If AWS_PROFILE is unset, then we forgive the current role not being the default role.
# Otherwise, we warn about a mismatch.
if [[ -n $AWS_PROFILE ]]; then
red "* AWS Credentials Mismatch! AWS_PROFILE is set to $AWS_PROFILE"
red "* That profile selects role $profile_arn"
red "* But STS reports current role is $current_role"
export ASSUME_ROLE=$(red-n '!mixed!')
return
elif [[ -n $AWS_VAULT ]]; then
red "* AWS Credentials Mismatch! AWS_VAULT claims to have set role to profile $AWS_VAULT"
red "* That profile selects role $profile_arn"
red "* But STS reports current role is $current_role"
red "* "
export ASSUME_ROLE=$(red-n '!mixed!')
return
fi

# If we are here, then we are not using AWS_VAULT or AWS_PROFILE, and the current role does not match the default profile.
# This is likely because we are using API keys directly in the environment or credentials file.
# Try to figure out a better name for the role.

# saml2aws will store the assumed role from sign-in as x_principal_arn in credentials file
# Default values from https://awscli.amazonaws.com/v2/documentation/api/latest/topic/config-vars.html
local creds_file="${AWS_SHARED_CREDENTIALS_FILE:-\~/.aws/credentials}"
local creds_file="${AWS_SHARED_CREDENTIALS_FILE:-${GEODESIC_AWS_HOME}/credentials}"
if [[ -r $creds_file ]]; then
role_name=$(crudini --get --format=lines "${creds_file}" | grep "$current_role" | head -1 | cut -d' ' -f 2)
fi

# Assumed roles are normally found in AWS config file, but using the role ARN,
# not the assumed role ARN. google2aws also puts login role in this file.
local config_file="${AWS_CONFIG_FILE:-\~/.aws/config}"
local config_file="${AWS_CONFIG_FILE:-${GEODESIC_AWS_HOME}/config}"
if [[ -z $role_name ]] && [[ -r $config_file ]]; then
local role_arn=$(printf "%s" "$current_role" | sed 's/:sts:/:iam:/g' | sed 's,:assumed-role/,:role/,')
role_name=$(crudini --get --format=lines "$config_file" | grep "$role_arn" | head -1 | cut -d' ' -f 3)
fi

# If we still don't have a profile name, make one up.
if [[ -z $role_name ]]; then
if [[ "$role_arn" =~ "role/OrganizationAccountAccessRole" ]]; then
role_name="$(printf "%s" "$role_arn" | cut -d: -f 5):OrgAccess"
echo "* $(red "Could not find profile name for ${role_arn} ; calling it \"${role_name}\"")" >&2
elif [[ $current_role =~ AWSReservedSSO_[^_]+_[0-9a-f]+$ ]]; then
# This is an Identity Center permissions set role
# current_role is "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_IdentityAdminRoleAccess_5c90026c17fbd1c2"
# Extract account ID using cut
local account_id=$(echo "$current_role" | cut -d':' -f5)
# Extract the full role part
local role_part=$(echo "$current_role" | cut -d':' -f6) # This gets everything after the 5th colon
# Extract the role name by isolating it from boilerplate
local sso_role_name=$(echo "$role_part" | cut -d'_' -f2) # This selects the second field delimited by '_'
role_name="${account_id}:${sso_role_name}"
else
role_name="$(printf "%s" "$role_arn" | cut -d/ -f 2)"
echo "* $(green "Could not find profile name for ${role_arn} ; calling it \"${role_name}\"")" >&2
fi
echo "* $(green "Could not find profile name for ${role_arn} ; calling it \"${role_name}\"")" >&2
fi
export ASSUME_ROLE="$role_name"
}
Expand All @@ -186,7 +255,7 @@ function refresh_current_aws_role_if_needed() {
local is_exported="^declare -[^ x]*x[^ x]* "
local aws_profile=$(declare -p AWS_PROFILE 2>/dev/null)
[[ $aws_profile =~ $is_exported ]] || aws_profile=""
local credentials_mtime=$(stat -c "%Y" ${AWS_SHARED_CREDENTIALS_FILE:-"~/.aws/credentials"} 2>/dev/null)
local credentials_mtime=$(stat -c "%Y" "${AWS_SHARED_CREDENTIALS_FILE:-${GEODESIC_AWS_HOME}/credentials}" 2>/dev/null)
local role_fingerprint="${aws_profile}/${credentials_mtime}/${AWS_ACCESS_KEY_ID}"
if [[ $role_fingerprint != $GEODESIC_AWS_ROLE_CACHE ]]; then
export_current_aws_role
Expand Down
22 changes: 22 additions & 0 deletions rootfs/etc/profile.d/chamber.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# This is a workaround for https://github.com/moby/buildkit/issues/5775
#
# The `chamber` command (https://github.com/segmentio/chamber) is a CLI for managing secrets.
# It is installed in Geodesic and referenced in various documentation.
#
# Chamber works with AWS SSM Parameter store to save encrypted parameters.
# By default, in uses a KMS key with the alias `parameter_store_key`
# to encrypt and decrypt the parameters. Geodesic supports using
# the AWS default key, with alias `ssm`, or a custom key.
#
# However, due to the issue with buildkit mentioned above,
# setting the required environment variable in the Dockerfile
# leads to a warning about it being a secret stored in the image.
#
# So, as a workaround, we allow you to set `CHAMBER_KMS_ALIAS` instead,
# and we will set the `CHAMBER_KMS_KEY_ALIAS` environment variable for you here.
#

if [[ -z "$CHAMBER_KMS_KEY_ALIAS" ]] && [[ -n "$CHAMBER_KMS_ALIAS" ]]; then
export CHAMBER_KMS_KEY_ALIAS="$CHAMBER_KMS_ALIAS"
unset CHAMBER_KMS_ALIAS
fi

0 comments on commit 4f2eac5

Please sign in to comment.