AWS IAM Roles & Permissions Series - Detailed Outlines

19 min read

AWS IAM Roles & Permissions Series - Detailed Outlines

A 4-part series for AWS Security Specialty exam preparation.


Part 1: IAM Foundations - Users, Roles, and Policy Evaluation

---
title: "AWS IAM Foundations: Users, Roles, and How Policies Actually Work"
description: "The mental model every security engineer needs for IAM. Understanding policy evaluation logic changed how I approach AWS security."
pubDate: "2025-XX-XX"
categories: ["cloud-security", "security-engineering", "aws"]
tags: ["AWS", "IAM", "Security", "Roles", "Policies", "Exam Prep"]
series: "AWS IAM Security Deep Dive"
part: 1
toc: true
---

Opening Hook (Draft)

The policy said Allow. The role was attached. CloudTrail showed the
AssumeRole worked. AccessDenied anyway.

Spent two hours on a Friday afternoon convinced AWS was broken. Turned
out the S3 bucket had a resource policy I didn't know about. It was
using an allow-list approach—my role ARN wasn't in it.

That's when I realized I didn't actually understand how IAM evaluates
permissions. I knew the pieces. Users, roles, policies. But not how
they fit together when there's more than one policy involved.

This is the mental model I wish someone had given me three years ago.

Why this works: Specific (Friday afternoon, S3 bucket), admits ignorance, no dramatic breach story—just a relatable debugging session.

Section 1: IAM Identity Types - Choosing the Right One

Users - When and When Not

  • Human access patterns only
  • Long-term credentials = long-term risk
  • Why service accounts as IAM users is an anti-pattern
  • CLI: aws iam list-users --query 'Users[?PasswordLastUsed==null]'

Groups - The Forgotten Organizer

  • Permission inheritance patterns
  • Why you can’t nest groups (and workarounds)
  • Common group structures (Developers, Admins, ReadOnly, Auditors)

Roles - The Secure Default

  • No permanent credentials
  • Assumable by users, services, accounts, federated identities
  • The trust policy + permission policy duo
  • CLI: aws iam list-roles --query 'Roles[].RoleName'

Decision Framework Table

ScenarioUseWhy
Developer console accessUser + MFA + Role assumptionAudit trail, temporary elevation
Lambda functionExecution RoleNo credentials in code
Cross-account accessRoleTrust policy controls who
CI/CD pipelineRole (OIDC)No stored secrets
EC2 workloadInstance Profile + RoleMetadata service provides creds

Section 2: Policy Types - The Six You Must Know

Identity-Based Policies

  • AWS Managed (convenience, broad)
  • Customer Managed (reusable, version-controlled)
  • Inline (coupled to identity, use sparingly)
  • Example comparison with same permission three ways

Resource-Based Policies

  • S3 bucket policies
  • KMS key policies (required for KMS!)
  • Lambda resource policies
  • SQS/SNS policies
  • Trust policies on roles
  • Key difference: Principal element required

Permission Boundaries

  • The “maximum permissions” ceiling
  • Use case: delegating IAM admin safely
  • How effective permissions = intersection
  • Diagram: Identity policy ∩ Permission boundary = Effective

Service Control Policies (SCPs)

  • Organization-level guardrails
  • Don’t grant, only restrict
  • Common patterns: region restriction, service blocking
  • Why SCPs don’t affect management account

Session Policies

  • Passed during AssumeRole
  • Further restricts session permissions
  • Use case: dynamic permission scoping

VPC Endpoint Policies

  • Controls access through the endpoint
  • Often forgotten attack surface
  • Example: restricting S3 endpoint to specific buckets

Section 3: Policy Evaluation Logic (Exam Gold)

The Evaluation Flowchart

Request comes in

Explicit Deny anywhere? → YES → DENIED
    ↓ NO
SCP allows? (if in Org) → NO → DENIED
    ↓ YES
Resource-based policy allows? → YES (some cases) → ALLOWED
    ↓ NO
Identity-based policy allows? → NO → DENIED
    ↓ YES
Permission boundary allows? → NO → DENIED
    ↓ YES
Session policy allows? (if applicable) → NO → DENIED
    ↓ YES
ALLOWED

Same-Account vs Cross-Account Differences

  • Same account: Resource OR Identity policy can allow
  • Cross-account: Resource AND Identity policy must allow
  • This trips up so many people (include example)

Implicit Deny vs Explicit Deny

  • Implicit: absence of Allow
  • Explicit: Deny statement
  • Why explicit Deny always wins
  • The “Deny first, then Allow” mental model is wrong

