0

I'm trying to create a private endpoint for an S3 bucket via Cloudfront using signed cookies. I've been able to successfully create a signed cookie function in Lambda that adds a cookie for my root domain.

However, when I call the Cloudfront endpoint for the S3 file I'm trying to access, I am getting a 403 error. To make things weirder, I'm able to copy & paste the URL into the browser and can access the file.

We'll call my root domain example.com. My cookie domain is .example.com, my development app URL is test.app.example.com and my Cloudfront endpoint URL is tilesets.example.com

Upon inspection of the call, it seems that the cookies aren't being sent. This is strange because my fetch call has credentials: "include" and I'm calling a subdomain of the cookie domain.

Configuration below:

S3:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>https://*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Cloudfront:

cloudfront

Not sure what I could be doing wrong here. It's especially weird that it works when I go directly to the link in the browser but not when I fetch, so guessing that's a CORS issue.

I've been logging the calls to Cloudfront, and as you can see, the cookies aren't being sent when using fetch in my main app:

#Fields: date time x-edge-location sc-bytes c-ip cs-method cs(Host) cs-uri-stem sc-status cs(Referer) cs(User-Agent) cs-uri-query cs(Cookie) x-edge-result-type x-edge-request-id x-host-header cs-protocol cs-bytes time-taken x-forwarded-for ssl-protocol ssl-cipher x-edge-response-result-type cs-protocol-version fle-status fle-encrypted-fields

2019-09-13  22:38:40    IAD79-C3    369 <IP>    GET <CLOUDFRONT ID>.cloudfront.net  <PATH URL>/metadata.json    403 https://test.app.<ROOT DOMAIN>/ Mozilla/5.0%2520(Macintosh;%2520Intel%2520Mac%2520OS%2520X%252010_14_6)%2520AppleWebKit/537.36%2520(KHTML,%2520like%2520Gecko)%2520Chrome/76.0.3809.132%2520Safari/537.36   -   -   Error   5kPxZkH8n8dVO57quWHurLscLDyrOQ0L-M2e0q6X5MOe6K9Hr3wCwQ==    tilesets.<ROOT DOMAIN>  https   281 0.000   -   TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Error   HTTP/2.0    -   -

Whereas when I go to the URL directly in the browser:

#Fields: date time x-edge-location sc-bytes c-ip cs-method cs(Host) cs-uri-stem sc-status cs(Referer) cs(User-Agent) cs-uri-query cs(Cookie) x-edge-result-type x-edge-request-id x-host-header cs-protocol cs-bytes time-taken x-forwarded-for ssl-protocol ssl-cipher x-edge-response-result-type cs-protocol-version fle-status fle-encrypted-fields

2019-09-13  22:32:38    IAD79-C1    250294  <IP>    GET <CLOUDFRONT ID>.cloudfront.net  <PATH URL>/metadata.json    200 -   Mozilla/5.0%2520(Macintosh;%2520Intel%2520Mac%2520OS%2520X%252010_14_6)%2520AppleWebKit/537.36%2520(KHTML,%2520like%2520Gecko)%2520Chrome/76.0.3809.132%2520Safari/537.36   -   CloudFront-Signature=<SIGNATURE>;%2520CloudFront-Key-Pair-Id=<KEY PAIR>;%2520CloudFront-Policy=<POLICY> Miss    gRkIRkKtVs3WIR-hI1fDSb_kTfwH_S2LsJhv9bmywxm_MhB7E7I8bw==    tilesets.<ROOT DOMAIN>  https   813 0.060   -   TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Miss    HTTP/2.0    -   -

Any thoughts?

stdmn
  • 63
  • 2
  • 8

1 Answers1

2

You have correctly diagnosed that the issue is that your cookies aren't being sent.

A cross-origin request won't include cookies with credentials: "include" unless the origin server also includes permission in its response headers:

Access-Control-Allow-Credentials: true

And the way to get S3 to allow that is not obvious, but I stumbled on the solution following a lead found in this answer.

Modify your bucket's CORS configuration to remove this:

<AllowedOrigin>*</AllowedOrigin>

...and add this, instead, specifically listing the origin you want to allow to access your bucket (from your description, this will be the parent domain):

<AllowedOrigin>https://example.com</AllowedOrigin>

(If you need http, that needs to be listed separately, and each domain you need to allow to access the bucket using CORS needs to be listed.)

This changes S3's behavior to include Access-Control-Allow-Credentials: true. It doesn't appear to be explicitly documented.

Do not use the following alternative, even though it would also work, without understanding the implications.

<AllowedOrigin>https://*</AllowedOrigin>

This also results in Access-Control-Allow-Credentials: true, so it "works" -- but it allows cross-origin from anywhere, which you likely do not want. With that said, do bear in mind that CORS is nothing more than a permissions mechanism that is applicable to well-behaved, non-malicious web browsers, only -- so setting the allowed origin settings to only allow the correct domain is important, but it does not magically secure your content against unauthorized access from elsewhere. I suspect you are aware of this, but it is important to keep in mind.

After these changes, you'll need to clear the browser cache and invalidate the CloudFront cache, and re-test. Once the CORS headers are being set correctly, your browser should send cookies, and the issue should be resolved.

Michael - sqlbot
  • 169,571
  • 25
  • 353
  • 427
  • Thanks for the detailed response. Unfortunately, I'm still getting the same errors. I'm wondering if it has something to do with Route53 rerouting the URL and cookies getting lost during that process. I'm also wondering if it may be some peculiarity with the fetch call, so I may try a full XMLHttpRequest to see if I can troubleshoot the problem there. Unfortunately, with all the redeploying, invalidation and publishing I have to do to get the domains right, it takes a while to test out different options. – stdmn Sep 15 '19 at 16:19
  • @stdmn Route 53 doesn't touch the request, it just provides resolution if the endpoint. If it were not set up correctly, you wouldn't see a CloudFront log entry. I tested the CORS configuration, above, before posting the answer, so I'm confident that it's valid, but there might be something additional. Did you configure the CloudFront cache behavior settings to whitelist/forward the 3 CORS request headers to S3? You need to have `Origin`, `Access-Control-Request-Method`, and `Access-Control-Request-Headers` whitelisted. – Michael - sqlbot Sep 15 '19 at 20:48
  • Got it! It was a stupid error in my front-end code. Accepted this answer, thanks for the help! – stdmn Sep 16 '19 at 18:27