open atlas
↑ Back to track
AWS, hands-on AWS · 01 · 02

IAM and the shared-responsibility model

AWS secures the cloud; you secure what's in it — and the dividing line moves by service. IAM is how you draw your half: identities, JSON policies, and an evaluation rule where explicit deny always wins over allow over implicit deny.

AWS Middle ◷ 17 min
Level
FoundationsJuniorMiddleSenior

A startup’s S3 bucket of customer invoices leaks. The post-incident question is blunt: was this AWS’s fault? It was not. AWS kept the disks encrypted, the data centre locked, and the S3 service itself patched — but a developer had attached an IAM policy with "Action": "s3:*" and "Resource": "*" to a CI user, baked that user’s long-lived access key into a public GitHub repo, and a scraper found it in nine minutes. Every failure in that chain was on the customer’s side of a line AWS draws very deliberately. Knowing exactly where that line falls — and how to configure your half with IAM — is the difference between a secure account and a breach waiting for a scraper.

By the end of this lesson you will know exactly where the security boundary falls for each service type, how to read and write an IAM policy that doesn’t hand attackers your account, and why a single explicit Deny beats every Allow in the stack.

The shared-responsibility model: where AWS stops and you start

AWS frames it in four words: AWS handles security of the cloud; you handle security in the cloud. AWS owns the hardware, the global infrastructure (regions, availability zones, the physical network), the hypervisor, and the internals of every managed service. You own your data, who can touch it, and how it is configured. The leaked-invoice bucket sat in an S3 service AWS keeps patched and durable — but the bucket policy, the IAM permissions, and the exposed key were all yours.

The critical nuance is that the line moves by service type. The more managed the service, the more AWS absorbs:

  • EC2 (a raw virtual machine): AWS gives you patched hardware and a hypervisor. You patch the guest OS, configure the firewall (security groups), manage the application, and encrypt the disks. This is the most responsibility you can hold.
  • Containers on ECS/Fargate: AWS additionally manages the host OS and the container runtime; you still own the image contents, IAM task roles, and app code.
  • Lambda: AWS manages the OS, the runtime, and the scaling. You own only your function code, its IAM execution role, and its dependencies.
  • S3 / DynamoDB (fully managed): AWS runs the entire service. Your remaining job is access control, encryption choices, and the data itself.

One responsibility never shifts no matter the service: identity and access management is always yours. That is the half this lesson is about.

Why this works

Why does the line move? Because “responsibility” tracks “control”. You cannot patch an OS you can’t log into — so on Lambda, where there is no OS you control, AWS owns patching. But you can always decide who is allowed to invoke that Lambda, so access management is irreducibly yours on every service. When you read a shared-responsibility diagram, the question to ask of any item is “who has the control surface to change this?” — that is who is responsible for it.

IAM identities and policies: the vocabulary

Why does this vocabulary matter in practice? Because every misconfigured access control — from the leaked invoice bucket to a privilege-escalation exploit — is either a wrong identity shape or a policy that’s too broad. Getting the vocabulary right means you stop making those mistakes by default.

IAM (Identity and Access Management) is the global service that decides who can do what. It has two halves: identities (the who) and policies (the what).

Identities come in three shapes:

  • Users — a long-lived identity for a human or a legacy system, optionally with a password and/or access keys.
  • Groups — a bucket of users that share policies; you attach a policy to the group, and every member inherits it. Groups are an organisational convenience, not an identity that can be assumed.
  • Roles — an identity with no permanent credentials. A principal (an EC2 instance, a Lambda function, a user from another account) assumes the role and receives short-lived, automatically-rotated credentials from STS. Roles are the senior default — more on why below.

Permissions are expressed as policies: JSON documents made of statements, each with up to four core keys.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::invoices-prod/uploads/*",
      "Condition": { "StringEquals": { "aws:RequestedRegion": "eu-central-1" } }
    }
  ]
}
  • EffectAllow or Deny.
  • Action — the API operations, e.g. s3:GetObject. Wildcards are legal (s3:*) and dangerous.
  • Resource — the ARNs this applies to. * means “every resource”, which is almost always too broad.
  • Condition — optional guards (region, source IP, MFA present, tag match) that must hold for the statement to apply.

The policy above grants exactly two actions, on exactly one bucket prefix, in exactly one region. Contrast the policy that leaked the invoices:

{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}

"Action": "*" on "Resource": "*" is administrator access to the entire account. Attached to a CI user whose key escaped, it is the difference between “an attacker can read one upload prefix” and “an attacker owns your account”. Granting only what a principal needs, and nothing more, is the principle of least privilege — the single most load-bearing idea in IAM.

IAM also imposes hard quotas that shape how you structure access, and senior engineers design against them: a single managed policy document is capped at 6,144 characters, you can attach at most 10 managed policies to one role/user/group, and an account is limited to roughly 5,000 roles and 300 customer-managed policies by default (some are soft limits you can raise). These ceilings are why teams converge on a few reusable, well-scoped managed policies plus permissions boundaries, rather than hand-writing a bespoke inline policy per principal — that approach hits the character cap and the attachment limit fast, and becomes unauditable long before it does.

How a request is decided: deny wins

When a principal makes a request, AWS gathers every policy that applies — identity-based policies on the user/role, resource-based policies on the target, plus boundaries and SCPs if present — and evaluates them with one rule you must hold in your head:

  1. Default is deny. A request with no matching Allow is rejected. This is the implicit deny.
  2. An explicit Allow overrides the implicit deny — if some applicable policy allows the action and nothing denies it, the request proceeds.
  3. An explicit Deny overrides everything. If any applicable policy denies the action, the request is rejected, no matter how many allows exist.

So the precedence is: explicit deny > explicit allow > implicit (default) deny. A Deny is a guarantee; an Allow is only effective in the absence of a Deny. This is why guardrails are written as denies — an SCP that denies disabling CloudTrail cannot be undone by any allow an account admin grants.

Applicable policies say…ResultWhy
No statement matches the actionDenyImplicit (default) deny — nothing granted it
One Allow, no DenyAllowAllow overrides the implicit deny
An Allow and an DenyDenyExplicit deny overrides any allow

Senior practice: roles over keys, root locked down

The mechanism above is only as safe as the credentials feeding it. AWS’s identity best practices are unambiguous, and they are exam-relevant:

  • Prefer roles over long-lived access keys. A role hands out short-lived credentials that auto-expire and rotate; a leaked role session dies quickly, a leaked access key works until someone notices. Give EC2 an instance profile (a role for the instance) and Lambda an execution role instead of embedding keys. For access across accounts, have the other account assume a role — never copy keys between accounts.
  • Never embed credentials in code or images. No access keys in source, in a container layer, or in environment variables checked into git. Use the role attached to the compute, or fetch secrets from Secrets Manager / SSM Parameter Store at runtime.
  • Lock the root user away. The account root can do anything and cannot be restricted by IAM policies. Enable MFA on it, delete its access keys entirely, and use it only for the handful of tasks that genuinely require root. Day-to-day work happens through IAM roles/users with least privilege.
  • Least privilege, then tighten. Start from the minimum, grant specific actions on specific resources, add Condition guards (require MFA, restrict region/IP), and use IAM Access Analyzer to find permissions nobody actually uses.

Together, these four practices collapse the blast radius of any single failure: a short-lived role credential limits the attacker’s window, least-privilege scoping limits what they can reach, no embedded keys removes the easiest exfiltration target, and a locked root prevents total account takeover. Skip any one of them and the remaining three are weaker — the chain is only as strong as its weakest link.

The real tension here is least privilege vs operational friction, and it has a failure mode at each extreme. Over-tighten and engineers hit AccessDenied mid-incident, can’t ship, and the pressure-valve response is the disaster: someone attaches AdministratorAccess “just to unblock the deploy” and never removes it — the most common way least-privilege accounts quietly drift to over-privileged. Under-tighten and you recreate the leaked-invoice blast radius. The senior resolution is not to pick a side but to make tightening cheap: derive policies from real usage (Access Analyzer generates a least-privilege policy from CloudTrail history), grant temporary elevated access through short-lived assumed roles rather than permanent grants, and treat a wildcard policy as a code-review red flag, not a convenience.

Pick the best fit

An EC2 application needs to read objects from one S3 bucket. How should it get those permissions?

Quiz

A user has an identity policy allowing s3:DeleteObject on a bucket. An SCP on their account explicitly denies s3:DeleteObject. They call DeleteObject. What happens?

Quiz

On a fully managed service like S3, which of these is AWS's responsibility under the shared-responsibility model?

Recall before you leave
  1. 01
    Explain the IAM policy evaluation rule and why guardrails are written as Deny statements.
  2. 02
    Why prefer IAM roles over long-lived access keys, and how does that apply to EC2, Lambda, and cross-account access?
Recap

The shared-responsibility model splits security in two: AWS secures of the cloud — the hardware, the global infrastructure, the hypervisor, and the internals of managed services — while you secure in the cloud — your data, your access configuration, and, on less-managed services, the guest OS, the security groups, and the app. The line moves with the service: on EC2 you patch the OS and run the firewall; on Lambda or S3 AWS absorbs the OS and runtime and you are left with code and, always, access control. IAM is how you draw your half. It has identities — users, groups, and (the senior default) roles that hand out short-lived credentials — and JSON policies built from Effect, Action, Resource, and Condition. Requests are decided by one rule: the default is an implicit deny, an explicit Allow beats it, and an explicit Deny beats everything, which is why guardrails are written as denies. The durable practices are least privilege (grant the minimum, scope to specific resources, add Condition guards), roles over long-lived keys (instance profiles for EC2, execution roles for Lambda, assume-role for cross-account), never embedding credentials in code or images, and locking the root user behind MFA with its access keys deleted. The leaked-invoice breach was every one of those rules broken at once — on the customer’s side of a line AWS draws very deliberately. Now when you see a wildcard policy or a long-lived access key in application config, you know what to change and exactly why the default-deny model means that mistake was always yours to prevent.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 5 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

Apply this

Put this lesson to work on a real build.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.