Cross-Account Access Overview

Welcome to the most comprehensive guide onAWScross-account access! We're going to walk through every single step, every parameter, and every response you'll see when setting up cross-account IAM roles.

Think of cross-account access like giving someone from another company a visitor badge that works in your building. The trust policy is like the security desk checking their credentials, and the permissions policy determines which floors and rooms they can access.

Today we'll cover Account A (the trusting account) creating roles that Account B (the trusted account) can assume. I'll explain every singleAWSCLI parameter, what it does, why we use it, and exactly what response you'll get back.

We'll be using real account IDs throughout this demo, so you can follow along step by step.

Understanding the Architecture

Before we dive into commands, let's understand exactly what we're building. We have twoAWSaccounts that need to work together securely.

Account A for Production wants to allow specific users from Account B to access certain resources, but with strict controls.

The beauty of cross-account roles is that Account B users don't need permanent credentials in Account A. They use their own credentials to assume a temporary role, getting short-lived tokens.

This is much more secure than creating permanent users in each account or sharing access keys. The credentials expire automatically, and you can track exactly who accessed what and when.

We'll see exactly how the Security Token Service (STS) orchestrates this dance between accounts.

Setting Up the Environment

Let's start by setting up ourAWSCLI environment properly. This is crucial because we'll be working with multiple accounts, and you need to be absolutely sure which account context you're operating in.

The first command,AWSsts get-caller-identity, is your best friend when working with multiple accounts. It tells you exactly which credentials you're using right now.

aws sts get-caller-identity breakdown:
- sts: Security Token Service
- get-caller-identity: Returns details about the IAM user or role whose credentials are used to call the operation
- No parameters needed - uses your currentAWScredentials

When you run this, you'll get back three critical pieces of information: your User ID (or Role ID), your Account number, and your ARN. This confirms you're working in the right account with the right permissions.

TheAWSconfigure list command shows you exactly which credential source is being used - is it from ~/.aws/credentials, environment variables, or an IAM role? This is essential for troubleshooting.

Creating the Trust Policy Document

Now we're creating the heart of cross-account access - the trust policy. This JSON document is like a whitelist that says "these specific entities from these specific accounts are allowed to assume this role."

I'm using a here-document (EOF syntax) to create the JSON file cleanly. This is much better than trying to escape quotes in a command line.

Trust Policy Components:
- Version: Always use "2012-10-17" (the latest policy language version)
- Effect: "Allow" grants permission (vs "Deny")
- Principal: WHO can assume this role
- Action: WHAT they can do (sts:AssumeRole for role assumption)
- Condition: Optional additional restrictions

The Principal "arn:aws:iam::account B:user/DevUser" is very specific - it allows only the user named "DevUser" from account account B. You could also use "arn:aws:iam::account B:root" to allow any user from that account, but that's less secure.

The ExternalId condition is crucial for security - it prevents the "confused deputy" problem where a malicious user might trick a service into performing actions on the wrong account.

Creating the Cross-Account Role

Now we're actually creating the IAM role using the trust policy we just defined. Let me break down every single parameter in thisAWSiam create-role command.

Parameter breakdown:
- --role-name: The name of the role (must be unique in your account)
- --assume-role-policy-document: The trust policy (who can assume this role)
- file://: TellsAWSCLI to read from a local file
- --description: Human-readable description (optional but recommended)
- --path: Organizes roles hierarchically (optional, defaults to "/")

When this command succeeds,AWSreturns a complete Role object with the ARN, creation date, and the trust policy you just attached. The ARN is what you'll use to reference this role from other accounts.

If you get an error here, it's usually because: 1) You don't have iam:CreateRole permission, 2) A role with that name already exists, or 3) There's a JSON syntax error in your trust policy.

The role exists now, but it has no permissions yet - it's like creating an empty security badge. We'll add permissions next.

Adding Permissions to the Role

A role without permissions is useless, so now we're going to attach a permissions policy. I'm showing you two approaches - using anAWSmanaged policy and creating a custom policy.

AWS managed policies like "ReadOnlyAccess" are maintained byAWSand automatically updated when new services are added. They're great for common use cases and followAWSbest practices.

attach-role-policy parameters:
- --role-name: The role we just created
- --policy-arn: The Amazon Resource Name of the policy to attach
-AWSmanaged policies always start with "arn:aws:iam::aws:policy/"

For custom policies, we use create-policy first, then attach-role-policy. The custom policy I'm showing allows S3 access but only to buckets with "dev" in the name - this is principle of least privilege in action.

