Josh von Schaumburg in security 8 minutes to read

AWS S3 Permissions to Secure your S3 Buckets and Objects

Given the many S3 breaches over the past year and some inaccurate information I have seen across various news outlets about the default security of S3, I thought it would be beneficial to demystify some of the complexities of S3 permissions. In this post, I will review all of the various ways in which a user can gain access to an S3 object (or entire bucket of objects) within S3 and provide an overview of the complex S3 permission model.

Understanding S3 Permissions

The first key point to remember regarding S3 permissions is that by default, objects cannot be accessed by the public. Regardless of what you read, S3 buckets are secured by default, and any breach of S3 data occurs due to deliberate human error or malicious behavior.

That said, there are three core principles in describing how a user can gain access to an object in S3:

  1. Through the legacy object or bucket access control lists (ACLs)
    Or, through the IAM service, which can be broken down into two sub-categories
  2. Through user permissions (user-based IAM policy)
  3. Through a bucket policy (resource-based IAM policy)

First, I will break down ACLs. But before that, another important point to clarify is that “accessing a bucket” involves many different types of actions. For both ACLs and IAM, there are actions against the bucket itself (CreateBucket, DeleteBucket, ListBucket, GetBucketPolicy, etc.), as well as actions against the objects within said bucket (ListObjects, GetObject, DeleteObject, GetObjectAcl, etc.). It is important to always understand what type of access is intended by configuring your ACLs correctly or by specifying the appropriate API actions in your IAM policies. Lastly, remember that final authorization to an API action is the least-privilege union of all permissions granted. This means that authorization decisions always default to DENY if no permissions are attached and an explicit DENY always overrides an explicit ALLOW.

Accessing S3 with ACLs

In March of 2006, AWS released its first public service, Simple Storage Service or S3 – storage for the Internet, offering highly reliable, low latency storage at a low, monthly cost. Of course, in 2006 there was no IAM service. There was no concept of IAM entities such as users or roles. There were no resource-based policies (see explanation in Accessing S3 with a Resource-Based IAM Policy section) to attach to buckets. In fact, there were no policies at all! Developers simply used their root access key for authentication, something you should now delete as a first step when creating an AWS account. And why use an access key at all in your application when you can use service roles? Instead, S3 was released with ACLs to control access to each bucket and object. In the picture below, you can control access to the bucket ACLs. If any of the four permissions are public, then the bucket is labeled as public:

You can also see that these ACLs can be adjusted for my own account, as well as for other AWS accounts, which would also need to then provide permissions to its IAM entities with a user-based policy. When you click on an object within a bucket, you will see the same options to control ACLs per each object (vs. applying to all objects in the bucket via bucket ACLs). Note that when an object ACL is public, no notification is provided by the UI. This notification only appears if an entire bucket is made public. That said, we have built an object ACL scanning solution that we have implemented for a number of our customers.

Finally, remember that S3 ACLs are a legacy system. They are useful for controlling access to individual objects, but for most all use cases, only using bucket/IAM policies is the correct approach.

Accessing S3 with a User-Based IAM Policy

This is the type of access with which most all AWS users are very familiar. Give an IAM user permission to an S3 bucket with a custom JSON policy, as seen below:

	"Version": "2012-10-17",
	"Statement": [{
		"Sid": "AllowBucketAccess"
		"Effect": "Allow",
		"Action": [
		"Resource": [

Or, attach a managed IAM policy to the user, such as AmazonS3FullAccess or AdministratorAccess. Alternatively, the same policies can be attached to any type of IAM role (e.g., cross-account role or a service role) to give a particular resource access to the objects within the S3 bucket. That user can then access the defined bucket or object APIs either programmatically (with an access key) or through the web GUI. You will notice that even with the appropriate permissions, an IAM user will not be able to navigate to an S3 object URL because clicking the URL does not apply the IAM user’s permissions. The user could, of course, download an object through the GUI or an access key and the API. For an S3 URL to provide access to a user, a pre-signed URL must be generated with the CLI (or an SDK), or the object must be made public.

Accessing S3 with a Resource-Based IAM Policy (or “Bucket Policy”)

Before diving in, it is important to understand the difference between a user-based IAM policy and a resource-based IAM policy (not to be confused with a resource-level policy!). A user-based policy is your standard type of policy that you would apply to an IAM entity (user, role, group); The IAM user who executes an action (or assumes a role that then executes an action) is implicitly the principal. A resource-based policy is very similar, except instead of identifying what a predefined IAM entity (or principal) can do, you actually identify the principal within the policy itself, and the resource (e.g., the S3 bucket, SQS queue, SNS topic, etc.) is predefined. That’s a mouthful; let’s look at an example:

	"Version": "2012-10-17",
	"Statement": [{
		"Sid": "Allow",
		"Effect": "Allow",
		"Principal": {
			"AWS": [
		"Action": "s3:GetObject",
		"Resource": "arn:aws:s3:::sharedbucket/*"

As you can see, a new IAM element (principal) has been added here as a JSON key. In this scenario, I have provided S3 access to two different AWS accounts (they could be accounts that I own, or they could be partner organizations, for example). I could also define a specific user within an account. The resource, of course, is the “sharedbucket”, which is the bucket to which the resource-based policy applies.

With a user-based policy, the principal is already assumed to be the user to which the policy is attached, so it is not necessary to include (see lack of principal in the previous policy). For resource-based policies, the principal can be an entire AWS account (meaning said AWS account would need to give its IAM users permissions to the bucket with a user-based policy), or it could also be an individual user in another AWS account. The principal can also be a wildcard (*) such as below, which is another way to make a bucket and all of its objects public:

      "Principal": "*",

In summary, there are three ways to make an object public:

  1. Through an object ACL
  2. Through a bucket ACL (which then gets applied to all objects with a DENY always trumping an ALLOW)
  3. Through a bucket policy (which then gets applied to all objects with a DENY always trumping an ALLOW)

I have now covered the different ways to grant users access to S3 objects, including how to make them completely public. It is a very deliberate action one must take that has absolutely nothing to do with any non-existent “S3 vulnerabilities” you may hear about in the news. Look for our future post, which will identify the best ways to secure your S3 buckets.

If you have any questions or comments, or you would like Trek10 to conduct an audit of your S3 buckets, feel free to reach out to us at