Contents
2 Chapter

IAM — Users, Groups, Roles, Policies

Sort out IAM's four elements — users · groups · roles · policies — that decide who you work as on AWS, all in one go. Covers JSON policy syntax, the essence of AssumeRole, and permission-design patterns that hold up even in a small team.

At the end of Chapter 1 Getting Started with AWS we said “don’t work as the root user.” So who should you work as? The answer is the users and roles you create with IAM (Identity and Access Management). This chapter starts the first setup you must complete right after sign-up.

IAM is a global service. No matter which Region you open the console in, you see the same users / roles / policies. And it’s free. You can create unlimited users at no extra cost. That’s why it’s the first service you should learn when you start using AWS.

In this chapter we sort out IAM’s four elements in one go and look at the core permission-design patterns that hold up in operations. The users and roles you create here show up again from Chapter 4 CLI and SDK onward, and the story gets stricter with MFA and least privilege in Chapter 6 security basics.

The big picture — IAM’s four elements #

ElementWhatWho / what uses it
UserA permanent credential tied to one person or one machineConsole login, access keys
GroupA bundle of usersAttach a policy to a group and it applies to all users inside
RoleA credential borrowed temporarilyEC2, Lambda, users in another account, etc.
PolicyA JSON document of what to allow / denyAttached to the three above to define permissions

The key point is that a policy is the permission. Users / groups / roles are containers that hold permissions, and the policy inside decides what you can actually do.

Policy — writing permissions in JSON #

A policy is a JSON document. Getting comfortable with this shape is 80% of learning IAM.

The simplest policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}

Read it this way: this allows the s3:GetObject action on all objects (*) in my-bucket. The meaning of each key:

KeyWhat
VersionPolicy syntax version. Always 2012-10-17
StatementAn array of permission rules
EffectAllow or Deny
ActionWhich API calls to allow / deny — <service>:<action>
ResourceOn which resource — by ARN
Condition (optional)Extra conditions — IP, time, tags, etc.

The form of Action #

Like s3:GetObject, it’s the form <service>:<action>. Wildcards are allowed too.

Action wildcard examples
"Action": "s3:Get*"          // all Get actions of s3
"Action": "s3:*"             // all actions of s3
"Action": ["s3:GetObject", "s3:PutObject"]  // several as an array

The form of Resource #

Written as an ARN. We saw the shape of ARNs in Chapter 1.

Resource examples
"Resource": "arn:aws:s3:::my-bucket"           // the bucket itself
"Resource": "arn:aws:s3:::my-bucket/*"         // all objects in the bucket
"Resource": "arn:aws:s3:::my-bucket/uploads/*" // only a specific prefix
"Resource": "*"                                // all resources (dangerous)

Condition — the most powerful control #

Conditions are what make a policy truly powerful.

Allow only from the company IP
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "arn:aws:s3:::my-bucket/*",
  "Condition": {
    "IpAddress": {
      "aws:SourceIp": ["203.0.113.0/24"]
    }
  }
}
Allow only in MFA-enabled sessions
{
  "Effect": "Allow",
  "Action": "iam:*",
  "Resource": "*",
  "Condition": {
    "Bool": { "aws:MultiFactorAuthPresent": "true" }
  }
}

Commonly used condition keys:

KeyWhat
aws:SourceIpRequest IP
aws:MultiFactorAuthPresentWhether MFA is used
aws:RequestTag/<key>Tag at creation time
aws:ResourceTag/<key>Resource tag
aws:CurrentTimeTime

Allow / Deny evaluation order #

When multiple policies overlap, IAM decides in the following order.

Evaluation order
1) If any explicit Deny exists → deny (end)
2) If any explicit Allow exists → allow
3) If neither → deny (default)

Deny always wins. That’s why guardrails like “this user must never touch prod resources” are written as Deny. The pattern is to pair a Deny with an Allow policy.

User — a permanent credential for a person / machine #

An IAM user is a separate ID, not an email address. It also gets its own console login URL.

IAM user console URL
https://<account-id>.signin.aws.amazon.com/console
or
https://<account-alias>.signin.aws.amazon.com/console

Creating a user #

In the console, go to IAM → Users → Add users.

ItemWhat
User nameNot an email. An identifier like curtis, dev-bot
Console accessPassword — for people
Programmatic accessAccess keys — for CLI / SDK (Chapter 4 CLI and SDK)
PermissionsAdd to a group, attach policies directly, or copy from another user

