AWS has a dozen policies. They're all one shape.
AWS keeps inventing policies. S3 bucket policy, SQS queue policy, SNS topic policy, Lambda function policy, KMS key policy, API Gateway resource policy, IAM role policy. It looks like a dozen things to learn.
It's one thing.
One shape
Every one of them is this JSON:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow", // or Deny
"Principal": { }, // who
"Action": ["s3:GetObject"], // what they may do
"Resource": "arn:...", // on which thing
"Condition": { } // when (optional)
}]
}Learn it once and you can read any AWS policy. The service prefix on the action (s3:, sqs:, kms:) is the only part that really changes.
The one split that matters
There are really just two kinds, and Principal ("who") is the tell.
- Identity-based policy. Attached to an IAM user, group, or role. It has no
Principal. The "who" is already settled: it's the identity you stapled the policy to. - Resource-based policy. Attached to a thing: a bucket, a queue, a key. It must name a
Principal, because the thing has no idea who's knocking until you say so.
That's the entire distinction. Put a policy on an identity, drop the Principal. Put it on a resource, name the Principal.
So the dozen names collapse into two piles:
| Identity-based (no Principal) | Resource-based (needs Principal) |
|---|---|
| IAM user / group / role policy | S3 bucket, SQS, SNS, Lambda, KMS key, Secrets Manager, API Gateway, VPC endpoint, DynamoDB, ... |
The resource list keeps growing. When unsure, ask: can I attach a policy directly to this thing? If yes, it's resource-based and wants a Principal.
Two that break the mold
- CloudFormation stack policy is a fake-out. Same-looking JSON, but it isn't about access at all. It guards which resources a stack update is allowed to touch. No Principal, because there's no "who."
- RCP (Resource Control Policy), added late 2024, is the new arrival. It sits at the AWS Organizations level and caps what every resource-based policy in your org is allowed to grant. Same family, bigger blast radius.
S3 is the cautionary tale
S3 is where this sprawl got out of hand. One bucket can be governed by a bucket policy, your IAM policies, and ACLs at the same time, with a Block Public Access switch over the top. Several knobs, one bucket.
The odd one out is the ACL. It predates IAM policies and doesn't use the JSON shape at all: no Effect, no Action, no Condition. Just a coarse "grant this account or group READ/WRITE," in its own format. The mechanism that never fit.
That overlap leaked real data: a bucket read private by its policy while an ACL quietly said otherwise. So AWS changed the default. New buckets now ship with ACLs disabled ("Bucket owner enforced"), and the guidance is to leave them off and use bucket policies alone. The cure for too many shapes was to delete one.
The takeaway
Next time AWS hands you a new "X policy," relearn nothing. Ask one question: does it have a Principal?
- No, it's identity-based. Attached to a who.
- Yes, it's resource-based. Attached to a what, and you're naming the who.
One shape, one question. The dozen names were never a dozen things.