UprootSecurityUprootSecurity

Phase 2 · AWS IAM · Lesson 1 of 4

AWS IAM: Users, Roles, Policies, and Boundaries

Article

·

25 min

·

+12 pts

AWS Identity and Access Management is where cloud security gets concrete. Every permission, every access decision, every audit finding in an AWS environment traces back to IAM. If the previous lessons in this phase gave you the conceptual framework — authentication vs authorization, principal vs resource, least privilege as a principle — this lesson gives you the actual building blocks you will encounter in every AWS account you audit, configure, or assess.

This is the longest lesson in the phase for a reason. AWS IAM has more moving parts than most identity systems, and the interactions between those parts are where security gaps hide. Understanding each component individually is necessary. Understanding how they interact is what makes you effective.

'Action: *, Resource: *' found in code review

IAM Users

An IAM user is a persistent identity within an AWS account. It has a name, an ARN (Amazon Resource Name), and can have two types of credentials: a console password for browser-based access and access keys for programmatic (CLI/API) access.

IAM users were the original way to grant access to AWS. In the early days of AWS, every person and every application got an IAM user. That model has significant problems:

  • Access keys are long-lived credentials. Once created, an access key remains valid until it is manually rotated or deleted. If a key is leaked in a git commit, an S3 upload, or a Slack message, the attacker has persistent access until someone discovers and revokes the key.
  • No automatic expiration. Unlike temporary credentials (which expire after minutes or hours), IAM user credentials persist indefinitely unless you build rotation processes around them.
  • Attribution challenges at scale. In large organizations, tracking which IAM users are active, which have stale credentials, and which have excessive permissions becomes an ongoing operational burden.

Modern AWS security practice avoids IAM users for day-to-day access. Instead, humans authenticate through an identity provider (Okta, Entra ID, AWS IAM Identity Center) and assume IAM roles with temporary credentials. IAM users still have two legitimate use cases:

  • Break-glass accounts. A small number of IAM users with MFA, no access keys, and administrator access serve as emergency accounts when the identity provider is down. These accounts should be locked in a safe, monitored by CloudTrail, and tested quarterly.
  • Legacy integrations. Some older AWS services and third-party tools require access keys. These should be documented, scoped to minimum permissions, and on a rotation schedule.

For GRC purposes, IAM users are a findings magnet. Auditors look for them specifically.

IAM Groups

An IAM group is a collection of IAM users. Groups have no credentials of their own — they exist solely to organize policy attachments. When you attach a policy to a group, every user in that group inherits those permissions.

The rule is simple: attach policies to groups, never to individual users. This keeps permissions auditable. When an auditor asks "who has S3 access?", you can point to the s3-readers group and its membership list. If policies are scattered across individual users, answering that question requires inspecting every user — a process that does not scale and inevitably misses something.

Groups typically mirror organizational structure:

  • security-team — CloudTrail, GuardDuty, Security Hub, IAM read-only
  • developers — CodeCommit, CodeBuild, specific S3 buckets, specific DynamoDB tables
  • billing-admins — Cost Explorer, Budgets, billing console

One important limitation: IAM groups cannot be referenced as principals in IAM policies. You cannot write a resource-based policy that grants access to a group — only to users, roles, or accounts. This is one of several reasons roles are preferred over groups for cross-account and service-to-service access patterns.

IAM Roles

IAM roles are the central mechanism for granting access in modern AWS environments. Unlike users, roles do not have permanent credentials. Instead, a principal assumes a role and receives temporary security credentials from the AWS Security Token Service (STS). These credentials expire automatically — typically after one hour, though the duration is configurable.

The assume-role flow works like this: a principal (a user, an application, another AWS service, or even a principal in a different AWS account) calls sts:AssumeRole, presenting the role's ARN. AWS checks the role's trust policy to verify whether the requesting principal is allowed to assume the role. If allowed, STS returns temporary credentials (an access key ID, a secret access key, and a session token) that carry the role's permissions until they expire.

Two patterns dominate real-world role usage:

Cross-account roles

When the security team in Account A needs to audit resources in Account B, they assume a role in Account B. The role in Account B has a trust policy that specifies which principals in Account A are allowed to assume it, and what conditions must be met (such as MFA). This is the standard pattern for cross-account access — no long-lived credentials are shared between accounts.

Here is a trust policy that allows a specific role in Account A to assume a role in Account B, but only if MFA is present:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:role/SecurityTeamRole"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}

Service roles

AWS services themselves assume roles to perform actions on your behalf. When a Lambda function executes, it assumes its execution role to get credentials for accessing other AWS resources. When an EC2 instance needs to read from S3, it uses an instance profile (a container for an IAM role) that gives it temporary credentials automatically via the instance metadata service.

Service roles are where least privilege matters most. A Lambda function that processes images should only have access to the specific S3 bucket containing those images and the specific DynamoDB table where it writes metadata — not AdministratorAccess. In practice, overprivileged service roles are one of the most common findings in AWS security assessments.

IAM Policies

IAM policies are JSON documents that define permissions. Every access decision in AWS ultimately resolves to one or more policies being evaluated. A policy contains one or more statements, and each statement has a consistent structure:

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

The fields:

  • Version — Always "2012-10-17". This is not a date you choose; it is the policy language version.
  • Statement — An array of permission blocks.
  • Sid (optional) — A human-readable identifier for the statement. Use it. Future-you will be grateful.
  • Effect"Allow" or "Deny". There is nothing else.
  • Action — The API actions this statement applies to. Format is service:ActionName. Wildcards are supported (s3:*, s3:Get*).
  • Resource — The ARN(s) this statement applies to. "*" means all resources — avoid it when possible.
  • Condition (optional) — Additional constraints: source IP, MFA requirement, time of day, encryption context, and dozens more.

Policies come in three flavors:

  • AWS managed policies — Created and maintained by AWS. Examples: AdministratorAccess, ReadOnlyAccess, AmazonS3ReadOnlyAccess. Convenient but often too broad for production use.
  • Customer managed policies — Created by your organization. These should be your primary policies in production because you control exactly what they allow.
  • Inline policies — Embedded directly on a user, group, or role. Harder to manage, harder to audit, harder to reuse. Avoid them unless you have a specific reason (such as a policy that should never accidentally be attached to the wrong principal).

Policies are also categorized by where they attach:

  • Identity-based policies attach to users, groups, or roles. They define what the principal is allowed (or denied) to do.
  • Resource-based policies attach to resources (S3 bucket policies, SQS queue policies, KMS key policies). They define who can access the resource, and they can grant cross-account access without the caller needing an identity-based policy.

Quick check

A developer's IAM role has an identity-based policy that allows s3:PutObject on all S3 buckets. However, the S3 bucket 'finance-data' has a resource-based bucket policy that explicitly denies s3:PutObject from that role. Can the developer write to the finance-data bucket?

Service Control Policies (SCPs)

Service Control Policies operate at the AWS Organizations level. They are not IAM policies in the traditional sense — they are guardrails that restrict what actions can be taken within an account or organizational unit (OU), regardless of what IAM policies exist in those accounts.

The critical distinction: SCPs restrict but never grant. An SCP that allows s3:* does not give anyone S3 access. It means S3 actions are permitted if the principal also has an identity-based policy allowing them. An SCP that denies cloudtrail:StopLogging means no one in the affected accounts can stop CloudTrail — not even the account root user.

Here is an SCP that prevents anyone from deleting or disabling CloudTrail:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ProtectCloudTrail",
      "Effect": "Deny",
      "Action": [
        "cloudtrail:StopLogging",
        "cloudtrail:DeleteTrail",
        "cloudtrail:PutEventSelectors"
      ],
      "Resource": "*"
    }
  ]
}

SCPs are attached to OUs or accounts. They cascade downward: an SCP on the root OU affects every account in the organization. An SCP on a specific OU affects only accounts in that OU. They do not affect the management account (the root account of the organization), which is why the management account should have almost no workloads running in it.

For GRC engineers, SCPs are the single most powerful compliance mechanism in AWS. They create organization-wide invariants: CloudTrail cannot be disabled, certain regions cannot be used, certain services cannot be accessed. When an auditor asks "how do you ensure logging cannot be tampered with?", the SCP is your evidence.

Permission Boundaries

Permission boundaries define the maximum permissions an identity-based policy can grant to a principal. They do not grant permissions on their own — they set a ceiling.

The use case is delegation. Imagine you want team leads to be able to create IAM roles for their teams, but you need to ensure they cannot create roles with more permissions than you intend. You create a permission boundary that allows only specific services, and you require team leads to attach that boundary to every role they create. Even if a team lead attaches AdministratorAccess to a role, the permission boundary limits the role's effective permissions to the intersection of the boundary and the identity-based policy.

Permission boundaries are an advanced concept that most organizations do not implement. But when they are implemented well, they solve a hard problem: enabling decentralized IAM management without risk of privilege escalation.

