Lab 2 · IAM Users, Roles & Policies¶
Security · 30% · Est. time: 30–40 min · Cost: \$0 (IAM is free; the optional EC2 step is Free-Tier eligible)
Goal. Feel the evaluation engine in your hands: create a group + user, watch an explicit deny beat an allow, build a role an EC2 instance can assume, and cap a role with a permissions boundary. Everything here maps directly to Topic 1.
Before you start
- Do this as an admin IAM user or role, not the root user.
- IAM itself costs nothing. The only thing in this lab that could bill is the optional
EC2 instance in Step 5 — it's Free-Tier eligible (
t2.micro/t3.micro, 750 hrs/mo for 12 months), but terminate it at the end regardless. - If you haven't set a billing alarm yet, do Lab 12 first.
What you'll build¶
- A Developers group with read-only S3 access.
- A user in that group, plus an explicit deny that overrides the group's allow.
- An EC2 role (instance profile) granting S3 read access — no keys anywhere.
- A permissions boundary that caps a role regardless of its attached policy.
Step 1 — Create a customer-managed policy¶
- Open the IAM console → left nav Policies → Create policy.
- Click the JSON tab and paste:
- Next → Policy name:
Lab-S3ReadOnly→ Create policy.
Tip
In the real world you'd scope Resource to specific bucket ARNs, not *. We use * to keep
the lab short — note this is not least privilege.
Step 2 — Create a group and attach the policy¶
- IAM → User groups → Create group.
- Group name:
Developers. - Under Attach permissions policies, search
Lab-S3ReadOnly, tick it. - Create group.
Step 3 — Create a user and see the group's permissions apply¶
- IAM → Users → Create user.
- User name:
lab-dev. (Leave console access off — we only need programmatic testing, and real humans should use IAM Identity Center, not IAM users.) - Next → Add user to group → tick Developers → Next → Create user.
The user now inherits s3:GetObject / s3:ListBucket from the group — it has no policy of its
own yet. Confirm with the simulator in the next step.
Step 4 — Watch an explicit deny override the allow¶
This is the single most important IAM concept, and you can prove it without any real resources using the Policy Simulator.
-
Attach an inline deny to the user:
- IAM → Users →
lab-dev→ Permissions tab → Add permissions → Create inline policy. - JSON tab, paste:
- Next → name
DenyS3Get→ Create policy.
- IAM → Users →
-
Open the IAM Policy Simulator: https://policysimulator.aws.amazon.com/ (or IAM console → user → Simulate shortcuts).
- Pick user
lab-dev, service S3, actions GetObject and ListBucket, then Run simulation.
Observe:
s3:ListBucket→ allowed (group grants it, nothing denies it).s3:GetObject→ denied — the inlineDenyoverrides the group'sAllow.
✅ You just demonstrated Explicit Deny > Allow. No object was ever created — the simulator evaluates policies exactly as the live engine does.
Step 5 — Create a role for EC2 (instance profile)¶
Goal: give an EC2 instance S3 read access without any stored keys.
- IAM → Roles → Create role.
- Trusted entity type: AWS service. Use case: EC2. Next.
- Attach
Lab-S3ReadOnly. Next. - Role name:
Lab-EC2-S3Read→ Create role. (The console auto-creates a matching instance profile.)
cat > ec2-trust.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole" }
]
}
EOF
aws iam create-role --role-name Lab-EC2-S3Read \
--assume-role-policy-document file://ec2-trust.json
aws iam attach-role-policy --role-name Lab-EC2-S3Read \
--policy-arn arn:aws:iam::$ACCOUNT_ID:policy/Lab-S3ReadOnly
# CLI-only: the instance profile the console makes for you
aws iam create-instance-profile --instance-profile-name Lab-EC2-S3Read
aws iam add-role-to-instance-profile \
--instance-profile-name Lab-EC2-S3Read --role-name Lab-EC2-S3Read
Notice the trust policy names ec2.amazonaws.com as the principal allowed to assume the role —
that's the "who can assume" half; Lab-S3ReadOnly is the "what it can do" half.
Optional — prove it end-to-end (Free-Tier EC2)
- Launch a
t3.micro(ort2.micro) Amazon Linux instance; under Advanced details → IAM instance profile, pickLab-EC2-S3Read. - Connect via EC2 Instance Connect / Session Manager (no SSH key needed).
- Run — note there are no credentials on the box:
- Terminate the instance immediately after (EC2 → Instance state → Terminate).
Step 6 — Cap a role with a permissions boundary¶
Show that a boundary limits a role below its attached policy.
- Create a boundary policy (IAM → Policies → Create policy → JSON):
Name it
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:*", "Resource": "*" } ] }Lab-Boundary-S3Only. - Create a role
Lab-Boundary-Demo(trusted entity: your account / same as above) and attach the AWS-managedAdministratorAccesspolicy to it. - On that role → Permissions boundary → Set boundary → choose
Lab-Boundary-S3Only.
aws iam create-policy --policy-name Lab-Boundary-S3Only \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"*"}]}'
aws iam create-role --role-name Lab-Boundary-Demo \
--assume-role-policy-document file://ec2-trust.json \
--permissions-boundary arn:aws:iam::$ACCOUNT_ID:policy/Lab-Boundary-S3Only
aws iam attach-role-policy --role-name Lab-Boundary-Demo \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
Simulate ec2:StopInstances and s3:PutObject for Lab-Boundary-Demo:
s3:PutObject→ allowed (both the admin policy and the boundary allow S3).ec2:StopInstances→ denied —AdministratorAccessallows it, but the boundary doesn't, and effective permissions are the intersection.
✅ You just proved a boundary caps even
AdministratorAccess.
Teardown¶
IAM resources don't bill, but clean up so your account stays tidy and the credential report stays meaningful.
Delete in this order (a policy can't be deleted while attached):
- Terminate the optional EC2 instance (if launched).
- Roles:
Lab-EC2-S3Read,Lab-Boundary-Demo(detach policies first if prompted). - Instance profile
Lab-EC2-S3Read(deleted with the role in the console). - User
lab-dev(remove from group + delete inline policy happens on user delete). - Group
Developers. - Policies:
Lab-S3ReadOnly,Lab-Boundary-S3Only.
# EC2 (if launched): terminate via console or `aws ec2 terminate-instances --instance-ids ...`
aws iam remove-role-from-instance-profile --instance-profile-name Lab-EC2-S3Read --role-name Lab-EC2-S3Read
aws iam delete-instance-profile --instance-profile-name Lab-EC2-S3Read
aws iam detach-role-policy --role-name Lab-EC2-S3Read --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/Lab-S3ReadOnly
aws iam delete-role --role-name Lab-EC2-S3Read
aws iam detach-role-policy --role-name Lab-Boundary-Demo --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam delete-role --role-name Lab-Boundary-Demo
aws iam delete-user-policy --user-name lab-dev --policy-name DenyS3Get
aws iam remove-user-from-group --user-name lab-dev --group-name Developers
aws iam delete-user --user-name lab-dev
aws iam delete-group --group-name Developers
aws iam delete-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/Lab-S3ReadOnly
aws iam delete-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/Lab-Boundary-S3Only
✅ What you should understand now¶
- A group carries permissions for its members but is not an identity you can log in as.
- An explicit
Denyoverrides anyAllow, no matter where the allow comes from — you saw it in the simulator (Explicit Deny > Allow > default Deny). - The Policy Simulator lets you test evaluation logic with zero real resources or risk.
- An EC2 role via an instance profile delivers temporary credentials with nothing stored on the instance — the trust policy says who can assume, the permission policy says what it can do.
- A permissions boundary caps effective permissions to the intersection with the
attached policy — it can shrink even
AdministratorAccess, and it never grants anything.
Related: Learn — IAM Deep Dive · Revise — IAM · Mock Exam 1