How to give a user access to a specific S3 folder?

on

Giving a IAM user access to a specific folder(s) in S3 sounds trivial when you think about it. But if you are reading this you know that it might not be the case.

But fret not for I do have an answer. I cannot say it is the best answer…. but it works for me and my colleagues. I will give you two examples and a little explanation. The simple one which is less permissive and another one with more granular control.

So what do you need to do? I am hoping that you already have some basics on IAM policy creation but if you don’t please look into this.

tl;dr Go to your AWS console and look for IAM, search the user, Click on “Add inline policy”, go for the json tab and that should get you started

You good? okay then lets get started. I will explain what each block of permissions do and why you need it in order of appearance. So that at the end I will end up with the final versions.

And please invest the time to read. Like I said this is not as easy as you think it is. So understanding what each block does will help you miles rather than just copy pasting.

Lovely, so this would be our use case for today:

  • We want to grant our user (lets call him Bob) access to a folder with the same name
  • Now inside this bucket we have other users as well for which he shouldn’t be able to read or write the objects
  • He also needs access to all files and sub-folders in his folder
  • The file structure looks something like this
    • Bucket: user-bucket
    • Folders
      • bob/
      • danny/
      • rutger/
      • hans/
		{
            "Sid": "AllowToSeeBucketListInTheConsole",
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:GetBucketLocation"
            ],
            "Resource": "arn:aws:s3:::*"
        },

The first block of code allows Bob to be able to get a list of all the buckets. This access is mandatory without it he would get access denied. There is no way around it.

		{
            "Sid": "AllowListBucket",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::user-bucket"
        },

The second block allows Bob to be able to list whatever is inside the bucket. You can either do something simple like the example above where he is able to list all the objects in the bucket or something more granular like the example below

		{
            "Sid": "AllowRootAndHomeListing",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::user-bucket",
            "Condition": {
                "StringEquals": {
                    "s3:prefix": [
                        ""
                    ],
                    "s3:delimiter": [
                        "/"
                    ]
                }
            }
        },

In this example we are limiting Bob only to list the start of the bucket via “” = user-bucket/. That way he is only able to list all the user folders and that is it.

But the string equals function means that it will allow Bob to list all the initial folders in the bucket and that is it. So we need to add an extra block so that Bob can list everything in his folder, for this we use StringLike so we are able to use the wildcard *. We also add bob/ so he is able to list the initial part of his folder.

		{
            "Sid": "AllowListingOfUserFolder",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::user-bucket",
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "bob/",
                        "bob/*"
                    ]
                }
            }
        },

Now Bob is able to list the files in his folder. The last step is granting him access to his objects. For simplicity if you wish to grant read access you need to use s3:GetObject and for writing you can use s3:PutObject. (Or you can grant something like s3:* and grant everything). You can work with this as you see fit. Since Bob needs to do both we add both.

		{
            "Sid": "AllowReadAndWriteAccess",
            "Effect": "Allow",
            "Action": ["s3:GetObject", "s3:PutObject"],
            "Resource": [
                "arn:aws:s3:::user-bucket/bob/",
                "arn:aws:s3:::user-bucket/bob/*"
            ]
        }

So at the end we end up with the simple example:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowToSeeBucketListInTheConsole",
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:GetBucketLocation"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Sid": "AllowListBucket",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::user-bucket"
        },
        {
            "Sid": "AllowReadAndWriteAccess",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": [
                "arn:aws:s3:::user-bucket/bob/",
                "arn:aws:s3:::user-bucket/bob/*"
            ]
        }
    ]
}

And the more granular example:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowToSeeBucketListInTheConsole",
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:GetBucketLocation"
            ],
            "Resource": "arn:aws:s3:::*"
        },
		{
            "Sid": "AllowRootAndHomeListing",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::user-bucket",
            "Condition": {
                "StringEquals": {
                    "s3:prefix": [
                        ""
                    ],
                    "s3:delimiter": [
                        "/"
                    ]
                }
            }
        },
		{
            "Sid": "AllowListingOfUserFolder",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::user-bucket",
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "bob/",
                        "bob/*"
                    ]
                }
            }
        },
        {
            "Sid": "AllowReadAndWriteAccess",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": [
                "arn:aws:s3:::user-bucket/bob/",
                "arn:aws:s3:::user-bucket/bob/*"
            ]
        }
    ]
}

The main difference between both is that the simple example can list all the objects in the bucket and the granular one limits it only to his folder. Now even though the simple one can list them (see that they exist) it doesn’t have any permissions. So it cannot read or write anything outside of his folder.

Thanks

I just wanted to thank this two lovely posts that really helped me get my head around this. If this posts is not enough I do recommend you check out them.