Amazon S3 provides comprehensive security and compliance capabilities that meet even the most stringent regulatory requirements. It gives you flexibility in the way you manage data for cost optimization, access control, and compliance. However, because the service is flexible, a user could accidentally configure buckets in a manner that is not secure. For example, let’s say you uploaded files to an Amazon S3 bucket with public read permissions, even though you intended only to share this file with a colleague or a partner. Although this might have accomplished your task to share the file internally, the file is now available to anyone on the internet, even without authentication.

In this blog post, we show you how to prevent your Amazon S3 buckets and objects from allowing public access. We discuss how to secure data in Amazon S3 with a defense-in-depth approach, where multiple security controls are put in place to help prevent data leakage. This approach helps prevent you from allowing public access to confidential information, such as personally identifiable information (PII) or protected health information (PHI).

Preventing your Amazon S3 buckets and objects from allowing public access

Every call to an Amazon S3 service becomes a REST API request. When your request is transformed via a REST call, the permissions are converted into parameters included in the HTTP header or as URL parameters. The Amazon S3 bucket policy allows or denies access to the Amazon S3 bucket or Amazon S3 objects based on policy statements, and then evaluates conditions based on those parameters. To learn more, see Using Bucket Policies and User Policies.

With this in mind, let’s say multiple AWS Identity and Access Management (IAM) users at Example Corp. have access to an Amazon S3 bucket and the objects in the bucket. Example Corp. wants to share the objects among its IAM users, while at the same time preventing the objects from being made available publicly.

To demonstrate how to do this, we start by creating an Amazon S3 bucket named examplebucket . After creating this bucket, we must apply the following bucket policy. This policy denies any uploaded object ( PutObject ) with the attribute x-amz-acl having the values public-read , public-read-write , or authenticated-read . This means authenticated users cannot upload objects to the bucket if the objects have public permissions.

