cloud · level 1

IAM Mental Model

Principals, actions, resources — how requests get allow or deny.

175 XP

IAM Mental Model

Every cloud request is a four-tuple: (principal, action, resource, context). IAM's job is to answer one question about that tuple: allow or deny?

If you remember nothing else, remember this: an IAM request is either explicitly allowed by some statement, explicitly denied by some statement (which wins), or implicitly denied because no statement matched. That's the entire algorithm.

Analogy

Think of a nightclub with a paranoid door policy. The bouncer sees four things about each person: who they are, what they want to do ("enter", "enter VIP"), where ("main floor", "rooftop"), and the context (it's 3am, it's raining, they're with a member). They flip through a binder of rules: if one page says "yes, let them in" and another page says "no, banned for life", the ban always wins. If no page says anything at all, the default is "no, step aside". Kind but firm, and utterly rule-bound.

The four things IAM sees

Part Example
Principal arn:aws:iam::111:user/alice, role/lambda-exec, service/s3.amazonaws.com
Action s3:GetObject, dynamodb:Query, iam:CreateUser
Resource arn:aws:s3:::bucket/key, arn:aws:dynamodb:us-east-1:111:table/orders
Condition source IP, MFA present, request time, encryption context

Policies are JSON documents with statements. Each statement is an Allow or Deny scoped to actions and resources, optionally gated by conditions.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadBucket",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": ["arn:aws:s3:::app-data", "arn:aws:s3:::app-data/*"]
    }
  ]
}

The evaluation algorithm

  1. Collect every policy that could apply to the request: identity-based, resource-based, permissions boundaries, SCPs, session policies.
  2. If any policy has a matching Deny statement — the final answer is DENY. Stop.
  3. Otherwise, if any policy has a matching Allow — ALLOW.
  4. Otherwise, implicit DENY.

Explicit Deny always wins. That's why permissions boundaries and service control policies work: they bolt a Deny fence around what a user could otherwise do.

Least privilege in practice

The default starting position is zero permissions. You grant the smallest scope that unblocks the task:

  • Scope actions: s3:GetObject, not s3:*.
  • Scope resources: arn:aws:s3:::app-logs/*, not *.
  • Scope principals: one role per workload, not one shared admin role.
  • Add conditions: aws:SourceIp, aws:MultiFactorAuthPresent, s3:x-amz-server-side-encryption.

The trap is that Allow: * feels productive during development and becomes permanent. Start tight and loosen on a concrete, logged 403.

Identity-based vs resource-based

Policy type Attached to Answers
Identity-based A user, group, or role "What can this principal do?"
Resource-based A bucket, queue, KMS key, Lambda "Who can touch this resource?"
Permissions boundary An IAM role "What's the maximum this role can ever do?"
SCP (Organizations) An OU or account "What can anyone in this account ever do?"

For cross-account access you usually need both: a resource-based policy on the resource and an identity-based policy on the principal. Either side alone is not enough.

Roles vs users

Prefer roles over long-lived IAM users. A role is a set of permissions any principal can temporarily assume, producing short-lived STS credentials. Users with static access keys are a credential leak waiting to happen.

Workload access patterns:

  • EC2 / ECS / Lambda: attach an execution role, SDK auto-assumes it.
  • CI/CD: use OIDC federation (GitHub Actions, GitLab) — no secrets in CI config.
  • Developers: federate through SSO / Identity Center, assume roles per account.

The traps

  • Wildcards in resources that look scoped. arn:aws:s3:::bucket/logs/* does not match the bucket itself (arn:aws:s3:::bucket). ListBucket fails silently.
  • Explicit Deny on the wrong scope. Deny s3:* on a resource you don't own blocks nothing.
  • Stale policies. IAM is eventually consistent; a change can take seconds to propagate. Re-run after a retry window before debugging further.
  • Trust policies vs permissions policies. A role has both. The trust policy says "who can assume me"; the permissions policy says "what I can then do". Both must match.

The one-line rule

Start with nothing. Grant the specific action on the specific resource, logged. Walk away when the test passes.

Tools in the wild

6 tools
  • Surfaces externally-shared resources + suggests least-privilege policy reductions.

    service
  • Try `Allow`/`Deny` decisions against any IAM principal without touching production.

    service
  • Cedarfree tier

    AWS's open-source policy language (the engine behind AVP); embeds in any app.

    library
  • General-purpose policy engine — Rego rules over JSON; runs anywhere.

    library
  • Prowlerfree tier

    Open-source AWS/GCP/Azure security audit tool; finds over-broad IAM, public buckets.

    cli
  • iamlivefree tier

    Records the actual IAM actions a CLI/SDK call makes; perfect for least-privilege design.

    cli