Worked Examples

  1. User in OU with SCP + managed policy + permission boundary
  2. Lambda assuming role to access S3 cross-account
  3. EC2 accessing KMS key (needs key policy!)

Section 4: Root Account - The Nuclear Option

  • When root is actually needed (close account, certain billing)
  • MFA types: Virtual, Hardware, FIDO (exam asks this)
  • Monitoring root usage with CloudTrail
  • CLI: aws cloudtrail lookup-events --lookup-attributes AttributeKey=Username,AttributeValue=root
  • SCP to restrict root (except for legit uses)

Section 5: Practical Exercises

Exercise 1: Map Your Current IAM

# Count users, roles, policies
aws iam get-account-summary

Exercise 2: Find Over-Privileged Identities

# Users with admin access
aws iam list-attached-user-policies --user-name USERNAME

Exercise 3: Test Policy Evaluation

  • Use IAM Policy Simulator
  • CLI: aws iam simulate-principal-policy

Exam Callouts Box

  • Policy evaluation order is tested heavily
  • Know the difference between resource-based and identity-based for cross-account
  • Permission boundaries come up in delegation scenarios
  • SCPs: remember management account is exempt

Closing

  • Teaser for Part 2: STS and temporary credentials
  • Key takeaway: IAM isn’t about granting access, it’s about precisely controlling it

Part 2: STS & Temporary Credentials - The Secure Way to Access AWS

---
title: "AWS STS Deep Dive: Temporary Credentials, Federation, and Secure Access Patterns"
description: "Why temporary credentials should be your default. From AssumeRole to SAML federation, here's how secure AWS access actually works."
pubDate: "2025-XX-XX"
categories: ["cloud-security", "security-engineering", "aws"]
tags: ["AWS", "STS", "Federation", "SAML", "OIDC", "AssumeRole", "Exam Prep"]
series: "AWS IAM Security Deep Dive"
part: 2
toc: true
---

Opening Hook (Draft)

Got a Slack DM from security at 11pm: "We found AWS keys in a public repo."

Ran `git log` on the commit. Keys were pushed 8 months ago. Created for
a contractor who left the company 6 months before that. Still active.
With PowerUserAccess.

Took us 3 hours to audit what those keys had touched. S3 buckets,
Lambda functions, DynamoDB tables. We couldn't be 100% sure nothing
was exfiltrated.

If that contractor had used a role instead? The credentials would've
expired in an hour. There'd be nothing to leak.

That's when I stopped creating IAM users for anything that wasn't
a human sitting at a keyboard.

Why this works: Real timeline (8 months, 6 months, 3 hours), specific services affected, no dramatic “breach” language—just the reality of credential cleanup.

Section 1: Why Temporary Credentials Matter

The Problem with Long-Lived Credentials

  • Access keys don’t expire by default
  • Rotation is manual and error-prone
  • Leaked key = persistent access until discovered
  • Your CLI audit command here (reference Part 1)

The STS Solution

  • Credentials that expire automatically
  • No secrets to store (for roles)
  • Built-in audit trail of who assumed what
  • Session duration controls

Section 2: The STS API Operations (Exam Favorites)

AssumeRole

  • Primary method for role assumption
  • Returns: AccessKeyId, SecretAccessKey, SessionToken, Expiration
  • Trust policy must allow the caller
  • Duration: 1 hour default, up to 12 hours (role setting)
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/TargetRole \
  --role-session-name MySession

AssumeRoleWithSAML

  • For SAML 2.0 federation (Active Directory, Okta, etc.)
  • User authenticates with IdP, gets SAML assertion
  • AWS validates assertion, returns temporary credentials
  • Duration: up to 12 hours
  • Exam: Know the flow diagram

AssumeRoleWithWebIdentity

  • For OIDC providers (Cognito, Google, GitHub Actions)
  • Modern approach for web/mobile apps
  • IRSA uses this under the hood
  • Exam: Difference from Cognito identity pools

GetSessionToken

  • For MFA-protected API access
  • Used with IAM user credentials
  • Returns temporary credentials with MFA context
  • Exam: When to use vs AssumeRole

GetFederationToken

  • For custom federation broker
  • More flexible policy passing
  • Less common, but exam tests it
  • Requires IAM user or root credentials

Comparison Table

OperationUse CaseCan Pass Policy?Max Duration
AssumeRoleRole assumptionYes (session policy)12h
AssumeRoleWithSAMLEnterprise SSOYes12h
AssumeRoleWithWebIdentityWeb/Mobile/CI/CDYes12h
GetSessionTokenMFA-protected accessNo36h
GetFederationTokenCustom brokerYes (required)36h

