IAM: Users, Groups & Policies

AWS · Lesson 7 of 30 7 min read Updated

Every request to AWS, whether it comes from the console, the CLI or your application, is checked by AWS Identity and Access Management (IAM). IAM answers two questions: who are you? (authentication) and are you allowed to do this? (authorisation). If you understand IAM well, the rest of AWS becomes much safer to use.

In this lesson you will learn the building blocks of IAM, how to read and write JSON policies, how AWS decides between allow and deny, and how to create a user, a group and a custom policy with the CLI.

Principals: who can make requests

A principal is an identity that can call AWS APIs:

  • Root user: the account owner. Covered in Create an AWS Account Safely; avoid it.
  • IAM user: a person or program with long-term credentials (a console password and, optionally, access keys).
  • IAM group: a collection of users. Groups cannot sign in; they exist to hand out permissions.
  • IAM role: an identity that is assumed for a short time and gives temporary credentials. Roles are the modern default for applications and for people in organisations; they get their own lesson.

IAM is global: a user created once works in every Region.

Policies: JSON documents of permissions

Permissions are written as policies in JSON. Here is a policy that lets someone list one bucket and read and upload files in it:

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListNotesBucket",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::priya-notes-2026"
    },
    {
      "Sid": "ReadWriteNotes",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::priya-notes-2026/*"
    }
  ]
}

The parts of a policy:

  • Version: always "2012-10-17", the current policy language version. It is not today's date.
  • Statement: a list of rules. Each has an optional Sid (a label), an Effect (Allow or Deny), one or more Actions (in the form service:Operation) and the Resource ARNs they apply to.
  • Condition (optional): extra tests, such as the Region, the source IP address, or whether MFA was used.

Notice that the two statements use different resources. Listing is an action on the bucket (arn:aws:s3:::priya-notes-2026), while reading and writing are actions on objects (arn:aws:s3:::priya-notes-2026/*). Mixing these up is one of the most common IAM errors.

Kinds of policies

  • AWS managed policies are written and updated by AWS, such as AdministratorAccess or ReadOnlyAccess. Easy to use, but often broader than you need.
  • Customer managed policies are written by you and can be attached to many users, groups and roles. Use these for least privilege.
  • Inline policies are embedded in one identity. Use them rarely, for a strict one-to-one link.
  • Resource-based policies are attached to a resource, such as an S3 bucket policy, and name a Principal who may access it.

Organisations add further guardrails: permissions boundaries cap what an identity's policies can grant, and service control policies (SCPs) and resource control policies (RCPs) in AWS Organizations set maximum permissions for whole accounts.

How AWS evaluates a request

AWS combines all policies that apply and follows three rules:

  1. By default, everything is implicitly denied.
  2. An Allow in an applicable policy permits the action.
  3. An explicit Deny always wins, even over an Allow.

That third rule makes deny statements powerful guardrails. This policy stops a group from launching EC2 resources anywhere except the two Indian Regions, no matter what other policies allow:

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "KeepEc2InIndia",
      "Effect": "Deny",
      "Action": "ec2:*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": ["ap-south-1", "ap-south-2"]
        }
      }
    }
  ]
}

Create a group, a user and a policy with the CLI

AWS recommends giving permissions to groups, not to individual users, so a new team member gets the right access by joining a group. Create an Admins group with the AWS managed AdministratorAccess policy:

Terminal
aws iam create-group --group-name Admins
aws iam attach-group-policy --group-name Admins \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
Output
{
    "Group": {
        "Path": "/",
        "GroupName": "Admins",
        "GroupId": "AGPASAMPLEGROUPID001",
        "Arn": "arn:aws:iam::111122223333:group/Admins",
        "CreateDate": "2026-10-30T05:12:44+00:00"
    }
}

Then create the user and add it to the group. attach-group-policy and add-user-to-group print nothing when they succeed.

Terminal
aws iam create-user --user-name priya-admin
aws iam add-user-to-group --user-name priya-admin --group-name Admins
Output
{
    "User": {
        "Path": "/",
        "UserName": "priya-admin",
        "UserId": "AIDASAMPLEUSERID0001",
        "Arn": "arn:aws:iam::111122223333:user/priya-admin",
        "CreateDate": "2026-10-30T05:13:02+00:00"
    }
}

Set the console password and register an MFA passkey for this user in the IAM console, so the password never appears in your terminal history. IDs that start with AIDA belong to users and AGPA to groups; you will meet AROA for roles in the next lesson.

Now save the bucket policy from the start of this lesson as notes-policy.json and create it as a customer managed policy:

Terminal
aws iam create-policy --policy-name NotesBucketAccess \
  --policy-document file://notes-policy.json \
  --query "Policy.Arn"
Output
"arn:aws:iam::111122223333:policy/NotesBucketAccess"

Test a policy before you use it

The IAM policy simulator checks what a policy allows without touching real resources. Here it tests a read and a delete:

Terminal
aws iam simulate-custom-policy \
  --policy-input-list file://notes-policy.json \
  --action-names s3:GetObject s3:DeleteObject \
  --resource-arns arn:aws:s3:::priya-notes-2026/semester-3.pdf \
  --query "EvaluationResults[].[EvalActionName, EvalDecision]" \
  --output text
Output
s3:GetObject	allowed
s3:DeleteObject	implicitDeny

implicitDeny means no statement allowed the action, so the default deny applied. An explicitDeny would mean a Deny statement matched.

Common mistakes

  • Attaching AdministratorAccess to everyone "to make it work". Start narrow and add only what is needed.
  • Using the bucket ARN for object actions (or /* for s3:ListBucket). The action fails with Access Denied.
  • Attaching policies directly to many users. Use groups so access stays consistent.
  • Writing "Version": "2026-10-30". The version must be 2012-10-17.
  • Forgetting that an explicit deny wins. If something is denied unexpectedly, search all applicable policies for a Deny.

Key points

  • IAM authenticates principals (root, users, roles) and authorises each API request with policies.
  • Groups hold users and are the right place to attach permissions; IAM is global, not per Region.
  • A policy statement has an Effect, Action, Resource and optional Condition; Version is always 2012-10-17.
  • Requests are denied by default, allowed by an Allow, and an explicit Deny always wins.
  • Prefer customer managed policies for least privilege and test them with the policy simulator.
  • Avoid long-term access keys; use aws login, Identity Center or roles.

Exercise

Write a customer managed policy called MatchPhotosReadOnly that lets the cricket club's volunteers list the bucket rohtak-cricket-photos and download (but not upload or delete) any object in it. Then write the simulator command to prove that s3:PutObject is denied.

Show solution
JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::rohtak-cricket-photos"
    },
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::rohtak-cricket-photos/*"
    }
  ]
}
Terminal
aws iam simulate-custom-policy \
  --policy-input-list file://match-photos.json \
  --action-names s3:GetObject s3:PutObject \
  --resource-arns arn:aws:s3:::rohtak-cricket-photos/final-2026.jpg \
  --query "EvaluationResults[].[EvalActionName, EvalDecision]" --output text

The simulator should report allowed for s3:GetObject and implicitDeny for s3:PutObject, because no statement allows uploads.

Quick Quiz

1. A user has a policy that allows s3:* and another that denies s3:DeleteObject. Can the user delete objects?

2. Where should you usually attach permissions for a team of developers?

3. What does "Version": "2012-10-17" mean in a policy?

4. The simulator returns implicitDeny. What does it mean?

Learn AWS with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call