DEV Community

Tejas Shinkar
Tejas Shinkar

Posted on Edited on

AWS IAM Hands-On Lab

This post is a practical account of my IAM lab session — three hands-on labs and five practice tasks covering Service Principals, Role Switching, Instance Profiles, and account security basics. Not a tutorial. Just what I did, where things broke, and what I actually learned from it. For the concepts behind these IAM practicals, see IAM Session.


The Setup

IAM (Identity and Access Management) is the access control layer for everything in AWS. After spending time on the theory — AAA framework, policies, trust relationships — I sat down to actually build it. Three labs, five practice tasks, one session.


Lab 1 — EC2 Accessing S3 Without Credentials

What I was trying to prove: An EC2 instance can access S3 without a single access key or secret key stored on it.

What I did:

Created an S3 bucket, launched an EC2 instance, then created an IAM Role (EC2-S3-FullAccess-Role) with ec2.amazonaws.com as the Service Principal and AmazonS3FullAccess attached. Attached the role to the EC2 instance via Modify IAM Role. SSH'd in and ran:

aws s3 ls
Enter fullscreen mode Exit fullscreen mode

Output: tejas-shinkar-s3-bucket — listed immediately. No aws configure. No access key. No secret key.

The thing that clicked:

The EC2 instance called the AWS Metadata Service at 169.254.169.254, got temporary credentials from the Instance Profile, and used them transparently. The application never touches credentials. They rotate automatically. This is the correct way — not the "paste keys into your script" approach I'd seen before.


Lab 2 — Users, Groups, Roles, and Role Switching

This was the most involved lab. Two parts.

Part A — Group + User

Created a group DevOpsTeam with PowerUserAccess, created dev-user-1 and added them to the group. Logged in as dev-user-1 and confirmed inherited permissions.

The structure:

DevOpsTeam → PowerUserAccess → dev-user-1

Clean. Policy attached once to the group — user gets it by membership. This is how it should be done at scale. Direct policy attachment to individual users is a maintenance problem waiting to happen.

Part B — Role Switching

Created S3AccessRole with AmazonS3FullAccess, then edited the Trust Policy to allow dev-user-1 to assume it:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::484908301955:user/dev-user-1"
    },
    "Action": "sts:AssumeRole"
  }]
}
Enter fullscreen mode Exit fullscreen mode

Logged in as dev-user-1, switched to S3AccessRole via the Console.

What I observed: S3 access worked. EC2 access was suspended. The role's permissions completely replaced my user's permissions for that session.

Key learning: Role switching isn't additive — it's a full replacement. If you expect to retain your existing permissions while adding new ones, you're thinking about it wrong. The role is a temporary identity, not an extension.

Switching back restored dev-user-1's PowerUserAccess immediately.


Lab 3 — Instance Profile with Least Privilege

The goal this time: ReadOnly access, not full access. Explicitly test the boundary.

Created EC2-S3-ReadOnly-Role with AmazonS3ReadOnlyAccess, attached it to a new EC2 instance during launch via the IAM Instance Profile field in Advanced Details.

SSH'd in and ran the tests:

# List buckets — should work
aws s3 ls
# → bucket listed ✅

# Download a file
aws s3 cp s3://tejas-shinkar-s3-bucket/test.txt .
# → downloaded ✅

# Try to upload — should fail
aws s3 cp test.txt s3://tejas-shinkar-s3-bucket/write-test.txt
# → AccessDenied ❌
Enter fullscreen mode Exit fullscreen mode

That AccessDenied on the write attempt was the point. It confirmed the policy boundary was real, not theoretical. The instance can read. It cannot write. The role defines the limit precisely.

Flow that makes this work:

EC2 → Instance Profile → IAM Role → Trust: ec2.amazonaws.com → Permission: S3 ReadOnly → Temporary credentials via Metadata Service → S3 access with defined limits


Practice Tasks — Five Things I Verified Independently

Beyond the labs, I worked through five standalone tasks. Each one reinforced something specific.


Task 1 — Root MFA

Enabled MFA on the Root account using Google Authenticator — Security Credentials → Assign MFA Device → scan QR → enter two consecutive codes → done.

The reason this matters: Root has unrestricted access to everything. A stolen password without MFA is total account compromise with no recovery path. This should be the first action after creating any AWS account, before anything else.


Task 2 — AdministratorAccess + Billing Access Test

Created an IAM user, attached AdministratorAccess directly, logged in and verified:

  • EC2 console — accessible ✅
  • S3 console — accessible ✅
  • Billing and Cost Management — denied ❌

The denial on Billing was unexpected. AdministratorAccess sounds like it covers everything. It doesn't. IAM access to Billing is disabled by default — Root must explicitly activate it from Account Settings. Good to discover this in a lab rather than when someone actually needs billing visibility and can't understand why they're blocked.


Task 3 — Free Tier Usage Review + $5 Budget Alert

Checked Billing → Free Tier to see current usage against limits, then set up a cost budget at $5/month with alert thresholds.

Non-negotiable for anyone learning on a Free Tier account. Forgotten resources — an EC2 left running, an unattached Elastic IP — add up silently. The budget alert catches it before it compounds.


Task 4 — DevOpsTeam Group Verification

Created DevOpsTeam with PowerUserAccess, added a user, logged in as that user and confirmed EC2 and S3 were accessible. Then checked Billing — denied, same as Task 2.

The reinforcement here was simple: group-based permission management works exactly as expected. The policy propagates to every member. Add someone to the group, they get access. Remove them, it's gone. No individual policy juggling.


Task 5 — Role Switching with Read-Only S3 + EC2 Suspension Verification

Created S3ReadOnlyRole, edited the Trust Policy to add dev-user-1's ARN, switched to the role, then verified both sides of the permission boundary:

  • S3 list and download — worked ✅
  • S3 upload — AccessDenied ❌ (ReadOnly policy enforced)
  • EC2 access — suspended while role was active ❌
  • Switch back to dev-user-1 — EC2 access returned immediately ✅

This task made the role switching mental model concrete. Before: PowerUserAccess across everything. During role: S3 ReadOnly only. After switching back: full PowerUserAccess restored. The role is genuinely temporary — not an add-on.


What I'd Carry Forward

Three things from this session that I'll apply in anything I build going forward:

1. Never hardcode credentials. Instance Profile + IAM Role is the correct pattern for EC2 accessing other AWS services. There's no legitimate reason to put access keys on an EC2 instance.

2. Trust Policy before you try to switch. The role won't work until the Trust Policy explicitly names who can assume it. Missing this step causes a confusing failure — the role exists, but the switch fails silently until you realize the Principal wasn't set.

3. Least Privilege is testable. Don't assume the policy is working — test the boundary. The AccessDenied on the write attempt in Lab 3 and Task 5 was proof the ReadOnly constraint was actually enforced. Proving it works is part of building it correctly.


Documenting my AWS learning journey. This is the part of Practical implementations of learning. Please checkout my another AWS CLOUD series if you want to explore concepts in detail.

Top comments (0)