How to enforce a CMK on almost all of a bucket, but allow SSE-S3 for a specific prefix?

0

I'm looking at a bucket that has a default SSE-KMS customer-managed key (CMK), and was previously misconfigured with a resource policy that was intended to require the use of that CMK for every PutObject, but was coded with an incorrect (root) principal, and so never enforced that rule.

Thankfully, nearly all users of that bucket just let the encryption default. But... I've discovered that one prefix in that bucket is used as a destination for Redshift UNLOAD commands, which cannot use a CMK; it uses SSE-S3.

Is it possible to write a rule that:

  • Requires the CMK for every principal and every object except that prefix,
  • Does not require encryption headers on the actual PutObject request (e.g. aws cp foobar s3://my_bucket/not-redshift/foobar should encrypt with the default key with no difficulty)
  • and preferably requires SSE-S3 for every principal and every object that is named with that prefix ?

Or do I need to split this prefix out onto its own bucket to get that behavior?

Just so nobody runs into these dead-ends:

  • PutObject does not support conditions based on the s3:prefix attribute.
  • While default encryption provides a virtual s3:x-amz-server-side-encryption-aws-kms-key-id value that can be used in resource policy conditions, it does not provide a virtual x-amz-server-side-encryption value, AFAICT.
2 Answers
0

Have you tried with a Service Control Policy and add Condition to not apply to a specific path? Just an idea as I haven’t tested that approach.

profile picture
EXPERT
answered 6 months ago
  • Thanks! I might have gone down that route, but all I needed in the end was NotResource.

0

I got help from AWS Support on this (thanks, Manish!). The trick is the NotResource clause, which I'd never noticed in the documentation before:

        {
            "Sid": "DenyWrongKey",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:PutObject",
            "NotResource": "arn:aws:s3:::MYBUCKET/redshift_exports/*",
            "Condition": {
                "ArnNotEquals": {
                    "s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:REGION:ACCOUNT:key/KEY_ID"
                }
            }
        },
        {
            "Sid": "RequireRedshiftBehaviorInRedshiftPrefix",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::MYBUCKET/redshift_exports/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:x-amz-server-side-encryption": "AES256"
                }
            }
        },

This does exactly what we wanted:

  1. If the PutObject is inside the Redshift prefix, it must have the behavior redshift does.
  2. If it's outside the Redshift prefix, it must be encrypted with the default CMK for this bucket.

Of course, having to use 3 NOTs to make an assertion isn't exactly intuitive (Deny, NotResource, ArnNotEquals). It also doesn't generalize well for default cases; e.g., it's hard to write a rule that says "if the x-amx-acl argument exists, it must have value 'bucket-owner-full-control'", because ...IfExists doesn't work in DENY. The ArnNotEquals works above because in the default case, S3 still acts like that header was provided.

answered 6 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions