How to enforce S3 default encryption

Amazon S3 default encryption sets encryption settings for all object uploads, but these settings are not enforced. This may cause unencrypted objects to be uploaded to the bucket. This blog gives you a bucket policy that enforces all object uploads to be encrypted.

The following CloudFormation template enforces the use of KMS encryption with a bucket policy.

  Resources:
    Bucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketEncryption:
          ServerSideEncryptionConfiguration:
            - ServerSideEncryptionByDefault:
                SSEAlgorithm: aws:kms
                KMSMasterKeyID: !GetAtt EncryptionKey.Arn

    BucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref Bucket
        PolicyDocument:
          Statement:
            - Sid: Restrict to Default- or KMS-encryption
              Effect: Deny
              Principal: '*'
              Action: 's3:PutObject'
              Resource: !Sub
                - '${BucketArn}/*'
                - BucketArn: !GetAtt Bucket.Arn
              Condition:
                'Null':
                  s3:x-amz-server-side-encryption: false
                StringNotEquals:
                  s3:x-amz-server-side-encryption: 'aws:kms'
            - Sid: Restrict KMS-key
              Effect: Deny
              Principal: '*'
              Action: 's3:PutObject'
              Resource: !Sub
                - '${BucketArn}/*'
                - BucketArn: !GetAtt Bucket.Arn
              Condition:
                StringNotEquals:
                  s3:x-amz-server-side-encryption: 'aws:kms'
                StringNotEqualsIfExists:
                  s3:x-amz-server-side-encryption-aws-kms-key-id: !GetAtt EncryptionKey.Arn

    EncryptionKey:
      Type: AWS::KMS::Key
      Properties:
        KeyPolicy:
          Version: '2012-10-17'
          Statement:
            - Sid: Enable IAM User Permissions
              Effect: Allow
              Principal:
                AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
              Action: 'kms:*'
              Resource: '*'

Lines 7 to 9 enable default encryption to automatically encrypt all objects uploaded without any encryption information. The default encryption is set to algorithm KMS and key EncryptionKey.

  - ServerSideEncryptionByDefault:
      SSEAlgorithm: aws:kms
      KMSMasterKeyID: !GetAtt EncryptionKey.Arn

The bucket policy enforces the default encryption settings by denying uploads with invalid encryption information. To [enforce KMS-encryption]({{<ref "#policy-encryption-algorithm" >}}) we deny uploads with invalid encryption algorithms using the s3:x-amz-server-side-encryption-condition key. To [enforce KMS-key EncryptionKey]({{<ref "#policy-encryption-key" >}}) we deny uploads with an invalid encryption key using the s3:x-amz-server-side-encryption-aws-kms-key-id-condition key.

Enforcing KMS-encryption {#policy-encryption-algorithm}

  - Sid: Restrict to Default- or KMS-encryption
    Effect: Deny
    Principal: '*'
    Action: 's3:PutObject'
    Resource: !Sub
      - '${BucketArn}/*'
      - BucketArn: !GetAtt Bucket.Arn
    Condition:
      'Null':
        s3:x-amz-server-side-encryption: false
      StringNotEquals:
        s3:x-amz-server-side-encryption: 'aws:kms'

The Null-condition allows uploads without encryption information and the StringNotEquals-condition denies uploads with invalid encryption information. Note that using StringNotEqualsIfExists doesn’t work for uploads without encryption information. The condition evaluates to true and denies the upload because of the Deny-effect.

Enforcing KMS-key {#policy-encryption-key}

  - Sid: Restrict KMS-key
    Effect: Deny
    Principal: '*'
    Action: 's3:PutObject'
    Resource: !Sub
      - '${BucketArn}/*'
      - BucketArn: !GetAtt Bucket.Arn
    Condition:
      StringNotEquals:
        s3:x-amz-server-side-encryption: 'aws:kms'
      StringNotEqualsIfExists:
        s3:x-amz-server-side-encryption-aws-kms-key-id: !GetAtt EncryptionKey.Arn

The StringNotEquals-condition scopes the policy to KMS-encrypted uploads. Additionally the StringNotEqualsIfExists-condition denies uploads with invalid encryption keys. Note that checking the encryption key is insufficient. S3 uses the AWS managed CMK when the algorithm is set, but the key isn’t.

If you specify x-amz-server-side-encryption:aws:kms, but don’t provide x-amz-server-side-encryption-aws-kms-key-id, Amazon S3 uses the AWS managed CMK in AWS KMS to protect the data.

Source: PutObject API Reference

Share this article: Tweet this post / Post on LinkedIn