AWS Security Blog

How to Prevent Uploads of Unencrypted Objects to Amazon S3

There are many use cases to prevent uploads of unencrypted objects to an Amazon S3 bucket, but the underlying objective is to protect the confidentiality and integrity of the objects stored in that bucket. AWS provides several services that help make this process easier, such as AWS Identity and Access Management (IAM) and AWS Key Management Service (KMS). By using an S3 bucket policy, you can enforce the encryption requirement when users upload objects, instead of assigning a restrictive IAM policy to all users.

In this blog post, I will show you how to create an S3 bucket policy that prevents users from uploading unencrypted objects, unless they are using server-side encryption with S3–managed encryption keys (SSE-S3) or server-side encryption with AWS KMS–managed keys (SSE-KMS).

Encryption primer

When thinking about S3 and encryption, remember that you do not “encrypt S3” or “encrypt an S3 bucket.” Instead, S3 encrypts your data at the object level as it writes to disks in AWS data centers, and decrypts it for you when you access it. You can encrypt objects by using client-side encryption or server-side encryption. Client-side encryption occurs when an object is encrypted before you upload it to S3, and the keys are not managed by AWS. With server-side encryption, Amazon manages the keys in one of three ways:

  • Server-side encryption with customer-provided encryption keys (SSE-C).
  • SSE-S3.
  • SSE-KMS.

Server-side encryption is about data encryption at rest—that is, S3 encrypts your data at the object level as it writes it to disks in its data centers and decrypts it for you when you access it. As long as you authenticate your request and you have access permissions, there is no difference in the way you access encrypted or unencrypted objects.

S3 uses a concept called envelope encryption to protect data at rest. Each object is encrypted with a unique key employing strong multi-factor encryption. As an additional safeguard, Amazon encrypts the key itself with a master key. S3 server-side encryption uses one of the strongest block ciphers available, 256-bit Advanced Encryption Standard (AES-256), to encrypt your data. The following diagram illustrates the encryption solutions discussed in this blog post.

Diagram illustrating the encryption solutions discussed in this blog post

Solution overview

To upload an object to S3, you use a Put request, regardless if called via the console, CLI, or SDK. The Put request looks similar to the following.

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
[11434 bytes of object data]

To encrypt an object at the time of upload, you need to add a header called x-amz-server-side-encryption to the request to tell S3 to encrypt the object using SSE-C, SSE-S3, or SSE-KMS. 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]

In order to enforce object encryption, create an S3 bucket policy that denies any S3 Put request that does not include the x-amz-server-side-encryption header. There are two possible values for the x-amz-server-side-encryption header: AES256, which tells S3 to use S3-managed keys, and aws:kms, which tells S3 to use AWS KMS–managed keys.

Background for use case #1:Using SSE-S3 managed keys

In the first use case, you enforce the use of SSE-S3, which allows S3 to manage the master keys:

  1. As each object is uploaded, a data encryption key is generated and the object is encrypted with the data encryption key using the AES256 block cipher.
  2. The data encryption key is encrypted with a master key maintained by Amazon.
  3. The encrypted object and the encrypted data encryption key are stored together on S3, and the master key is stored separately by Amazon.
  4. The object is protected because the object can only be decrypted using the data encryption key, which is itself encrypted with the master key. Amazon regularly rotates the master key for additional security.

SS3-S3 is a good solution to protect data when you are not required to manage the master key. A sample S3 bucket policy that implements the solution is shown in the following implementation section. The policy needs to cover two conditions in order to deny the object upload. The first condition looks for the s3:x-amz-server-side-encryption key with a value of AES256. The second condition looks for a Null value for the s3:x-amz-server-side-encryption key.

Implementing use case #1: Using SSE-S3 managed keys

To implement this policy, navigate to the S3 console and follow these steps:

  1. Choose the target bucket in the left pane.
  2. Expand Permissions in the right pane, and choose Edit bucket policy.
  3. Copy the following policy, paste it in that bucket policy box, and then click Save. (Throughout this solution, be sure to replace <bucket_name> with the actual name of your bucket.)
 {
     "Version": "2012-10-17",
     "Id": "PutObjPolicy",
     "Statement": [
           {
                "Sid": "DenyIncorrectEncryptionHeader",
                "Effect": "Deny",
                "Principal": "*",
                "Action": "s3:PutObject",
                "Resource": "arn:aws:s3:::<bucket_name>/*",
                "Condition": {
                        "StringNotEquals": {
                               "s3:x-amz-server-side-encryption": "AES256"
                         }
                }
           },
           {
                "Sid": "DenyUnEncryptedObjectUploads",
                "Effect": "Deny",
                "Principal": "*",
                "Action": "s3:PutObject",
                "Resource": "arn:aws:s3:::<bucket_name>/*",
                "Condition": {
                        "Null": {
                               "s3:x-amz-server-side-encryption": true
                        }
               }
           }
     ]
 }

You have now created an S3 bucket policy that will deny any Put requests that do not include a header to encrypt the object using SSE-S3.

Background for use case #2: Using SSE-KMS managed keys

In the second use case, you need not only to force object encryption, but also to manage the lifecycle of the encryption keys. You use KMS to create encryption keys centrally, define the policies that control how keys can be used, and audit key usage to prove keys are being used correctly. You can use these keys to protect your data in S3 buckets. The first time you add an SSE-KMS–encrypted object to a bucket in a region, a default customer master key (CMK) is created for you automatically. The CMK is used for SSE-KMS encryption, unless you select a CMK that you created separately using KMS. Creating your own CMK can give you more flexibility, including the ability to create, rotate, disable, and define access controls, and to audit the encryption keys used to protect your data.

