As a cloud support engineer at AWS, I am frequently asked how to restrict Amazon S3 bucket access to a specific AWS Identity and Access Management (IAM) role. Typically, customers attempt to do this in the same way they would for an IAM user: by using a bucket policy with an explicit Deny statement for all Principals (users and roles) that should not have access. The downside of this approach is that it requires maintaining the bucket policy. If you add a new IAM user to your account with an action of s3:*, that user will gain access to the bucket. You can invert the logic and leverage the NotPrincipal element in the Deny statement of the bucket policy instead of specifying a list of users to block. This element creates an explicit Deny for any user not listed in its value.
However, it turns out that this inverted logic approach has issues when dealing with IAM roles because the Principal value for such roles contains two Amazon Resource Names (ARNs): the role ARN and the assumed-role ARN. The role ARN is the identifier for the IAM role itself, while the assumed-role ARN is used to identify the role session in logs. When using the NotPrincipal element, both ARNs must be included for this method to work correctly, with the second ARN containing a variable name. Usually, you should specify a wildcard to represent the variable string, but you cannot specify wildcards in the Principal or NotPrincipal elements. In this blog post, I will show you how to use Conditions (instead of the NotPrincipal element) to restrict S3 bucket access to a specific IAM role or user within an account. Even if another user in the same account has an Admin policy or a policy with s3:*, they will still be denied access unless explicitly listed. For example, you can use this method to configure buckets for access by instances within an Auto Scaling group. You can also use this method to restrict access to buckets with high security requirements.
Solution Overview
The solution described in this article uses a bucket policy to manage access to the S3 bucket—even if the entity has access to the full S3 API. The following diagram shows how this solution applies to a bucket within the same account.

- IAM user policies and role user policies grant access to
s3:*. - The S3 bucket policy restricts access to only that role.
- Both IAM users and roles can access the bucket in the account. The role can access both buckets, but the user can only access the bucket without an attached bucket policy. Even though both the role and user have full
s3:*permissions, the bucket policy denies access to anyone who does not have that role.
The main difference in the cross-account approach is that each bucket must have a bucket policy attached. The following diagram shows how this approach applies to a cross-account deployment scenario.