Section 3: Role Trust Policies - Who Can Assume

Trust Policy Anatomy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "unique-id-from-partner"
        }
      }
    }
  ]
}

Principal Types

  • AWS account: "AWS": "arn:aws:iam::123456789012:root"
  • Specific role/user: "AWS": "arn:aws:iam::123456789012:role/RoleName"
  • AWS service: "Service": "lambda.amazonaws.com"
  • Federated: "Federated": "arn:aws:iam::123456789012:saml-provider/ADFS"
  • OIDC: "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"

The Confused Deputy Problem

  • Third-party assumes role on your behalf
  • Without protection, any customer of theirs could access your resources
  • Solution: External ID in condition
  • Exam loves this scenario

Section 4: Cross-Account Access Patterns

Pattern 1: Direct Role Assumption

Account A (Trusting) ←── Account B (Trusted)
     Role                    User/Role
     Trust policy allows B   Has sts:AssumeRole permission

Pattern 2: Role Chaining

User → Role A → Role B → Role C
  • Maximum 1 hour session when chaining
  • Each hop requires trust + permission
  • Exam: Know the limitations

Pattern 3: Resource-Based + Role

  • Some services allow direct cross-account via resource policy
  • S3, KMS, SQS, SNS, Lambda
  • When to use which approach

Setting Up Cross-Account Access Step-by-step with CLI commands:

# In trusting account - create role
aws iam create-role --role-name CrossAccountRole \
  --assume-role-policy-document file://trust-policy.json

# In trusted account - allow assumption
aws iam attach-user-policy --user-name Developer \
  --policy-arn arn:aws:iam::TRUSTED:policy/AssumeRolePolicy

Section 5: Federation Deep Dive

SAML 2.0 Federation

  • Enterprise identity providers (AD FS, Okta, Azure AD)
  • Flow: User → IdP → SAML assertion → AWS → Temp credentials
  • Setting up SAML provider in AWS
  • Attribute mapping (role selection)

OIDC Federation

  • Modern alternative to SAML
  • GitHub Actions, GitLab CI, Kubernetes
  • Creating OIDC provider
  • Trust policy with conditions
{
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/main"
    }
  }
}

AWS IAM Identity Center (SSO)

  • Centralized access management
  • Permission sets = IAM policies
  • Works with external IdPs
  • Exam: Know the difference from direct federation

Cognito Integration

  • User Pools (authentication) vs Identity Pools (authorization)
  • Authenticated vs unauthenticated access
  • Linking to IAM roles

Section 6: MFA-Protected API Access

When MFA Is Required

  • Condition: aws:MultiFactorAuthPresent
  • Condition: aws:MultiFactorAuthAge

The GetSessionToken Flow

# Get MFA-protected credentials
aws sts get-session-token \
  --serial-number arn:aws:iam::123456789012:mfa/user \
  --token-code 123456

Policy Example: Require MFA for Sensitive Actions

{
  "Effect": "Deny",
  "Action": ["iam:*", "organizations:*"],
  "Resource": "*",
  "Condition": {
    "BoolIfExists": {
      "aws:MultiFactorAuthPresent": "false"
    }
  }
}

Section 7: Session Duration and Limits

Duration Limits by Method

  • AssumeRole: 15min to 12h (role’s MaxSessionDuration)
  • AssumeRoleWithSAML: 15min to 12h
  • AssumeRoleWithWebIdentity: 15min to 12h
  • GetSessionToken: 15min to 36h
  • GetFederationToken: 15min to 36h
  • Role chaining: Maximum 1 hour (hard limit)

Setting MaxSessionDuration

aws iam update-role --role-name MyRole --max-session-duration 43200

Practical Exercises

Exercise 1: Set Up Cross-Account Access

  • Create role in Account A
  • Assume from Account B
  • Verify with aws sts get-caller-identity

Exercise 2: GitHub Actions OIDC

  • Create OIDC provider
  • Create role with trust policy
  • Test from GitHub workflow

Exercise 3: MFA-Protected CLI Access

  • Configure MFA device
  • Use GetSessionToken
  • Verify sensitive actions require MFA

Exam Callouts Box

  • Know all 5 STS operations and when to use each
  • Role chaining = 1 hour max (tested frequently)
  • External ID prevents confused deputy
  • SAML vs OIDC vs Cognito differences
  • GetSessionToken is for MFA, not role assumption

Closing

  • Teaser for Part 3: Advanced patterns (ABAC, conditions, permission boundaries)
  • Key takeaway: Temporary credentials should be your default

Part 3: Advanced Permission Patterns - ABAC, Conditions, and Boundaries

---
title: "Advanced AWS IAM Patterns: ABAC, Conditions, Permission Boundaries, and SCPs"
description: "Moving beyond basic policies. How to implement attribute-based access control, bulletproof permission boundaries, and organizational guardrails."
pubDate: "2025-XX-XX"
categories: ["cloud-security", "security-engineering", "aws"]
tags: ["AWS", "IAM", "ABAC", "SCPs", "Permission Boundaries", "Exam Prep"]
series: "AWS IAM Security Deep Dive"
part: 3
toc: true
---

Opening Hook (Draft)

I was managing 127 IAM policies across 4 AWS accounts. Every time we
onboarded a new team, I'd spend half a day copying policies, updating
ARNs, and hoping I didn't miss anything.

Then a colleague asked: "Why don't you just use tags?"

Took me a weekend to rewrite everything with ABAC. One policy replaced
23 others. When the Platform team spun up last month, I didn't touch
IAM at all. Tagged their role, tagged their resources. Done.

The advanced IAM patterns aren't complicated. They're just not the
first thing anyone teaches you.

Why this works: Specific numbers (127 policies, 4 accounts, 23 replaced), mundane problem (onboarding teams), no vendor-speak—just efficiency gains.

Section 1: IAM Conditions - Precision Access Control

Condition Basics

"Condition": {
  "ConditionOperator": {
    "ConditionKey": "value"
  }
}

Common Condition Operators

  • StringEquals, StringLike (wildcards)
  • ArnEquals, ArnLike
  • IpAddress, NotIpAddress
  • DateGreaterThan, DateLessThan
  • Bool, Null
  • ForAllValues, ForAnyValue (for multi-valued keys)

Global Condition Keys (Exam Favorites)

KeyUse Case
aws:SourceIpRestrict by IP/CIDR
aws:SourceVpcRestrict to VPC
aws:SourceVpceRestrict to VPC endpoint
aws:PrincipalTag/keyCheck caller’s tags
aws:ResourceTag/keyCheck resource tags
aws:RequestTag/keyCheck tags in request
aws:PrincipalOrgIDRestrict to organization
aws:PrincipalAccountRestrict to specific account
aws:MultiFactorAuthPresentRequire MFA
aws:MultiFactorAuthAgeMFA recency
aws:CurrentTimeTime-based access
aws:SecureTransportRequire HTTPS
aws:CalledViaCheck calling service

Service-Specific Condition Keys

  • S3: s3:prefix, s3:x-amz-acl, s3:ExistingObjectTag
  • EC2: ec2:ResourceTag, ec2:InstanceType, ec2:Region
  • DynamoDB: dynamodb:Attributes, dynamodb:LeadingKeys
  • KMS: kms:ViaService, kms:CallerAccount

Real-World Condition Examples

Restrict to corporate IPs:

{
  "Condition": {
    "IpAddress": {
      "aws:SourceIp": ["203.0.113.0/24", "198.51.100.0/24"]
    }
  }
}

Allow only during business hours:

{
  "Condition": {
    "DateGreaterThan": {"aws:CurrentTime": "2025-01-01T09:00:00Z"},
    "DateLessThan": {"aws:CurrentTime": "2025-01-01T17:00:00Z"}
  }
}

Force encryption on S3 uploads:

{
  "Effect": "Deny",
  "Action": "s3:PutObject",
  "Resource": "*",
  "Condition": {
    "Null": {
      "s3:x-amz-server-side-encryption": "true"
    }
  }
}

Section 2: ABAC - Attribute-Based Access Control

RBAC vs ABAC

  • RBAC: Permissions based on role
  • ABAC: Permissions based on attributes (tags)
  • Why ABAC scales better

The ABAC Model in AWS

  • Principal tags (on users/roles)
  • Resource tags (on AWS resources)
  • Request tags (in API calls)
  • Session tags (passed during AssumeRole)

ABAC Policy Pattern

{
  "Effect": "Allow",
  "Action": ["ec2:StartInstances", "ec2:StopInstances"],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "ec2:ResourceTag/Project": "${aws:PrincipalTag/Project}",
      "ec2:ResourceTag/Environment": "${aws:PrincipalTag/Environment}"
    }
  }
}

This allows: Users can only manage EC2 instances tagged with their same Project and Environment.

Setting Up ABAC

