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}/*"]
}
]
}
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"]}}
}
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":""}}
}
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": ["*/*"]}}
}
]
}
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)