The blast radius of a single IAM role — still.anerdatwork.com
The blast radius of a single IAM role
IdentityMarch 4, 20249 min read

The blast radius of a single IAM role

We drew the graph properly for the first time. It had edges nobody in the room could account for.

One role, ten paths

You’ve got an IAM role that grants s3:* on arn:aws:s3:::prod-data-*. Seems reasonable for the analytics team. But when we drew the dependency graph for the first time, we found something unexpected.

That single role had access to twelve downstream services. Each of which could create credentials. Which meant your blast radius wasn’t one role—it was an entire ecosystem.

{
  "Role": "analytics-prod",
  "DirectPermissions": ["s3:*"],
  "BlastRadius": {
    "services_with_credentials": 12,
    "cross_account_access": true,
    "assumable_by_external": ["lambda.amazonaws.com"]
  }
}

The edges nobody saw

We mapped the permission graph properly using AWS IAM Access Analyzer. What we found:

  • A Lambda function assumed this role and wrote results to a different S3 bucket
  • That bucket had lifecycle policies that copied data to a disaster recovery account
  • The DR account had permissions to restore deleted objects from our audit log bucket

One compromised credential meant we couldn’t prove what hadn’t been tampered with.

Drawing the graph right this time

# Simple script to enumerate role dependencies
import boto3

iam = boto3.client('iam')
role_policies = iam.list_role_policies(RoleName='analytics-prod')
for policy in role_policies['PolicyNames']:
    print(f"\n{policy}:")
    print(iam.get_role_policy(RoleName='analytics-prod', PolicyName=policy))

What we changed

  1. Principle of least privilege, actually: Split the monolithic role into twelve scoped roles
  2. Credential rotation on detection: Any anomaly triggers immediate rotation
  3. Graph-based auditing: Weekly runs of access analyzer to catch drift
  4. Human review of new edges: Any cross-account permission requires approval

The blast radius of a single IAM role is only as big as you’re willing to let it get. Draw the graph before the adversary does.