Module 2 · AWS IAM Deep Dive

Manish Garg
Manish Garg Associate of (ISC)² · RingSafe
Apr 19, 2026
10 min read
Read as

Last updated: April 29, 2026

100% Free

No signup. No paywall. No catch. One of our 10 most-requested practitioner modules — published in full so anyone can learn for free. We earn through consulting, not by gating knowledge.

See all 10 free modules →

AWS IAM is the single largest source of cloud misconfigurations. It’s also AWS’s most powerful feature. Master it and you can architect least-privilege cleanly; fumble it and you ship the kind of blast radius that makes every new access key a production-impacting event.

AWS IAM is the single largest source of cloud misconfigurations. It’s also AWS’s most powerful feature. Master it and you can architect least-privilege cleanly; fumble it and you ship the kind of blast radius that makes every new access key a production-impacting event.

This module is the concrete IAM practitioner’s guide. You’ve seen the mental models in Module 1. Now we write actual policies, understand how AWS evaluates them, and cover the patterns that Indian fintech auditors, SOC 2 assessors, and your pen-testers actually look for.

The four core objects

  • User — a named identity (typically human). Has long-lived credentials. Use sparingly; prefer SSO-federated access
  • Group — a collection of users. Attach policies here; users inherit via membership
  • Role — an assumable identity. No long-lived credentials; temporary credentials via STS. Used by workloads (EC2, Lambda) and humans (via AssumeRole)
  • Policy — a JSON document describing permissions. Attached to users, groups, or roles (identity-based), or directly to resources (resource-based)

Policy anatomy

An IAM policy is a JSON document with one or more Statement blocks:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowReadFromSpecificBucket",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::ringsafe-app-data",
        "arn:aws:s3:::ringsafe-app-data/*"
      ],
      "Condition": {
        "StringEquals": {
          "aws:SourceIp": "203.0.113.0/24"
        }
      }
    }
  ]
}

Five key elements per statement:

  • Effect — Allow or Deny
  • Action — one or more service:API calls
  • Resource — ARNs affected
  • Condition — optional constraints (IP range, MFA presence, time of day, tags, etc.)
  • Principal — only in resource-based policies, defines who the policy applies to

Policy evaluation logic

When a request arrives, AWS evaluates multiple policies in a specific order:

  1. Organizational Service Control Policies (SCPs) — guardrails at the org level. If an SCP denies, the request is denied regardless of any other policy
  2. Identity-based policies attached to the requesting principal
  3. Resource-based policies attached to the target resource
  4. Permission boundaries — maximum permissions a principal can have
  5. Session policies — passed at AssumeRole time

The logic:

  • Explicit Deny wins. If any applicable policy says Deny, the request is denied
  • Explicit Allow required. For the request to succeed, at least one applicable policy must explicitly Allow. Default is Deny
  • SCPs and permission boundaries cap maximum permissions; they don’t grant

Most real-world confusion: “I have AdministratorAccess but can’t access this bucket.” Likely because an SCP at the org level denies, or the bucket’s resource policy doesn’t include the caller’s account.

Patterns you should know

Role assumption flow

A developer wants temporary admin access. Instead of giving them AdministratorAccess on their user, they assume a role:

# Role trust policy — who can assume
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"AWS": "arn:aws:iam::111111111111:user/priya"},
    "Action": "sts:AssumeRole",
    "Condition": {
      "Bool": {"aws:MultiFactorAuthPresent": "true"},
      "NumericLessThan": {"aws:MultiFactorAuthAge": "3600"}
    }
  }]
}

# Role's permission policy — what the role can do
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
  }]
}

Priya has no permanent admin. To get admin, she must assume the role with MFA within the last hour. Session is 1-hour by default. This shrinks blast radius — a stolen user credential alone doesn’t grant admin without MFA.

Workload identity for EC2 / EKS / Lambda

Never put access keys on a running workload. Use the service’s native role attachment:

  • EC2 — Instance Profile; app reads temp creds from the instance metadata service (use IMDSv2, never IMDSv1)
  • Lambda — Execution Role; creds injected into the function’s environment at runtime
  • EKS (Kubernetes) — IAM Roles for Service Accounts (IRSA); service account annotated with role ARN, pod auto-receives creds
  • ECS — Task Role (similar to EC2 instance profile, scoped to task)

Least-privilege for common workloads

A common requirement: a Lambda function that reads from one DynamoDB table and writes to one S3 bucket. The policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:Query"
      ],
      "Resource": "arn:aws:dynamodb:ap-south-1:111111111111:table/ringsafe-users"
    },
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::ringsafe-exports/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:ap-south-1:111111111111:log-group:/aws/lambda/ringsafe-export:*"
    }
  ]
}