The best practice is not to attach a policy directly to a user. Attach it through a group. The reason is covered in the next section.

Access keys — the most dangerous credential a user holds #

An access key is a two-line string.

The shape of an access key
Access Key ID:     AKIAIOSFODNN7EXAMPLE
Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Anyone who has these two values can exercise all of that user’s permissions. So follow these rules.

  • A user has at most 2 access keys (two coexist briefly during rotation).
  • Never put them in git or code (the root-key-exposure case in Chapter 1).
  • Rotate every 90 days (covered in detail in Chapter 6 security basics).
  • When possible, use a Role instead of a user access key.

Group — the unit of user management #

A group is a unit that bundles users. Attach a policy to a group and it applies automatically to all users in that group.

A common shape of group design
Admins         → AdministratorAccess
Developers     → PowerUserAccess + (IAM deny policy)
ReadOnly       → ReadOnlyAccess
Billing        → billing-console access only

Why attach to a group instead of directly to a user #

Suppose a new developer joins.

In the direct-to-user model, you pick one existing developer and follow their list of policies to attach. Then “what was that policy I added a week ago for that person?” creates omissions, and over time each developer’s permissions diverge subtly.

In the group model, you just add them to the Developers group and you’re done. Permission changes are applied consistently to everyone by changing only the group’s policy, and auditing and review are clean at the group level.

The rule is this: for IAM users, forbid inline policies and directly attached policies, and grant all permissions through groups or roles.

Role — a credential borrowed temporarily #

A Role isn’t anyone’s credential; it’s a credential that anything can borrow temporarily. It has no permanent password or access key. Instead, when someone or something makes AssumeRole, AWS issues a temporary credential valid for 1 ~ 12 hours.

This is the most important point in IAM. It’s hard at first, but once you get used to it, the real power of IAM appears.

When Roles are actually used #

ScenarioWho calls AssumeRole
EC2 accessing S3The EC2 instance (automatically)
Lambda writing to DynamoDBLambda (automatically)
An ECS Task calling another serviceThe Task (automatically)
A user in another AWS account accessing some resources of this accountThat user, explicitly
GitHub Actions pushing to ECRA GitHub Actions Workflow via OIDC
Temporary privilege escalation (break-glass)A user, explicitly

In environments like EC2 / Lambda / ECS, AWS automatically calls AssumeRole and feeds the credential to the instance. The code just calls the SDK. There’s no need to handle access keys. This is the core.

A role’s two policies #

A role has two kinds of policy.

1) Trust Policy — decides who can borrow this role.

A role EC2 can borrow
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}

The Principal item is who goes in — a service, another account, a user, an OIDC provider, etc.

2) Permission Policy — decides what you can do after borrowing it.

