Skip to content
C Codeloom
AWS

AWS IAM Basics: Users, Roles, and Policies

Learn AWS IAM fundamentals: root vs IAM users, JSON policies, roles for services, least privilege, and common pitfalls that lead to security incidents.

·6 min read · By Yash Kesharwani
Intermediate 10 min read

What you'll learn

  • The difference between the root account and IAM users
  • How to read and write IAM policy JSON documents
  • How IAM roles let services act on your behalf safely
  • How to apply the principle of least privilege in practice
  • Common IAM pitfalls and how to avoid them

Prerequisites

  • A working AWS account — see [What is AWS](/blog/what-is-aws)
  • Basic familiarity with AWS services like [EC2](/blog/aws-ec2-basics) and [S3](/blog/aws-s3-basics)

AWS Identity and Access Management (IAM) is the gatekeeper for everything in your AWS account. Every API call, every console click, and every service-to-service interaction is authorized through IAM. Getting IAM right is the difference between a secure account and a breach waiting to happen.

This guide walks through the building blocks of IAM — users, groups, roles, and policies — and shows how they fit together.

Root user vs IAM users

When you create an AWS account, you get a root user. The root user has unrestricted access to every service, every region, and every billing setting. It is also the only identity that can perform certain account-level actions like closing the account or changing the support plan.

Because of this power, you should almost never use the root account day to day. Best practice is:

  • Enable multi-factor authentication (MFA) on the root user immediately.
  • Create an IAM user with admin privileges for yourself.
  • Lock the root credentials away and only use them when absolutely required.

An IAM user is a long-term identity with its own credentials (a password for the console, access keys for the CLI/SDK). Users can be grouped into IAM groups so policies can be applied at the group level rather than to each user individually.

aws iam create-user --user-name alice
aws iam create-group --group-name Developers
aws iam add-user-to-group --user-name alice --group-name Developers

Policies: the JSON behind every permission

A policy is a JSON document that describes what actions are allowed or denied on which resources. Every permission decision in AWS reduces to evaluating these documents.

Here is a minimal policy that grants read-only access to a specific S3 bucket:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadCompanyBucket",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::company-data",
        "arn:aws:s3:::company-data/*"
      ]
    }
  ]
}

Key fields:

  • Version — always 2012-10-17. It is the policy language version, not a date you should change.
  • EffectAllow or Deny. Explicit Deny always wins over Allow.
  • Action — the API operations being controlled, written as service:Operation.
  • Resource — the ARNs the actions apply to. Use * only when you really mean every resource.
  • Condition (optional) — add constraints like source IP, MFA presence, or tag values.

Policies come in two main flavors. Managed policies are standalone, reusable, and can be attached to many identities. Inline policies are embedded directly into a single user, group, or role and disappear when that identity is deleted.

Roles: identities for services and federated users

A role is an identity with permissions, but it has no permanent credentials. Instead, an entity assumes the role and receives temporary credentials that expire (typically within an hour).

Roles are the right answer in three common scenarios:

  1. Services acting on your behalf. When an EC2 instance needs to read from S3, attach a role to the instance instead of baking access keys into the AMI.
  2. Cross-account access. Let a user in account A assume a role in account B without creating a second user.
  3. Federated identities. Map corporate SSO users or Lambda functions to AWS permissions.

A role has two policies attached:

  • A trust policy — who is allowed to assume this role.
  • One or more permission policies — what the role can do once assumed.

Trust policy for an EC2 role:

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

Least privilege in practice

“Least privilege” means giving each identity the smallest set of permissions it needs to do its job — nothing more. It sounds simple, but in practice it requires discipline.

A few habits that help:

  • Start with deny, add as needed. Begin with no permissions and add specific Allow statements as the workload reveals what it actually uses.
  • Scope resources tightly. Prefer arn:aws:s3:::orders/* over arn:aws:s3:::*.
  • Use IAM Access Analyzer. It can generate a tight policy from CloudTrail logs of what an identity actually called over the past 90 days.
  • Review with aws iam simulate-principal-policy. Test what a user can and cannot do before deploying.
  • Rotate access keys regularly and prefer short-lived credentials from roles.

Common pitfalls

A few traps that catch teams over and over:

  • Wildcards everywhere. "Action": "*" and "Resource": "*" on the same statement is essentially admin. Reserve this for break-glass roles only.
  • Storing access keys in code. Even private repos leak. Use roles for EC2, ECS, and Lambda; use AWS SSO or aws configure sso for humans.
  • Forgetting Deny precedence. A single Deny overrides every Allow. Useful for guardrails, but also easy to lock yourself out.
  • One mega-policy for everything. Split policies by job function (read-only, deploy, billing). Smaller policies are easier to audit.
  • Not enabling MFA on privileged users. A leaked password without MFA is game over.
  • Ignoring service control policies (SCPs). In AWS Organizations, SCPs sit above IAM and can deny actions account-wide. If a permission “should work” but does not, check the SCP first.

Putting it together

A clean IAM setup for a small team usually looks like this:

Root user (locked away, MFA enabled)
 |
 +-- IAM group: Admins      -> AdministratorAccess policy
 +-- IAM group: Developers  -> custom dev policy + read-only billing
 +-- IAM group: ReadOnly    -> ReadOnlyAccess managed policy
 |
 +-- IAM role: EC2AppRole   -> S3 read, DynamoDB read/write
 +-- IAM role: LambdaRole   -> CloudWatch logs, SQS send
 +-- IAM role: CIDeployRole -> assumed by GitHub Actions via OIDC

Notice the pattern: humans go into groups, services go into roles, and nothing carries long-lived secrets if it can be avoided.

Wrap up

IAM rewards thinking carefully up front. Spend time on group structures, write narrow policies, prefer roles over access keys, and audit regularly with Access Analyzer and CloudTrail. The result is an account that is both easier to operate and dramatically harder to compromise.

Once IAM is solid, the rest of AWS — EC2, S3, Lambda, and beyond — becomes much safer to build on.