Skip to main content

AWS IAM — The ONE Service You Must Master First

· 5 min read
Goel Academy
DevOps & Cloud Learning Hub

You can learn EC2, S3, Lambda, and every other AWS service out there, but if you get IAM wrong, none of it matters. One misconfigured policy and your S3 bucket is on the news. Let's make sure that never happens to you.

Why IAM Before Everything Else?

Every single API call to AWS is authenticated and authorized through IAM. When you launch an EC2 instance, upload to S3, or invoke a Lambda function — IAM is the gatekeeper. If you skip it and jump straight to "fun" services, you'll inevitably hardcode access keys, use wildcard permissions, and create security nightmares that take weeks to untangle.

IAM is free. There's no excuse not to learn it properly.

Users vs Roles vs Groups vs Policies

Here's the mental model:

EntityWhat It IsWhen to Use
UserA person or application with permanent credentialsHuman users who need console/CLI access
GroupA collection of usersAssigning identical permissions to a team
RoleTemporary credentials assumed by entitiesEC2 instances, Lambda functions, cross-account access
PolicyJSON document defining permissionsAttached to any of the above

The golden rule: Humans get users. Machines get roles. Always.

# Create a user
aws iam create-user --user-name deploy-bot

# Create a group and add the user
aws iam create-group --group-name developers
aws iam add-user-to-group --user-name deploy-bot --group-name developers

# Attach a managed policy to the group
aws iam attach-group-policy \
--group-name developers \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

Anatomy of an IAM Policy

Every IAM policy is a JSON document with this structure:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3BucketAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": "203.0.113.0/24"
}
}
}
]
}

Key fields:

  • Effect: Allow or Deny (Deny always wins)
  • Action: Which API calls are permitted
  • Resource: Which AWS resources this applies to
  • Condition: Optional constraints (IP range, MFA, time of day)

The Least Privilege Principle (For Real)

Everyone says "use least privilege." Here's what that actually looks like in practice:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllExceptSpecificBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::production-logs/2025/*"
}
]
}

Compare that to what most people actually write:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}

The second policy gives full S3 access to every bucket in your account. That includes the bucket with your database backups, your CloudTrail logs, and your customer data. Don't do this.

Enforcing MFA on Your Account

Every AWS account should enforce MFA. Here's a policy that denies all actions unless the user has authenticated with MFA:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllExceptMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:ListMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
# Enable a virtual MFA device for a user
aws iam enable-mfa-device \
--user-name admin-user \
--serial-number arn:aws:iam::123456789012:mfa/admin-user \
--authentication-code1 123456 \
--authentication-code2 789012

Cross-Account Roles

When you need one AWS account to access resources in another (and you will), don't share access keys. Use cross-account roles:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-here"
}
}
}
]
}
# Assume the cross-account role
aws sts assume-role \
--role-arn arn:aws:iam::444455556666:role/CrossAccountS3Access \
--role-session-name my-session \
--external-id unique-external-id-here

The response gives you temporary credentials (access key, secret key, session token) that expire automatically. No permanent keys floating around in config files.

The 5 Most Common IAM Mistakes

  1. Using "Resource": "*" everywhere — Be specific. Name the ARN.
  2. Not using roles for EC2 — If your EC2 instance has hardcoded AWS keys, you're doing it wrong. Attach an instance profile instead.
  3. Ignoring the root account — Lock it down, enable MFA, never use it for daily work.
  4. Not rotating credentials — Access keys should be rotated every 90 days maximum.
  5. Skipping IAM Access Analyzer — It's free and tells you exactly which resources are shared externally.
# Create an IAM Access Analyzer
aws accessanalyzer create-analyzer \
--analyzer-name my-account-analyzer \
--type ACCOUNT

# List findings (externally shared resources)
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/my-account-analyzer

IAM Access Analyzer — Your Free Security Audit

IAM Access Analyzer scans your policies and identifies resources shared with external entities. It catches things like:

  • S3 buckets with public access
  • IAM roles assumable by other accounts
  • KMS keys shared externally
  • Lambda functions with cross-account access

Run it. Review the findings. Fix what shouldn't be public. This single tool has prevented more data breaches than most paid security products.

What's Next?

Now that you understand who can do what in your AWS account, it's time to learn about the most used (and most misunderstood) service: Amazon S3. In the next post, we'll cover bucket policies, encryption, lifecycle rules, and the 10 things most engineers get wrong about object storage.


This is Part 2 of our AWS series. IAM is the foundation — everything else builds on top of it.