11

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/*"
        }
    ]
}
carpiediem
  • 1,918
  • 22
  • 41
  • 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 `AccessDenied`? What about if you go to `http://your.bucket.name.s3.amazonaws.com/index.html`? – Michael - sqlbot Oct 25 '18 at 10:06
  • 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 Oct 26 '18 at 01:12
  • Any ideas that might help me narrow down the problem some more? – carpiediem Oct 30 '18 at 16:04
  • 1
    I believe you have more than one issue. I'll look through your question again within a few hours, time permitting. – Michael - sqlbot Oct 30 '18 at 16:48
  • 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 Oct 31 '18 at 04:40
  • I've just realized that giving my CloudFront-specific IAM account access to the bucket doesn't automatically give it permission to read individual objects in the bucket. When I specifically added the account to a few of the objects, they were accessible through https://subdomain.mydomain.com/path.js. So, I need to either (a) set the permissions of all 71 objects individually, (b) modify my bucket policy to give the right permissions, or (c) change my deployment code to give permissions to that account when new objects are uploaded to S3. **Any tips on the best option?** – carpiediem Oct 31 '18 at 04:51
  • I'm using the AWS command line tools to sync my repository to S3. I'll give this a try: `aws s3 sync build/ s3://subdomain.mydomain.com --grants read=id=CANONICALIDLONGSTRING` (from https://docs.aws.amazon.com/cli/latest/reference/s3/sync.html) – carpiediem Oct 31 '18 at 05:06
  • It might be CloudFront cache, but it seems like I'm still getting the `NoSuchKey` error... – carpiediem Oct 31 '18 at 05:17
  • Nevermind, seems to work. No I just need to handle routing issues: http://aserafin.pl/2016/03/23/react-router-on-amazon-s3/ – carpiediem Oct 31 '18 at 05:28
  • I'm so close, but so far. The subdomain is serving HTML, now, but it's the `index.html` from a previous build, which points to a JavaScript chunk that no longer exists (`main.99a08c07f7b367776977.chunk.js` has been replaced in the most recent build by `main.520950622f00cc72bd68.chunk.js`). `index.html` in my local repo is correct, but the HTML served by CloudFront still points to the old JS. Invalidations don't sem to have any affect... – carpiediem Oct 31 '18 at 06:30
  • re: default root object, [*"Enter only the object name, for example, `index.html`. Do not add a `/` before the object name."*](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html) That would explain the "no such key" error. – Michael - sqlbot Oct 31 '18 at 07:49
  • Yeah, I fixed that, but it was too late to edit the comment. It's `index.html` now. Now I'm curious if I have a Route53 issue, since xxxxxxxxxxx.cloudfront.net is serving the most recent deployment, but subdomain.mydomain.com is not. – carpiediem Oct 31 '18 at 09:05

2 Answers2

9

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.

carpiediem
  • 1,918
  • 22
  • 41
0

Apart from the solutions the other answer mentioned, there's another solution too that can surely resolve this error if other answers could not. While creating a distribution for a bucket, you should not choose an Origin domain from the dropdown where there are bucket names listed, instead go for a custom one (by filling it by yourself), which you will find in your bucket's Properties tab under the heading of Bucket website endpoint. You may find more details regarding this solution in the accepted answer to this question: CloudFront + S3 Website: "The specified key does not exist" when an implicit index document should be displayed

Daniyal Malik
  • 578
  • 2
  • 6
  • 15