Amazon S3 is changing the handling of the root identity in bucket policy on October 20, 2023. AWS is known for security and backwards compatibility, so what’s the deal?

AWS announced this change via email in February with follow-up to affected accounts in September.

Here’s the core explanation from the follow-up email we received (emphasis added):

Amazon S3 is updating its bucket policy evaluation to make it consistent with how similar policies are evaluated across AWS. If you have a statement in one of your bucket policies whose principal refers to the account that owns the bucket, this statement will now apply to all IAM Principals in that account instead of applying to only the account’s root identity. This update will affect buckets in at least one of your AWS accounts. We will being deploying this update on October 20, 2023.

[… SNIP …]

The authorization update applies to S3 bucket permission statements that follow this pattern:

{

“Effect”: “Deny”,

“Principal”: {

“AWS”: $BUCKET_OWNER_ACCOUNT_ID

},

“Action”:

“Resource”:

“Condition”:

}

Full ‘S3 changes to bucket authorization are coming in October 2023’ email (pdf)

We found this explanation ambiguous.

Does this apply to all statements? Or just statements that Deny? Clearly it applies to Principal but what about NotPrincipal?

These are important questions because this change expands the scope of the statement.

Recall that an AWS account’s root user identity is the identity that controls the account and is linked to the email address the account was created with. The account root user is not an IAM user or role but you can refer to it in IAM policies.

With this change, S3 will expand the root identity to mean all IAM principals in the account. So the scope of the statements will expand accordingly.

The key question is which bucket policy statements will S3 expand root to all IAM principals in the account?

We care deeply about who has access to what, so we asked in the AWS Community Builders and Cloud Security Forum communities. First Richard Fan, AWS Community Builder, shared a helpful pointer to a Terragrunt issue indicating the change only applies to Deny statements.

We then had a meeting with a number of senior S3 engineers, who are actively working on this change, along with senior members of the AWS Security teams and they confirmed that:

S3 only expands the bucket owner root identity in a bucket policy when the statement’s Effect is Deny and the root identity is specified in a Principal element.

Here’s a summary of where the bucket owner’s root identity is and is not expanded:

EffectPrincipal Element TypeRoot Expanded?
AllowPrincipal, NotPrincipalno
DenyPrincipalyes (the change)
DenyNotPrincipalno
Table 1: When is root expanded in S3 bucket policy statements

Thank you to AWS & S3 security for helping us understand this change in detail.

Before diving into the details, let’s consider some principles that will help you think about this behavior change.

AWS will make security strictly, stricter

AWS Security

After the change, the way S3 handles the root identity in a bucket policy depends on whether root appears in the Principal or NotPrincipal element and whether the Effect is Allow or Deny. If you stare closely at the logic it might be confusing.

But it is much simpler to think about if you keep in mind that:

  1. Changing policy evaluation to expand effective access would be an unthinkable breakage of backwards-compatibility to AWS.
  2. AWS will happily trade semantic symmetry for real-world security.

Now for the details and effects of this change.

What is not changing: root in Allow statement

When you grant Allow permission to the root identity of the bucket owner’s account, it doesn’t really do anything. This is because the root identity has implicit full access to the bucket as a result of its ownership. So allowing root in the bucket policy is a redundant no-op.

This change S3 will not expand the root identity to all IAM principals in the account because that would grant more access than what’s already allowed. That’s a gross violation of “strictly, stricter” security.

What is changing: root in Principal of Deny statement

Bucket policy statements with a Deny effect and Principal element are changing. When a Deny statement references the bucket owner’s root identity in the Principal element, S3 will expand that to mean all IAM users and roles in the bucket owner’s account.

Let’s work through a couple of examples.

In the following examples, assume the 111122223333 and 444455556666 accounts both have Alice and Bob IAM principals with policies attached to their identities that grant them full access to all S3 resources.

Example: Deny root identity in Principal element of bucket policy

Suppose you’d like to prevent anyone from writing to a bucket, EXAMPLE-BUCKET, which is owned by account 111122223333. To accomplish that, you might write (or have written) a statement like:

{
  "Effect": "Deny",
  "Principal": {
    // The $BUCKET_OWNER_ACCOUNT_ID
    // alternatively: arn:aws:iam::111122223333:root or canonical id a1b2c3d4...
    "AWS": "111122223333"
  },
  "Action": ["s3:PutObject", "s3:DeleteObject"],
  "Resource": ["arn:aws:s3:::EXAMPLE-BUCKET/*"]
}

The effective access allowed by this policy before and after the change for s3:PutObject and s3:DeleteObject is:

Graphic showing which principals have access to a bucket where root is denied access in the Principal element.
Figure 1. How principals’ access is affected by Deny root in Principal element of bucket policy before & after change

Before the change, this statement only denies the root identity from performing the put and delete actions. This is not what our example intended. If the 111122223333 account contains IAM users or roles with IAM policies that grant s3:PutObject to EXAMPLE-BUCKET (such as Alice and Bob), then the principal could write objects to the bucket. This matching of logical intent is what AWS is trying to correct for customers.

After the change, S3 will expand the root identity to all IAM users, roles, and STS identities in the account. Then, IAM users and roles within the 111122223333 account will be denied permission to write objects into the bucket. As AWS stated in their email, this change makes S3 consistent with other AWS services in this regard. They will be denied regardless of whether there is a policy attached to their identity or another statement in the bucket policy that allows access to the bucket because Deny takes precedence over Allow.

This change may close off access that IAM principals in the account relied on before the change. If you received a notification that you have a bucket affected by this change or observe unexpected AccessDenied errors, you should verify the bucket policy allows the access that IAM principals need.

(Missed the emails? Check for S3 Security notifications in the event log of your Account Health dashboard.)

What is not changing: root in NotPrincipal of Deny statement

S3 is not changing how it handles a Deny statement containing a NotPrincipal element referencing the root identity of the bucket owner. Yes, the way S3 handles the root identity in a Principal element is different from NotPrincipal in a Deny statement.

We won’t explore the hypothetical details here, but the effect of expanding root in the Deny-NotPrincipal case would reduce the scope of that Deny effect and expand effective access. So it violates the “strictly, stricter” security rule.

But what are the effects of denying root in NotPrincipal anyway?

Example: Deny root identity in NotPrincipal element of bucket policy

Let’s switch to the 444455556666 account and consider the case of a bucket policy containing a statement with a Deny effect and a NotPrincipal that references the bucket owner account identity.

Note: AWS recommends against using Deny with NotPrincipal (we agree). It’s difficult to use correctly. Instead, consider the Deny-Principal-Condition statement form recommended below.

Consider AWS’ example for denying access with NotPrincipal in a resource policy:

{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Deny",
        "NotPrincipal": {"AWS": [
            "arn:aws:iam::444455556666:user/Bob",
            "arn:aws:iam::444455556666:root"
        ]},
        "Action": "s3:*",
        "Resource": [
            "arn:aws:s3:::EXAMPLE-BUCKET",
            "arn:aws:s3:::EXAMPLE-BUCKET/*"
        ]
    }]
}

The effective access allowed by this policy both before and after the change is:

Figure 2. Effective principal access for Deny root in NotPrincipal element of bucket policy before & after change

Before and after the change, this statement denies all access to the bucket except for the root identity and the Bob user. This covers:

  • other IAM principals within the 444455556666 account, e.g. an Alice user
  • other AWS accounts and external IAM principals, e.g. account 111122223333 or a role arn:aws:iam::111122223333:user/alice
    • Note: The example bucket policy does not grant cross-account access with an Allow statement so no access from 111122223333 is possible before or after the change.

If you have policies like this, consider this an opportunity to revisit those statements and convert them to the DenyPrincipalCondition form like:

{
  "Sid": "DenyEveryoneElse",
  "Effect": "Deny",
  "Action": "s3:*",
  "Resource": [
    "arn:aws:s3:::EXAMPLE-BUCKET/*",
    "arn:aws:s3:::EXAMPLE-BUCKET"
  ],
  "Principal": {
    "AWS": "*"
  },
  "Condition": {
    "ArnNotEquals": {
      "aws:PrincipalArn": [
        "arn:aws:iam::444455556666:user/Bob"
      ]
    }
  }
}

This form is usually easier to reason about and also enables you to match IAM principals with wildcards using the ArnNotLike condition operator.

Our free S3 bucket policy generators for CDK and Terraform / OpenTofu can do this for you.

Summary

Starting on October 20, 2023, when an S3 bucket policy statement with, and only with, a Deny effect references the root identity of the bucket owner in a Principal element, then S3 will expand the root identity to mean all of the IAM principals in the bucket owner’s account. Results:

  1. For a Deny statement with root in the Principal element, the scope of the Deny increases to all IAM principals in the account. IAM principals in the account may lose access that they currently rely on.
  2. All other statements are unaffected by this change.

And don’t worry — if figuring out who has access to AWS APIs and data makes your head hurt, then consider letting our IAM access analyzer do that for you.