Tag the role:

aws iam tag-role --role-name DeveloperRole \
  --tags Key=Project,Value=Phoenix Key=Environment,Value=dev

Tag the resources:

aws ec2 create-tags --resources i-1234567890abcdef0 \
  --tags Key=Project,Value=Phoenix Key=Environment,Value=dev

Session Tags Pass tags during role assumption:

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/MyRole \
  --role-session-name MySession \
  --tags Key=Department,Value=Engineering

ABAC Benefits

  • Fewer policies to manage
  • Scales with resources automatically
  • Self-service within boundaries
  • Audit-friendly

Section 3: Permission Boundaries - Delegating Safely

The Delegation Problem

  • You want developers to create IAM roles for their apps
  • But you don’t want them to create admin roles
  • Solution: Permission boundaries

How Boundaries Work

Effective Permissions = Identity Policy ∩ Permission Boundary

Creating a Permission Boundary

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:*",
        "dynamodb:*",
        "lambda:*",
        "logs:*"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Deny",
      "Action": [
        "iam:*",
        "organizations:*",
        "account:*"
      ],
      "Resource": "*"
    }
  ]
}

Applying the Boundary

aws iam put-role-permissions-boundary \
  --role-name DeveloperRole \
  --permissions-boundary arn:aws:iam::123456789012:policy/DeveloperBoundary

Forcing Boundary on Created Roles Require developers to attach boundary to any role they create:

{
  "Effect": "Allow",
  "Action": ["iam:CreateRole", "iam:AttachRolePolicy"],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/DeveloperBoundary"
    }
  }
}

Boundary vs SCP

AspectPermission BoundarySCP
ScopeSingle identityEntire account/OU
Applied toUsers, RolesAccounts
Grants permissionsNoNo
Management account exemptN/AYes

Section 4: Service Control Policies (SCPs)

SCP Fundamentals

  • Organization-level guardrails
  • Don’t grant, only restrict
  • Affect all principals in account (except management account)
  • Evaluated before identity and resource policies

SCP Strategy: Deny List vs Allow List

Deny List (block specific actions):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": ["organizations:LeaveOrganization"],
      "Resource": "*"
    }
  ]
}

Allow List (only permit specific actions):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ec2:*", "s3:*", "lambda:*"],
      "Resource": "*"
    }
  ]
}

Common SCP Patterns

Region restriction:

{
  "Effect": "Deny",
  "Action": "*",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestedRegion": ["us-east-1", "us-west-2", "eu-west-1"]
    }
  }
}

Prevent disabling security services:

{
  "Effect": "Deny",
  "Action": [
    "guardduty:DeleteDetector",
    "guardduty:DisassociateFromMasterAccount",
    "securityhub:DisableSecurityHub",
    "config:DeleteConfigurationRecorder",
    "cloudtrail:DeleteTrail",
    "cloudtrail:StopLogging"
  ],
  "Resource": "*"
}

Require IMDSv2:

{
  "Effect": "Deny",
  "Action": "ec2:RunInstances",
  "Resource": "arn:aws:ec2:*:*:instance/*",
  "Condition": {
    "StringNotEquals": {
      "ec2:MetadataHttpTokens": "required"
    }
  }
}

SCP Inheritance

  • Root → OU → Nested OU → Account
  • Effective = intersection of all SCPs in path
  • Must have explicit Allow at every level (if using allow-list approach)

Section 5: IRSA - IAM Roles for Service Accounts (EKS)

The Problem IRSA Solves

  • Pods need AWS access
  • Old way: Node role (too broad) or inject credentials (risky)
  • IRSA: Per-pod IAM roles via OIDC

How IRSA Works

Pod with ServiceAccount
    → OIDC token injected by EKS
    → Pod calls STS AssumeRoleWithWebIdentity
    → Temporary credentials returned
    → Pod accesses AWS services

Setting Up IRSA

  1. Create OIDC provider:
eksctl utils associate-iam-oidc-provider \
  --cluster my-cluster --approve
  1. Create IAM role with trust policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:default:my-service-account"
        }
      }
    }
  ]
}
  1. Annotate ServiceAccount:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service-account
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/MyPodRole

IRSA vs Pod Identity

  • IRSA: Uses OIDC, more setup, works everywhere
  • Pod Identity: Simpler, EKS-managed, newer
  • Know both for the exam

Section 6: Resource-Based vs Identity-Based Decision Framework

When to Use Resource-Based Policies

  • Cross-account access without role assumption
  • Service integrations (Lambda invoke, S3 events)
  • Centralized resource with multiple accessors