Compare to the common anti-pattern:

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

Both “work.” The first makes a compromise a minor incident; the second makes it a catastrophe. When a pen-tester discovers the Lambda’s role, the first gives them one table and one bucket to poke; the second gives them the entire DynamoDB and S3 surface.

Conditions — the refinement layer

IAM Conditions let you enforce fine-grained constraints:

Condition Use
aws:MultiFactorAuthPresent Require MFA on sensitive actions
aws:SourceIp Restrict to your VPN / office IP range
aws:SourceVpc / aws:SourceVpce Restrict to requests from a specific VPC / VPC endpoint
aws:ResourceTag / aws:PrincipalTag Attribute-based access control (ABAC) — match principal tag to resource tag
aws:RequestTag Require specific tags on newly-created resources
aws:SecureTransport Require HTTPS (TLS)
aws:CurrentTime Time-bound permissions
aws:userid Principal-specific matching

Real example — restrict S3 access to your VPC only (prevents data exfil even with compromised credentials):

{
  "Effect": "Deny",
  "Action": "s3:*",
  "Resource": [
    "arn:aws:s3:::sensitive-bucket",
    "arn:aws:s3:::sensitive-bucket/*"
  ],
  "Condition": {
    "StringNotEquals": {
      "aws:SourceVpce": "vpce-0abc1234def5678"
    }
  }
}

Attached to the bucket as resource policy: any request not coming through the specific VPC endpoint is denied, even if the caller has s3:* via identity policy.

The patterns auditors flag in Indian fintech

From engagements across RBI-regulated fintechs in 2024-25, the most common findings:

  1. Users with long-lived access keys older than 90 days — audit finding even if rarely used
  2. Roles with * in Action or Resource — especially if the role is assumed by cross-account principals
  3. AdministratorAccess attached to named users rather than SSO-federated — admin rights without MFA or session bounds
  4. Permission boundaries not applied to privileged roles — nothing caps what Admin can do
  5. No SCPs at organisation level — account compromise can create new high-privilege resources unimpeded
  6. EC2 instance profiles with broader permissions than the running workload needs — Instance Metadata API abuse surface
  7. IAM policies without Condition blocks — permissions granted with no context checks
  8. Cross-account role trust policies listing entire accounts rather than specific principals
  9. IMDSv1 still enabled — server-side request forgery (SSRF) in an app can steal instance credentials
  10. No logging or alerting on privilege escalation indicators (creation of AdministratorAccess policies, role assumption by unexpected principals)

Privilege escalation paths in IAM

The Rhino Security Labs research on IAM privilege escalation is canonical. Roughly 20+ classified paths. The common ones:

  • iam:CreateAccessKey on another user — attacker makes new key for target user, then acts as target
  • iam:AttachUserPolicy / AttachRolePolicy — attach AdministratorAccess to self
  • iam:PutUserPolicy / PutRolePolicy — embed inline admin policy
  • iam:PassRole + lambda:CreateFunction — create a Lambda with a privileged role attached, invoke, escalate
  • iam:PassRole + iam:UpdateAssumeRolePolicy — modify role trust policy to allow self
  • sts:AssumeRole on cross-account role with broad trust — if the trust policy trusts a whole account, anyone in that account can assume

Tools like Pacu and PMapper automate discovery of these paths. Run them against your own accounts monthly.

SSO federation — the better default for humans

For human users, long-lived IAM users are increasingly an anti-pattern. Better:

  • Centralise identity in an IdP (Okta, Azure AD, Google Workspace, AWS SSO/IAM Identity Center)
  • Federation maps IdP groups → AWS roles
  • Humans assume roles via the IdP — no long-lived AWS credentials ever exist
  • Temporary credentials rotate every 1-12 hours via session length
  • MFA enforced at the IdP; no extra AWS-side MFA required
  • Offboarding is a single IdP action — all AWS access revoked automatically

IAM Identity Center (formerly AWS SSO) is AWS’s managed service for this. For orgs with an existing IdP, federate into IAM Identity Center; for standalone, use it directly with its built-in user store.

Per-service cheat sheet

Common IAM gotchas in specific services:

  • S3 — Bucket Policy (resource-based) interacts with IAM policy (identity-based). Public access is often bucket-level. Enable Account-Level Block Public Access
  • KMS — Key Policy is required (not just IAM policy). A key with empty key policy is unusable even by the account root
  • Lambda — Execution role needs PassRole permission on itself for some actions; function’s own permissions are different from who can invoke
  • EC2 — Instance Profile ≠ Role; they’re distinct objects that reference each other. Deletion order matters
  • RDS — IAM database authentication available but often overlooked; use it
  • Secrets Manager / SSM Parameter Store — policies on the secret itself control who can retrieve; in addition to IAM on the caller

