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:
Effect | Principal Element Type | Root Expanded? |
Allow | Principal , NotPrincipal | no |
Deny | Principal | yes (the change) |
Deny | NotPrincipal | no |
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.
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:
- Changing policy evaluation to expand effective access would be an unthinkable breakage of backwards-compatibility to AWS.
- 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:
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
(such as EXAMPLE-BUCKET
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:
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. anAlice
user - other AWS accounts and external IAM principals, e.g. account
111122223333
or a rolearn:aws:iam::111122223333:user/alice
- Note: The example bucket policy does not grant cross-account access with an
Allow
statement so no access from111122223333
is possible before or after the change.
- Note: The example bucket policy does not grant cross-account access with an
If you have policies like this, consider this an opportunity to revisit those statements and convert them to the Deny
–Principal
–Condition
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:
- For a
Deny
statement with root in thePrincipal
element, the scope of theDeny
increases to all IAM principals in the account. IAM principals in the account may lose access that they currently rely on. - 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.
Recent Comments