When to Use Identity-Based Policies

  • Same-account access
  • Portable permissions (user moves, permissions follow)
  • Complex permission logic

Services Supporting Resource-Based Policies

  • S3 (bucket policies)
  • KMS (key policies - REQUIRED)
  • Lambda (resource policies)
  • SQS, SNS
  • API Gateway
  • Secrets Manager
  • ECR
  • EventBridge

The KMS Special Case

  • Key policy is REQUIRED (can’t use identity-only)
  • Must explicitly allow account or principals
  • Grants as alternative to policy statements

Practical Exercises

Exercise 1: Implement ABAC for EC2

  • Tag roles and instances
  • Create ABAC policy
  • Test access

Exercise 2: Set Up Permission Boundary Delegation

  • Create boundary policy
  • Allow role creation with boundary
  • Verify can’t escalate

Exercise 3: Deploy SCPs

  • Create test OU
  • Apply region restriction SCP
  • Verify enforcement

Exam Callouts Box

  • Know global vs service-specific condition keys
  • ABAC uses tag-based conditions
  • Permission boundary ∩ Identity policy = Effective
  • SCPs don’t affect management account
  • KMS requires key policy (identity-only doesn’t work)
  • IRSA uses AssumeRoleWithWebIdentity

Closing

  • Teaser for Part 4: Security, auditing, and misconfigurations
  • Key takeaway: Advanced patterns enable scale without sacrificing security

Part 4: IAM Security, Auditing, and Common Misconfigurations

---
title: "IAM Security: Auditing, Misconfigurations, and Privilege Escalation Prevention"
description: "Finding the holes before attackers do. IAM misconfigurations, privilege escalation paths, and how to audit your AWS environment."
pubDate: "2025-XX-XX"
categories: ["cloud-security", "security-engineering", "aws"]
tags: ["AWS", "IAM", "Security Audit", "Privilege Escalation", "Access Analyzer", "Exam Prep"]
series: "AWS IAM Security Deep Dive"
part: 4
toc: true
---

Opening Hook (Draft)

Ran IAM Access Analyzer for the first time on an account I'd been managing
for two years. Found 14 resources with public access. Three S3 buckets,
two Lambda functions, and nine IAM roles that could be assumed by external
accounts.

I knew about one of the buckets. The others were news to me.

Spent the next week tracing how each one got that way. Turns out our
CloudFormation templates had been copying the same overly permissive trust
policy for 18 months. Every new microservice inherited it. Nobody noticed
because nothing broke.

That's when I understood something: IAM doesn't fail loudly. It fails by
quietly allowing more than it should. You don't find these problems by
waiting for incidents. You find them by actively looking.

Here's how to look.

Why this works: Specific numbers (14 resources, 3 buckets, 2 Lambda, 9 roles, 18 months), admits ignorance (“The others were news to me”), mundane cause (CloudFormation copy-paste), no dramatic breach—just the reality of permission creep.

Section 1: Common IAM Misconfigurations

Overly Permissive Policies

The classic mistakes:

// DON'T DO THIS
{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}

// OR THIS
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}

Finding them:

# Find policies with wildcards
aws iam list-policies --scope Local --query 'Policies[].Arn' --output text | \
while read arn; do
  aws iam get-policy-version \
    --policy-arn "$arn" \
    --version-id $(aws iam get-policy --policy-arn "$arn" --query 'Policy.DefaultVersionId' --output text) \
    --query 'PolicyVersion.Document' | grep -l '"Action": "\*"' && echo "$arn"
done

Overly Permissive Trust Policies

// DANGEROUS - Any AWS account can assume
{
  "Principal": {"AWS": "*"},
  "Action": "sts:AssumeRole"
}

// DANGEROUS - Any principal in another account
{
  "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
  "Action": "sts:AssumeRole"
}

Missing MFA Requirements

  • Console access without MFA
  • Sensitive actions without MFA condition
  • Root account without MFA

Unused Credentials

# Users who haven't logged in
aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 -d | \
  awk -F',' '$5 == "false" {print $1}'

# Access keys not used in 90 days
aws iam list-access-keys --user-name USERNAME
aws iam get-access-key-last-used --access-key-id AKIAXXXXXXXX

Inline Policies Sprawl

  • Hard to audit
  • Not version controlled
  • Often forgotten
# Find all inline policies
aws iam list-users --query 'Users[].UserName' --output text | \
xargs -I {} sh -c 'aws iam list-user-policies --user-name {} --query "PolicyNames" --output text && echo "User: {}"'

