What you will learn
- What IAM is and why every AWS action passes through it first
- The Root User — what it is, when to use it, and why you should almost never touch it
- IAM Users, Groups, and how permissions flow from group to individual
- How to read and write IAM Policy JSON — every field explained
- Why explicit Deny always wins no matter what
- IAM Roles — why they exist and how AWS services use them safely
- MFA, password policy, and the rules around access keys
- The two audit tools that keep your account clean as it grows
Why this matters
Every single action in AWS — launching an EC2, reading an S3 file, deleting a database — goes through IAM first. Get IAM wrong and you either lock your team out of what they need or hand an attacker the keys to everything.
This is not a one-time setup. IAM is something you audit, tighten, and maintain as your team grows. The most common AWS security incidents — credentials leaked on GitHub, over-permissive roles, inactive users with full admin access — are all IAM failures. Engineers who treat IAM as a formality are the ones who wake up to compromised accounts and unexpected bills.
What is IAM and the Root User Problem
IAM (Identity and Access Management) is a global AWS service that controls two things for every single request made in your account:
Who are you? → Authentication (proving your identity)What can you do? → Authorization (what actions are allowed)IAM is not region-specific. A user you create today works in Mumbai, Singapore, and Virginia equally.
When you first open an AWS account, you get a Root User — created with your account email and password. Root has complete, unrestricted access to everything: billing, IAM, every service, every region. No IAM policy in the world can limit it.
Root User reality: Full access to billing, support, all 200+ AWS services Cannot be restricted by any IAM policy — ever If credentials are stolen → entire account is compromised When to actually use Root (only these 4 cases): Initial account setup — creating your first IAM admin user Managing billing and payment methods Account recovery Closing the AWS accountSecurityEnable MFA on Root immediately after account creation. Never create Access Keys for Root. Never use it for daily work. Think of Root like a master key to your entire office building — kept locked in a safe and taken out only in genuine emergencies.
Users, Groups, and How Permissions Flow
IAM Users represent one real person or one application. One person = one user. Always. Sharing users makes auditing impossible — if something goes wrong you cannot tell who did it.
Each IAM user gets:
- A username and password → for Console login
- Access Key ID and Secret Access Key → for CLI and SDK access
IAM Groups are containers for users with the same job role. Attach a policy to the group once — everyone inside inherits it automatically.
Example team at a Bangalore fintech startup: Group: Developers → Arjun, Rohan → EC2 Full + S3 ReadGroup: Testers → Sneha → EC2 Read-onlyGroup: Admins → Priya → AdministratorAccess Rohan is also added to Group: Audit Team → IAM ReadOnlyResult: Rohan gets Developer permissions PLUS Audit permissions combinedThree rules that trip engineers up:
Groups contain users only — you cannot put a group inside another groupA user can belong to multiple groups — permissions from all groups combineA user can exist outside all groups — allowed but not recommendedRememberAlways attach policies to Groups, not directly to individual users. When someone changes roles, just move them between groups. Individual user policies pile up silently and become impossible to audit over time.
IAM Policies — Reading and Writing Permissions
A Policy is a JSON document that defines exactly what actions are allowed or denied on which AWS resources. This is the actual engine behind every permission in AWS.
A real policy — read-only access to one S3 bucket:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowReadPaymentLogs", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::razorpay-payment-logs", "arn:aws:s3:::razorpay-payment-logs/*" ] }, { "Sid": "ExplicitlyDenyDelete", "Effect": "Deny", "Action": "s3:DeleteObject", "Resource": "arn:aws:s3:::razorpay-payment-logs/*" } ]}This lets someone read payment logs but blocks any delete — even if another policy tries to allow it.
Every field explained:
| Field | Required | What it does |
|---|---|---|
| Version | Yes | Policy language version — always use "2012-10-17" |
| Sid | No | A readable label for the statement — helps you audit later |
| Effect | Yes | Allow or Deny — what happens when this rule matches |
| Action | Yes | Which AWS API calls — e.g. s3:GetObject, ec2:* |
| Resource | Yes | Which resource — * means all, or a specific ARN |
| Principal | Sometimes | Who the policy applies to — used in resource-based policies |
| Condition | No | Extra conditions — IP range, MFA required, time of day |
RememberEffect: Deny always wins over Effect: Allow. If one policy allows an action and another denies it, Deny wins every time with no exceptions. This is the explicit deny rule. Statement order inside a policy does not matter — AWS evaluates all statements together.
The most common S3 ARN mistake:
s3:ListBucket applies to the bucket itself: "Resource": "arn:aws:s3:::my-bucket" s3:GetObject and s3:PutObject apply to objects inside: "Resource": "arn:aws:s3:::my-bucket/*" You need both statements for full S3 read access.Using only one or the other is why S3 policies silently fail.Three types of policies:
| Type | Created by | When to use |
|---|---|---|
| AWS Managed | AWS | Standard roles — AmazonS3ReadOnlyAccess, AdministratorAccess |
| Customer Managed | You | Custom rules specific to your organisation |
| Inline | You — directly on a user or role | Avoid — not reusable, hard to audit |
How permissions combine:
Developers group → EC2FullAccess → Arjun, Rohan, Priya all get itAudit Team group → IAMReadOnlyAccess → Rohan also gets thisInline Policy → special rule → Fred only — attached to his user Rohan = EC2Full + IAMReadOnly combinedFred = his inline policy onlyIAM Roles — Permissions for AWS Services
IAM Roles solve a different problem from users. AWS services like EC2, Lambda, and ECS need to call other AWS services. How do you give an EC2 instance permission to read S3 without storing credentials on it?
The wrong way — what beginners do:
Developer puts AWS credentials directly on EC2: AWS_ACCESS_KEY_ID = AKIAIOSFODNN7EXAMPLE AWS_SECRET_ACCESS_KEY = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY → Keys in config → config pushed to GitHub → repo public for 10 minutes→ Automated scanner finds keys within 60 seconds→ Account compromised. Bills run up. Data exfiltrated.The right way — IAM Roles:
Attach IAM Role to the EC2 instance ↓Role has policy: Allow s3:GetObject on payment-logs bucket ↓AWS SDK on EC2 automatically fetches TEMPORARY credentials ↓Credentials expire in 1-12 hours and auto-rotate ↓No keys stored anywhere. Nothing to leak. Nothing to rotate manually.User vs Role — the key difference:
| Feature | IAM User | IAM Role |
|---|---|---|
| Designed for | Humans and long-running apps | AWS services |
| Credentials | Long-term — password and access keys | Temporary — auto-rotated |
| Risk if stolen | High — valid until manually rotated | Low — expires automatically |
| Example | Arjun logs into Console | EC2 reads from S3 |
Common roles you create in every production project:
EC2 Instance Role → app reads S3, writes to DynamoDB, calls Secrets ManagerLambda Execution Role → Lambda writes logs to CloudWatch, queries RDSECS Task Role → container sends messages to SQS, reads from S3CodeDeploy Role → pipeline pushes deployments to EC2 instancesCloudFormation Role → CloudFormation creates resources on your behalfTipIf you are ever tempted to put an access key inside an EC2 instance, Lambda function, or Docker container — stop. Create a Role instead. The SDK fetches temporary credentials automatically. No keys. No risk. No rotation.
Securing Access — MFA, Password Policy, Access Keys
MFA — Multi-Factor Authentication
MFA adds a second layer. Even if someone steals Priya's password, they cannot log in without her physical device.
Without MFA: Password stolen via phishing → attacker logs in → full account access With MFA: Password stolen → attacker tries to log in → AWS asks for 6-digit code → Attacker does not have Priya's phone → blocked completelyMFA device options:
| Type | Device | How it works |
|---|---|---|
| Virtual MFA | Google Authenticator, Authy | App generates 6-digit code every 30s — free |
| U2F Security Key | YubiKey | Plug in and tap — strongest physical option |
| Hardware Key Fob | Gemalto device | Physical rotating display — corporate environments |
| GovCloud Key Fob | SurePassID device | Same but for AWS GovCloud US only |
Password Policy
Set account-wide rules for all IAM users:
- Minimum length (12+ characters recommended)
- Require uppercase, lowercase, numbers, special characters
- Allow users to change their own passwords
- Force expiration every 90 days
- Prevent reuse of last N passwords
Location: IAM → Account Settings → Password Policy
Access Keys — for CLI and SDK access
Access Key ID ≈ username (an identifier — safe to share)Secret Access Key ≈ password (a secret — never share this)Non-negotiable rules:
- Never commit to Git — scanners watch GitHub, GitLab, Bitbucket 24/7
- Never hardcode in application source code
- Never share with teammates — each person generates their own
- Deactivate immediately if you suspect a leak
- For EC2, Lambda, and ECS — never use keys at all, use Roles
Setting up AWS CLI:
## Configure with your IAM user access keysaws configure ## You will be prompted for:## AWS Access Key ID → your key ID## AWS Secret Access Key → your secret key## Default region name → ap-south-1## Default output format → json ## Verify it worksaws iam get-useraws s3 lsaws ec2 describe-instances --region ap-south-1TipAWS CloudShell is a browser-based terminal inside the Console — pre-configured with your credentials, zero local setup. Perfect for quick commands without configuring your local machine.
Three ways to access AWS:
| Method | Protected by | Use for |
|---|---|---|
| AWS Management Console | Password + MFA | Humans, browser, one-off tasks |
| AWS CLI | Access Keys | Automation, scripts, CI/CD |
| AWS SDK (Python, Node, Java) | Access Keys | Application code calling AWS APIs |
IAM Security Audit Tools
Once teams grow, permissions drift silently. People leave but their users stay active. A service has permissions for 10 actions but only ever calls 2.
IAM Credentials Report — account-level snapshot
Downloads a CSV of every IAM user and their credential status:
- Password enabled, MFA active, access keys active or inactive
- Last login date and last key use date
- Find: users inactive for 90+ days (probably left the company), unused access keys
Location: IAM → Credential Report → Download
IAM Access Advisor — per-user permission usage
Shows which AWS services a specific user or role actually accessed and when. Use this to trim unused permissions.
Real example — trimming Arjun's permissions: Arjun currently has: EC2Full + S3Full + RDSFull + LambdaFull + CloudWatchFull Access Advisor shows: S3 → accessed 2 days ago CloudWatch → accessed 1 week ago EC2 → accessed 5 months ago RDS → never accessed Lambda → never accessed Action: Remove RDS and Lambda. Review EC2.Result: Smaller blast radius if Arjun's credentials are ever compromised.Location: IAM → Users → select user → Access Advisor tab
RememberCredentials Report = audit the entire account at once. Access Advisor = trim permissions for one specific user or role. Run both every month in any production account.
Hands-on Lab — Set Up IAM the Right Way From Day One
This is the exact setup every AWS account should have before anything else is built.
Step 1 — Enable MFA on Root
Log in as RootTop right → Account name → Security credentialsMulti-factor authentication → Assign MFA deviceChoose: Authenticator app → scan QR code with Google AuthenticatorEnter two consecutive codes to confirm → MFA enabledStep 2 — Create your first IAM Admin user
IAM → Users → Create userUsername: priya-adminConsole access: Enable → Custom password → set a strong passwordPermissions: Attach policies directly → AdministratorAccessCreate user Log out of Root. Log back in as priya-admin.Use this account for all daily work from now on.Step 3 — Create Groups with correct policies
IAM → User Groups → Create groupGroup name: DevelopersAttach policies: AmazonEC2FullAccess + AmazonS3ReadOnlyAccessCreate group Create another:Group name: TestersAttach policies: AmazonEC2ReadOnlyAccessCreate groupStep 4 — Create a developer user and add to group
IAM → Users → Create userUsername: arjun-devConsole access: Enable → set passwordAdd user to group: DevelopersCreate user Verify: IAM → Users → arjun-dev → PermissionsYou should see: AmazonEC2FullAccess and AmazonS3ReadOnlyAccess inherited from Developers groupStep 5 — Create access keys for CLI
IAM → Users → arjun-dev → Security credentialsCreate access key → Use case: CLI → CreateDownload the CSV — the Secret Access Key is shown only onceStep 6 — Configure CLI and test
aws configure --profile arjun-dev## Enter the access key ID and secret from the CSV## Region: ap-south-1## Output format: json ## Test — should workaws s3 ls --profile arjun-devaws ec2 describe-instances --region ap-south-1 --profile arjun-dev ## Test a denied action — should failaws iam list-users --profile arjun-dev## Error: Access Denied (arjun-dev has no IAM permissions)Step 7 — Create an IAM Role for EC2
IAM → Roles → Create roleTrusted entity: AWS service → EC2Permissions: AmazonS3ReadOnlyAccessRole name: ec2-s3-read-role → Create Launch or select an EC2 instance:Actions → Security → Modify IAM Role → ec2-s3-read-role → Update SSH into the instance and test:## No aws configure needed — role provides credentials automaticallyaws s3 ls --region ap-south-1## Works immediately aws sts get-caller-identity## Shows the role ARN — confirms role is active, not a personal keyStep 8 — Download and review Credentials Report
IAM → Credential Report → Download reportOpen the CSV and look for:password_last_used = N/A → user never logged in, consider removingaccess_key_1_last_used_date = old → key not used, deactivate itmfa_active = false → no MFA, security riskExpected result: Root is MFA-protected and unused. You have a working admin user for daily work. A developer user with scoped group permissions is set up. CLI access is configured and tested. You have read your first credentials report.
Common Mistakes to Avoid
Common MistakeStoring AWS access keys on an EC2 instance by running
aws configureon the server. If that instance is ever compromised — through a vulnerability, a leaked SSH key, or a misconfigured Security Group — the attacker gets your AWS credentials. Use IAM Roles. The SDK fetches temporary credentials automatically. No keys stored anywhere.
Common MistakeCreating one shared IAM user for an entire team. When that credential is leaked (and it eventually will be), you cannot tell which team member made which API call. You cannot revoke one person's access without affecting everyone. One person = one IAM user. Always.
Common MistakeAttaching policies directly to individual users instead of groups. Three months later you have 20 users each with a different set of directly-attached policies. No one knows what permissions anyone has. Put everyone in groups. Manage permissions on the group. One change affects everyone in the group instantly.
SecurityThe IAM Credentials Report shows access keys that have never been used or have not been used in 90+ days. Every one of those is a credential that could be leaked without you noticing. If it is not being used, it should not exist. Deactivate first, then delete after a week if nothing breaks.