Allow S3 read inside the role
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::my-bucket/*"
  }]
}

EC2 instance profile flow #

The credential flow of EC2 → S3
[EC2 instance]
   │ 1) request credentials from instance metadata (169.254.169.254)
[IAM]
   │ 2) issue temporary credentials of the role attached to the instance
[SDK inside EC2]
   │ 3) call S3 with the credentials
[S3]

The code inside EC2 doesn’t know the access key. boto3 or aws-sdk pulls the credential from the metadata and uses it on its own. It fundamentally reduces key-exposure incidents.

Managed policies vs inline policies #

Policies are also split by how they’re made.

KindWhatRecommendation
AWS managedMade by AWS (e.g., AdministratorAccess, ReadOnlyAccess)Beginner / standard roles
Customer managedPolicies I make. Reusable across multiple users / rolesThe operational standard — recommended
InlineGranted directly to only one user / group / roleNot recommended — not reusable, hard to track

Commonly used AWS managed policies #

NameWhat
AdministratorAccessEvery permission. Dangerous, next to root
PowerUserAccessAlmost every permission except IAM
ReadOnlyAccessRead-only across all services
BillingBilling console
<Service>FullAccess (e.g., AmazonS3FullAccess)All of one service
<Service>ReadOnlyAccessRead of one service

The common flow is to start with managed policies and gradually narrow, moving to customer-managed.

Operational permission design — patterns that work #

The skeleton of permission design that holds up even in a small team.

1) 4 groups + 3 roles #

UnitNameWhat
GroupAdminsAdministratorAccess (MFA enforced)
GroupDevelopersPowerUserAccess + IAM deny
GroupReadOnlyReadOnlyAccess
GroupBillingBilling / cost
RoleEC2-AppRoleThe role the app uses (S3, RDS, etc.)
RoleLambda-WorkerRoleFor Lambda
RoleCICDRoleBorrowed by GitHub Actions / CodeBuild

2) Permission Boundary #

A shield that decides “whatever this IAM user does, only within this limit.” It blocks the incident of a junior developer creating a new policy to raise their own permissions.

Pattern
Developers group → PowerUserAccess
   ↓ and give every user a permission boundary
Permission boundary → "DevOnly" — can only create dev/* resources

3) Enforce MFA #

Enforce it on root and all IAM users. The shape of the enforcement policy is covered in detail in Chapter 6 security basics.

4) Separate users ↔ roles #

Users are only people; machines are all roles. When this is clear, access-key incidents almost never happen.

5) Guardrails with conditions #

Keep developers from touching prod-* tagged resources
{
  "Effect": "Deny",
  "Action": "*",
  "Resource": "*",
  "Condition": {
    "StringEquals": { "aws:ResourceTag/env": "prod" }
  }
}

Use tags consistently, and a single policy line separates environments (the same foundation as the tag strategy in Chapter 3 cost management).

Policy Simulator — a verification tool #

The IAM Policy Simulator (policysim.aws.amazon.com) is a tool that simulates “is this action possible?” for a user / group / role. Use it to confirm a policy change did what you intended.

Flow
1) Select a user or role
2) Select the action to simulate (e.g., s3:DeleteObject)
3) Enter the resource ARN (optional)
4) "Run Simulation" → Allowed / Denied and which policy influenced it

The IAM Access Analyzer in Chapter 6 security basics also helps with checking.

Creating an IAM user — hands-on #

Now that you’re here, we recommend creating one IAM user yourself. From Chapter 4 CLI and SDK on, this user and role are needed.

Hands-on order
1. Console → IAM → Users → Add users
2. User name: your name (e.g., curtis)
3. Console access: enable, auto-generate password
4. Attach policies directly: AdministratorAccess (for learning — narrow later)
5. Right after creation → enable MFA immediately (enforcement method in Chapter 6)
6. Access keys: issue just one for CLI
7. Download the .csv — the Secret is shown only on this screen

Right after this, move on to Chapter 3 cost management and Chapter 6 security basics.

Common pitfalls #

  • Attaching policies directly to users — it looks fine up to 10 people, but at 30 permissions start diverging subtly. On a breach you can’t tell whose permissions reach where. Gather them into groups.
  • Overuse of Resource: "*" — it’s convenient at first, but on a breach the blast radius is infinite. Narrow with ARNs in every role you can.
  • Inline policies — fast when you slam one in, but a month later you don’t know who added it and it isn’t reusable. Move to named, version-managed customer-managed policies.
  • Granting access keys directly inside EC2 — code that solves EC2 → S3 credentials with an access key should move to an instance profile (role). Forget the key rotation and an incident lies dormant forever.
  • A “test user” left as-is — a former employee’s access key, a bot user created once and forgotten, all remain. Make an auto-disable / delete policy for users unused for 30 / 90 days, and check at a glance with the IAM Credential Report.
  • An access key on the root user — the most common incident in Chapter 1. Root gets console and MFA only; never create an access key.

Exercises #

  1. Based on §“Allow / Deny evaluation order,” write a JSON fragment of the Statement you’d add to make a user with AdministratorAccess attached still unable to delete objects in a specific bucket.
  2. In §“A role’s two policies,” write one sentence each on what the Trust Policy and Permission Policy decide, and connect which of these flows the role_arn + source_profile profile in Chapter 4 CLI and SDK reproduces in the CLI.
  3. Assume a small team you’ll run and rewrite the §“4 groups + 3 roles” table to fit your environment. Pick one machine role from it and note how that role keeps the “users = people, machines = roles” principle from Chapter 6 security basics.

In short: IAM consists of four elements — users, groups, roles, and policies — and a policy is the permission. A policy is JSON of Effect + Action + Resource (+ Condition), and Deny beats Allow. Bundle people into groups and let machines receive credentials via roles, and not putting keys in code is the basis of preventing incidents.

Next chapter #

Now that you have an IAM user, billing comes next. The biggest incident in the first few days is sleeping resources inflating the invoice. In the next Chapter 3 cost management, we sort out the billing alerts and free-tier limit monitoring you need to set up right after sign-up, plus operational-stage Cost Explorer and a tag strategy.

X