When you attach a policy,AWSdoesn't return any output on success - silence is golden here. You can verify the attachment worked by using get-role or list-attached-role-policies.

Setting Up the Trusted Account

Now we switch contexts to Account B. This is where the user who will assume the cross-account role lives.

The user in Account B needs specific permissions to assume roles in other accounts. The sts:AssumeRole permission is what allows them to call the AssumeRole API.

Why we need sts:AssumeRole permission:
- It's not automatic - users need explicit permission
- You can restrict which roles they can assume
- You can add conditions (MFA, IP restrictions, etc.)
- It's auditable in CloudTrail

The policy I'm showing allows assuming any role in Account A, but you could be more restrictive and specify exact role ARNs. Notice the condition requiring an ExternalId - this must match what's in the trust policy.

Creating the user and attaching this policy gives them the capability to assume cross-account roles, but they still need to be explicitly allowed by each role's trust policy.

The AssumeRole Process

This is the moment of truth - where Account B's user actually assumes the role in Account A. TheAWSsts assume-role command is the bridge between accounts.

assume-role parameters explained:
- --role-arn: The full ARN of the role to assume
- --role-session-name: A name for this session (appears in CloudTrail)
- --external-id: Must match the trust policy's condition
- --duration-seconds: How long the credentials last (900-43200 seconds)

The response contains temporary credentials: AccessKeyId, SecretAccessKey, and SessionToken. These credentials are only valid for the specified duration and only have the permissions granted to the assumed role.

The session name is crucial for auditing - it appears in all CloudTrail logs, so use meaningful names like "DevUser Deployment Task followed by date rather than generic names.

If this fails, check: 1) Trust policy allows your user, 2) ExternalId matches, 3) You have sts:AssumeRole permission, 4) The role exists in the target account.

Using the Temporary Credentials

Once you have the temporary credentials, you need to configure yourAWSCLI to use them. There are several ways to do this, and I'll show you the most practical approaches.

The export commands set environment variables that theAWSCLI automatically picks up. This is temporary - they only last for your current shell session.

Environment variables explained:
- AWS_ACCESS_KEY_ID: The temporary access key
- AWS_SECRET_ACCESS_KEY: The temporary secret key
- AWS_SESSION_TOKEN: Required for temporary credentials
- AWS_DEFAULT_REGION: Optional but recommended

Alternatively, you can useAWSconfigure set to store the credentials in a named profile. This is more permanent and allows you to switch between multiple assumed roles easily.

TheAWSsts get-caller-identity command now shows you're using the assumed role - your ARN will show the role name and session name. This confirms the role assumption worked.

Now anyAWSCLI commands you run will use the permissions of the assumed role, not your original user permissions.

Advanced Trust Policy Features

Let's explore advanced trust policy features that give you granular control over when and how roles can be assumed.

Time-based conditions are incredibly powerful for temporary access scenarios. You can create contractor roles that only work during specific time periods or maintenance windows that only activate during scheduled maintenance times.

Advanced conditions:
- DateGreaterThan/DateLessThan: Time-based access
- IpAddress: Restrict by source IP
- StringEquals: Exact string matching
- Bool: Boolean conditions (like MFA requirements)

MFA requirements are essential for privileged roles. The AWS MultiFactor AuthPresent condition ensures the user provided a second factor, and AWS MultiFactorAuthAge ensures it was recent.

IP address restrictions are great for hybrid environments where you want to ensure access only comes from your corporate network or specific VPNs.

When you have multiple conditions, they all must be true (AND logic). Use multiple statements for or logic.

Automation and Scripting

In real-world scenarios, you'll want to automate the role assumption process. Let me show you a practical script that handles the entire workflow.

This script demonstrates several best practices: error handling, credential validation, and automatic session cleanup.

Script components:
- Input validation: Check required parameters
- Error handling: Graceful failure with meaningful messages
- Credential parsing: Extract values from JSON response
- Session management: Set up and clean up credentials

The jq tool is invaluable for parsing JSON responses fromAWSCLI. The -r flag outputs raw strings (without quotes), making them suitable for shell variables.

Always validate that the role assumption worked before proceeding with other operations. A simpleAWSsts get-caller-identity check can prevent confusing errors later.

Consider adding logging to your automation scripts - knowing when roles were assumed and by whom is crucial for security auditing.

Monitoring and Auditing

Cross-account access creates additional security considerations, so monitoring and auditing become even more critical.

