Restrict Access to S3 Buckets to Predefined Users, Groups, or Roles

We at QloudX manage hundreds of AWS accounts for our customers. One of our very large enterprise customers has around 80 AWS accounts that we maintain for them.

As you can imagine, keeping track of what everyone is doing in each of these accounts, takes a lot of work. We want all our users to freely do whatever their job role requires but we also want to enforce guardrails such that they can’t deviate from their regular tasks too much.

Despite our best efforts, things do fall through the cracks every now & then, as is expected in such a large infrastructure. One such situation arose when our S3 bill spiked last month. We quickly looked through all our S3 usage & discovered buckets that “looked like” they haven’t been used in a while. They’re just sitting there with a ton of data, just wasting money.

But we were not sure that these S3 buckets were indeed unused. We had to be sure before we could delete them entirely. Our first thought was to go check the CloudTrail event history to find out whether these buckets had been accessed anytime in the past 90 days. After all, we did have CloudTrail enabled everywhere.

Unfortunately, we discovered that to track S3 usage, you must enable logging of data events in a CloudTrail trail, but that wasn’t the case for us. So instead of enabling it now & then waiting for the events to be collected for the next few weeks, we decided to do a scream test! 😄

The Scream Test

Our plan was to block access to the (few hundred) S3 buckets in question to everyone, except us, the admins. If in the coming weeks, someone “screamed,” we would know that that bucket is in use & cannot be deleted.

We blocked access by setting a bucket policy on the buckets. However, wrapping our heads around the complex logic of the AWS policy syntax wasn’t easy. I won’t lie; we did end up locking ourselves out of our own S3 buckets a number of times, while we were testing these policies.

If you’re in a similar situation & wish to avoid some mental anguish, here’s the policy:

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Principal": "*",
        "Action": "s3:*",
        "Resource": [
            "arn:aws:s3:::BUCKET-NAME",
            "arn:aws:s3:::BUCKET-NAME/*"
        ],
        "Condition": {
            "ForAllValues:ArnNotLike": {
                "aws:PrincipalArn": [
                    "arn:aws:iam::*:role/MyDevOpsRole",
                    "arn:aws:iam::*:role/MyPowerUserRole",
                    "arn:aws:iam::*:role/MyAdminRole"
                ]
            }
        }
    }
}

Looks deceptively simple, doesn’t it? Can you believe it took us a few hours to get this right? Let me break it down for you:

  • The “Effect” is “Deny” because we want to deny access to everyone except for a specific set of users
  • The “Principal” is “*” because we really mean “everyone”
  • The “Action” is “s3:*” because we want to deny everything
  • The resource list MUST include both arn:aws:s3:::BUCKET-NAME & arn:aws:s3:::BUCKET-NAME/* because some S3 actions apply to the bucket itself, while others are applicable to the objects inside the bucket

The Policy Condition

The condition is the most important part of this policy & the most difficult to construct. It is the only thing keeping us from denying everything to everyone!

First comes the condition operator, ArnNotLike in this case. As its name implies, we’re checking whether the ARN of the principal (user) performing an S3 action is not like the ARN pattern we specified later on.

This would have been a lot simpler if we were checking against just one ARN, but that wasn’t the case with us. So after a bit of digging around in the documentation, we found ForAllValues. ForAllValues lets us provide multiple ARNs & have AWS compare the incoming principal’s ARN against each one in our list.

Next in the condition is aws:PrincipalArn. This tells AWS that what we specify as the value of the condition is an ARN. And finally, there are the values: a list of IAM role ARNs in our case. Notice the * in place of the account number. We needed that since this policy was going to be deployed to several buckets across several accounts. If you are working with a single account, you could put the account number here & change ArnNotLike to ArnNotEquals.

Conclusion

So there you have it: a policy to block access to S3 for everyone except a group/role of users. Unless you’re a policy ninja already, I hope this article has taught you something new & valuable that you can actually use in your day-to-day tasks. 😊 If you like wrangling your brain over such fine logic, this AWS documentation page should be a fun read for you.

About the Author ✍🏻

Harish KM is a Principal DevOps Engineer at QloudX & a top-ranked AWS Ambassador since 2020. 👨🏻‍💻

With over a decade of industry experience as everything from a full-stack engineer to a cloud architect, Harish has built many world-class solutions for clients around the world! 👷🏻‍♂️

With over 20 certifications in cloud (AWS, Azure, GCP), containers (Kubernetes, Docker) & DevOps (Terraform, Ansible, Jenkins), Harish is an expert in a multitude of technologies. 📚

These days, his focus is on the fascinating world of DevOps & how it can transform the way we do things! 🚀

One Reply to “Restrict Access to S3 Buckets to Predefined Users, Groups, or Roles”

  1. Aryaman Gupta says:

    It’s not working for aws sagemaker roles.

Leave a Reply to Aryaman Gupta Cancel reply

Your email address will not be published. Required fields are marked *