7
votes

My intention is to have my static website files (in React, if that's a factor) accessible only via my domain and not directly through S3 URLs. It seems to be working on my own computer (though that might be CloudFront cache from when the bucket was public), but other clients receive only S3 messages in XML. Requesting the domain without any path gives a response. Requesting any path (e.g. /index.html, a file in my bucket) gives a response with the code NoSuchKey.

What am I doing wrong? Here's the current configuration.

  • In Route 53, I'm pointing the appropriate subdomain at the CloudFront distribution with a CNAME record (xxxxxxxxxxx.cloudfront.net.)
  • In ACM, I have a certificate that covers the subdomain (*.mydomain.com)
  • In CloudFront, I have a distribution with those domain name (xxxxxxxxxxx.cloudfront.net) and alternate domain name (subdomain.mydomain.com). - It's enabled and has been in the deployed state for several hours now.
  • It has a single origin, with domain name subdomain.mydomain.com.s3.amazonaws.com
  • I chose to restrict bucket access and selected an existing identity for origin access. I had CloudFront update the bucket policy earlier today.
  • The distribution has a single behavior record which redirects HTTP to HTTPS and only allows GET and HEAD methods
  • My S3 bucket name matches the Route 53 record (subdomain.mydomain.com)
  • Static website hosting is enabled, with both the index and error documents set to index.html
  • The bucket policy was autogenerated. It includes a single identity and limits use to the s3:GetObject action on resource arn:aws:s3:::subdomain.mydomain.com/*
  • CORS configuration is empty
  • Inside the bucket is a React app, with index.html as its entry point.

Edit: my bucket policy (do I need to add another action?)

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EZOBXXXXXXXXX"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::subdomain.mydomain.com/*"
        }
    ]
}
1
Quite a bit of info, here. It's very thorough -- and we genuinely appreciate that -- but much of it is unrelated. So, a couple of checks are in order: If you go directly to http://your.bucket.name.s3.amazonaws.com do you get an XML listing of objects, or do you get XML with <Code>AccessDenied</Code>? What about if you go to http://your.bucket.name.s3.amazonaws.com/index.html? - Michael - sqlbot
Yeah, I figured that most of it is unrelated; I just can't seem to figure out what IS important. Both of the URLs you suggested return AccessDenied, as I'd expect. https://region-code.amazonaws.com/my.bucket.name does the same thing. - carpiediem
Any ideas that might help me narrow down the problem some more? - carpiediem
I believe you have more than one issue. I'll look through your question again within a few hours, time permitting. - Michael - sqlbot
I also set the Default Root Object to /index.html by editing the General tab of my CloudFront distribution, so now the domain root also serves the NoSuchKey error. - carpiediem

1 Answers

4
votes

Two changes got this working nicely. Thanks to Michael for his help.

  1. ~~I modified the permissions of individual objects in my S3 bucket. I had thought that the bucket policy (above) was sufficient but that turned out not to be the case. The bucket policy refers to an origin access identity (OAI) in CloudFront. Now I reference the canonical user ID of that same origin access identity when running the sync command in the AWS command line interface (CLI). In package.json scripts: "deploy": "aws s3 sync build/ s3://subdomain.mydomain.com --delete --grants read=id=S3CANONICALIDOFORIGINACCESSIDENTITY"~~
  2. I specified a Default Root Object as index.html in my CloudFront distribution. (Click the Edit button in the General tab.) This tells CloudFront what to serve if it can't match the request path with an object in the S3 bucket. This is critical for client-side routing, like I'm doing in React.
  3. I had been issuing invalidations to my CloudFront distribution against /index.html, then feeling confused when my app didn't seem to update. I think this was because, although index.html is always served to users, it's never explicitly requested. My routes looked lik / or /dashboard, which were not being invalidated. Now, I clear the CloudFront cache by invalidating /*.

Edit: It's been a few years, but I don't think #1 is necessary. I've set up several distributions since then with only the bucket policy defining permissions.