Skip to content

Instantly share code, notes, and snippets.

@andrew-templeton
Last active February 4, 2026 20:08
Show Gist options
  • Select an option

  • Save andrew-templeton/35448042c64fe9b148f7b682bd4df399 to your computer and use it in GitHub Desktop.

Select an option

Save andrew-templeton/35448042c64fe9b148f7b682bd4df399 to your computer and use it in GitHub Desktop.
AWS SSO to read-only session for agent use
#!/bin/bash
# aws-readonly
# Creates a 12-hour read-only AWS session for agent use
# Auto-creates the ReadOnlyAccess role via CloudFormation if it doesn't exist
#
# Usage: aws-readonly <sso-profile-name> [output-profile-name]
#
# Example:
# aws-readonly csc-prod-admin # Creates csc-prod-admin-readonly
# aws-readonly csc-prod-admin agent-ro # Creates agent-ro
set -e
# Configuration
READONLY_ROLE="${AWS_READONLY_ROLE:-AgentReadOnlyAccess}"
STACK_NAME="agent-readonly-role"
# Parse arguments
SSO_PROFILE="${1:?Usage: aws-readonly <sso-profile-name> [output-profile-name]}"
OUTPUT_PROFILE="${2:-${SSO_PROFILE}-readonly}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() { echo -e "${GREEN}[+]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
error() { echo -e "${RED}[x]${NC} $1" >&2; }
# Step 1: Extract account ID from profile config
log "Reading profile config for: ${SSO_PROFILE}"
ACCOUNT_ID=$(aws configure get sso_account_id --profile "${SSO_PROFILE}" 2>/dev/null || true)
if [ -z "$ACCOUNT_ID" ]; then
error "Could not find sso_account_id for profile '${SSO_PROFILE}'"
error "Make sure the profile exists in ~/.aws/config with SSO settings"
echo ""
echo "Expected format in ~/.aws/config:"
echo " [profile ${SSO_PROFILE}]"
echo " sso_start_url = https://your-sso.awsapps.com/start"
echo " sso_region = us-east-1"
echo " sso_account_id = 123456789012"
echo " sso_role_name = AdministratorAccess"
exit 1
fi
log "Account ID: ${ACCOUNT_ID}"
# Step 2: Initiate SSO login
log "Initiating SSO login (browser will open)..."
aws sso login --profile "${SSO_PROFILE}"
# Step 3: Verify SSO session works and get caller identity
log "Verifying SSO session..."
CALLER_IDENTITY=$(aws sts get-caller-identity --profile "${SSO_PROFILE}" --output json)
CALLER_ARN=$(echo "$CALLER_IDENTITY" | jq -r '.Arn')
log "Authenticated as: ${CALLER_ARN}"
# Extract the role name and get full ARN (SSO roles have paths like /aws-reserved/sso.amazonaws.com/)
SSO_ROLE_NAME=$(echo "$CALLER_ARN" | sed -n 's|.*assumed-role/\([^/]*\)/.*|\1|p')
SSO_ROLE_ARN=$(aws iam get-role --profile "${SSO_PROFILE}" --role-name "${SSO_ROLE_NAME}" --query "Role.Arn" --output text)
log "SSO Role: ${SSO_ROLE_ARN}"
# Step 4: Check if role exists, create via CloudFormation if not
ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/${READONLY_ROLE}"
log "Checking if ${READONLY_ROLE} role exists..."
if ! aws iam get-role --role-name "${READONLY_ROLE}" --profile "${SSO_PROFILE}" >/dev/null 2>&1; then
warn "Role ${READONLY_ROLE} does not exist. Creating via CloudFormation..."
# Create CloudFormation template
CFN_TEMPLATE=$(cat <<EOF
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Read-only role for agent access - created by aws-readonly script'
Parameters:
TrustedRoleArn:
Type: String
Description: ARN of the role allowed to assume this read-only role
Resources:
AgentReadOnlyRole:
Type: AWS::IAM::Role
Properties:
RoleName: ${READONLY_ROLE}
MaxSessionDuration: 43200
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Ref TrustedRoleArn
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/ReadOnlyAccess
Tags:
- Key: CreatedBy
Value: aws-readonly-script
- Key: Purpose
Value: Agent read-only access
Outputs:
RoleArn:
Description: ARN of the created read-only role
Value: !GetAtt AgentReadOnlyRole.Arn
EOF
)
# Deploy CloudFormation stack
log "Deploying CloudFormation stack: ${STACK_NAME}..."
CFN_TEMP_FILE=$(mktemp -t cfn-readonly).yaml
echo "$CFN_TEMPLATE" > "$CFN_TEMP_FILE"
aws cloudformation deploy \
--profile "${SSO_PROFILE}" \
--stack-name "${STACK_NAME}" \
--template-file "$CFN_TEMP_FILE" \
--parameter-overrides "TrustedRoleArn=${SSO_ROLE_ARN}" \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset
rm -f "$CFN_TEMP_FILE"
log "Waiting for stack to complete..."
aws cloudformation wait stack-create-complete \
--profile "${SSO_PROFILE}" \
--stack-name "${STACK_NAME}" 2>/dev/null || \
aws cloudformation wait stack-update-complete \
--profile "${SSO_PROFILE}" \
--stack-name "${STACK_NAME}" 2>/dev/null || true
# Verify role was created
STACK_STATUS=$(aws cloudformation describe-stacks \
--profile "${SSO_PROFILE}" \
--stack-name "${STACK_NAME}" \
--query "Stacks[0].StackStatus" \
--output text)
if [[ "$STACK_STATUS" != *"COMPLETE"* ]] || [[ "$STACK_STATUS" == *"ROLLBACK"* ]]; then
error "CloudFormation stack failed with status: ${STACK_STATUS}"
error "Check the AWS Console for details"
exit 1
fi
log "CloudFormation stack deployed successfully (${STACK_STATUS})"
# Brief pause for IAM propagation
log "Waiting for IAM propagation..."
sleep 5
else
log "Role ${READONLY_ROLE} already exists"
fi
# Step 5: Assume the read-only role
# Note: Role chaining (assuming from an assumed role like SSO) limits to 1 hour max
EFFECTIVE_DURATION=3600 # 1 hour - role chaining limit
log "Assuming read-only role (${READONLY_ROLE}) for account ${ACCOUNT_ID}..."
log "Session duration: ${EFFECTIVE_DURATION}s (1 hour - role chaining limit)"
SESSION_NAME="agent-session-$(date +%Y%m%d)"
CREDS=$(aws sts assume-role \
--profile "${SSO_PROFILE}" \
--role-arn "${ROLE_ARN}" \
--role-session-name "${SESSION_NAME}" \
--duration-seconds "${EFFECTIVE_DURATION}" \
--output json 2>&1) || {
error "Failed to assume role ${ROLE_ARN}"
echo "$CREDS"
exit 1
}
# Extract credentials
ACCESS_KEY=$(echo "$CREDS" | jq -r '.Credentials.AccessKeyId')
SECRET_KEY=$(echo "$CREDS" | jq -r '.Credentials.SecretAccessKey')
SESSION_TOKEN=$(echo "$CREDS" | jq -r '.Credentials.SessionToken')
EXPIRATION=$(echo "$CREDS" | jq -r '.Credentials.Expiration')
# Step 6: Write to credentials file
CREDS_FILE="$HOME/.aws/credentials"
touch "$CREDS_FILE"
# Remove existing profile section if present
if grep -q "^\[${OUTPUT_PROFILE}\]" "$CREDS_FILE" 2>/dev/null; then
log "Removing existing ${OUTPUT_PROFILE} profile..."
awk -v profile="[${OUTPUT_PROFILE}]" '
$0 == profile { skip=1; next }
/^\[/ { skip=0 }
!skip { print }
' "$CREDS_FILE" > "$CREDS_FILE.tmp"
mv "$CREDS_FILE.tmp" "$CREDS_FILE"
fi
# Append new credentials
cat >> "$CREDS_FILE" << EOF
[${OUTPUT_PROFILE}]
aws_access_key_id = ${ACCESS_KEY}
aws_secret_access_key = ${SECRET_KEY}
aws_session_token = ${SESSION_TOKEN}
# Expires: ${EXPIRATION}
# Source: ${SSO_PROFILE}
# Account: ${ACCOUNT_ID}
# Role: ${READONLY_ROLE}
EOF
log "Credentials written to profile: ${OUTPUT_PROFILE}"
# Step 7: Verify the session works
log "Verifying readonly session..."
IDENTITY=$(aws sts get-caller-identity --profile "${OUTPUT_PROFILE}" --output json)
ASSUMED_ARN=$(echo "$IDENTITY" | jq -r '.Arn')
echo ""
echo "=========================================="
echo -e "${GREEN}Session created successfully!${NC}"
echo "=========================================="
echo "Profile: ${OUTPUT_PROFILE}"
echo "Source: ${SSO_PROFILE}"
echo "Account: ${ACCOUNT_ID}"
echo "Role: ${READONLY_ROLE}"
echo "Expires: ${EXPIRATION}"
echo "ARN: ${ASSUMED_ARN}"
echo ""
echo "Usage:"
echo " aws s3 ls --profile ${OUTPUT_PROFILE}"
echo " AWS_PROFILE=${OUTPUT_PROFILE} aws ec2 describe-instances"
echo ""
echo "For agents:"
echo " 'Use AWS_PROFILE=${OUTPUT_PROFILE} for all AWS CLI commands'"
echo "=========================================="
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment