By the end you'll know
- The three kinds of IAM identity, users, groups and roles, and which one to use for people and for programs
- How to read an IAM policy line by line: Effect, Action, Resource, Condition
- How AWS decides whether a request is allowed, and why a Deny always wins
- The kinds of policy, from the ones on a role to the service control policies over a whole account
- Why roles and temporary keys beat users with access keys
- How people sign in at companies, through IAM Identity Center, and how the new sign-up does it for you
Before you start
- You need: the account from Lesson 2, and AWS CloudShell.
- Cost: $0. IAM is free, and so are the temporary keys a role hands out.
- The script:
iam-lab.pyfrom the practice folder'slesson-03. Everything it creates is namedjr-lesson3-*, and its last step deletes it all. - The example: Juniper & Rye, the made-up bakery, giving someone read-only access. Every output below is from a real run on November 9, 2026.
The short version
- IAM (Identity and Access Management) decides who can do what in an AWS account. It's free, and every request to AWS goes through it.
- A policy is a rulebook written in JSON: Allow or Deny, which actions, on which resources, under which conditions.
- Every request starts denied. A policy has to allow it, and an explicit Deny anywhere beats every Allow.
- Roles beat users. A role hands out keys that expire on their own; an IAM user's access keys last until someone deletes them.
- People sign in through IAM Identity Center at companies, not as IAM users. The new sign-up enforces that: it blocks groups and console passwords for IAM users.
- Test a policy before anyone uses it, with the policy simulator.
What this lesson costs
| Part | Cost |
|---|---|
| IAM policies, roles and users | $0 |
| Temporary keys from a role (AWS STS, the Security Token Service) | $0 |
| The policy simulator | $0 |
| This lesson | $0, and nothing left behind |
A rulebook for the bakery
Juniper & Rye has a few people and a few rules:
- The cashier may open the till and read the menu, but not change prices.
- The head baker may change the menu.
- Nobody may take money out of the safe, except the owner. Even if someone's job description says "handles money", the safe rule wins.
That's IAM. The people are identities, the things they do are actions, the things they do them to are resources, and the rules are policies. The safe rule is an explicit deny: it overrides anything else that says yes. Everything below builds on that.
Who: users, groups and roles
| Identity | What it is | Credentials | Use it for |
|---|---|---|---|
| IAM user | One person or program, inside one account | A console password and/or access keys, which last until deleted | Only programs that can't use a role, like some third-party tools |
| Group | A set of users that share policies | None of its own | Giving the same permissions to several users at once |
| Role | A set of permissions that someone or something switches into | Temporary keys, from 15 minutes to 12 hours, that expire on their own | Programs on AWS (servers, Lambda functions), pipelines, and people signing in through Identity Center |
A role is like the bakery's "on duty" badge: you pick it up when your shift starts, it opens the doors you need, and it stops working when the shift ends. Nobody takes it home. A user's access key is a key you cut and keep, and if it's lost it keeps working for whoever finds it. That's why AWS's own best practices say: people should sign in through federation with temporary credentials, and workloads should use roles.
So who are the people? At companies, IAM Identity Center: one place where staff sign in once, with MFA, and pick which account and role to use. Their sessions are temporary, and when they leave the company, one switch turns everything off.
What the new sign-up allows
iam-lab.py fence tries each kind of identity and deletes every test item straight away. In CloudShell, upload iam-lab.py (Actions, then Upload file), then:
(CloudShell)
chmod +x iam-lab.py
./iam-lab.py fence
== What IAM allows here (each test item is deleted straight away)
allowed create a policy
allowed create a role
denied create a group: AccessDenied (explicit deny in a service control policy)
allowed create a user (no password, no keys)
denied give that user a console password: AccessDenied (explicit deny in a service control policy)
== Identity Center
info Identity Center instances visible from this account: 0
- Policies and roles: allowed. Those are what the rest of this lesson, and the track, use.
- Groups and console passwords: blocked by a service control policy, the same kind of rule that blocked the contacts in Lesson 2. An IAM user here can exist, but only for a program; it can never sign in to the console.
- Identity Center: none visible. AWS runs it from its own organization, outside your account. When you invite someone through Manage team, that's Identity Center at work.
In other words, the new sign-up enforces AWS's own advice: people come in through managed sign-in, and IAM is for roles and policies.
What: reading a policy
A policy is a JSON document. Here's the one the lab creates, for a role that may look at IAM but change nothing:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadIam",
"Effect": "Allow",
"Action": ["iam:Get*", "iam:List*"],
"Resource": "*"
}
]
}
| Part | Meaning | Here |
|---|---|---|
Version | The policy language version. It's always this date; it isn't when you wrote it. | The current language |
Statement | A list of rules | One rule |
Sid | An optional label for the rule | "ReadIam" |
Effect | Allow or Deny | Allow |
Action | What may be done, as service:Action. * matches anything. | Every IAM action starting with Get or List |
Resource | Which things, by ARN (Amazon Resource Name). * means all. | Anything |
Condition | Optional: only when something is true | None here |
An ARN names one thing in AWS: arn:aws:iam::111122223333:role/jr-lesson3-reader is a role called jr-lesson3-reader in account 111122223333. A condition narrows a rule further, for example only when the request used MFA:
"Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } }
The kinds of policy
| Kind | Attached to | Says | In this track |
|---|---|---|---|
| Identity-based, managed or inline | A user, group or role | What this identity may do | This lesson |
| Trust policy | A role | Who may switch into this role | This lesson |
| Resource-based | A thing, like an S3 bucket | Who may use this thing | Lesson 15, S3 |
| Service control policy (SCP) | A whole account, from its organization | The most anything in the account may do. It grants nothing by itself. | Met in Lesson 2; written in Lesson 36 |
| Permissions boundary, session policy | A role or a session | An upper limit | Named here; for later |
A managed policy is a separate object you attach to many identities; an inline policy lives inside one identity and goes when it goes. AWS also publishes AWS managed policies, like ReadOnlyAccess, which are a quick start but rarely least privilege: they're written for every customer, not for you.
How AWS decides
Three rules cover almost everything:
- Every request starts denied. AWS calls this an implicit deny.
- An Allow lets it through, if nothing else stops it.
- An explicit Deny, in any policy, beats every Allow. It can't be outvoted.
Service control policies sit above all of it: if the account's organization doesn't allow an action, no policy inside the account can.
Build it: a read-only role for the bakery
Create it, and test it before anyone uses it
In the console: IAM > Roles > Create role. To test a policy before anyone uses it, open the Policy simulator, linked from the IAM dashboard under Tools.
(CloudShell)
./iam-lab.py setup
== Create the reader policy and role
created policy jr-lesson3-read-iam: allow iam:Get* and iam:List* on everything
created role jr-lesson3-reader, trusted by this account, with jr-lesson3-read-iam attached
== Test the policy before using it (the policy simulator)
allowed iam:ListRoles
allowed iam:GetRole
implicitDeny iam:CreateRole
implicitDeny iam:DeleteRole
implicitDeny s3:ListAllMyBuckets
The role needs two policies. The permissions policy is the one above. The trust policy says who may switch into the role, here anyone in this account who's allowed to:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "sts:AssumeRole"
}
]
}
:root here means "this account", not the root user.
The policy simulator checked each action against the policy without anyone using it: two allowed, three implicitDeny, AWS's name for "nothing allows it". Run it on any policy you write before it goes anywhere near real work.
Switch into the role
(CloudShell)
./iam-lab.py try
== Signed in as assumed-role jr-lesson3-reader, with keys that expire in 15 minutes
allowed list roles
allowed list users
allowed read its own role
denied create a role: AccessDenied (no policy allows it)
denied list S3 buckets: AccessDenied (no policy allows it)
The script asked AWS STS for the role's keys, which came back set to expire in 15 minutes, then tried five things. The results match the simulator exactly: reading IAM works, creating anything doesn't, and S3, another service entirely, isn't mentioned in the policy, so it's denied.
Add one Deny
The lab adds an inline policy to the role:
{ "Effect": "Deny", "Action": "iam:ListUsers", "Resource": "*" }
In the console: IAM > Roles > pick the role > Add permissions > Create inline policy.
(CloudShell)
./iam-lab.py deny
== Added inline policy jr-lesson3-no-list-users: deny iam:ListUsers
== Signed in as assumed-role jr-lesson3-reader, with keys that expire in 15 minutes
allowed list roles
denied list users: AccessDenied (explicit deny in an IAM policy)
allowed read its own role
denied create a role: AccessDenied (no policy allows it)
denied list S3 buckets: AccessDenied (no policy allows it)
List users flipped to denied, while list roles, covered by the very same Allow, kept working. The iam:List* Allow still matches iam:ListUsers, but a Deny can't be outvoted. Look at the reasons, too: "explicit deny" versus "no policy allows it". Reading that difference in an error message tells you whether to remove a deny or add an allow.
Clean up
In the console: IAM > Roles > tick the role > Delete.
(CloudShell)
./iam-lab.py cleanup
== Clean up
deleted inline policy jr-lesson3-no-list-users
deleted detach jr-lesson3-read-iam
deleted role jr-lesson3-reader
deleted policy jr-lesson3-read-iam
gone role jr-lesson3-sneaky, if the test made it
== Left over: nothing
The order matters: a role can't be deleted while it has policies, and a managed policy can't be deleted while it's attached. jr-lesson3-sneaky is the role try attempted to create; it was never made, so there was nothing to delete.
Never do this
Never paste "Action": "*" with "Resource": "*" to make an error go away. That's every action on everything: administrator access. Read the error instead. "No policy allows it" means add one specific action; "explicit deny" means find the deny.
Writing policies well
- Least privilege. Start with nothing and add only what a job needs. Broad is fine while exploring; narrow it before it's real.
- Name actions, not wildcards, once you know them:
s3:GetObject, nots3:*. - Name resources where you can: one bucket's ARN, not
*. - Use conditions for the important ones: MFA, a specific network, a tag.
- Test first in the simulator, and let IAM Access Analyzer check policies and suggest narrower ones from what a role actually used.
- Remove what nobody uses. IAM shows when each role and permission was last used.
In real systems
- People never get IAM users. They sign in through IAM Identity Center, often connected to the company's existing directory, and switch into roles per account.
- Programs on AWS use roles, delivered automatically: a server's instance profile, a Lambda function's execution role, a pipeline's OIDC (OpenID Connect) role in Lesson 31.
- Policies are code. They live in Git, go through review, and get deployed with Terraform (Lesson 29), not typed into the console.
- Organizations set the outer limits with SCPs, like the ones you've now hit twice. Lesson 36 writes your own.
Spot the problem
Work out what's wrong before you read the explanation.
"It only needs the photos bucket." A developer's upload script fails with AccessDenied, so they attach this to its role and the error goes away:
{ "Effect": "Allow", "Action": "s3:*", "Resource": "*" }
What's wrong? The policy gives every S3 action on every bucket in the account, including deleting them and changing who can read them, when the script needed to upload to one bucket. The fix: name the action and the resource, s3:PutObject on arn:aws:s3:::juniper-rye-photos/*, then check it in the policy simulator: s3:PutObject on that bucket allowed, s3:DeleteBucket denied. Then check: does the original error say "no policy allows it"? If so, one specific allow was all it needed.
Keep this
| Remember | Why |
|---|---|
| Every request starts denied | To allow something, a policy has to say so |
| An explicit Deny beats every Allow | Denies are how you set hard limits |
| SCPs sit above the account | Nothing inside can override them |
| Roles, not access keys | Temporary keys expire on their own |
| People through Identity Center | One sign-in, MFA, one switch to remove access |
| Simulate before you attach | Find out what a policy does before anyone relies on it |
| "No policy allows it" vs "explicit deny" | Add an allow, or find the deny |
Where you are now
You can read a policy line by line, predict whether AWS will allow a request, and explain why a Deny always wins. You've built a read-only role, tested it in the simulator, used it with keys that expired on their own, and overruled it with one deny. You also know why the new sign-up blocks groups and console passwords: people sign in through Identity Center, and programs use roles. Next, Lesson 4 puts the AWS command line interface on your own machine, signed in with short-lived credentials instead of keys in a file.
Check yourself
A role has an Allow for iam:List* and a Deny for iam:ListUsers. What happens when it lists users?
A request matches no policy at all. What does AWS do?
What does a role's trust policy decide?
Why prefer a role to an IAM user with access keys for a program?
Your role has full access, but an action fails with an explicit deny in a service control policy. What fixes it inside the account?
In a policy, what does a Resource of * mean?
How do people usually sign in to AWS at a company?
What does the policy simulator do?
Key terms
- IAM (Identity and Access Management): the AWS service that decides who can do what. Free.
- Identity, or principal: who's asking: a user, a role, or a service.
- IAM user: an identity with long-lived credentials. For programs that can't use a role.
- Group: a set of users that share policies.
- Role: permissions someone or something switches into, with temporary keys.
- AWS STS (Security Token Service): hands out a role's temporary keys.
- Policy: a JSON document of rules: Effect, Action, Resource, and optionally Condition.
- ARN (Amazon Resource Name): the unique name of one thing in AWS.
- Managed policy: a policy you attach to many identities. Inline policy: one that lives inside a single identity.
- Trust policy: a role's policy saying who may switch into it.
- Implicit deny: denied because nothing allowed it. Explicit deny: denied because a policy said Deny.
- Service control policy (SCP): an organization's limit over a whole account; it grants nothing by itself.
- Least privilege: only the permissions a job needs.
- Policy simulator: predicts what a policy allows, without running anything.
- IAM Identity Center: where people sign in once and switch into roles across accounts.
Go deeper
- AWS: Security best practices in IAM (opens in a new tab), the source for "people through federation, workloads through roles".
- AWS: Policy evaluation logic (opens in a new tab), the full version of this lesson's diagram, including resource policies and boundaries.
- AWS: What is IAM Identity Center (opens in a new tab).
- Try: write a policy that allows
iam:Get*only when the request used MFA, run it through the simulator, and see what it says without MFA.