{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyPublicReadACL", "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": [ "s3:PutObject", "s3:PutObjectAcl" ], "Resource": "arn:aws:s3:::examplebucket/*", "Condition": { "StringEquals": { "s3:x-amz-acl": [ "public-read", "public-read-write", "authenticated-read" ] } } }, { "Sid": "DenyPublicReadGrant", "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": [ "s3:PutObject", "s3:PutObjectAcl" ], "Resource": "arn:aws:s3:::examplebucket/*", "Condition": { "StringLike": { "s3:x-amz-grant-read": [ "*http://acs.amazonaws.com/groups/global/AllUsers*", "*http://acs.amazonaws.com/groups/global/AuthenticatedUsers*" ] } } }, { "Sid": "DenyPublicListACL", "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "s3:PutBucketAcl", "Resource": "arn:aws:s3:::examplebucket", "Condition": { "StringEquals": { "s3:x-amz-acl": [ "public-read", "public-read-write", "authenticated-read" ] } } }, { "Sid": "DenyPublicListGrant", "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "s3:PutBucketAcl", "Resource": "arn:aws:s3:::examplebucket", "Condition": { "StringLike": { "s3:x-amz-grant-read": [ "*http://acs.amazonaws.com/groups/global/AllUsers*", "*http://acs.amazonaws.com/groups/global/AuthenticatedUsers*" ] } } } ] }

To better understand what is happening in this bucket policy, we’ll explain each statement. Let’s start with the first statement.

{ "Sid": "DenyPublicReadACL", "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": [ "s3:PutObject", "s3:PutObjectAcl" ], "Resource": "arn:aws:s3:::examplebucket/*", "Condition": { "StringEquals": { "s3:x-amz-acl": [ "public-read", "public-read-write", "authenticated-read" ] } } }

This statement accomplishes the following:

“Deny any Amazon S3 request to PutObject or PutObjectAcl in the bucket examplebucket when the request includes one of the following access control lists (ACLs): public-read , public-read-write , or authenticated-read .”

Remember that IAM policies are evaluated not in a first-match-and-exit model. Instead, IAM evaluates first if there is an explicit Deny. If there is not, IAM continues to evaluate if you have an explicit Allow and then you have an implicit Deny.

The above policy creates an explicit Deny. Even when any authenticated user tries to upload ( PutObject ) an object with public read or write permissions, such as public-read or public-read-write or authenticated-read , the action will be denied. To understand how S3 Access Permissions work, you must understand what Access Control Lists (ACL) and Grants are. You can find the documentation here.

Now let’s continue our bucket policy explanation by examining the next statement.

{ "Sid": "DenyPublicReadGrant", "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": [ "s3:PutObject", "s3:PutObjectAcl" ], "Resource": "arn:aws:s3:::examplebucket/*", "Condition": { "StringLike": { "s3:x-amz-grant-read": [ "*http://acs.amazonaws.com/groups/global/AllUsers*", "*http://acs.amazonaws.com/groups/global/AuthenticatedUsers*" ] } } }

This statement is very similar to the first statement, except that instead of checking the ACLs, we are checking specific user groups’ grants that represent the following groups:

AuthenticatedUsers group . Represented by http://acs.amazonaws.com/groups/global/AuthenticatedUsers , this group represents all AWS accounts. Access permissions to this group allow any AWS account to access the resource. However, all requests must be signed (authenticated).

. Represented by , this group represents all AWS accounts. Access permissions to this group allow any AWS account to access the resource. However, all requests must be signed (authenticated). AllUsers group. Represented by http://acs.amazonaws.com/groups/global/AllUsers , access permissions to this group allow anyone on the internet access to the resource. The requests can be signed (authenticated) or unsigned (anonymous). Unsigned requests omit the Authentication header in the request.

For more information about which parameters you can use to create bucket policies, see Using Bucket Policies and User Policies.

Now that you know how to deny object uploads with permissions that would make the object public, you just have two statement policies that prevent users from changing the bucket permissions (Denying s3:PutBucketACL from ACL and Denying s3:PutBucketACL from Grants).

Below is how we’re preventing users from changing the bucket permisssions.

{ "Sid": "DenyPublicListACL", "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "s3:PutBucketAcl", "Resource": "arn:aws:s3:::examplebucket", "Condition": { "StringEquals": { "s3:x-amz-acl": [ "public-read", "public-read-write", "authenticated-read" ] } } }, { "Sid": "DenyPublicListGrant", "Effect": "Deny", "Principal": { "AWS": "*" }, "Action": "s3:PutBucketAcl", "Resource": "arn:aws:s3:::examplebucket", "Condition": { "StringLike": { "s3:x-amz-grant-read": [ "*http://acs.amazonaws.com/groups/global/AllUsers*", "*http://acs.amazonaws.com/groups/global/AuthenticatedUsers*" ] } } }

As you can see above, the statement is very similar to the Object statements, except that now we use s3:PutBucketAcl instead of s3:PutObjectAcl , the Resource is just the bucket ARN, and the objects have the “ /* ” in the end of the ARN.

In this section, we showed how to prevent IAM users from accidently uploading Amazon S3 objects with public permissions to buckets. In the next section, we show you how to enforce multiple layers of security controls, such as encryption of data at rest and in transit while serving traffic from Amazon S3.

Securing data on Amazon S3 with defense-in-depth

Let’s say that Example Corp. wants to serve files securely from Amazon S3 to its users with the following requirements:

The data must be encrypted at rest and during transit.

The data must be accessible only by a limited set of public IP addresses.

All requests for data should be handled only by Amazon CloudFront (which is a content delivery network) instead of being directly available from an Amazon S3 URL. If you’re using an Amazon S3 bucket as the origin for a CloudFront distribution, you can grant public permission to read the objects in your bucket. This allows anyone to access your objects either through CloudFront or the Amazon S3 URL. CloudFront doesn’t expose Amazon S3 URLs, but your users still might have access to those URLs if your application serves any objects directly from Amazon S3, or if anyone gives out direct links to specific objects in Amazon S3.

A domain name is required to consume the content. Custom SSL certificate support lets you deliver content over HTTPS by using your own domain name and your own SSL certificate. This gives visitors to your website the security benefits of CloudFront over an SSL connection that uses your own domain name, in addition to lower latency and higher reliability.

To represent defense-in-depth visually, the following diagram contains several Amazon S3 objects (A) in a single Amazon S3 bucket (B). You can encrypt these objects on the server side. You also can configure the bucket policy such that objects are accessible only through CloudFront, which you can accomplish through an origin access identity (C). You then can configure CloudFront to deliver content only over HTTPS in addition to using your own domain name (D).

Defense-in-depth requirement 1: Data must be encrypted at rest and during transit

Let’s start with the objects themselves. Amazon S3 objects—files in this case—can range from zero bytes to multiple terabytes in size (see service limits for the latest information). Each Amazon S3 bucket includes a collection of objects, and the objects can be uploaded via the Amazon S3 console, AWS CLI, or AWS API.

You can encrypt Amazon S3 objects at rest and during transit. At rest, objects in a bucket are encrypted with server-side encryption by using Amazon S3 managed keys or AWS Key Management Service (AWS KMS) managed keys or customer-provided keys through AWS KMS. You also can encrypt objects on the client side by using AWS KMS managed keys or a customer-supplied client-side master key.

If you choose to use server-side encryption, Amazon S3 encrypts your objects before saving them on disks in AWS data centers. To encrypt an object at the time of upload, you need to add the x-amz-server-side-encryption header to the request to tell Amazon S3 to encrypt the object using Amazon S3 managed keys (SSE-S3), AWS KMS managed keys (SSE-KMS), or customer-provided keys (SSE-C). There are two possible values for the x-amz-server-side-encryption header: AES256 , which tells Amazon S3 to use Amazon S3 managed keys, and aws:kms , which tells Amazon S3 to use AWS KMS managed keys.

The following code example shows a Put request using SSE-S3.

PUT /example-object HTTP/1.1 Host: myBucket.s3.amazonaws.com Date: Wed, 8 Jun 2016 17:50:00 GMT Authorization: authorization string Content-Type: text/plain Content-Length: 11434 x-amz-meta-author: Janet Expect: 100-continue x-amz-server-side-encryption: AES256 [11434 bytes of object data]

If you choose to use client-side encryption, you can encrypt data on the client side and upload the encrypted data to Amazon S3. In this case, you manage the encryption process, the encryption keys, and related tools. You encrypt data on the client side by using AWS KMS managed keys or a customer-supplied, client-side master key.

Defense-in-depth requirement 2: Data must be accessible only by a limited set of public IP addresses

At the Amazon S3 bucket level, you can configure permissions through a bucket policy. For example, you can limit access to the objects in a bucket by IP address range or specific IP addresses. Alternatively, you can make the objects accessible only through HTTPS.

The following bucket policy allows access to Amazon S3 objects only through HTTPS (the policy was generated with the AWS Policy Generator). Here the bucket policy explicitly denies ( "Effect": "Deny" ) all read access ( "Action": "s3:GetObject" ) from anybody who browses ( "Principal": "*" ) to Amazon S3 objects within an Amazon S3 bucket if they are not accessed through HTTPS ( "aws:SecureTransport": "false" ).

{ "Version": "2012-10-17", "Id": "Policy1504640911349", "Statement": [ { "Sid": "Stmt1504640908907", "Effect": "Deny", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::/*", "Condition": { "Bool": { "aws:SecureTransport": "false" } } } ] }

Defense-in-depth requirement 3: Data must not be publicly accessible directly from an Amazon S3 URL

Next, configure Amazon CloudFront to serve traffic from within the bucket. The use of CloudFront serves several purposes:

CloudFront is a content delivery network that acts as a cache to serve static files quickly to clients.

Depending on the number of requests, the cost of delivery is less than if objects were served directly via Amazon S3.

Objects served through CloudFront can be limited to specific countries.

Access to these Amazon S3 objects is available only through CloudFront. We do this by creating an origin access identity (OAI) for CloudFront and granting access to objects in the respective Amazon S3 bucket only to that OAI. As a result, access to Amazon S3 objects from the internet is possible only through CloudFront; all other means of accessing the objects—such as through an Amazon S3 URL—are denied. CloudFront acts not only as a content distribution network, but also as a host that denies access based on geographic restrictions. You apply these restrictions by updating your CloudFront web distribution and adding a whitelist that contains only a specific country’s name (let’s say Liechtenstein). Alternatively, you could add a blacklist that contains every country except that country. Learn more about how to use CloudFront geographic restriction to whitelist or blacklist a country to restrict or allow users in specific locations from accessing web content in the AWS Support Knowledge Center.

Defense-in-depth requirement 4: A domain name is required to consume the content

To serve content from CloudFront, you must use a domain name in the URLs for objects on your webpages or in your web application. The domain name can be either of the following:

The domain name that CloudFront automatically assigns when you create a distribution, such as d111111abcdef8.cloudfront.net

Your own domain name, such as example.com

For example, you might use one of the following URLs to return the file image.jpg:

http://d111111abcdef8.cloudfront.net/images/image.jpg

http://example.com/images/image.jpg

You use the same URL format whether you store the content in Amazon S3 buckets or at a custom origin, like one of your own web servers.

Instead of using the default domain name that CloudFront assigns for you when you create a distribution, you can add an alternate domain name that’s easier to work with, like example.com . By setting up your own domain name with CloudFront, you can use a URL like this for objects in your distribution: http://example.com/images/image.jpg .

Let’s say that you already have a domain name hosted on Amazon Route 53. You would like to serve traffic from the domain name, request an SSL certificate, and add this to your CloudFront web distribution. The SSL offloading occurs in CloudFront by serving traffic securely from each CloudFront location. You also can configure CloudFront to deliver your content over HTTPS by using your custom domain name and your own SSL certificate. Serving web content through CloudFront reduces response from the origin as requests are redirected to the nearest edge location. This results in faster download times than if the visitor had requested the content from a data center that is located farther away.

Summary

In this post, we demonstrated how you can apply policies to Amazon S3 buckets so that only users with appropriate permissions are allowed to access the buckets. Anonymous users (with public-read/public-read-write permissions) and authenticated users without the appropriate permissions are prevented from accessing the buckets.

We also examined how to secure access to objects in Amazon S3 buckets. The objects in Amazon S3 buckets can be encrypted at rest and during transit. Doing so helps provide end-to-end security from the source (in this case, Amazon S3) to your users.

If you have feedback about this blog post, submit comments in the “Comments” section below. If you have questions about this blog post, start a new thread on the Amazon S3 forum or contact AWS Support.

Want more AWS Security how-to content, news, and feature announcements? Follow us on Twitter.