Foundations
Identity-based vs. resource-based policies
Identity policies attach to who is acting; resource-based policies attach to what is being accessed and name a Principal. Within one account either can grant access; across accounts, both sides must.
Published · Updated .
AWS access decisions combine several policy types. The two you will write most often are identity-based policies, attached to IAM users, groups and roles, and resource-based policies, attached to a resource such as an S3 bucket, a KMS key, an SQS queue or an IAM role (whose trust policy is a resource-based policy). Mixing them up is one of the most common reasons a policy “does nothing” or is rejected outright.
The structural difference: Principal
An identity policy already knows who it applies to: the identity it is attached to. So it has no Principal element; IAM rejects identity policies that include one. A resource-based policy is attached to the thing being accessed, so it must say who is allowed: every statement names a Principal (or NotPrincipal).
| Aspect | Identity-based | Resource-based |
|---|---|---|
| Attached to | User, group or role | Bucket, key, queue, topic, function, secret, role (trust policy)… |
| Principal element | Not allowed | Required |
| Resource element | Required | Usually required; a role trust policy has none |
| Managed policies | Managed or inline | Always inline on the resource |
| Can grant another account | No (only its own identities) | Yes, by naming the external principal |
How AWS combines them in one account
When a principal accesses a resource in its own account, AWS evaluates both kinds and the result is their union: an Allow in either is enough, as long as nothing explicitly denies the request. Two important exceptions, called out in the IAM documentation: role trust policies and KMS key policies must themselves explicitly allow the principal. An identity policy alone cannot let someone assume a role or use a key whose policy does not allow it.
There is a subtler interaction with permissions boundaries. In the same account, a resource-based policy that names an IAM user ARN, or an assumed-role session ARN, grants access even if the principal's boundary does not include the action (an implicit deny in the boundary does not limit it). A resource-based policy that names the role ARN is still limited by the role's boundary and session policies. An explicit deny anywhere always wins.
Across accounts, both sides must allow
For a request from account A to a resource in account B, account B's resource-based policy must allow the principal from A, and account A's identity policies must allow the principal to make the call. This is why cross-account access is a two-sided change, and why the cross-account S3 guide always shows two policies.
The same access, expressed both ways
Suppose the role ReportReader in account 111122223333 must read objects in a bucket in the same account. As an identity policy attached to the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadReports",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/reports/*"
}
]
}Or as a statement in the bucket policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReportReaderReadsReports",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/ReportReader"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/reports/*"
}
]
}Either is sufficient on its own here. Choose by ownership: if the team that owns the bucket should decide who reads it, use the bucket policy; if the team that owns the workload should manage its permissions in one place, use the identity policy. Many organisations combine them: identity policies grant, and bucket policies add guardrail denies (TLS only, organisation only).
Role trust policies are resource-based policies
A role has two sides. Its permissions policies (identity-based) say what the role can do; its trust policy (resource-based) says who can become the role. A trust policy names a Principal and uses STS actions such as sts:AssumeRole, and it has no Resource element, because the role itself is the resource.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LambdaServiceAssumesRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}Common mistakes
- Pasting a bucket policy into an IAM policy. IAM rejects the
Principalelement. Remove it, or attach the document to the bucket instead. - A bucket policy without a Principal. S3 rejects it. The previous version of this site's S3 generator had exactly this defect; it now requires a principal.
- Trusting an entire account when one role is meant.
arn:aws:iam::444455556666:rootas a principal delegates the decision to that account's administrators. - Granting cross-account access on one side only. Both the resource policy and the caller's identity policy must allow the request.
Verification
- Validate with IAM Access Analyzer using the right policy type:
IDENTITY_POLICYfor identity policies,RESOURCE_POLICYplus a resource type (for exampleAWS::S3::Bucket) for resource policies. - Turn on an IAM Access Analyzer external access analyzer: it reports resources whose resource-based policies grant access to principals outside your account or organisation.
- Test from the principal itself; CloudTrail records which identity made each call.