CloudTrail logs every AssumeRole call with details about who assumed what role, when, and from where. The sourceIPAddress field is particularly valuable for detecting anomalous access patterns.

Key CloudTrail fields for AssumeRole:
- eventName: "AssumeRole"
- sourceIPAddress: Where the request came from
- userIdentity: Who made the request
- requestParameters: Role ARN and session name
- responseElements: Whether it succeeded

TheAWSlogs filter-log-events command lets you search CloudTrail logs programmatically. You can create automated alerts for unusual patterns like role assumptions from unexpected IP addresses or outside business hours.

Access Analyzer can help you identify unused cross-account access - if a role hasn't been assumed in 90 days, maybe it's time to review whether it's still needed.

Set up CloudWatch alarms for failed AssumeRole attempts - repeated failures might indicate an attack or misconfiguration.

Troubleshooting Common Issues

Let's walk through the most common issues you'll encounter with cross-account access and how to debug them systematically.

The first step in any troubleshooting is alwaysAWSsts get-caller-identity to confirm which account and user you're operating as. Many issues stem from being in the wrong account context.

Systematic troubleshooting approach:
1. Verify current identity and account
2. Check trust policy syntax and principals
3. Confirm user has sts:AssumeRole permission
4. Validate role exists and is assumable
5. Check condition requirements (ExternalId, MFA, etc.)

TheAWSiam simulate-principal-policy command is incredibly valuable for testing permissions without actually performing actions. You can test whether a specific user can assume a specific role.

CloudTrail is your forensic tool - look for AssumeRole events to see exactly what happened and why it failed. The errorCode and errorMessage fields provide specific failure reasons.

Remember that IAM is eventually consistent - if you just created a role or policy, wait a few seconds before testing. This catches many mysterious "access denied" errors.

Security Best Practices

Cross-account access amplifies both the benefits and risks of yourAWSsecurity posture, so following best practices is crucial.

External IDs are your first line of defense against confused deputy attacks. Always use them for third-party integrations, and make them unique and unpredictable.

Security best practices:
- Use specific principals, not account-wide trust
- Require external IDs for third-party access
- Implement time-based access controls
- Monitor and alert on unusual access patterns
- Regular access reviews and cleanup

Principle of least privilege is even more important in cross-account scenarios. Start with minimal permissions and add only what's needed. The ReadOnlyAccess policy is often sufficient for initial integrations.

Consider usingAWSOrganizations SCPs (Service Control Policies) to add guardrails around cross-account access. You can prevent certain roles from being created or assumed.

Regular access reviews should include cross-account relationships. Use Access Analyzer findings to identify overly permissive trusts or unused access paths.

Real-World Implementation Guide

Let's wrap up with a comprehensive implementation checklist that you can use in production environments.

Start with a pilot implementation between non-production accounts to validate your approach. This lets you work out the kinks without risking production systems.

Implementation checklist:
✓ Document the business requirement
✓ Design the access pattern
✓ Create test accounts for validation
✓ Implement monitoring and alerting
✓ Test failure scenarios
✓ Train users on proper usage
✓ Establish regular review cycles

Always have a rollback plan. Cross-account access can be removed quickly by updating trust policies, but make sure you understand the impact on dependent systems.

Document your external IDs and session naming conventions. Future troubleshooting will be much easier if you have consistent, meaningful naming patterns.

Consider implementing automated credential rotation for long-term cross-account relationships. While assumed role credentials are temporary, the base credentials used to assume them should be rotated regularly.

Thank you for following this comprehensive guide! You now have all the tools and knowledge needed to implement secure, auditable cross-account access in AWS.

Cross-Account Access with IAM Roles

Complete Implementation Guide

Comprehensive, line-by-lineAWSCLI tutorial for secure cross-account access

graph TB A[Account B
444455556666
DevUser] -->|1. AssumeRole Request| B[AWS STS] B -->|2. Validate Trust Policy| C[Account A
111122223333
CrossAccountRole] C -->|3. Check Permissions| D[Trust Policy] D -->|4. Allow/Deny| B B -->|5. Return Temporary Creds| A A -->|6. Access Resources| E[AWS Resources
in Account A]
What You'll Learn:
  • EveryAWSCLI parameter explained in detail
  • Step-by-step role creation and assumption
  • Advanced security configurations
  • Troubleshooting and monitoring
  • Production-ready automation scripts

Cross-Account Architecture

The Cross-Account Security Model

Two accounts working together with temporary, auditable access

