AWS IAM · Beginner guide

Users, Roles
& Policies

The three pillars of AWS security — explained with a building analogy, interactive examples, and zero assumed knowledge.

IAM User
= Employee badge
IAM Role
= Contractor badge
IAM Policy
= Door access list
Chapter 1

The office building

Before a single line of JSON, let's build a mental model that makes IAM click — permanently.

AWS is a corporate office building. Everything inside — servers, databases, storage rooms — are AWS services. IAM is the entire security system that controls who gets in, what rooms they can enter, and what they can do inside.
// This analogy maps 1-to-1 — keep it in mind throughout every section
Building analogy — three concepts side by side
IAM User
Full-time employee
// permanent photo ID badge
A real person with their name on a permanent badge. They walk up to reception every morning, scan their badge, enter their PIN, and get access to the floors they're approved for.
🔑
Has a username + password for the console, and access keys for programmatic API calls.
IAM Role
Contractor badge
// temporary, no permanent owner
A spare badge kept in a drawer at reception. When a delivery robot arrives, reception hands it the badge. The robot can now enter certain rooms. When its job is done, the badge goes back in the drawer — the robot has no permanent identity.
🤖
AWS services (Lambda, EC2, Bedrock) use Roles — no passwords, no keys, AWS handles it automatically.
IAM Policy
Door access list
// laminated sheet on every door
A laminated card pinned to every door in the building. It lists who can enter, what they can do inside (read files? delete files?), and which specific rooms it applies to. Badges without matching entries on the list get denied.
📋
A JSON document with Effect, Action, and Resource — the three fields that define any permission.
🏢
They work together, always
A User or Role by itself does nothing. A Policy by itself does nothing. You attach a Policy to a User or Role — that's when permissions come alive. Think: badge + access list = access.
⚠️
Default is deny
AWS denies every action by default. Nothing is allowed until a Policy explicitly says Allow. If your code gets "Access Denied", a Policy is either missing or has the wrong Action/Resource.
🔍
"Access Denied" always means IAM
Whenever you see this error in CloudWatch or your SDK, stop and check: which Role is attached to your service? What Policy does it have? Does the Policy include the exact Action your code is calling?
Chapter 2

IAM Users — the employees

A permanent identity tied to a human being. One person, one User. Always.

An IAM User is a full-time employee with a photo ID. They have a permanent badge with their name on it. They show up every morning, scan in, and access the floors they're cleared for. If they quit, you deactivate their badge — you don't change everyone else's.
// "One badge per person" is the mental model. Never share Users between people.
IAM User — properties explorer
Click any property to understand it
Properties
Login credentials
An IAM User can have two kinds of credentials — one for humans, one for code.
Credential typeUsed forExample
PasswordLogging into the AWS Console websiteYou typing in a browser
Access Key ID + SecretCLI commands and SDK calls from codeaws s3 ls in terminal
⚠️
Never put access keys in your source code or commit them to git. Use Secrets Manager or IAM Roles instead.
Permissions
Users get permissions in two ways.
Direct policy attachment
Attach a Policy JSON directly to the User. Simple for one-off cases, harder to manage at scale.
Via IAM Groups
Add the User to a Group (e.g. "Developers"). Attach policies to the Group. All group members inherit those permissions automatically.
Best practice: never attach policies directly to Users. Use Groups. When a developer changes teams, you move them between groups — no policy rewiring needed.
MFA — Multi-Factor Authentication
A second layer of security on top of your password.
MFA is like a second key for your office badge. Even if someone steals your badge, they can't get in without the second key (your phone app showing a 6-digit code).
🚨
Enable MFA for every human IAM User — especially any User with admin permissions. A stolen password alone should never be enough to log in.
IAM Groups
Groups are how you manage permissions at scale.
Group: Developers
Policy: ReadEC2, InvokeLambda
alice (User)
Inherits group policies
A User can belong to multiple groups. Their effective permissions are the union of all group policies plus any directly attached policies.
Root user — the master key
The account you created AWS with. Treat it like a nuclear launch code.
☢️
Never use root for daily work. Root has unlimited, unstoppable power — it can delete your entire AWS account. Create a regular IAM User for yourself and lock root away.
What root can do that nobody else can
Close the AWS account · Change account email · Manage billing · Remove all IAM restrictions · Grant support plan changes
How to secure root right now
1. Enable MFA on root immediately   2. Create a personal IAM User with AdministratorAccess   3. Log out of root   4. Never log in again unless you absolutely must
Chapter 3

IAM Roles — the contractor badge

An identity that AWS services assume. No password, no access keys — AWS rotates credentials automatically.

An IAM Role is a contractor badge kept in a drawer. When a delivery robot arrives, reception hands it the badge. The robot enters the rooms it's allowed into, does its job, and returns the badge. The badge has no permanent owner — anyone on the approved guest list can pick it up temporarily.
// Roles = no static credentials. AWS generates short-lived tokens automatically. This is always safer.
01
Why services can't use Users
The fundamental reason Roles exist

Imagine your Lambda function needs to read from S3. You might think: "I'll create a User called 'lambda-user' and hardcode its access keys in the function." This is a serious security mistake:

Why hardcoded keys are dangerous
Keys in code end up in logs, error messages, git history, and crash reports. One leak and an attacker has permanent access until you manually rotate the key.
What Roles do instead
AWS automatically provides short-lived credentials (15 min – 12 hrs). Even if they leak, they expire. Your code never sees a static key — it just calls the SDK normally.
typescript — correct approach
// WRONG — never do this
const s3 = new S3Client({
  credentials: { accessKeyId: "AKIA...", secretAccessKey: "..." }
});

// CORRECT — attach a Role to your Lambda/EC2, then just:
const s3 = new S3Client({}); // SDK auto-picks up Role credentials
02
Trust Policy — the guest list
Who is allowed to assume this Role

Every Role has two separate policies. The Trust Policy answers "who can pick up this badge?" The Permission Policy answers "what rooms can they enter?". You need both.

json — trust policy (lambda can assume this role)
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Service": "lambda.amazonaws.com" // only Lambda can assume this
    },
    "Action": "sts:AssumeRole"
  }]
}
Common Principal values: lambda.amazonaws.com · ec2.amazonaws.com · bedrock.amazonaws.com · ecs-tasks.amazonaws.com
03
Attaching a Role to services
Lambda, EC2, Bedrock — each attaches differently
ServiceHow to attach a RoleWhere in console
LambdaSet Execution Role when creating, or edit afterwardsConfiguration → Permissions
EC2Attach an IAM Instance Profile to the instanceActions → Security → Modify IAM role
ECS / FargateSet Task Role in Task DefinitionTask Definition → Task role
Bedrock AgentAssign a service role with Lambda invoke permissionsAgent creation wizard → Role
Once a Role is attached, you never touch credentials in your code again. Delete any existing hardcoded keys immediately.
04
Users vs Roles — side by side
When to use which
IAM UserIAM Role
Who uses itA human beingAn AWS service (Lambda, EC2…)
CredentialsPassword + access keysNo static credentials — tokens auto-generated
PermanencePermanent until deletedCredentials expire (15 min–12 hrs)
Console loginYesNo
MFA supportYes — always enableN/A (no login)
Best practiceOne per human, least privilegeOne per service, least privilege
Chapter 4

IAM Policies — the access list

The JSON document that defines exactly what actions are allowed or denied, on which resources.