Section 2: Privilege Escalation Paths

The Dangerous Permissions

PermissionRiskEscalation Path
iam:CreatePolicyHighCreate admin policy
iam:AttachUserPolicyHighAttach admin to self
iam:AttachRolePolicyHighAttach admin to assumable role
iam:CreatePolicyVersionHighUpdate existing policy
iam:SetDefaultPolicyVersionHighActivate malicious version
iam:PassRoleHighPass powerful role to service
iam:CreateAccessKeyMediumCreate keys for other users
iam:UpdateAssumeRolePolicyHighAllow self to assume role
iam:PutUserPolicyHighInline policy on self
iam:PutRolePolicyHighInline policy on role
lambda:CreateFunction + iam:PassRoleHighExecute as any passable role
ec2:RunInstances + iam:PassRoleHighLaunch with any passable role
sts:AssumeRoleVariesDepends on assumable roles

Example Escalation: Lambda + PassRole

# Attacker has: lambda:CreateFunction, lambda:InvokeFunction, iam:PassRole

# 1. Create function with admin role
aws lambda create-function \
  --function-name escalate \
  --role arn:aws:iam::123456789012:role/AdminRole \
  --handler index.handler \
  --runtime python3.9 \
  --zip-file fileb://malicious.zip

# 2. Invoke function - code runs as AdminRole
aws lambda invoke --function-name escalate response.json

Example Escalation: CreatePolicyVersion

# Attacker has: iam:CreatePolicyVersion on a policy attached to them

# 1. Create new version with admin access
aws iam create-policy-version \
  --policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}' \
  --set-as-default

Preventing Escalation

  • Permission boundaries (Part 3)
  • Deny statements for dangerous actions
  • Monitor IAM changes via CloudTrail
  • Regular access reviews

Section 3: IAM Access Analyzer

What Access Analyzer Does

  • Finds resources shared externally
  • Identifies overly permissive policies
  • Validates policies before deployment
  • Generates policies from activity

Setting Up Access Analyzer

# Create analyzer
aws accessanalyzer create-analyzer \
  --analyzer-name my-analyzer \
  --type ACCOUNT

# List findings
aws accessanalyzer list-findings \
  --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/my-analyzer

Finding Types

  • External access to S3 buckets
  • External access to IAM roles
  • External access to KMS keys
  • External access to Lambda functions
  • External access to SQS queues
  • External access to Secrets Manager secrets

Policy Validation

# Validate a policy before applying
aws accessanalyzer validate-policy \
  --policy-document file://policy.json \
  --policy-type IDENTITY_POLICY

Policy Generation from CloudTrail

# Start policy generation
aws accessanalyzer start-policy-generation \
  --policy-generation-details '{
    "principalArn": "arn:aws:iam::123456789012:role/MyRole"
  }'

# Get generated policy
aws accessanalyzer get-generated-policy \
  --job-id JOB_ID

Section 4: Auditing IAM with CloudTrail

Critical IAM Events to Monitor

EventRisk LevelWhat to Look For
CreateUserHighUnexpected new users
CreateAccessKeyHighKeys for other users
AttachUserPolicyHighAdmin policy attachments
AttachRolePolicyHighRole privilege changes
CreatePolicyVersionHighPolicy modifications
UpdateAssumeRolePolicyHighTrust policy changes
ConsoleLoginMediumFailed attempts, unusual times
SwitchRoleMediumCross-account activity
AssumeRoleMediumRole assumption patterns

CloudTrail Queries

# Find all IAM changes in last 7 days
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventSource,AttributeValue=iam.amazonaws.com \
  --start-time $(date -d '7 days ago' '+%Y-%m-%d') \
  --query 'Events[].{Time:EventTime,Event:EventName,User:Username}'

# Find privilege escalation attempts
for event in CreateUser AttachUserPolicy AttachRolePolicy PutUserPolicy PutRolePolicy CreatePolicyVersion; do
  echo "=== $event ==="
  aws cloudtrail lookup-events \
    --lookup-attributes AttributeKey=EventName,AttributeValue=$event \
    --start-time $(date -d '30 days ago' '+%Y-%m-%d') \
    --query 'Events[].{Time:EventTime,User:Username,IP:SourceIPAddress}'
done

CloudWatch Alarms for IAM

{
  "source": ["aws.iam"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventName": [
      "AttachUserPolicy",
      "AttachRolePolicy",
      "CreatePolicyVersion",
      "PutUserPolicy",
      "PutRolePolicy",
      "CreateAccessKey"
    ]
  }
}