graph TB subgraph "Account A: 111122223333 (Production)" A1[IAM Role: CrossAccountRole] A2[Trust Policy: Who can assume?] A3[Permissions Policy: What can they do?] A4[AWS Resources: S3, EC2, etc.] end subgraph "Account B: 444455556666 (Development)" B1[IAM User: DevUser] B2[User Policy: sts:AssumeRole] end B1 -->|AssumeRole| A1 A1 --> A4 A2 -.->|Controls| A1 A3 -.->|Defines| A1
Component Purpose Location
Trust Policy Defines WHO can assume the role Account A (Target)
Permissions Policy Defines WHAT the role can do Account A (Target)
User Policy Allows sts:AssumeRole action Account B (Source)
Temporary Credentials Short-lived access tokens Generated by STS

Environment Setup & Verification

1

Verify Current Account Context

aws sts get-caller-identity
Command Breakdown:
aws -AWSCommand Line Interface
sts - Security Token Service
get-caller-identity - Returns details about the IAM entity making the request
No parameters required - Uses currentAWScredentials
# Expected Response: { "UserId": "AIDACKCEVSQ6C2EXAMPLE", "Account": "111122223333", "Arn": "arn:aws:iam::111122223333:user/AdminUser" } # What each field means: # UserId: Unique identifier for the IAM entity # Account:AWSAccount ID you're operating in # Arn: Amazon Resource Name showing entity type and name
2

CheckAWSCLI Configuration

aws configure list
Shows your credential configuration:
Name: Configuration setting name
Value: Current value (credentials masked)
Type: Source of the setting (config file, env var, etc.)
Location: Where the setting is defined
✅ Verification Complete: You're now confirmed to be operating in Account A (account A) with administrative privileges.

Creating the Trust Policy Document

3

Create Trust Policy JSON File