An IAM Policy is the laminated sheet pinned to every door. It says: "Effect: Allowed. Who can enter: Developers. Rooms: Storage Room B and Meeting Room 3. Actions: Read files, print files — not delete files." Remove the sheet and nobody gets in, regardless of their badge.
// Effect + Action + Resource — three fields. That's the whole model.
01
Anatomy of a policy statement
Effect · Action · Resource — decoded
FieldWhat it meansBuilding analogy
EffectAllow or Deny — is this permission granted or blocked?"Access: Allowed" or "Access: Denied" stamped on the door sign
ActionWhich AWS API calls are allowed (e.g. s3:GetObject, ec2:DescribeInstances)"Can read files" / "Can print files" — specific verbs, not blanket access
ResourceWhich specific AWS resource (identified by ARN). Use * for all, but avoid in production"Applies to Room 302 only" vs "Applies to entire floor 3"
json — your first policy, annotated
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadMyDocsBucket",     // a friendly name for this rule
      "Effect": "Allow",             // Allow or Deny
      "Action": [
        "s3:GetObject",           // read a file
        "s3:ListBucket"           // list files in the bucket
      ],
      "Resource": [
        "arn:aws:s3:::my-docs-bucket",    // the bucket itself
        "arn:aws:s3:::my-docs-bucket/*"  // all objects inside
      ]
    }
  ]
}
02
Types of policies
AWS Managed vs Customer Managed vs Inline
☁️
AWS Managed
Pre-built by AWS for common use cases. Examples: AmazonS3ReadOnlyAccess, AWSLambdaBasicExecutionRole. Good for getting started — but may include more permissions than you actually need.
✍️
Customer Managed
You write the JSON yourself. Precise control over every action and resource. Best practice for production — start with AWS Managed to get working, then tighten with your own.
📌
Inline
Embedded directly inside one specific User or Role. Avoid this — when you delete the User/Role, the policy disappears. Harder to audit and reuse across services.
03
Understanding ARNs
Amazon Resource Names — the address of every AWS thing

An ARN is the unique postal address of any AWS resource. You use them in Policy Resource fields to say exactly which resource the policy applies to.

ARN format
arn  :  aws  :  s3     :           :                   : my-bucket
arn  :  aws  :  lambda :  us-east-1 :  123456789012      : function:myFunc
arn  :  aws  :  iam    :            :  123456789012      : role/MyRole
 ^       ^       ^          ^               ^                   ^
prefix  cloud  service   region        account-id           resource
S3 ARNs have no region or account ID — buckets are globally unique by name. IAM ARNs have no region — IAM is global. Everything else has both.
04
Explicit Deny always wins
The priority order of Allow / Deny
Action requested
Explicit Deny?
Any policy says Deny = instant block
Explicit Allow?
At least one policy says Allow
Default Deny
No Allow found = blocked
⚠️
An explicit Deny statement overrides any number of Allow statements. Use it to create hard guardrails — e.g. "nobody can ever delete production S3 buckets, regardless of any other policy".
Chapter 5

Interactive policy builder

Select a service and actions to see the policy JSON update in real time. A starting point for your own policies.

🔧 Policy builder — select your settings on the left, see JSON on the right
Effect
Service
Actions (select multiple)
Resource ARN
Statement ID (optional)
Generated JSON
This is a starting point — always review generated policies before using them. Replace * in Resource with specific ARNs for production use.
Chapter 6

Real-world scenarios

See exactly how Users, Roles, and Policies combine in common AI and MCP setups.

