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).

Identity-based versus resource-based policies
AspectIdentity-basedResource-based
Attached toUser, group or roleBucket, key, queue, topic, function, secret, role (trust policy)…
Principal elementNot allowedRequired
Resource elementRequiredUsually required; a role trust policy has none
Managed policiesManaged or inlineAlways inline on the resource
Can grant another accountNo (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:

Identity policy on ReportReader · IAM identity-based policyOpen in Inline Policy Generator
{
  "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:

Bucket policy statement · Amazon S3 bucket policyOpen in S3 Bucket Policy Generator
{
  "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.

Trust policy: only the Lambda service can assume the role · IAM role trust policy
{
  "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 Principal element. 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:root as 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

  1. Validate with IAM Access Analyzer using the right policy type: IDENTITY_POLICY for identity policies, RESOURCE_POLICY plus a resource type (for example AWS::S3::Bucket) for resource policies.
  2. 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.
  3. Test from the principal itself; CloudTrail records which identity made each call.

Official AWS documentation