Mastering AWS IAM Policies: A Simple, Practical Guide

Mastering AWS IAM Policies: A Simple, Practical Guide

December 3, 2025

In AWS, policies are the foundation of access control. They define who (or what) can perform which actions on specific resources, and under what conditions. This guide combines straightforward explanations with real-world examples to help you understand and apply them effectively.

Core Concepts in Plain English

A policy in AWS is a JSON document that defines permissions. It answers the question: “Who can do what to which resources under what conditions?” Policies are evaluated in a strict order: any explicit Deny wins immediately, followed by checking for an Allow; if there's no Allow, it's a default Deny.

  • Identity-based policies – attached to IAM users, groups, or roles (most common)
  • Resource-based policies – attached directly to resources (S3, KMS, etc.)
  • Permissions boundaries – hard ceiling on what a user/role can do
  • Service Control Policies (SCPs) – organization-wide restrictions (only deny)
  • Session policies – temporary restrictions when assuming a role
  • Trust policies – define who can assume an IAM role

Although AWS allows you to attach policies directly to users, groups, roles, and resources, that flexibility often creates confusion and long-term permission drift. In real environments, the cleanest, most maintainable approach is the User → Group → Role pattern. Human IAM users should not receive functional permissions directly. Instead, they belong to Groups, which grant them the authority to assume specific Roles. These Roles hold the actual identity-based permissions, providing temporary and least-privilege access for tasks such as development, operations, or production support.

This design keeps long-lived user credentials low-privilege, enforces MFA through role assumption, simplifies audits, and mirrors how modern AWS Identity Center (SSO) operates internally. While some AWS services still rely on resource-based policies (e.g., S3, KMS) for cross-account or service-to-service access, your identity-based access model remains cleanest when actual permissions live in well-structured, customer-managed Role policies. Just because AWS lets you attach a policy in many places doesn’t mean you should—centralizing permissions in Roles is the scalable, secure default.

1. Identity-Based Policies (Most Common)

AWS identity-based policies are JSON documents attached to IAM users, groups, or roles that explicitly define their permissions. These policies outline which actions an identity is allowed to perform, the resources those actions apply to, and any conditions that must be met. They govern what an identity can do inside your AWS environment.

  • AWS Managed Policies: Prebuilt policies created and maintained by AWS for common tasks (e.g., ReadOnlyAccess, AWSLambda_FullAccess). They’re convenient starting points but often too broad to meet strict least-privilege requirements.
  • Customer Managed Policies: Custom policies that you design and manage within your AWS account. They provide finer-grained control and can be reused across multiple users, groups, and roles — making them the preferred option for production environments.
  • Inline Policies: Policies attached directly to a single IAM user, group, or role in a one-to-one relationship. Because they’re tightly coupled, they’re deleted automatically when the identity is deleted. Inline policies are best reserved for exceptional, identity-specific permissions.
Example: Developer full access in dev region with MFA required
{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringEquals": {"aws:RequestedRegion": "us-east-1"},
        "Bool": {"aws:MultiFactorAuthPresent": "true"}
      }
    }]
  }
Example: Production support engineer (no billing or dangerous IAM)
{
    "Version": "2012-10-17",
    "Statement": [
      {"Effect": "Allow", "Action": ["ec2:*","rds:*","elasticloadbalancing:*","cloudwatch:*","logs:*","ssm:*"], "Resource": "*"},
      {"Effect": "Deny", "Action": ["aws-portal:*","iam:CreateUser","iam:DeleteUser"], "Resource": "*"}
    ]
  }

2. Resource-Based Policies

Attached to the resource itself (S3, KMS, SNS, etc.). Perfect for cross-account or public access.