- User policies for the IAM role and IAM user policies in the bucket account both grant access to
s3:*. - If any person’s
user:iddiffers from the corresponding value of the role, and the policy defines what actions the role can perform on the bucket, the bucket policy denies their access. - The bucket policy allows access to the role from other accounts.
- IAM users and roles can access the bucket without a Deny in the bucket policy. The role can access both buckets simultaneously because the Deny only applies to principals whose
user:iddoes not equal the corresponding value of the role.
Understanding the NotPrincipal Element and Its Usage
You can use the NotPrincipal element in IAM or S3 bucket policies to allow access to resources only for specific groups of users. This element allows you to block all users not defined in its value array, even if they have an Allow in their own IAM user policies. Therefore, if you have a user who should have access to all buckets except one in S3, you can define this directly on that bucket without editing the user’s IAM policy stack.
However, this is more complex for IAM roles because roles are defined by two ARNs in the Principal: the role ARN and the assumed-role ARN. The role ARN (arn:aws:iam::ACCOUNTNUMBER:role/ROLE-NAME) is static and independent of the role session initiator. (In this article, remember to replace placeholder information with your own account details.) The assumed-role ARN (arn:aws:sts::ACCOUNTNUMBER:assumed-role/ROLE-NAME/ROLE-SESSION-NAME) varies depending on the role session name definition. For API calls made by a user assuming a role, you can learn about this by looking at the following Identity element in AWS CloudTrail entries.
{
"type": "AssumedRole",
"principalId": "AROAJI4AVVEXAMPLE:ROLE-SESSION-NAME",
"arn": "arn:aws:sts::ACCOUNTNUMBER:assumed-role/ROLE-NAME/ROLE-SESSION-NAME",
"accountId": "ACCOUNTNUMBER",
"accessKeyId": "ASIAEXAMPLEKEY",
"sessionContext": {
"attributes": {
"mfaAuthenticated": "false",
"creationDate": "XXXX-XX-XXTXX:XX:XXZ"
},
"sessionIssuer": {
"type": "Role",
"principalId": "AROAJI4AVV3EXAMPLEID",
"arn": "arn:aws:iam::ACCOUNTNUMBER:role/ROLE-NAME",
"accountId": "ACCOUNTNUBMER",
"userName": "ROLE-SESSION-NAME"
}
}
}
In this Identity element, you can see the role ARN and the assumed-role ARN. Depending on the user assuming the role, the ROLE-SESSION-NAME may also vary. The principalId value also contains this information, but formatted in a way that cannot be used outside the Principal element of a bucket policy. I use this information when writing bucket policies.
Granting Same-Account Bucket Access to a Specific Role
When accessing a bucket from the same account, you generally do not need to use a bucket policy. This is because the bucket policy defines access already granted by the user’s direct IAM policy. S3 bucket policies are typically used for cross-account access, but you can also use them to restrict access via an explicit Deny, which applies to all principals, whether they are in the same account as the bucket or in a different account.
Each IAM entity (user, group, or role) has a defined aws:userid variable. You need to use this variable in the bucket policy to specify the role or user as an exception within the condition element. The aws:userId value for an assumed role is defined as UNIQUE-ROLE-ID:ROLE-SESSION-NAME (e.g., AROAEXAMPLEID:userdefinedsessionname).
To obtain the AROAEXAMPLEID for an IAM role, follow these steps:
- Ensure you have installed the AWS CLI and open a command prompt or shell.
- Run the following command:
aws iam get-role --role-name ROLE-NAME. - In the output, look for the
RoleIdstring starting withAROA. You will use this in the bucket policy to scope bucket access to only this role.
In the aforementioned CloudTrail code example, this ID is found in the principalId element. The value of this element is important because AWS policy variables can also be checked as strings in IAM policies. Instead of specifying the role and assumed-role ARNs in the NotPrincipal element, you can use the aws:userId value in a StringNotLike condition along with a wildcard string. Within the aws:userId value, you also need to include the Root user of the account so that the bucket does not become completely inaccessible if the defined role is deleted. The userId for the Root account is the account number.
Using the AROAEXAMPLEID you just retrieved via the AWS CLI, you can create conditional logic so that the bucket policy scopes bucket access to users accessing the bucket using this role. Using conditional logic instead of the NotPrincipal element supports the use of wildcard strings, allowing any role session name to be accepted.
Now that you have the role ID to allow access, you need to block access from other users in the same account as the bucket. A policy to block users who are not using the IAM role or Root account credentials from accessing the bucket and its objects looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::MyExampleBucket",
"arn:aws:s3:::MyExampleBucket/*"
],
"Condition": {
"StringNotLike": {
"aws:userId": [
"AROAEXAMPLEID:*",
"111111111111"
]
}
}
}
]
}
You can use the same policy for IAM users. An IAM user has a unique ID starting with AIDA, which you can use for this purpose. To find this unique ID, follow these steps:
- After installing the AWS CLI, open a command prompt or shell.
- Run the command:
aws iam get-user --user-name USER-NAME - In the output, look for the
userIdstring starting withAIDAEXAMPLEID.
Once you find the userId string, you can place it in the aws:userId condition array, as shown in the following example:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::MyExampleBucket",
"arn:aws:s3:::MyExampleBucket/*"
],
"Condition": {
"StringNotLike": {
"aws:userId": [
"AROAEXAMPLEID:*",
"AIDAEXAMPLEID",
"111111111111"
]
}
}
}
]
}
Granting Cross-Account Bucket Access to a Specific IAM Role
In the previous section, I showed you how to limit S3 bucket access to a specific IAM role or user within the same account. Now, I will show you how to restrict access to a specific user and role in another account. When granting cross-account bucket access to an IAM user or role, you must define what actions the IAM user or role can perform through that access. In a previous AWS Security Blog, Jim Scharf wrote about the permissions required to allow IAM entities to access the bucket via CLI/API and the console. Using the information provided in that previous blog post, the CLI/API-level access bucket policy should look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/ROLENAME"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::MyExampleBucket"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/ROLENAME"
},
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::MyExampleBucket/*"
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::MyExampleBucket",
"arn:aws:s3:::MyExampleBucket/*"
],
"Condition": {
"StringNotLike": {
"aws:userId": [
"AROAEXAMPLEID:*",
"111111111111"
]
}
}
}
]
}
The following policy shows the service operations required for console-level access, such as those used with the IAM switch role feature of the console.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/ROLENAME"
},
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/ROLENAME"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::MyExampleBucket"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/ROLENAME"
},
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::MyExampleBucket/*"
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::MyExampleBucket",
"arn:aws:s3:::MyExampleBucket/*"
],
"Condition": {
"StringNotLike": {
"aws:userId": [
"AROAEXAMPLEID:*",
"111111111111"
]
}
}
}
]
}
To grant API/CLI access to an IAM user in another account, you need to add the IAM user’s AIDAEXAMPLEID to the aws:userId condition, just as we did in the previous section. In addition to the aws:userId condition, you also need to add the full ARN of the IAM user to the Principal element of these policies. Note that you cannot grant cross-account console access to an IAM user because the user would need a role in the target account, but you can grant bucket access via API/CLI. It looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": [
{
"AWS": [
"arn:aws:iam::222222222222:role/ROLENAME",
"arn:aws:iam::222222222222:user/USERNAME"
]
}
],
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::MyExampleBucket"
},
{
"Effect": "Allow",
"Principal": [
{
"AWS": [
"arn:aws:iam::222222222222:role/ROLENAME",
"arn:aws:iam::222222222222:user/USERNAME"
]
}
],
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::MyExampleBucket/*"
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::MyExampleBucket",
"arn:aws:s3:::MyExampleBucket/*"
],
"Condition": {
"StringNotLike": {
"aws:userId": [
"AROAEXAMPLEID:*",
"AIDAEXAMPLEID",
"111111111111"
]
}
}
}
]
}
In addition to including role permissions in the bucket policy, you also need to define these permissions in the user policy of the IAM user or role. These permissions can be added to a customer managed policy and attached to the role or user in the IAM console, as detailed in the following policy document:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::MyExampleBucket"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::MyExampleBucket/*"
}
]
}
Following the guidelines in this article, you can restrict S3 bucket access to specific IAM roles or users within your local account and across different accounts, even if the user has an Admin policy or a policy with s3:*. This logic has various applications, and requirements for different use cases may vary. For example, you can use this method to set up buckets for access by instances within an Auto Scaling group. Just as with buckets containing personal records and account information, you can also use this method to restrict access to buckets with high security requirements. Always remember that it is best practice to grant permissions only to the resources necessary to perform the required tasks.