6

I would like to use AWS's Server Side Encryption (SSE) with the AWS Key Management Service (KMS) to encrypt data at rest in S3. (See this AWS blog post detailing SSE-KMS.)

However, I also have the requirement that I use Cloudfront Presigned URLs.

How can I set up a Cloudfront distribution to use a key in AWS KMS to decrypt and use S3 objects encrypted at rest?

(This Boto3 issue seems to be from someone looking for the same answers as me, but with no results).

Chris W.
  • 37,583
  • 36
  • 99
  • 136

3 Answers3

6

This was previously not possible because CloudFront didn't support it and because (as I mentioned in comments on John's answer -- which was on the right track) there was no way to roll-your-own solution with Lambda@Edge because the X-Amz-Cf-Id request header --generated on the back side of CloudFront and visible only to S3, not to the trigger invocation -- would invalidate any signature you tried to add to the request inside a Lambda@Edge trigger, because signing of all X-Amz-* headers is mandatory.

But the X-Amz-Cf-Id header value is now exposed to a Lambda@Edge trigger function in the event structure -- not with the other request headers, but as a simple string attribute -- at event.Records[0].cf.config.requestId.

With that value in hand, you can use the execution role credentials and the built-in SDK in the Lambda@Edge environment to generate a signature and and add the necessary headers (including an Authorization header with the derived credential identifier and freshly-generated signature) to the request.

This setup does not use an Origin Access Identifier (OAI) because the Lambda@Edge trigger's IAM Execution Role is used instead of an OAI to persuade S3 that the request is authorized.

Achraf Souk has published an official AWS blog post explaining the solution from start to finish.

https://aws.amazon.com/blogs/networking-and-content-delivery/serving-sse-kms-encrypted-content-from-s3-using-cloudfront/

Michael - sqlbot
  • 169,571
  • 25
  • 353
  • 427
1

Use S3 Presigned URLs. This AWS article discusses how to generate urls using Java, but this is easily ported to another language.

Server-Side Encryption with AWS Key Management Service (SSE-KMS)

John Hanley
  • 74,467
  • 6
  • 95
  • 159
  • I suspect the answer is more complicated than that. CloudFront won't pass-through an S3 signed URL cleanly, because the `X-Amz-Cf-Id` header that CloudFront injects into the back-end request isn't user-accessible, yet must be signed because it is an `X-Amz-*` header and these require inclusion in V4 signatures. So S3 should reject such a request. I'm not sure CloudFront can work with any form of SSE other than SSE-S3, which works transparently. – Michael - sqlbot May 04 '18 at 08:50
  • The only scenario I can think of that *might* work would be if the bucket is in a V4-only region, because in those regions, a CloudFront Origin Access Identity would generate a V4 signature for the request. For V2/V4 regions, OAIs generate V2 signatures. There may still be an unsupported aspect, here, because of the kind of principal that an OAI is. Testing required to confirm or deny. – Michael - sqlbot May 04 '18 at 09:00
  • @Michael-sqlbot Have you tested this scenario? I'm curious if something could be done using Lambda@Edge which, allegedly, use V4 signature for accessing the origin. – Laurent Jalbert Simard Sep 05 '18 at 21:15
  • @LaurentJalbertSimard I haven't tested it, but I found this on the official forum: [*"CloudFront currently does not support KMS server-side encryption for S3. The reason this does not work is that viewer requests through CloudFront does not have access to the KMS credentials used to encrypt the s3 objects. We would recommend using signed URLs and Origin Access Identities without KMS. We are aware of this limitation and a feature request is submitted to the CloudFront team."*](https://forums.aws.amazon.com/thread.jspa?messageID=819324&tstart=0) – Michael - sqlbot Sep 06 '18 at 00:38
  • 1
    Lambda@Edge could be used by doing a fetch from S3 using the SDK inside the edge function, instead of letting the OAI sign the request, but you're limited there to 1MB maximum response bodies. – Michael - sqlbot Sep 06 '18 at 00:39
  • @Michael-sqlbot thanks for taking the time to answer, I will add a +1 to the feature request through my account manager. – Laurent Jalbert Simard Sep 11 '18 at 21:56
0

The following setup works for us:

In your application, generate a signed URL that Cloudfront can validate (https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html).

Instead of using OAI, you create a Lambda@Edge origin request function as per: https://aws.amazon.com/blogs/networking-and-content-delivery/serving-sse-kms-encrypted-content-from-s3-using-cloudfront/

Please note that if your bucket contains an '.' (ours did), there's a bug in the JS code that can be mitigated with something like:

// Infer the region from the host header
// const region = options.host.split('.')[2];

const hostArr = options.host.split('.');
const region = hostArr [hostArr.length - 3];

Lastly, we added an origin response Lambda@Edge to wash away headers that we do not want exposed. Esp the X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id that includes the AWS Account ID.

Lastly I'd like to comment on the above statement/comment that Lamda@Edge response bodies are limited to 1 MB, this only applies to content generated (or modified if you include the body) by the lambda function. When using the Lambda@Edge function above, the response from the S3 origin has no such limit, we are serving objects >> 1MB (normally 100+ MB).

Niclas
  • 81
  • 3