devuplabs.cloud
PreviewLab5 hours

Delegation & Federation

Practice role assumption, cross-account access, federation, and permission boundaries, delegation without privilege creep.

Prerequisites

0 of 7 checked

Mental Model: How Delegation Works

The two-sided door: a role needs both doors open to be usable.

  • Trust policy (the outer door): who is allowed to knock. Controlled by the role owner.
  • Permission policy (the inner door): what the session can do once inside. Also controlled by the role owner.
  • sts:AssumeRole in the caller's identity policy: the caller's permission to knock. Controlled by the caller's account.

AssumeRole: The Full Flow

Goal

Create a role, assume it from a user identity, inspect the temporary credentials, make API calls with them, and observe what happens when the session expires or you exceed the max session duration. Understand session policies as a further restriction on assumed sessions.

Estimated time: 1–2 hours


Create a role and assume it

What's happening here

sts:AssumeRole returns three things: a temporary AccessKeyId, SecretAccessKey, and SessionToken. All three are required together for any API call made under the assumed identity. The SessionToken is what distinguishes assumed-role credentials from long-lived user credentials, every API call using assumed creds must include it in the AWS-Security-Token header (the SDK does this automatically). The Expiration timestamp tells you exactly when the creds stop working. Default session duration is 1 hour; max depends on the role's MaxSessionDuration setting (1–12 hours).

bash
# Create a role that trusts the current caller identity (yourself)
CALLER_ARN=$(aws sts get-caller-identity --query Arn --output text)
echo "Current caller: $CALLER_ARN"

cat > /tmp/trust-self.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"AWS": "$CALLER_ARN"},
    "Action": "sts:AssumeRole"
  }]
}
EOF

export TARGET_ROLE="${LAB_PREFIX}-target"

aws iam create-role \
  --role-name $TARGET_ROLE \
  --assume-role-policy-document file:///tmp/trust-self.json \
  --max-session-duration 3600 | jq '.Role | {RoleName, Arn, MaxSessionDuration}'

export TARGET_ROLE_ARN=$(aws iam get-role --role-name $TARGET_ROLE --query 'Role.Arn' --output text)
echo "Target role ARN: $TARGET_ROLE_ARN"

# Attach a permission: list S3 buckets only
aws iam attach-role-policy \
  --role-name $TARGET_ROLE \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
bash
# Assume the role
CREDS=$(aws sts assume-role \
  --role-arn $TARGET_ROLE_ARN \
  --role-session-name "lab-session-$(date +%s)" \
  --duration-seconds 900)

echo "Assumed role credentials:"
echo $CREDS | jq '.Credentials | {
  AccessKeyId,
  SessionTokenPrefix: (.SessionToken | .[0:20] + "..."),
  Expiration,
  note: "All three fields are required together"
}'

# Export the temporary credentials into shell environment
export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r '.Credentials.SessionToken')

# Verify the assumed identity
aws sts get-caller-identity | jq '{assumed_role_arn: .Arn, account: .Account}'

get-caller-identity now returns an ARN in the format arn:aws:sts::{account}:assumed-role/{role-name}/{session-name}: not the original user ARN. You are now acting as the role, not as yourself.

bash
# Test: list S3 buckets (allowed by role)
aws s3 ls 2>&1 | head -5

# Test: try to list IAM users (NOT allowed by role)
aws iam list-users 2>&1 | head -3

# Test: try to create an S3 bucket (role has ReadOnly, not write)
aws s3api create-bucket --bucket "${LAB_PREFIX}-test" 2>&1 | head -3

s3 ls works. iam list-users fails with AccessDenied. create-bucket fails. The role's permission policy is AmazonS3ReadOnlyAccess: no writes, no IAM access. The assume-role step created a session bounded by the role's permissions.

bash
# Unset the assumed creds to return to your original identity
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

# Confirm you're back to original identity
aws sts get-caller-identity | jq '.Arn'

Checkpoint: successfully assumed role, confirmed identity switch, observed permission boundary of the role.


Session policies: further restrict an assumed session

What's happening here

When calling assume-role: you can pass a session policy: an additional inline policy that further restricts the assumed session. The effective permissions of the session are the intersection of the role's permission policies AND the session policy. A session policy can only restrict, it can never grant permissions the role doesn't already have. This is useful for: (1) an orchestrator that assumes a role but gives each task a scoped-down subset of permissions, (2) a human operator who wants to scope their own blast radius for a risky task.

Unlock all 24 AWS services & 291+ lab sessions (~180 hours)

Pricing