DEV Community

Cover image for IAM Policy to list an S3 bucket, except for the top-level (root) of the bucket
John Rotenstein for AWS

Posted on

IAM Policy to list an S3 bucket, except for the top-level (root) of the bucket

I saw a question on StackOverflow (Allow S3 bucket top-level listing to specific users only) that posed an interesting challenge:

  • Allow a user to list the contents of an Amazon S3 bucket
  • But... Don't let them list the top-level of the bucket

I'm always up for a challenge!

IAM Policy Variables

The question reminded me a bit of IAM Policy Elements: Variables and Tags, which allows the construction of an IAM Policy that inserts a username in appropriate spots:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": ["s3:ListBucket"],
      "Effect": "Allow",
      "Resource": ["arn:aws:s3:::mybucket"],
      "Condition": {"StringLike": {"s3:prefix": ["${aws:username}/*"]}}
    },
    {
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Effect": "Allow",
      "Resource": ["arn:aws:s3:::mybucket/${aws:username}/*"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This policy says:

  • Let the user list/get/put objects
  • But only in a folder that matches my IAM User username

That's close, but sort of opposite of "list everything except the top-level".

Playing with the ARN

Next, I tried playing with the ARN to see whether I could grant permission for any subfolder within a bucket:

arn:aws:s3:::BUCKET-NAME/*/*

Granting permission for that ARN is saying "Only for a path within the bucket that contains a slash". Thus, it should work for sub-folders but not the top-level.

Unfortunately, the s3:ListBucket permission applies to the bucket itself rather than the contents of the bucket. When granting s3:ListBucket, you must provide the ARN of the bucket without using /*.

Playing with the Prefix

This led me to the idea using the Prefix to specify which folders can be used. The excellent Grant IAM User Access to a Folder in an Amazon S3 Bucket article from AWS Support shows some examples of how to use prefixes, such as:

{
     "Action": ["s3:ListBucket"],
     "Effect": "Allow",
     "Resource": ["arn:aws:s3:::BUCKET-NAME"],
     "Condition":{"StringEquals":{"s3:prefix":["","media"]}}
}
Enter fullscreen mode Exit fullscreen mode

This permits listing of the bucket, but only the top-level folder, or the media folder.

This is getting closer!

I then tried applying negative logic with StringNotEquals, stating that listing was permitted if the prefix was not empty:

{
     "Action": ["s3:ListBucket"],
     "Effect": "Allow",
     "Resource": ["arn:aws:s3:::BUCKET-NAME"],
     "Condition":{"StringNotEquals":{"s3:prefix":""}}
}
Enter fullscreen mode Exit fullscreen mode

This correctly denied my listing the root directory, but permitted listing a sub-folder. However, it also allowed me to do this:

aws s3 ls s3://BUCKET-NAME/a

This command would list all objects that started with a. This was permitted because the prefix was not empty. Therefore, the solution is invalidated because people could try every letter/number as a prefix to view the contents of the top-level of the bucket.

The Solution

More playing led me to this solution:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::BUCKET-NAME",
            "Condition": {"StringLike": {"s3:prefix": ["*/*"]}}
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

This says that listing the bucket is permitted if the Prefix contains a slash. This prevents listing of the top-level of the bucket and allows any sub-folder to be listed.

It also requires that at least one slash exists in the path, so this will work:

aws s3 ls s3://BUCKET-NAME/folder/

However, this will not work since it does not contain a slash:

aws s3 ls s3://BUCKET-NAME/folder

You'll also find my solution on StackOverflow: Allow S3 bucket top-level listing to specific users only

Top comments (0)