This is one of the very few (if not the only) usecase for an explicit Deny with a NotPrincipal:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"NotPrincipal": {
"AWS": "arn:aws:iam::<your-account-number>:root"
},
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::ih-deploy-bucket/*",
"arn:aws:s3:::ih-deploy-bucket"
]
}
]
}
This will explicitly deny all principals that are not (and not only) the root account user, including IAM users, assumed role sessions and federated users in that account.
And since the root user always has explicit Allows for all actions on all resources, an actual Allow is given by the root user's identity-based permissions, so the root user will have access to that bucket.
The reason why this works is that a caller identity working in your account has always multiple principals simultaneously, which are being evaluated by IAM for a policy statement:
- the account principal
arn:aws:iam::<your-account-number>:root
- the user, assumed role or federated user principal
In the case of an explicit Allow if you only used the root account principal in a Principal rule in a policy statement, then any user in that account will match the allow and will be given access, since the account principal is always part of a user's principal list in that account.
However, in the case of a Deny with a NotPrincipal, things are a bit different. Here, the list of NotPrincipals must whitelist all principals of the caller's identity to be not denied.
This fact somewhat shines through in the AWS documentation about NotPrincipal: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notprincipal.html
When you use NotPrincipal with Deny, you must also specify the account
ARN of the not-denied principal. Otherwise, the policy might deny
access to the entire account containing the principal. Depending on
the service that you include in your policy, AWS might validate the
account first and then the user. If an assumed-role user (someone who
is using a role) is being evaluated, AWS might validate the account
first, then the role, and then the assumed-role user.