┌─────────────────────────────────────────────────────────────┐
│                    AWS Organization                         │
│                                                             │
│   ┌──────────────────────────────────────────────────────┐  │
│   │          Service Control Policies (SCPs)             │  │
│   │          Restrict actions across all accounts        │  │
│   │          in the OU. Never grant — only limit.        │  │
│   └──────────────────────┬───────────────────────────────┘  │
│                          │                                  │
│            ┌─────────────┴────────────┐                     │
│            ▼                          ▼                     │
│   ┌────────────────┐       ┌────────────────────┐          │
│   │  OU: Security  │       │  OU: Production    │          │
│   └───────┬────────┘       └────────┬───────────┘          │
│           │                         │                       │
│           ▼                         ▼                       │
│   ┌────────────────┐       ┌────────────────────┐          │
│   │  Account:      │       │  Account:          │          │
│   │  Security-Prod │       │  App-Prod          │          │
│   │                │       │                    │          │
│   │  ┌───────────┐ │       │  ┌──────────────┐  │          │
│   │  │ IAM Roles │ │       │  │ IAM Roles    │  │          │
│   │  │  + Policies│ │       │  │  + Policies  │  │          │
│   │  │  + Perm    │ │       │  │  + Perm      │  │          │
│   │  │  Boundaries│ │       │  │  Boundaries  │  │          │
│   │  └───────────┘ │       │  └──────────────┘  │          │
│   └────────────────┘       └────────────────────┘          │
└─────────────────────────────────────────────────────────────┘

Effective permissions = Identity-based policy
                        ∩ Permission boundary (if set)
                        ∩ SCP (if in an Organization)
                        ∩ Session policy (if applicable)

Explicit deny in ANY layer → DENIED

AWS IAM hierarchy: Organization-level controls constrain account-level identities and policies

What GRC engineers verify

Understanding the building blocks is necessary. Knowing what to check when you audit an AWS environment is what makes you effective. Here is the verification checklist.

GRC Engineer's focus

When assessing an AWS environment's IAM posture, verify these controls:

  • No root account usage. The root user of each account should have MFA enabled, no access keys, and zero usage in CloudTrail (except for the small number of tasks that require root). Root usage is a critical finding in every framework.
  • MFA on all human identities. Every IAM user with console access must have MFA. Every federated login through your IdP must require MFA. No exceptions.
  • Access key hygiene. Check the IAM Credential Report for access keys older than 90 days, access keys that have never been used, and users with two active access keys (a common sign of failed rotation).
  • Least privilege enforcement. Use IAM Access Analyzer and Access Advisor to identify permissions that are granted but never used. Overprivileged roles are the most common IAM finding.
  • No inline policies. All permissions should be managed through managed policies (AWS or customer) attached to groups or roles. Inline policies are harder to audit and easier to miss.
  • Cross-account role inventory. Document every role that can be assumed from outside the account. Verify the trust policies require MFA and restrict to specific principal ARNs, not entire accounts.
  • SCPs protecting critical services. CloudTrail, Config, GuardDuty, and Security Hub should be protected by SCPs that prevent disabling or deletion.
  • Unused IAM entities. Users, roles, and policies that have not been used in 90+ days should be flagged for removal.

Audit evidence sources

When an auditor asks you to demonstrate IAM compliance, you need concrete evidence, not architecture diagrams alone.

Audit evidence

Key evidence artifacts for AWS IAM compliance:

  • IAM Credential Report — A CSV downloadable from the IAM console showing every IAM user, their password status, MFA status, access key age, and last activity dates. This is your primary evidence for credential hygiene controls.
  • IAM Access Advisor — Shows the last time each service was accessed by a user or role. If a role has S3 full access but has never called an S3 API, Access Advisor reveals the gap between granted and used permissions.
  • AWS Config rules — Enable managed rules for IAM compliance: iam-root-access-key-check, iam-user-mfa-enabled, iam-user-unused-credentials-check, iam-policy-no-statements-with-admin-access. These run continuously and produce compliance findings.
  • IAM Access Analyzer — Identifies resources shared with external principals (cross-account access, public access). Generates findings when a resource policy grants access outside the zone of trust.
  • CloudTrail logs — Every IAM API call is logged: CreateUser, AttachRolePolicy, AssumeRole, CreateAccessKey. These logs provide the audit trail for who changed what and when.
  • SCPs and OU structure — Export the Organizations tree and all SCP attachments to demonstrate preventive controls at the organization level.

Quick check

You discover an IAM user with two active access keys, one of which was last used 14 months ago. The user has the AdministratorAccess policy attached directly (inline). What are the findings?

What to carry forward

AWS IAM is the most detailed identity system you will encounter in GRC work because it has the most layers. Users provide persistent credentials but should be rare. Groups organize policies but cannot be used as principals in resource-based policies. Roles provide temporary credentials and are the preferred mechanism for everything. Policies define permissions as JSON documents with Effect, Action, Resource, and Condition. SCPs create organization-wide guardrails that cannot be overridden. Permission boundaries cap what identity-based policies can grant.

The next lesson dives into exactly how AWS evaluates a request when all of these layers apply simultaneously — the policy evaluation logic that determines whether any given API call is allowed or denied.

AWS IAM: Users, Roles, Policies, and Boundaries — UprootSecurity Bootcamp