Test S3 ABAC locally with iam-lens

tl;dr

AWS just made managing access to S3 much more powerful, and a bit more complicated. S3 now supports attribute-based access control (ABAC) for general-purpose S3 buckets. With iam-lens, you can simulate and understand exactly how enabling ABAC on a bucket will affect access to a bucket before enabling it. Just add --s3-abac-override enabled to your simulate and who-can commands to see the effects of enabling ABAC on a bucket.

Table of Contents

New S3 Attribute Based Access Control Functionality

In November 2025, AWS announced attribute-based access control (ABAC) for Amazon S3 general purpose buckets. It works more or less as you would expect. Tags on the bucket can be used with the context keys aws:ResourceTag/<tag-key> and s3:BucketTag/<tag-key>.

For example, this policy will only allow access to buckets (or objects in buckets) where the bucket tag Dept is Accounting.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AccountingBuckets",
      "Effect": "Allow",
      "Action": ["s3:ListBucket", "s3:GetObject"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/Dept": "Accounting"
        }
      }
    }
  ]
}

There’s one important caveat: Before this policy will work on a bucket, ABAC must be enabled on each S3 bucket. I assume there are concerns about ABAC suddenly working on S3 buckets in your account without you ensuring it has the effects you want. To enable ABAC on a bucket use the PutBucketAbac API. This is available in the console, the AWS CLI, boto3, and AWS SDKs.

The big question is: what is the impact of enabling ABAC on an S3 bucket?

S3 ABAC Support in iam-collect and iam-lens

iam-collect will now download the ABAC status of every bucket in your account and store the result in abacEnabled in the bucket metadata. This requires a new permission: s3:GetBucketAbac.

iam-lens will use the bucket information in your iam-collect data store to enable ABAC when running simulate or who-can. So by default, it will do what you expect.

Previewing S3 ABAC Locally

Seeing ABAC after it’s enabled is great, but the key question is:

How will access change if ABAC is enabled for a bucket?

Let’s say we have a role arn:aws:iam::111111111111:role/BobInAccounting that has this inline policy attached:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AccountingBuckets",
      "Effect": "Allow",
      "Action": ["s3:ListBucket", "s3:GetObject"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/Dept": "Accounting"
        }
      }
    }
  ]
}

This policy grants access to list and get objects in any bucket that has ABAC enabled and the Dept tag is set to Accounting.

Let’s say we have a bucket called arn:aws:s3:::accounts-receivable with the Dept set to Accounting, but ABAC is NOT enabled.

Preview ABAC for a principal with iam-lens simulate

You can test this access using the simulate command:

iam-lens simulate --principal arn:aws:iam::111111111111:role/BobInAccounting \
                  --resource arn:aws:s3:::accounts-receivable \
                  --action s3:ListBucket
> Implicitly Denied

This returns implicitly denied because even though the tags match, ABAC is not enabled on the bucket, so BobInAccounting does not have access.

To test what effective access would be if you did enable ABAC add the --s3-abac-override argument.

iam-lens simulate --principal arn:aws:iam::111111111111:role/BobInAccounting \
                  --resource arn:aws:s3:::accounts-receivable \
                  --action s3:ListBucket \
                  --s3-abac-override enabled
> Allowed

Do the same thing with s3:GetObject by using an S3 object ARN:

iam-lens simulate --principal arn:aws:iam::111111111111:role/BobInAccounting \
                  --resource arn:aws:s3:::accounts-receivable/invoice.pdf \
                  --action s3:GetObject
> Implicitly Denied

iam-lens simulate --principal arn:aws:iam::111111111111:role/BobInAccounting \
                  --resource arn:aws:s3:::accounts-receivable/invoice.pdf \
                  --action s3:GetObject \
                  --s3-abac-override enabled
> Allowed

Using this, you can preview how your policies behave before actually updating your sensitive buckets or manually updating data in your iam-collect data store.

Preview ABAC for a bucket with iam-lens who-can

who-can lets you flip the question: instead of asking what one role can do, it tells you who can access a given resource.

who-can starts with a resource and determines which principals have access to it.

Let’s use the same bucket and see what we find.

who-can --resource arn:aws:s3:::accounts-receivable \
        --actions s3:ListBucket
{
  "simulationCount": 42,
  "allowed": [
    {
      "principal": "arn:aws:iam::111111111111:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_AdministratorAccess_demo",
      "service": "s3",
      "action": "ListBucket",
      "level": "list"
    }
  ],
  "allAccountsChecked": false,
  "accountsNotFound": [],
  "organizationsNotFound": [],
  "organizationalUnitsNotFound": [],
  "principalsNotFound": []
}

Similar to what we found using simulate, since ABAC is not enabled on the bucket BobInAccounting does not have access to list the bucket.

You can override this using the --s3-abac-override argument:

who-can --resource arn:aws:s3:::accounts-receivable \
        --actions s3:ListBucket \
        --s3-abac-override enabled
{
  "simulationCount": 42,
  "allowed": [
    {
      "principal": "arn:aws:iam::111111111111:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_AdministratorAccess_demo",
      "service": "s3",
      "action": "ListBucket",
      "level": "list"
    },
    # 🪣 BobInAccounting is included in the output now because we are simulating ABAC being enabled on the bucket.
	{
      "principal": "arn:aws:iam::111111111111:role/BobInAccounting",
      "service": "s3",
      "action": "ListBucket",
      "level": "list"
    },
  ],
  "allAccountsChecked": false,
  "accountsNotFound": [],
  "organizationsNotFound": [],
  "organizationalUnitsNotFound": [],
  "principalsNotFound": []
}

Do the same thing for S3 object access using an S3 object ARN:

# This will not include BobInAccounting
who-can --resource arn:aws:s3:::accounts-receivable/invoice.pdf \
        --actions s3:GetObject

# This will include BobInAccounting
who-can --resource arn:aws:s3:::accounts-receivable/invoice.pdf \
        --actions s3:GetObject \
        --s3-abac-override enabled

If no --actions are specified, who-can will automatically test all actions for the resource. You can use simple bash tools to preview the effects of enabling ABAC on a bucket and its objects.

# Test all bucket actions without and with ABAC
who-can --resource arn:aws:s3:::accounts-receivable \
        --sort > bucket.json

who-can --resource arn:aws:s3:::accounts-receivable \
        --sort \
        --s3-abac-override enabled > bucket-abac.json

# Test all object actions without and with ABAC
who-can --resource arn:aws:s3:::accounts-receivable/invoice.pdf \
        --sort > object.json

who-can --resource arn:aws:s3:::accounts-receivable/invoice.pdf \
        --sort \
        --s3-abac-override enabled > object-abac.json

# 👀 Compare bucket access with and without ABAC enabled
diff bucket.json bucket-abac.json
10a11,16
>       "principal": "arn:aws:iam::111111111111:role/BobInAccounting",
>       "service": "s3",
>       "action": "ListBucket",
>       "level": "list"
>     },
>     {

# 👀 Compare object access with and without ABAC enabled
diff object.json object-abac.json
10a11,16
>       "principal": "arn:aws:iam::111111111111:role/BobInAccounting",
>       "service": "s3",
>       "action": "GetObject",
>       "level": "read"
>     },
>     {

Build Confidence Before You Flip the Switch

S3 ABAC is a long-awaited and powerful way to manage access to S3 at scale. We hope iam-lens helps you move faster and make the internet a little bit safer for all of us while leveraging it.

If you have any questions or feedback please file an issue or join our Discord.