MCP server running on EC2
Your Node.js MCP server runs 24/7 on an EC2 instance. It needs to list S3 buckets and describe EC2 instances.
EC2 Instance
your MCP server
IAM Role
attached to EC2
IAM Policy
S3+EC2 permissions
AWS APIs
S3, EC2
json — EC2 MCP server policy
{
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "s3:ListAllMyBuckets", "s3:ListBucket", "s3:GetObject",
      "ec2:DescribeInstances", "ec2:DescribeRegions"
    ],
    "Resource": "*" // tighten to specific ARNs in production
  }]
}
The Trust Policy on the Role should allow "ec2.amazonaws.com" as the Principal. The EC2 instance automatically picks up the Role's credentials — no code changes needed.
Lambda MCP tool handler
Each MCP tool is a separate Lambda function. Each needs specific permissions for what it does.
Claude Desktop
MCP client
API Gateway
HTTPS endpoint
Lambda + Role
tool logic
S3 / DynamoDB
data layer
json — minimal lambda execution role
{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
      "Resource": "arn:aws:logs:*:*:*"  // CloudWatch Logs — always needed
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::my-bucket/*"
    },
    {
      "Effect": "Allow",
      "Action": ["dynamodb:GetItem", "dynamodb:PutItem"],
      "Resource": "arn:aws:dynamodb:us-east-1:123456:table/AgentMemory"
    }
  ]
}
AWS Bedrock AI Agent
A fully-managed AI Agent in Bedrock that calls Lambda functions as tools and searches a Knowledge Base.
Bedrock Agent
+ IAM Role
Lambda Tools
invoke permission
Knowledge Base
OpenSearch / S3
Claude model
InvokeModel permission
json — bedrock agent service role
{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-*"
    },
    {
      "Effect": "Allow",
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:us-east-1:123456:function:my-tool-*"
    }
  ]
}
The Trust Policy for a Bedrock Agent Role uses "bedrock.amazonaws.com" as the Principal service.
Developer personal setup
You're a developer who needs to deploy Lambda functions, view logs, and interact with S3. Here's a safe personal IAM User setup.
Your IAM User
One User per developer. Enable MFA. Generate access keys for CLI use. Add to a "Developers" Group — never attach policies directly to your User.
Developers Group policy
Allow: deploy Lambda, view CloudWatch Logs, read S3 in the dev environment. Deny: delete production S3 buckets, modify IAM, access billing.
json — developer group policy (safe subset)
{
  "Statement": [
    {
      "Sid": "DeveloperAccess",
      "Effect": "Allow",
      "Action": [
        "lambda:*",
        "logs:*",
        "s3:Get*", "s3:List*",
        "ec2:Describe*",
        "bedrock:InvokeModel"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DenyProduction",        // explicit deny overrides everything
      "Effect": "Deny",
      "Action": ["s3:Delete*", "iam:*"],
      "Resource": "*"
    }
  ]
}
Chapter 7

Test your understanding

Six questions. Click an answer to get instant feedback explaining why it is right or wrong.

1. Your Lambda function gets an "Access Denied" error when trying to write to S3. What is the most likely cause?
2. You need to give your EC2 instance access to read from DynamoDB. What should you do?
3. A Policy has both an Allow for s3:DeleteObject and an explicit Deny for s3:DeleteObject. What happens when a user tries to delete an S3 object?
4. What is the purpose of the Trust Policy on an IAM Role?
5. What is the safest way to structure permissions for a team of 5 developers?
6. In the building analogy, an IAM Policy is most like:
Chapter 8

The golden rules of IAM

Five principles that prevent 90% of security incidents and "Access Denied" headaches.

01
Least privilege — give only what's needed
Start with zero permissions and add only what your code actually calls. A Lambda that only reads from S3 should have only s3:GetObject — not s3:*, not *. The smaller the blast radius, the safer you are when something goes wrong.
02
Never use root for anything except root-only tasks
Root is the master key to your entire AWS account — infinite, unrevokable power. Enable MFA on root immediately, create a personal IAM User with AdministratorAccess for your daily work, then lock root away. You should go months without using it.
03
Use Roles for services — never hardcode credentials
Your Lambda function, EC2 instance, and Bedrock Agent should all authenticate via IAM Roles — never via hardcoded access keys in code or environment variables. Roles give short-lived auto-rotating credentials. Keys in code are a breach waiting to happen.
04
Enable MFA for every human IAM User
Multi-factor authentication means a stolen password alone can't log in — the attacker also needs your phone. Enable MFA on every User, make it mandatory via an IAM Policy condition, and use a hardware key (YubiKey) for admin accounts.
05
Audit regularly with IAM Access Analyzer
AWS offers a free tool (IAM → Access Analyzer) that scans your policies and flags over-permissive rules, public S3 buckets, external access to your resources, and unused permissions. Run it before every production deployment and act on its findings.

Pre-launch checklist

Root account has MFA enabled
Root is not used for daily work
Every developer has their own IAM User
All IAM Users have MFA enabled
No hardcoded access keys in any code file
Every service (Lambda, EC2) uses an IAM Role
No policy uses Action: "*" on Resource: "*"
IAM Access Analyzer has been run and findings resolved
CloudTrail is enabled to audit all API calls
Secrets are stored in Secrets Manager, not env vars