KMS provides the key management infrastructure that you can use to generate and manage your own encryption keys:

  1. As each object is uploaded, a data encryption key is generated and the object is encrypted with that data encryption key by using the AES256 block cipher.
  2. The data encryption key is then encrypted with a KMS CMK that is managed by you via KMS, and the encrypted object and the encrypted data encryption key are stored together.
  3. The object is protected because the object can only be decrypted using the data encryption key, which is itself encrypted with the CMK. You will be responsible for maintaining the lifecycle of your CMK, including key rotation.

Implementing use case #2: Using SSE-KMS managed keys

To implement use case #2, follow the steps described in use case #1 and substitute the following bucket policy. The logic behind the bucket policy solution for our second use case is similar to the first use case, but instead of looking for the x-amz-server-side-encryption header with the value of AES256, you will look for the same header with the value of aws:kms, as shown in the following policy.

{
       "Version": "2012-10-17",
       "Id": "PutObjPolicy",
       "Statement": [
           {
                "Sid": "DenyIncorrectEncryptionHeader",
                "Effect": "Deny",
                "Principal": "*",
                "Action": "s3:PutObject",
                "Resource": "arn:aws:s3:::<bucket_name>/*",
                "Condition": {
                    "StringNotEquals": {
                          "s3:x-amz-server-side-encryption": "aws:kms"
                             }
                   }
           },
           {
                "Sid": "DenyUnEncryptedObjectUploads",
                "Effect": "Deny",
                "Principal": "*",
                "Action": "s3:PutObject",
                "Resource": "arn:aws:s3:::<bucket_name>/*",
                "Condition": {
                    "Null": {
                          "s3:x-amz-server-side-encryption": true
                            }
                    }
           }
    ]
}

Testing the solutions

To test the solutions, you can use the IAM policy simulator to ensure each policy in this post works as intended. Because the IAM policy simulator includes support for AWS CLI and AWS SDK, you can automate the testing process. For a more detailed overview of the IAM policy simulator and how to test resource policies, see Testing IAM Policies with the IAM Policy Simulator and Verify Resource-Based Permissions Using the IAM Policy Simulator.

To access the IAM policy simulator, navigate to the IAM console and select Policy Simulator under Additional Information on the right side of the console. To set up the IAM policy simulator for testing:

  1. Choose a user from the left pane (see the following screenshot).
  2. Ensure Mode is set to Existing Policies.

Image of choosing a user and mode

  1. Ensure the user has an IAM policy with the following policy and that you have selected the Policy check box.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::<bucket_name>"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::<bucket_name>/*",
        }
    ]
}
  1. Select S3 from the list of services, as shown in the following screenshot.Image of selecting S3 from the services drop-down list
  2. Select PutObject from the Actions drop-down list
    .Image of selecting PutObject from the Actions drop-down list
  3. Expand Amazon S3 under Action Settings and Results to expose the ARN field, as shown in the following screenshot.
  4. Type the ARN of the S3 bucket.
  5. Be sure to select the check box for Include Resource Policy.Image of typing the ARN and selecting Include Resource Policy

You are now ready to begin testing the solutions. For each policy, you need to test three possible variations on an S3 Put request. The following table outlines the test conditions and expected results.

Bucket policy Service Action ARN s3:x-amz-server-side-encryption Expected results
Force
SSE-S3
S3 PutObject arn:aws:s3:::<bucket_name>/ Blank Denied
Force
SSE-S3
S3 PutObject arn:aws:s3:::<bucket_name>/ AES256 Allowed
Force
SSE-S3
S3 PutObject arn:aws:s3:::<bucket_name>/ aws:kms Denied
Force
SSE-KMS
S3 PutObject arn:aws:s3:::<bucket_name>/ Blank Denied
Force
SSE-KMS
S3 PutObject arn:aws:s3:::<bucket_name>/ AES256 Denied
Force
SSE-KMS
S3 PutObject arn:aws:s3:::<bucket_name>/ aws:kms Allowed
  1. Simulate making a PutObject call without supplying a value for s3:x-amz-server-side-encryption.
  2. Click Run Simulation.
  3. As expected by looking at the preceding table, the result is denied, as shown in the following screenshot.Image showing the result as denied
  4. Simulate a value of AES256 for SSE-S3.
  5. Click Run Simulation to see that the result is allowed, as shown in the following screenshot.Image showing the result is allowed
  1. Last, simulate a value of aws:kms for SSE-KMS.
  2. Click Run Simulation and see that the result is denied.Image of simulating a value of aws:kms for SSE-KMS

The results should align with the preceding conditions table. To dive deeper into why the Action was allowed or denied, click the Show statement link (highlighted in the following screenshot) to see which policy allowed or denied the action.

Image of Show statement link

Repeat each test condition to validate that the bucket policies match the expected results for each use case. The IAM policy simulator helps you understand, test, and validate how your resource-based policies and IAM policies work together to grant or deny access to AWS resources. For more information about troubleshooting policies, see Troubleshoot IAM Policies.

Conclusion

In this post, I have demonstrated how to create an S3 bucket policy that prevents unencrypted objects from being uploaded unless they are using SSE-S3 or SSE-KMS. Most importantly, I showed how to test this S3 bucket policy by using the IAM policy simulator to validate the policy.

If you have comments, submit them in the “Comments” section below. If you have questions, start a new thread on the IAM forum.

– Michael

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