A policy is a JSON document that allows or denies actions. AWS has several policy types, and they attach to different things: identities, resources, accounts and sessions. Some grant permissions. Others only set a ceiling. When a request arrives, AWS reads every policy that applies and combines them. When you hit an AccessDenied error, knowing which type does what is how you find the cause.
This note assumes you know users, groups and roles from IAM basics. The JSON syntax itself is in Writing IAM policies.
Grant vs guardrail#
Seven policy types sounds like a lot, but they split into two groups. The first thing to learn about each one is whether it can grant access at all.
| Policy type | Attaches to | Grants? | Typical use |
|---|---|---|---|
| Identity-based (managed or inline) | User, group, role | ✓ | Day-to-day permissions |
| Resource-based | A resource (S3 bucket, SQS queue, KMS key) | ✓ | Cross-account access to one resource |
| Trust policy | A role | ✓ (who may assume it) | Who can use a role |
| Service control policy (SCP) | Account or OU in AWS Organizations | ✗ | Org-wide guardrails |
| Permissions boundary | User or role | ✗ | Cap what delegated admins can grant |
| Session policy | One assumed-role or federated session | ✗ | Narrow one session further |
| Access control list (ACL) | Some resources, e.g. S3 objects | ✓ | Legacy cross-account grants |
Guardrails (✗) only remove permissions. A permission must still come from an identity-based or resource-based policy.
Identity-based policies#
The rest of the note takes each type in the table's order, starting with the one you'll write most. Identity-based policies attach to a user, group or role and say what that identity may do. They come in three forms.
- AWS managed policies are written and updated by AWS, such as
AmazonS3ReadOnlyAccess. They are quick to use but often broad. - Customer managed policies are standalone policies you write. You can attach one to many identities, and AWS keeps versions so you can roll back.
- Inline policies are embedded in one identity. They are deleted with it and can't be reused.
Prefer customer managed policies. Use inline only for a strict one-to-one link, where the permission must never be attached elsewhere by accident.
Resource-based policies#
Identity policies are written from the caller's side. Resource-based policies flip that: they attach to a resource and list who (the Principal) may do what to it. S3 bucket policies, SQS queue policies, SNS topic policies, KMS key policies and Lambda function policies are all resource-based.
Their main use is cross-account access. A bucket policy can name a role in another account directly. For that request to succeed, both sides must agree: the bucket policy must allow the other account's principal, and that principal's own identity policy must allow the action.
Trust policies#
A role is itself a resource, so it gets a resource-based policy too. That's the trust policy, which names who may call sts:AssumeRole (or a variant such as sts:AssumeRoleWithWebIdentity).
"Service": "ec2.amazonaws.com"lets EC2 instances use the role."AWS": "arn:aws:iam::123456789012:root"lets that account delegate the role to its own principals."Federated"principals let SAML or OIDC identity providers use it.
A role is only as safe as its trust policy. For roles that third parties assume, add a condition such as sts:ExternalId so another customer of that third party can't trick it into using your role.
Service control policies (SCPs)#
The remaining types are guardrails, and the widest one works across whole accounts. SCPs belong to AWS Organizations. You attach them to the root, an organizational unit (OU) or a single account. They set the maximum permissions for every principal in the affected member accounts.
- SCPs never grant. An action that the SCP allows still needs an identity or resource policy.
- An action that an SCP denies is denied for everyone in that account, whatever other policies say.
- SCPs do apply to the root user of member accounts. This is how an org stops a member root user from, for example, leaving the organization.
- SCPs don't apply to the management account, or to service-linked roles.
Common SCPs deny actions outside approved Regions, block disabling CloudTrail, or block deleting backups. AWS Organizations also has resource control policies (RCPs), which set the same kind of ceiling on resources instead of principals.
Permissions boundaries#
SCPs cap whole accounts. A permissions boundary does the same for a single identity: it is a managed policy that you set as the ceiling for one user or role. The identity's effective permissions are the overlap of its identity policies and its boundary.
Boundaries solve delegation. Say you let a team create roles for their own Lambda functions. Without a boundary, they could create a role with AdministratorAccess and escalate. With a policy that says "you may create roles only if you attach boundary X", every role they make stays inside X.
Session policies#
The narrowest guardrail lasts only as long as one session. A session policy is passed in when you assume a role or get federated credentials. It narrows that one session. The session gets the overlap of the role's policies and the session policy, never more.
Use them when one role serves many callers, and each caller should see only part of what the role allows. The role itself is unchanged.
Access control lists (ACLs)#
The last row of the table is a grant mechanism from before IAM policies. ACLs are an older, non-JSON way to grant access, mainly on S3 buckets and objects. They name a grantee (an account or a predefined group) and a permission such as READ, WRITE or FULL_CONTROL.
New S3 buckets have ACLs disabled by default (Object Ownership set to "bucket owner enforced"). Use bucket policies instead. VPC network ACLs share the name but are a firewall feature, not an IAM policy.
How AWS evaluates a request#
Now that each type is clear, here's how they combine on a real request. AWS gathers every policy that applies and follows a fixed order, and three rules sum it up:
- Default deny. If nothing allows the action, it is denied.
- Explicit deny wins. A
Denyin any applicable policy beats everyAllow. - Guardrails cap, never grant. SCPs, RCPs, boundaries and session policies must each allow the action. If any of them doesn't, the request is denied.
Worked examples#
The three rules are short, but they interact in ways that surprise people. These cases show them in action.
| Situation | Result | Why |
|---|---|---|
| A user is in group A (allows S3) and group B (denies S3) | Denied | Explicit deny wins |
The role allows ec2:*, the SCP allows only listed Regions |
Denied outside those Regions | The SCP caps the role |
The boundary allows s3:*, but no identity policy mentions S3 |
Denied | A boundary never grants |
| Same account: the bucket policy allows a role, but the role has no S3 policy | Allowed | In one account, either side's allow is enough |
| Cross-account: the bucket policy allows a role, but the role has no S3 policy | Denied | Across accounts, both sides must allow |
The same-account rule has exceptions. KMS key policies and role trust policies must allow the principal directly. When in doubt, test with the IAM policy simulator.
Key takeaways#
- Only identity-based policies, resource-based policies (including trust policies) and ACLs grant access.
- SCPs, RCPs, permissions boundaries and session policies only cap what other policies grant.
- An explicit deny in any applicable policy beats every allow. With no allow, the default is deny.
- SCPs restrict member accounts, including their root users, but not the management account.
- Cross-account access needs an allow on both sides: the resource policy and the caller's identity policy.
- Prefer customer managed policies; use inline policies only for strict one-to-one links.