cat > cross-account-trust-policy.json << 'EOF' { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::444455556666:user/DevUser" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "UniqueExternalId12345" } } } ] } EOF
Trust Policy Component Analysis:
cat > filename << 'EOF' - Creates file with here-document syntax
Version: "2012-10-17" - Latest IAM policy language version
Effect: "Allow" - Grants permission (vs "Deny")
Principal - Specifies WHO can assume this role
Action: "sts:AssumeRole" - The specific permission being granted
Condition - Additional requirements that must be met
sts:ExternalId - Prevents confused deputy attacks
Security Note: The ExternalId must be unique and kept secret. It prevents unauthorized role assumptions even if someone knows your account ID and role name.
cat cross-account-trust-policy.json
Verification command: Display the file contents to verify JSON syntax before using it

Creating the Cross-Account Role

4

Create IAM Role with Trust Policy

aws iam create-role \ --role-name CrossAccountDeveloperRole \ --assume-role-policy-document file://cross-account-trust-policy.json \ --description "Role for developers in Account B to access Account A resources" \ --path /cross-account/ \ --max-session-duration 3600
Parameter Deep Dive:
--role-name - Name for the role (unique within account)
--assume-role-policy-document - Trust policy defining who can assume this role
file:// - Prefix telling CLI to read from local file
--description - Human-readable description (optional but recommended)
--path - Hierarchical path for organization (optional, defaults to "/")
--max-session-duration - Maximum duration in seconds (900-43200)
# Expected Response: { "Role": { "Path": "/cross-account/", "RoleName": "CrossAccountDeveloperRole", "RoleId": "AROA1234567890EXAMPLE", "Arn": "arn:aws:iam::111122223333:role/cross-account/CrossAccountDeveloperRole", "CreateDate": "2024-12-05T10:30:00+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [...] }, "Description": "Role for developers in Account B to access Account A resources", "MaxSessionDuration": 3600 } }
✅ Role Created Successfully: The ARN arn:aws:iam::111122223333:role/cross-account/CrossAccountDeveloperRole is what Account B will use to assume this role.

Adding Permissions to the Role

5

Option A: AttachAWSManaged Policy

aws iam attach-role-policy \ --role-name CrossAccountDeveloperRole \ --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
attach-role-policy parameters:
--role-name - Name of the role to attach policy to
--policy-arn - Amazon Resource Name of the policy
arn:aws:iam::aws:policy/ - Prefix for allAWSmanaged policies
No output on success -AWSCLI returns nothing when attachment succeeds
6

Option B: Create and Attach Custom Policy

cat > s3-dev-access-policy.json << 'EOF' { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::dev-*", "arn:aws:s3:::dev-*/*" ] } ] } EOF
aws iam create-policy \ --policy-name S3DevAccess \ --path /cross-account/ \ --policy-document file://s3-dev-access-policy.json \ --description "S3 access limited to dev buckets"
create-policy parameters:
--policy-name - Name for the new policy
--path - Organizational path (matches role path)
--policy-document - JSON document defining permissions
--description - Human-readable description
aws iam attach-role-policy \ --role-name CrossAccountDeveloperRole \ --policy-arn arn:aws:iam::111122223333:policy/cross-account/S3DevAccess

Setting Up the Trusted Account (Account B)

⚠️ Context Switch: The following commands must be run in Account B (444455556666) with appropriate credentials.
7

Verify Account B Context

aws sts get-caller-identity
# Expected Response for Account B: { "UserId": "AIDACKCEVSQ6C2EXAMPLE", "Account": "444455556666", "Arn": "arn:aws:iam::444455556666:user/AdminUser" }
8

Create User Policy for AssumeRole Permission

cat > assume-role-policy.json << 'EOF' { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::111122223333:role/cross-account/CrossAccountDeveloperRole", "Condition": { "StringEquals": { "sts:ExternalId": "UniqueExternalId12345" } } } ] } EOF
Policy components explained:
Action: "sts:AssumeRole" - Permission to assume roles
Resource - Specific role ARN from Account A
Condition - Must provide matching ExternalId
• This policy allows assuming only the specific role we created
9

Create User and Attach Policy

aws iam create-user --user-name DevUser
aws iam create-policy \ --policy-name AssumeRolePolicy \ --policy-document file://assume-role-policy.json
aws iam attach-user-policy \ --user-name DevUser \ --policy-arn arn:aws:iam::444455556666:policy/AssumeRolePolicy

The AssumeRole Process

10

Assume the Cross-Account Role

aws sts assume-role \ --role-arn arn:aws:iam::111122223333:role/cross-account/CrossAccountDeveloperRole \ --role-session-name DevUser-Session-20241205 \ --external-id UniqueExternalId12345 \ --duration-seconds 3600
assume-role parameter breakdown:
--role-arn - Full ARN of the role to assume (from Account A)
--role-session-name - Name for this session (appears in logs)
--external-id - Must match the trust policy condition
--duration-seconds - How long credentials last (900-43200 seconds)
• Session name should be descriptive for auditing purposes
# Expected Response: { "Credentials": { "AccessKeyId": "ASIAQ3EGABCDEFGHIJKL", "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "SessionToken": "IQoJb3JpZ2luX2VjEHcaCXVzLXdlc3QtMiJGMEQCIGVy...", "Expiration": "2024-12-05T11:30:00+00:00" }, "AssumedRoleUser": { "AssumedRoleId": "AROA1234567890EXAMPLE:DevUser-Session-20241205", "Arn": "arn:aws:sts::111122223333:assumed-role/cross-account/CrossAccountDeveloperRole/DevUser-Session-20241205" } }
⚠️ Important: These are temporary credentials that expire after the specified duration. The SessionToken is required for all API calls.
✅ Role Assumption Successful: You now have temporary credentials to access Account A resources with the permissions granted to CrossAccountDeveloperRole.

Using the Temporary Credentials

11

Method 1: Environment Variables (Temporary)

export AWS_ACCESS_KEY_ID="ASIAQ3EGABCDEFGHIJKL" export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjEHcaCXVzLXdlc3QtMiJGMEQCIGVy..." export AWS_DEFAULT_REGION="us-east-1"
Environment variables explained:
AWS_ACCESS_KEY_ID - Temporary access key from AssumeRole response
AWS_SECRET_ACCESS_KEY - Temporary secret key from response
AWS_SESSION_TOKEN - Required for temporary credentials
AWS_DEFAULT_REGION - Optional but recommended
• These override any credentials in ~/.aws/credentials
12

Method 2:AWSCLI Profile (Persistent)

aws configure set aws_access_key_id "ASIAQ3EGABCDEFGHIJKL" --profile cross-account aws configure set aws_secret_access_key "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" --profile cross-account aws configure set aws_session_token "IQoJb3JpZ2luX2VjEHcaCXVzLXdlc3QtMiJGMEQCIGVy..." --profile cross-account aws configure set region us-east-1 --profile cross-account
Profile method advantages:
--profile cross-account - Creates named profile for these credentials
• Persists across shell sessions
• Can switch between multiple assumed roles easily
• Use with --profile flag on subsequent commands
13

Verify Role Assumption

aws sts get-caller-identity # or with profile: aws sts get-caller-identity --profile cross-account
# Expected Response (showing assumed role): { "UserId": "AROA1234567890EXAMPLE:DevUser-Session-20241205", "Account": "111122223333", "Arn": "arn:aws:sts::111122223333:assumed-role/cross-account/CrossAccountDeveloperRole/DevUser-Session-20241205" }

Advanced Trust Policy Features

14

Time-Based Access Control

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::444455556666:user/ContractorUser" }, "Action": "sts:AssumeRole", "Condition": { "DateGreaterThan": { "aws:CurrentTime": "2024-01-01T00:00:00Z" }, "DateLessThan": { "aws:CurrentTime": "2024-12-31T23:59:59Z" }, "StringEquals": { "sts:ExternalId": "Contractor2024" } } } ] }
Time-based conditions:
DateGreaterThan - Access starts after this date
DateLessThan - Access ends before this date
aws:CurrentTime - Current UTC time
• Perfect for contractor or temporary access scenarios
15

MFA Requirement

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::444455556666:user/AdminUser" }, "Action": "sts:AssumeRole", "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" }, "NumericLessThan": { "aws:MultiFactorAuthAge": "3600" } } } ] }
MFA conditions:
aws:MultiFactorAuthPresent - Must be authenticated with MFA
aws:MultiFactorAuthAge - MFA must be within 3600 seconds (1 hour)
• Essential for privileged roles requiring additional security
aws sts assume-role \ --role-arn arn:aws:iam::111122223333:role/AdminRole \ --role-session-name AdminSession \ --serial-number arn:aws:iam::444455556666:mfa/AdminUser \ --token-code 123456
MFA assume-role parameters:
--serial-number - ARN of the MFA device
--token-code - Current MFA token value
• Required when trust policy mandates MFA

Automation and Scripting

16

Production-Ready AssumeRole Script

#!/bin/bash # assume_role.sh - Production script for role assumption set -euo pipefail # Configuration ROLE_ARN="arn:aws:iam::111122223333:role/cross-account/CrossAccountDeveloperRole" EXTERNAL_ID="UniqueExternalId12345" SESSION_NAME="AutomatedSession-$(date +%Y%m%d-%H%M%S)" DURATION=3600 # Function to assume role and export credentials assume_role() { echo "Assuming role: $ROLE_ARN" # Call STS assume-role RESPONSE=$(aws sts assume-role \ --role-arn "$ROLE_ARN" \ --role-session-name "$SESSION_NAME" \ --external-id "$EXTERNAL_ID" \ --duration-seconds "$DURATION" \ --output json) # Extract credentials using jq ACCESS_KEY=$(echo "$RESPONSE" | jq -r '.Credentials.AccessKeyId') SECRET_KEY=$(echo "$RESPONSE" | jq -r '.Credentials.SecretAccessKey') SESSION_TOKEN=$(echo "$RESPONSE" | jq -r '.Credentials.SessionToken') EXPIRATION=$(echo "$RESPONSE" | jq -r '.Credentials.Expiration') # Export environment variables export AWS_ACCESS_KEY_ID="$ACCESS_KEY" export AWS_SECRET_ACCESS_KEY="$SECRET_KEY" export AWS_SESSION_TOKEN="$SESSION_TOKEN" echo "✅ Role assumed successfully" echo "🕐 Credentials expire at: $EXPIRATION" # Verify assumption AWSsts get-caller-identity } # Error handling if ! command -v jq &> /dev/null; then echo "❌ Error: jq is required but not installed" exit 1 fi # Execute assume_role
Script components explained:
set -euo pipefail - Strict error handling
jq -r - Parse JSON and output raw strings
• Dynamic session names with timestamps
• Input validation and error handling
• Automatic credential verification
chmod +x assume_role.sh ./assume_role.sh

Monitoring and Auditing

17

CloudTrail Log Analysis

aws logs filter-log-events \ --log-group-name "CloudTrail/Management" \ --filter-pattern "{ ($.eventName = AssumeRole) && ($.responseElements.assumedRoleUser.arn = \"*CrossAccountDeveloperRole*\") }" \ --start-time 1701763200000 \ --max-items 20
filter-log-events parameters:
--log-group-name - CloudTrail log group
--filter-pattern - JSON filter for AssumeRole events
--start-time - Unix timestamp in milliseconds
--max-items - Limit number of results
• Filters for specific role assumptions
18

Monitor Failed AssumeRole Attempts

aws logs filter-log-events \ --log-group-name "CloudTrail/Management" \ --filter-pattern "{ ($.eventName = AssumeRole) && ($.errorCode EXISTS) }" \ --start-time $(date -d '1 hour ago' +%s)000
Failed attempt monitoring:
$.errorCode EXISTS - Filter for failed attempts
$(date -d '1 hour ago' +%s)000 - Last hour in milliseconds
• Critical for security monitoring
19

Access Analyzer for Cross-Account Review

aws accessanalyzer list-findings \ --analyzer-arn arn:aws:access-analyzer:us-east-1:111122223333:analyzer/cross-account-analyzer \ --filter resourceType=AWS::IAM::Role
Access Analyzer benefits:
--analyzer-arn - Specific analyzer ARN
--filter - Focus on IAM roles
• Identifies external access to your resources
• Helps validate intended access patterns
Security Monitoring Best Practices:
  • Set up CloudWatch alarms for failed AssumeRole attempts
  • Monitor AssumeRole events from unexpected IP addresses
  • Review Access Analyzer findings regularly
  • Track session duration and frequency patterns

Troubleshooting Common Issues

20

Systematic Debugging Approach

Error Message Likely Cause Debug Command
"Access Denied" on AssumeRole Trust policy doesn't allow principal aws iam get-role --role-name RoleName
"Invalid ExternalId" ExternalId mismatch Check trust policy condition
"User not authorized" Missing sts:AssumeRole permission aws iam list-attached-user-policies
"Role does not exist" Wrong account or role name aws iam list-roles
21

Testing with Policy Simulator

aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::444455556666:user/DevUser \ --action-names sts:AssumeRole \ --resource-arns arn:aws:iam::111122223333:role/cross-account/CrossAccountDeveloperRole \ --context-entries ContextKeyName=sts:ExternalId,ContextKeyValues=UniqueExternalId12345,ContextKeyType=string
simulate-principal-policy parameters:
--policy-source-arn - ARN of the principal to simulate
--action-names - Action to test (sts:AssumeRole)
--resource-arns - Target role ARN
--context-entries - Condition context (ExternalId)
• Tests permissions without actually performing actions
22

Comprehensive Debug Commands

# 1. Verify current identity aws sts get-caller-identity # 2. Check if role exists in target account aws iam get-role --role-name CrossAccountDeveloperRole # 3. Verify trust policy aws iam get-role --role-name CrossAccountDeveloperRole --query 'Role.AssumeRolePolicyDocument' # 4. Check user's assume role permissions aws iam list-attached-user-policies --user-name DevUser # 5. Test assume role with debug output aws sts assume-role \ --role-arn arn:aws:iam::111122223333:role/cross-account/CrossAccountDeveloperRole \ --role-session-name DebugSession \ --external-id UniqueExternalId12345 \ --debug

Security Best Practices

Production Security Checklist

Security Principle Implementation CLI Command Example
Least Privilege Minimal necessary permissions aws iam attach-role-policy --policy-arn ...:policy/ReadOnlyAccess
External ID Usage Prevent confused deputy attacks --external-id "UniqueSecretValue123"
Time Restrictions Limit access to business hours Trust policy with DateGreaterThan/DateLessThan
MFA Requirements Require multi-factor authentication --serial-number arn:aws:iam::...:mfa/user --token-code 123456
IP Restrictions Allow only from trusted networks Trust policy with IpAddress condition
Session Duration Minimize credential lifetime --duration-seconds 900 (15 minutes minimum)
23

Regular Security Audits

# Check for roles not used in last 90 days aws iam generate-service-last-accessed-details \ --arn arn:aws:iam::111122223333:role/cross-account/CrossAccountDeveloperRole \ --granularity SERVICE_LEVEL
generate-service-last-accessed-details:
--arn - ARN of the role to analyze
--granularity - SERVICE_LEVEL for detailed service usage
• Returns job ID for async processing
• Helps identify unused cross-account access
# Get the generated report aws iam get-service-last-accessed-details \ --job-id 1234567890abcdef1234567890abcdef12345678
🔒 Critical Security Reminders:
  • Never share ExternalId values in public repositories
  • Rotate ExternalId values quarterly
  • Monitor for AssumeRole attempts from unexpected sources
  • UseAWSOrganizations SCPs for additional guardrails
  • Document all cross-account relationships

Real-World Implementation Guide

24

Production Deployment Checklist

Pre-Implementation Phase

  • ✅ Document business requirements and access patterns
  • ✅ Design role hierarchy and permission boundaries
  • ✅ Generate unique ExternalId values
  • ✅ Create test accounts for validation
  • ✅ Establish monitoring and alerting
25

Complete Implementation Script

#!/bin/bash # complete_cross_account_setup.sh - Full implementation script set -euo pipefail # Configuration TRUSTING_ACCOUNT="111122223333" TRUSTED_ACCOUNT="444455556666" ROLE_NAME="CrossAccountDeveloperRole" USER_NAME="DevUser" EXTERNAL_ID="$(openssl rand -hex 16)" # Generate random ExternalId SESSION_DURATION="3600" echo "🚀 Starting cross-account setup between accounts $TRUSTED_ACCOUNT → $TRUSTING_ACCOUNT" echo "📝 Generated ExternalId: $EXTERNAL_ID (save this securely!)" # Phase 1: Create trust policy cat > trust-policy.json << EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::$TRUSTED_ACCOUNT:user/$USER_NAME" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "$EXTERNAL_ID" }, "Bool": { "aws:MultiFactorAuthPresent": "true" }, "NumericLessThan": { "aws:MultiFactorAuthAge": "3600" } } } ] } EOF # Phase 2: Create role in trusting account echo "📋 Creating role in trusting account..." aws iam create-role \ --role-name "$ROLE_NAME" \ --assume-role-policy-document file://trust-policy.json \ --description "Cross-account role for $TRUSTED_ACCOUNT" \ --max-session-duration "$SESSION_DURATION" # Phase 3: Attach permissions echo "🔑 Attaching permissions..." aws iam attach-role-policy \ --role-name "$ROLE_NAME" \ --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess # Phase 4: Create assume role policy for trusted account cat > assume-role-policy.json << EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::$TRUSTING_ACCOUNT:role/$ROLE_NAME", "Condition": { "StringEquals": { "sts:ExternalId": "$EXTERNAL_ID" } } } ] } EOF echo "✅ Setup complete!" echo "📋 Next steps:" echo " 1. Switch to account $TRUSTED_ACCOUNT" echo " 2. Create user '$USER_NAME' if not exists" echo " 3. Attach the assume-role-policy.json to the user" echo " 4. Test with:AWSsts assume-role --role-arn arn:aws:iam::$TRUSTING_ACCOUNT:role/$ROLE_NAME --role-session-name TestSession --external-id $EXTERNAL_ID" # Cleanup rm -f trust-policy.json assume-role-policy.json
26

Testing and Validation

# Test script - run in trusted account #!/bin/bash test_cross_account_access() { local ROLE_ARN="arn:aws:iam::111122223333:role/CrossAccountDeveloperRole" local EXTERNAL_ID="your-external-id-here" echo "🧪 Testing cross-account access..." # Test 1: Verify current identity echo "Current identity:" AWSsts get-caller-identity # Test 2: Attempt role assumption echo "Attempting role assumption..." RESPONSE=$(aws sts assume-role \ --role-arn "$ROLE_ARN" \ --role-session-name "TestSession-$(date +%s)" \ --external-id "$EXTERNAL_ID" \ --duration-seconds 900 \ 2>&1) || { echo "❌ Role assumption failed: $RESPONSE" return 1 } echo "✅ Role assumption successful!" # Test 3: Extract and test credentials ACCESS_KEY=$(echo "$RESPONSE" | jq -r '.Credentials.AccessKeyId') SECRET_KEY=$(echo "$RESPONSE" | jq -r '.Credentials.SecretAccessKey') SESSION_TOKEN=$(echo "$RESPONSE" | jq -r '.Credentials.SessionToken') # Test 4: Verify assumed role identity AWS_ACCESS_KEY_ID="$ACCESS_KEY" \ AWS_SECRET_ACCESS_KEY="$SECRET_KEY" \ AWS_SESSION_TOKEN="$SESSION_TOKEN" \ AWSsts get-caller-identity echo "✅ All tests passed!" } test_cross_account_access

🎉 Congratulations!

You've successfully implemented secure cross-account access with comprehensive monitoring, error handling, and security best practices.


Key Takeaways:
  • EveryAWSCLI parameter has a specific purpose and security implication
  • Trust policies and permission policies work together to control access
  • ExternalId is crucial for preventing confused deputy attacks
  • Comprehensive logging and monitoring are essential for security
  • Automation scripts should include robust error handling
📚 Additional Resources:
  • AWS IAM Best Practices Documentation
  • AWS Security Token Service API Reference
  • AWS CloudTrail User Guide for IAM Events
  • AWS Access Analyzer User Guide
1 / 15