Hardening checklist

  1. Root account: MFA, no access keys, unused for daily operations, credentials in a vault
  2. All human users: SSO-federated or at minimum MFA-required
  3. All workloads: IAM roles, not long-lived keys
  4. Access Advisor: review quarterly, trim unused permissions
  5. SCPs at organisation level: deny root account key creation, deny SCP modification, deny region egress outside your scope
  6. Permission boundaries on privileged roles
  7. CloudTrail: enabled everywhere, logs to separate security account, integrity validation on
  8. GuardDuty: enabled in every region of every account
  9. IAM Access Analyzer: enabled at org level; surfaces unintended external access
  10. Regular Pacu / PMapper runs against own account (bi-annually at minimum)

Quick reference summary

  • IAM objects: User, Group, Role, Policy
  • Policy elements: Effect, Action, Resource, Condition, Principal (resource-based only)
  • Evaluation: SCP → Identity → Resource → Permission Boundary → Session. Explicit Deny wins; Explicit Allow required
  • Workload identities (roles) over long-lived keys — always
  • Least privilege in policies: specific Action + specific Resource; avoid *:*
  • Conditions are the refinement layer: MFA, source IP, VPC, tags, time, TLS
  • Audit findings in Indian fintech: stale keys, * permissions, no SCPs, no permission boundaries, IMDSv1
  • Privilege-escalation paths: CreateAccessKey, AttachPolicy, PutPolicy, PassRole + Lambda, AssumeRole on broad trust
  • SSO federation for humans; IAM Identity Center is the managed option
  • Hardening: MFA root + workload roles + SCPs + permission boundaries + CloudTrail + GuardDuty + Access Analyzer + quarterly review

Take the quiz. Next: S3 Security & Misconfigurations — where we dig into the single service that has caused more publicly-disclosed data breaches than any other.

🧠
Check your understanding

Module Quiz · 20 questions

Pass with 80%+ to mark this module complete. Unlimited retries. Each question shows an explanation.

Up next
Module 3 · S3 Security & Misconfigurations · Basic tier

Continue →

Real-World Case Study: Code Spaces, 2014

The story. Code Spaces was a UK code-hosting startup competing with GitHub. Profitable. Real customers. On 17 June 2014, the company died in 12 hours.

The technical chain.

  1. An attacker acquired the AWS root account credentials. (Method never disclosed — phishing the founder is the most likely theory.)
  2. Attacker logged into the AWS Console at 09:00.
  3. Attacker created several new IAM users with admin rights and demanded ransom via Code Spaces’ Twitter.
  4. Code Spaces’ founder logged in at 11:00 and revoked the rogue IAM users.
  5. Attacker — still holding root — saw this. Began deletion as retaliation.
  6. Within an hour: every EC2 instance, every EBS volume, every S3 bucket, every AMI, every RDS instance, every off-site backup (which, fatally, was also in AWS) — gone.

By 21:00 the company posted: “Code Spaces will not be able to operate beyond this point. The cost of resolving this issue to date and the cost of refunding customers who have data on the cloud, who do not wish to operate with us as a company any longer, has put Code Spaces in an irreversible position.”

What IAM controls would have prevented this.

  • MFA on root. Hardware token. Locked in a safe. Used only for billing and for emergencies that can’t be handled by IAM Identity Center.
  • Service Control Policies (SCPs) at the AWS Organization level — even root cannot delete certain resources without breaking the SCP first (which itself requires a 24-hour delay if configured).
  • S3 Object Lock in compliance mode on backup buckets — once written, the data cannot be deleted by anyone, including root, until the retention period expires.
  • Cross-account backup vault — backups in a different AWS account whose IAM is not reachable from production credentials. Geographically separate region.
  • CloudTrail to a separate account with deny-delete on the trail itself. So even an attacker can’t cover their tracks.

The takeaway. AWS root credentials are nuclear. Treat them as such. The blast radius of a compromised IAM-not-root account is whatever IAM Identity Center scoped them to. The blast radius of a compromised root, with no SCPs and no off-account backups, is your company. Build for the latter scenario.

Want this for your team?

Custom team training + practitioner advisory

Beyond the free academy — we run private workshops, vCISO advisory, and red-team exercises tailored to your stack. For Indian SMBs scaling past their first hire.

Book team training call Replies in 4 working hrs · India-only · Senior consultants