AWS for Platform Engineers›01 · IAM in depth
Learning Hub / Cloud — OpenStack, AWS & EKS / AWS for Platform Engineers

Lesson 01 of 7 · Modules

IAM in depth

IAM is the foundation of everything on AWS. Learn how requests are evaluated, the difference between identity and resource policies, roles and trust policies, conditions, permission boundaries and SCPs, and how to get to least privilege.

Foundation → Practitioner
Key wordsIAMpoliciesrolestrust policyAssumeRoleexplicit denypermission boundariesSCPsconditionsIdentity Center

Every AWS call is an IAM decision

Every request to AWS (from the console, the CLI, Terraform or a pod) is authenticated (who are you?) and authorised (may you do this action on this resource?). Get IAM right and everything else is safer.

AWS is a huge building full of rooms (services) and cupboards (resources). IAM is the security desk. Your badge says who you are. A policy is a list pinned to the badge: "may open cupboards labelled logs on floor 2". A role is a temporary visitor badge that people or robots can borrow for a while, if the trust policy says they're allowed to borrow it.

Principals, policies, roles

Concept What it is
Principal Who makes the request: an IAM user, a role session, an AWS service, a federated user
Identity-based policy Attached to a user, group or role: what this identity can do
Resource-based policy Attached to a resource (S3 bucket, KMS key, SQS queue): who may use this resource
Role An identity with no long-term credentials, assumed for temporary credentials
Trust policy On a role: who may assume it

A least-privilege policy (JSON):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadAppConfig",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::acme-app-config/prod/*"
    }
  ]
}

A trust policy letting EC2 instances assume a role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "ec2.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}

How a request is evaluated

1. Any explicit Deny in any applicable policy?       → DENIED
2. Organisation SCPs / resource control policies allow it? (if not → denied)
3. Permission boundary (if set) allows it?          (if not → denied)
4. Session policy (if any) allows it?               (if not → denied)
5. An identity-based or resource-based policy Allows it? → ALLOWED
6. Otherwise                                          → implicitly DENIED

(Simplified; cross-account access needs allows on both sides.) The core rule: explicit Deny beats Allow, and no Allow means deny.

Conditions: context-aware rules

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

Common condition keys: aws:RequestedRegion, aws:SourceVpce, aws:PrincipalTag/…, aws:ResourceTag/…, aws:MultiFactorAuthPresent. Tag-based conditions (ABAC) let one policy scale across teams: "you may manage resources tagged team=your-team".

Guard-rails: permission boundaries and SCPs

  • Permission boundary: a maximum set of permissions for a user or role. Useful when letting teams create their own roles without escalating.
  • Service Control Policies (SCPs): in AWS Organizations, the maximum for every principal in an account or OU, even account admins. Typical uses: deny leaving the organisation, deny disabling CloudTrail/GuardDuty, restrict regions (lesson 07).

People and machines

Who How they should authenticate
People IAM Identity Center (SSO) federated with your IdP, with MFA; short sessions into roles per account
EC2 instances Instance profile (a role)
EKS pods EKS Pod Identity or IRSA (see Amazon EKS in Production)
CI (GitHub Actions, etc.) OIDC federation to a role, with no stored keys (see CI/CD & Software Supply Chain)
Other clouds / on-prem IAM Roles Anywhere, or workload identity federation

Avoid IAM users with access keys; where they must exist, rotate keys and alert on their use.

Try it: least privilege, verified (sandbox account)

  1. Create an S3 bucket and a role app-reader whose trust policy allows your principal to assume it, and whose policy allows only s3:GetObject on bucket/prod/*.
  2. aws sts assume-role, export the temporary credentials, and confirm aws sts get-caller-identity shows the role session.
  3. Read prod/config.json (allowed), try dev/config.json and s3:PutObject (denied). Read the error messages.
  4. Use aws iam simulate-principal-policy to test the same actions without making requests.
  5. Add a region-restriction Deny and confirm a call in another region is denied even though the Allow exists.

Going deeper: IAM at scale

  • Use IAM Access Analyzer to find resources shared outside your organisation and to generate least-privilege policies from CloudTrail activity.
  • Write policies with Terraform modules and review them like code; linters (e.g. parliament, checkov) catch wildcards and risky actions.
  • iam:PassRole is powerful: it lets a principal hand a role to a service (e.g. launch an EC2 instance with an admin role). Scope it tightly.
  • CloudTrail records every IAM decision's API call. Alert on root account use, policy changes, and access-key creation.

Recap

  • Everything is a principal making a request, evaluated against policies.
  • Explicit Deny > Allow > implicit deny; SCPs and permission boundaries cap what's possible.
  • Roles + trust policies give temporary credentials: use them for people (SSO), EC2, pods and CI.
  • Least privilege with specific actions, resources and conditions; verify with the policy simulator.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.