Most engineers think…
Most engineers think IAM security is just "give people the AWS Managed AdministratorAccess policy and move on," or that a leaked access key is no big deal because "we'll rotate it." So they hand out wildcards and bake long-lived keys into apps.
Wrong — and it is the most common way real AWS accounts get popped. IAM's actual job is least privilege: an identity should hold the smallest set of permissions for its task, granted through roles with temporary credentials wherever possible, evaluated by a strict order where any explicit Deny wins. A "*" policy is a single master key; a long-lived AKIA key in a repo is valid until a human notices. The whole discipline is shrinking the door and shortening the time a stolen credential is useful.
① IAM building blocks — users, groups, roles, policies
Meet Sneha, a fresh L1 cloud engineer at Flipkart. On day two she opens the IAM console and sees four words she has to get straight: users, groups, roles, policies. Get these four right and the rest of AWS security clicks; get them muddled and you will hand out a master key by accident. So let us build them up one at a time, like the pass-register at a society gate.
A user is a long-lived identity — one human (Sneha) or one application. It can have a console password and a pair of long-lived access keys (an ID starting AKIA plus a secret). A group is just a labelled bucket of users — attach a policy to the group (say, Developers) and every member inherits it, so you manage permissions once instead of per person.
A role is the one that trips people up. A role has permissions but no permanent password or key. Instead a trusted principal — an EC2 instance, a Lambda function, a user in another account, or a human signing in through SSO — assumes the role and receives temporary credentials from STS that expire (often in an hour). Think hotel: a user with an access key is owning the building and carrying a key that never changes; a role is the front desk handing you a keycard that stops working at checkout.
A policy is the JSON document that says what is allowed. There are two flavours you must never confuse. An identity-based policy is attached to a user/group/role and says what that identity may do. A resource-based policy is attached to the resource (an S3 bucket, a KMS key) and says who may touch this thing. The dead giveaway: a resource-based policy has a "Principal" field; an identity-based one does not.
Now the flow. Every API call is a request with three parts: the principal (who is asking), the action (e.g. s3:GetObject), and the resource (which bucket). AWS gathers every policy attached to that identity and that resource and checks them. The default answer is no — if nothing explicitly allows the action, it is implicitly denied. You spend your IAM life carving narrow Allow holes out of that default deny.
Why do roles beat long-lived access keys? Because a leaked AKIA key is valid until a human notices and revokes it — that could be months, and meanwhile it is in someone's clipboard, a Git commit, a Slack message. A role's STS credentials (ASIA…) expire on their own, usually within an hour, so a leak self-heals. That is why AWS now tells you to use roles and federation for humans and instance/task roles for apps, and to treat access keys as a last resort.
And the root user? It is the account owner — unlimited power, and it cannot be reined in by any IAM policy or SCP. So you use it for almost nothing: only the handful of tasks that genuinely require it (changing account settings, restoring a locked-out admin, certain billing/tax actions, S3 MFA-delete). Lock it with hardware MFA, delete its access keys, and do daily work as a far less powerful role.
The four building blocks, one tap each
Tap each card. These four are the vocabulary every AWS interview and the SCS-C02 exam assumes you own cold.
Long-lived identity; a group is a bucket of users sharing one policy. So: manage permissions on the group, not per person.
Permissions with no permanent key; a principal assumes it for temporary creds that expire. So: a leak self-heals in ~1 hour.
Attached to a user/role: 'what I may do'. No Principal field. So: this is where you write least privilege.
Attached to an S3 bucket/KMS key: 'who may touch me'. Has a Principal field. So: this is how cross-account access is granted.
A long-lived access key is like handing out photocopies of your Aadhaar card — once a copy is loose, it works forever and you cannot un-copy it. A role assumed through STS is like an Aadhaar OTP: a one-time, short-lived token the system issues on demand and that expires by itself. Both prove who you are; only one limits the damage when it leaks. That single difference — does the credential expire on its own? — is why AWS pushes roles over keys.
Rahul at Infosys finds a teammate has put an IAM user's access key directly in a Lambda function's environment variable so it can read S3. What is the better design?
Pause & Predict
Predict: you create a brand-new IAM user with NO policies attached and the user signs in. What can they do in the account, and why? Type your guess.
② How policies are evaluated — explicit Deny wins
Here is the single most important rule in all of IAM, and it is on the exam in three different costumes: an explicit Deny overrides any Allow. When a request arrives, AWS gathers every policy type that could apply — the SCP, the identity-based policy, any resource-based policy, the permission boundary, and any session policy — and looks for a Deny first.
The order, straight from the IAM evaluation logic: (1) is the request explicitly Denied by any policy? If yes, stop — denied. (2) If not, is it explicitly Allowed, and do all the guardrails permit it? (3) If nothing allows it, the implicit (default) Deny applies. Allow is the exception; Deny — explicit or default — is the resting state.
How the layers combine matters. Identity-based and resource-based policies in the same account are a union — an Allow in either is enough. But a permission boundary, an SCP and the identity policy are an intersection — the action must be allowed by all three. AWS states it plainly: with a boundary and an SCP present, "the boundary, the SCP, and the identity-based policy must all allow the action." That is why a developer can have AdministratorAccess attached and still be unable to touch a region an SCP blocks.
Now read a policy. Every statement has four things: Effect (Allow or Deny), Action (the API calls), Resource (the ARNs it applies to), and an optional Condition (extra tests like MFA or source IP). The danger you must learn to spot in code review is "Action":"*" with "Resource":"*" — that is admin on everything, a single master key. The exam loves to hide a wildcard in a plausible-looking policy.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "ReadReportsPrefixOnly",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::flipkart-reports",
"arn:aws:s3:::flipkart-reports/finance/*"
],
"Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } }
}]
}$ aws s3 ls s3://flipkart-reports/finance/ --profile sneha 2026-06-10 09:14:22 18211 q4-summary.csv $ aws s3 rm s3://flipkart-reports/finance/q4-summary.csv --profile sneha delete failed: ... An error occurred (AccessDenied) when calling the DeleteObject operation: User is not authorized to perform: s3:DeleteObject
Symptom: a developer's identity policy clearly has "Effect":"Allow" for the action, yet the call returns AccessDenied. The cause is almost always a Deny somewhere higher — an SCP at the org blocking the region or service, a permission boundary that doesn't include the action, or an explicit Deny in another attached policy. Fix: don't keep adding Allows. Run aws iam simulate-principal-policy or open IAM → Policy simulator, and check for an SCP/boundary intersection before you touch the identity policy.
Priya at ICICI has a user with the managed AdministratorAccess policy (Allow on *). The org also has an SCP that denies all actions in ap-south-2. Can she create an EC2 instance in ap-south-2?
Pause & Predict
Predict: a bucket's resource-based policy Allows arn:aws:iam::123456789012:role/Analyst to read it, but that role's own identity policy says nothing about S3. In the SAME account, can the role read the bucket? Type your guess.
③ Least privilege in practice — start minimal and grow
"Least privilege" sounds like a poster on the wall until you have to ship it. The workable method is: start from nothing and grow. Give the identity the smallest policy you think it needs, let the workload run, then use real usage data to add only what was actually used and remove what was not. AWS gives you two tools for exactly this: last-accessed information and IAM Access Analyzer.
Access Analyzer does three jobs you'll lean on. It finds external/cross-account exposure — a bucket or role shared outside your account or organization (provable security, not guesswork). It surfaces unused access — roles with no activity, unused access keys and passwords, and service- or action-level permissions a role was granted but never used. And it can generate a policy from your CloudTrail history, so you get a least-privilege starting point instead of a blank page. In 2025 AWS extended these unused-access and custom-policy-check findings to more regions (including GovCloud), a sign of how central this has become.
Two structures stop least privilege from quietly eroding as your account grows. A permission boundary caps what an identity can ever do — vital when you let a team lead create roles, because the boundary guarantees they can't mint a role more powerful than themselves (the effective permission is the intersection of the role's policy and the boundary). An SCP does the same one level up, at the AWS Organizations level, as a guardrail across whole accounts — e.g. "no one, not even an account admin, may disable CloudTrail or leave ap-south-1."
Conditions are how you make a broad-looking Allow safe. Three you'll use constantly: require MFA with aws:MultiFactorAuthPresent; restrict to office/VPN ranges with aws:SourceIp; and gate on tags so a policy only touches resources tagged for that team (attribute-based access control). Conditions turn "can read any prod bucket" into "can read prod buckets tagged team=payments, from the office IP, with MFA on."
Now the model that powers almost everything cross-account: the role + trust policy. A role has two policies. Its permission policy says what the role can do once assumed. Its trust policy says who is allowed to assume it — that's the resource-based policy with the Principal field. To let an auditor in the security account read your prod account, you create a role in prod whose trust policy names the security account's principal, and grant that principal sts:AssumeRole on their side. Both sides must agree.
▶ Watch a cross-account AssumeRole, step by step
Karthik, an auditor in the security account (111111111111), needs read-only access to the production account (123456789012). Follow the temporary-credential handshake. Press Play for the healthy path, then Break it to see the failure.
aws accessanalyzer start-policy-generation \
--policy-generation-details '{"principalArn":"arn:aws:iam::123456789012:role/etl-job"}' \
--cloud-trail-details file://trail-window.json
# then, once it finishes:
aws accessanalyzer get-generated-policy \
--job-id 4d2f9b1a-7c33-4f2e-9a01-6b5e8c0a1122{
"jobDetails": { "status": "SUCCEEDED" },
"generatedPolicyResult": {
"generatedPolicies": [{ "policy": "{\"Version\":\"2012-10-17\",
\"Statement\":[{\"Effect\":\"Allow\",
\"Action\":[\"s3:GetObject\",\"dynamodb:PutItem\"],
\"Resource\":[\"arn:aws:s3:::etl-src/*\", ...]}]}" }]
}
}Aditya at TCS faces this
Aditya, an L2 engineer, is told a contractor role can read 'any S3 bucket' and the auditors flagged it. He must cut it to least privilege without breaking the running ETL job.
The role was created with s3:* on Resource * months ago 'to get unblocked'. Most of that is unused — but nobody knows exactly which buckets/actions the job really touches, so no one dares trim it.
Instead of guessing, he reads the role's last-accessed data and runs Access Analyzer policy generation over the last 90 days of CloudTrail to see the actions/resources actually used.
AWS Console > IAM > Roles > contractor-etl > Access Advisor (last accessed) + IAM > Access Analyzer > Generate policyHe replaces s3:* / Resource * with the generated policy: s3:GetObject + s3:ListBucket scoped to the two buckets the job actually used, with a tag and MFA condition. He keeps a permission boundary on the role so it can never re-expand.
The ETL job still runs green; a test call to an unrelated bucket now returns AccessDenied; Access Analyzer shows zero unused-permission findings for the role.
Meera at HCL wants to safely let team leads create IAM roles for their projects, but guarantee no lead can create a role with admin powers. Which IAM feature does that directly?
Pause & Predict
Predict: you scope a role down to least privilege using Access Analyzer's generated policy. Six months later a new feature needs one extra action. What's the safe way to add it, and what should you NOT do? Type your guess.
④ The hardening checklist — 10 rules + the PassRole trap
Time to turn everything into a checklist you can run on a real account on Monday. None of this is exotic — it's the boring discipline that keeps your account out of a breach write-up. We'll walk the high-value rules, hit the one privilege-escalation trap that catches even seniors, recreate the Create role screen, then leave you a cheat-sheet.
Identities & credentials. Lock the root user with hardware MFA and delete its access keys — use it only for the genuine root-only tasks (account settings, restoring a locked-out admin, certain billing/tax actions, S3 MFA-delete). Turn on MFA for every human. Rotate or eliminate access keys — better still, replace them with roles/SSO so there's nothing long-lived to rotate. And no inline admin: don't paste AdministratorAccess or "*" inline onto users; grant through groups/roles with scoped policies.
Visibility & guardrails. Keep IAM Access Analyzer on to catch public or cross-account exposure and unused access. Keep CloudTrail on — an organization trail records every AssumeRole and API call, which is both your forensic record and the feed for alarms (e.g. alert on root sign-in). Add SCPs for org-wide non-negotiables and permission boundaries wherever you delegate role creation.
Now the trap. iam:PassRole is how you attach a role to a service (give an EC2 instance or a Lambda its role). The escalation: a low-privileged user who has iam:PassRole plus a launch permission like ec2:RunInstances or lambda:CreateFunction can pass an admin role to a new instance/function and then read its credentials — becoming admin without ever editing their own policy. Security researchers list this among the most common AWS privilege-escalation paths.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PassOnlyAppRoleToEc2",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/app-runtime-role",
"Condition": {
"StringEquals": { "iam:PassedToService": "ec2.amazonaws.com" }
}
}]
}# user tries to pass a DIFFERENT, more powerful role: $ aws ec2 run-instances --iam-instance-profile Name=admin-profile ... An error occurred (AccessDenied) when calling the RunInstances operation: User: arn:aws:iam::123456789012:user/dev is not authorized to perform: iam:PassRole on resource: arn:aws:iam::123456789012:role/AdminRole
Don't eyeball it — test it. Use aws iam simulate-principal-policy (or IAM → Policy simulator) to confirm the actions you intend ARE allowed and the ones you don't intend are denied. Then run IAM Access Analyzer policy validation to catch overly broad statements and "Resource":"*" warnings, and check the role's Access Advisor tab after a week to remove services it never touched. Allowed-what-you-meant + denied-everything-else + zero unused findings = shippable.
For the cert: this lesson maps onto Domain 4 — Identity and Access Management (16% of the SCS-C02 exam), and IAM concepts bleed into the Infrastructure Security and Data Protection domains too. The exam tests exactly what you just learned — policy evaluation order, roles vs long-lived credentials, permission boundaries vs SCPs, and reading a policy JSON to spot the over-permissive line. Get these solid and a big slice of the exam (and your first cloud-security job) is in reach.
An interviewer at PhonePe asks Neha: "A developer only has iam:PassRole and ec2:RunInstances — no admin policy. Why is that a privilege-escalation risk, and how do you fix it?"
🤖 Ask the AI Tutor
Tap any question — instant, scoped to this lesson. No login, no waiting.
Pre-curated from AWS docs + community Q&A, scoped to this lesson. For a live prod issue, paste your export into chat.techclick.in.
📝 Wrap-up assessment — six more
You've answered 4 inline. Six left. 70% (7 of 10) marks the lesson complete on your profile. Tap Submit all answers at the end.
🧠 In your own words
Type one line: In one line, why does a leaked IAM role credential hurt far less than a leaked IAM user access key? Then compare to the expert version.
🗣 Teach a friend
Best way to lock it in — explain it in one line to a teammate. Tap to generate a paste-ready summary.
📖 Glossary
- IAM
- Identity and Access Management — the global, free AWS service that decides who (principal) can do what (action) to which resource.
- Role
- An identity with permissions but no permanent credentials; a trusted principal assumes it via STS for temporary, expiring credentials.
- STS
- Security Token Service — issues short-lived credentials (keys starting ASIA + a session token) when a principal assumes a role.
- Access key
- A long-lived credential pair (ID starting AKIA + secret) for an IAM user or app. No expiry — risky if leaked; prefer roles.
- Identity-based policy
- A JSON policy attached to a user/group/role saying what that identity may do. Has no Principal field.
- Resource-based policy
- A policy attached to a resource (S3 bucket, KMS key) saying who may touch it. Has a Principal field.
- Explicit Deny
- A Deny statement that overrides any Allow, in any policy type — the top rule of IAM evaluation.
- Permission boundary
- An advanced policy setting the maximum permissions an identity can ever have; effective permission = policy ∩ boundary.
- SCP
- Service Control Policy — an AWS Organizations guardrail that caps the maximum permissions for accounts/identities in scope; grants nothing by itself.
- Trust policy
- A role's AssumeRolePolicyDocument — the resource-based policy with a Principal field that names who may assume the role.
- iam:PassRole
- The permission to hand an IAM role to an AWS service (attach a role to an instance/function). A common privilege-escalation primitive when unscoped.
- IAM Access Analyzer
- Service that finds external/cross-account exposure, flags unused access, validates policies, and generates least-privilege policies.
📚 Sources
- AWS IAM User Guide — "Policy evaluation logic" (same-account union of identity + resource policies; intersection of identity policy, permissions boundary and SCP; explicit Deny overrides any Allow; default implicit Deny). docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html
- AWS IAM User Guide — "Refine permissions using last accessed information" + "Using IAM Access Analyzer" (last-accessed data, unused-access findings, external-access findings, policy generation from CloudTrail). docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_last-accessed.html · docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html
- AWS IAM User Guide — "AWS account root user" + "Root user best practices" (the short list of tasks that require root; lock with MFA, delete root access keys, use almost never). docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html · docs.aws.amazon.com/IAM/latest/UserGuide/root-user-best-practices.html
- Hacking The Cloud + Praetorian + Rhino Security Labs — AWS IAM privilege escalation via iam:PassRole with ec2:RunInstances / lambda:CreateFunction / ecs:RunTask / glue:CreateDevEndpoint (community practitioner research on the escalation primitive). hackingthe.cloud/aws/exploitation/iam_privilege_escalation/ · praetorian.com/blog/privilege-escalation-in-aws-with-passrole-attacks/ · github.com/RhinoSecurityLabs/AWS-IAM-Privilege-Escalation
- AWS What's New (2025) — "IAM Access Analyzer supports additional analysis findings and checks in AWS GovCloud (US) Regions" (recent expansion of unused-access + custom-policy checks; ongoing push toward least privilege). aws.amazon.com/about-aws/whats-new/2025/07/iam-access-analyzer-findings-checks-govcloud/
- AWS Certified Security – Specialty (SCS-C02) Exam Guide — Domain 4 Identity and Access Management = 16% of scored content (plus IAM coverage across Infrastructure Security and Data Protection). d1.awsstatic.com/training-and-certification/docs-security-spec/AWS-Certified-Security-Specialty_Exam-Guide_C02.pdf
What's next?
You've locked down WHO can do WHAT in the account. Next we move to the network edge: two AWS firewalls that confuse everyone — Security Groups and NACLs — and the stateful-vs-stateless difference that decides which one actually blocks your traffic.