Example: S3 bucket only readable via CloudFront
{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Service": "cloudfront.amazonaws.com"},
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-website/*",
      "Condition": {"StringEquals": {"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1234567890ABC"}}
    }]
  }
Example: Cross-account data lake access
{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::999999999999:role/analytics-role"},
      "Action": "s3:*",
      "Resource": ["arn:aws:s3:::company-data-lake","arn:aws:s3:::company-data-lake/*"]
    }]
  }

3. Permissions Boundaries

Guardrails — even AdministratorAccess cannot exceed them.

Example: No one can launch huge EC2 instances (cost control)
{
    "Version": "2012-10-17",
    "Statement": [
      {"Effect": "Allow","Action":"ec2:RunInstances","Resource":"arn:aws:ec2:*:*:instance/*",
      "Condition":{"StringEquals":{"ec2:InstanceType":["t3.nano","t3.micro","t3.small","t3.medium","t3.large"]}}},
      {"Effect": "Allow","Action": "ec2:*","Resource": "*"}
    ]
  }

4. Service Control Policies (SCPs)

Organization-wide restrictions (only Deny). Even root is bound.

Example: Prevent disabling CloudTrail anywhere
{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Deny",
      "Action": ["cloudtrail:DeleteTrail","cloudtrail:StopLogging","cloudtrail:UpdateTrail"],
      "Resource": "*"
    }]
  }

5. Session Policies

Temporary restrictions when assuming a role.

Example: Contractor sees only their own S3 folder
{
    "Version": "2012-10-17",
    "Statement": [
      {"Effect":"Allow","Action":"s3:ListBucket","Resource":"arn:aws:s3:::confidential-client-data"},
      {"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::confidential-client-data/2025/contractor123/*"}
    ]
  }

6. Trust Policies

An AWS trust policy is a JSON document attached to an IAM role that defines which principals—users, services, or entire AWS accounts—are allowed to assume that role. It establishes the trust relationship required for granting temporary, secure access without exposing long-term credentials. For example, a trust policy might let an EC2 instance assume a role or allow an external AWS account to access resources in your account.

Key Functions

  • Specifies who can assume the role: The Principal element identifies the entities permitted to take on the role.
  • Controls assumption patterns: It’s essential for use cases such as allowing AWS services to call other services or enabling cross-account delegation.
  • Defines trust: It forms the trust relationship between the role and the principals that may assume it.
  • JSON-based structure: The policy is written in JSON and is part of the role’s configuration.

How It Works

A trust policy is a form of resource-based policy attached to IAM roles. It works alongside identity-based policies, which define what actions the role can perform once assumed.

When AWS receives an AssumeRole request, it evaluates both:

  1. The trust policy on the target role (who is allowed to assume it)
  2. The identity-based policy of the caller (whether they are allowed to request the role)
Example: GitHub Actions OIDC (no secrets needed)
{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"},
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"},
        "StringLike": {"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:*"}
      }
    }]
  }

Key Concepts & Best Practices

ConceptReal-World UseExample Snippet
Least PrivilegeCI/CD only touches specific stacks"Resource": "arn:aws:cloudformation:*:*:stack/myapp-*/*"
Condition KeysOnly from company IPs"Condition": {"IpAddress": {"aws:SourceIp": "203.0.113.0/24"}}
Deny Overrides AllowBlock dangerous actions in SCPs"Effect": "Deny", "Action": "iam:CreateUser"
Principal *Public website via CloudFront only"Principal": "*" + CloudFront condition

Summary Table of Policy Types

Policy TypeAttached ToCan Grant Cross-Account?Typical Use Case
Identity-basedUser, Group, RoleYes (with resource policy)Normal permissions
Resource-basedS3, KMS, SNS, etc.YesCross-account, public
Permissions BoundaryUser or RoleN/A (restrict only)Guardrails
SCP (Organizations)Account or OUN/A (deny only)Enterprise governance
Session PolicyAssumed sessionRestricts onlyContractors, auditors
Trust PolicyIAM RoleYesWho can assume role

These are the exact policies used by real companies every day. Copy, adapt, and secure your environment.


  • By the Eclipsos AWS Architecture Team
  • Empowering Innovation Through Cloud

Need Help with Your Project?

Contact our experts for personalized assistance with your cloud and software development needs.