Section 5: IAM Credential Reports and Last Accessed

Credential Report

# Generate report
aws iam generate-credential-report

# Download and analyze
aws iam get-credential-report --query 'Content' --output text | base64 -d > cred-report.csv

# Find users without MFA
cat cred-report.csv | awk -F',' '$4 == "true" && $8 == "false" {print $1}'

# Find old access keys
cat cred-report.csv | awk -F',' '$9 != "N/A" && $9 < "2024-01-01" {print $1, $9}'

Last Accessed Information

# Generate service last accessed report
JOB_ID=$(aws iam generate-service-last-accessed-details \
  --arn arn:aws:iam::123456789012:role/MyRole \
  --query 'JobId' --output text)

# Get results
aws iam get-service-last-accessed-details --job-id $JOB_ID

Use this to:

  • Identify unused permissions
  • Right-size policies
  • Find abandoned roles

Section 6: IAM Best Practices Checklist

Identity Management

  • MFA enabled for all human users
  • MFA enabled for root account (hardware key preferred)
  • No access keys for root account
  • IAM users only for human access (not workloads)
  • Federated access for enterprise (SSO/SAML)
  • Regular access key rotation (< 90 days)
  • Credential report reviewed monthly

Policies

  • No wildcard (*) actions in production policies
  • No wildcard (*) resources without conditions
  • Customer managed policies (not inline)
  • Version control for all policies
  • Permission boundaries for delegation
  • SCPs for organizational guardrails

Roles

  • Roles for all workloads (EC2, Lambda, ECS, etc.)
  • Trust policies explicitly scoped
  • External ID for third-party access
  • Session duration appropriately limited
  • IRSA for EKS workloads

Monitoring & Auditing

  • CloudTrail enabled in all regions
  • CloudTrail logs to central S3 bucket
  • Access Analyzer enabled
  • Alerts for critical IAM events
  • Regular access reviews (quarterly minimum)
  • Unused credentials removed

Section 7: Remediation Patterns

Cleaning Up Overprivileged Access

  1. Generate last accessed report
  2. Identify unused services
  3. Create new least-privilege policy
  4. Test with Policy Simulator
  5. Apply and monitor
  6. Iterate

Responding to Compromised Credentials

USER="compromised-user"

# 1. Apply immediate deny policy
aws iam put-user-policy --user-name $USER --policy-name EmergencyDeny \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"*","Resource":"*"}]}'

# 2. Disable all access keys
aws iam list-access-keys --user-name $USER --query 'AccessKeyMetadata[].AccessKeyId' --output text | \
xargs -I {} aws iam update-access-key --user-name $USER --access-key-id {} --status Inactive

# 3. Remove console access
aws iam delete-login-profile --user-name $USER 2>/dev/null

# 4. Investigate via CloudTrail
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=Username,AttributeValue=$USER \
  --start-time $(date -d '30 days ago' '+%Y-%m-%d')

Practical Exercises

Exercise 1: Run a Full IAM Audit

  • Generate credential report
  • Check for MFA compliance
  • Find inactive users
  • Review admin access

Exercise 2: Set Up IAM Monitoring

  • Create EventBridge rule for IAM changes
  • Configure SNS alerts
  • Test with policy change

Exercise 3: Identify Escalation Paths

  • List all roles with iam:PassRole
  • Identify which services they can pass to
  • Review trust policies

Exam Callouts Box

  • Know privilege escalation patterns (PassRole, CreatePolicyVersion, etc.)
  • Access Analyzer: external sharing, policy validation, policy generation
  • CloudTrail event names for IAM operations
  • Credential report contents and use cases
  • Difference between Access Advisor and Access Analyzer

Closing

  • Series recap: Foundations → STS → Advanced Patterns → Security
  • Final takeaway: IAM is the foundation of AWS security - master it
  • Call to action: Audit your own environment this week

Series Summary

PartFocusExam WeightKey Topics
1FoundationsHighUsers, roles, policy types, evaluation logic
2STS & FederationHighAssumeRole, SAML, OIDC, cross-account
3Advanced PatternsMedium-HighABAC, conditions, boundaries, SCPs, IRSA
4Security & AuditingMediumMisconfigs, escalation, Access Analyzer, monitoring

Cross-References to Your Existing Posts

  • AWS CLI Security Manual: Part 4 builds on these commands
  • Secrets Management: Part 2 connects to role-based secrets access
  • Separation vs Least Privilege: Part 1 & 3 expand on these concepts
cloud-security security-engineering aws