Hugo multilingual site deploy to aws s3 error "AccessDenied" - amazon-s3

I'm hosting a static website on AWS S3, served through AWS Cloudfront. Site is build with Hugo in multilingual mode. So it redirects from index page "/" to default lang index page url /en . On this page I get error access denied. When I manually type URL /en/index.html page loaded normally.
How should I setup AWS bucket or Hugo to show page properly?

Find answer in this post
CloudFront + S3 Website: "The specified key does not exist" when an implicit index document should be displayed
I need to use the bucket's web site hosting endpoint as the origin domain name in Cloudfront setup

One way of doing it is to follow the link shared by #qwelp,whereby the solution states that if you are using S3 REST API endpoint as opposed to the static website hosting, you would need to change the origin of your CloudFront from "S3" to "custom" origin.
The other way to solve this is implement a lambda edge function that redirects all user requests that requests files in hugo sub-directories. As quoted inAWS Knowledge Center here ;
use Lambda#Edge to be able to use CloudFront with an S3 origin access
identity and serve a default root object on subdirectory URLs.
From AWS Knowledge Center
Why isn't CloudFront returning my default root object from a
subdirectory? Issue Why isn't Amazon CloudFront returning my default
root object from a subfolder or subdirectory? Resolution The default
root object feature for CloudFront supports only the root of the
origin that your distribution points to. CloudFront doesn't return
default root objects in subdirectories. For more information, see
Specifying a Default Root Object.
If your CloudFront distribution must return the default root object
from a subfolder or subdirectory, you can integrate Lambda#Edge with
your distribution. For an example configuration, see Implementing
Default Directory Indexes in Amazon S3-backed Amazon CloudFront
Origins Using Lambda#Edge.
Important: You'll be charged an additional fee when you use Lambda#Edge. For more information, see Lambda#Edge Pricing Details.
As stated in AWS Knowledge Center here
"In this example, you use the compute power at the CloudFront edge to inspect the request as it’s coming in from the client. Then re-write the request so that CloudFront requests a default index object (index.html in this case) for any request URI that ends in ‘/’."
The Lambda Edge Function in JS:
'use strict';
exports.handler = (event, context, callback) => {
// Extract the request from the CloudFront event that is sent to Lambda#Edge
var request = event.Records[0].cf.request;
// Extract the URI from the request
var olduri = request.uri;
// Match any '/' that occurs at the end of a URI. Replace it with a default index
var newuri = olduri.replace(/\/$/, '\/index.html');
// Log the URI as received by CloudFront and the new URI to be used to fetch from origin
console.log("Old URI: " + olduri);
console.log("New URI: " + newuri);
// Replace the received URI with the URI that includes the index page
request.uri = newuri;
// Return to CloudFront
return callback(null, request);
};
Ensure that you gave the Lambda#Edge function IAM permissions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:*:*:*"
]
}
]
}

I suffered the same problem and I ended up setting up my S3 bucket as public, I didn't want to pay an extra Lambda for handling this.
Here is a post explaining the situation.

Related

Modifying CloudFront Origin Request for S3

I have two S3 buckets behind a CloudFront distribution.
One bucket is the default behavior origin; one is an additional behavior under a specific path pattern.
The problem is that CloudFront uses the whole path to request the bucket object, which requires the exact path to exist as part of the object name in the bucket.
Path pattern auth/* requires an auth/index.html object if I request .../auth/index.html.
If I change the path, I would have to change all the object names in the bucket.
Is there a way around this?
Thanks to a comment, I found a solution with Lambda#Edge.
If you used the path pattern auth/* in a CloudFront behavior so that all URLs starting with auth/ are routed to a specific S3 bucket, and you want to save all your files in that bucket without an auth/ prefix. Then you need to add this edge Lambda to your CloudFront behavior:
const prefix = "auth/"
exports.handler = async (e) => {
const { request } = e.Records.pop().cf;
if (request.uri.contains(prefix))
request.uri = request.uri.replace(prefix, "");
return request;
});
The event type is origin request.
On a request to auth/index.html CloudFront will fetch index.html from your associated bucket and not auth/index.html
Since origin requests are only sent to the S3 bucket when a cache miss happens, this should be a quite cheap solution.

Point subfolder in S3 bucket to *.buildmywebsite domain [duplicate]

I've got a pretty specific problem here, we've got a system that we already have and maintain, the system involves using subdomains to route people to specific apps.
on a traditional server that goes like follows; we have a wildcard subdomain, *.domain.com that routes to nginx and serves up a folder
so myapp.domain.com > nginx > serves up myapp app folder > myapp folder contains a static site
I'm trying to migrate this in some way to AWS, I basically need to do a similar thing in AWS, I toyed with the idea of putting each static app into an s3 bucket and then the wildcard domain in route 53 but i'm unsure how s3 would know which folder to serve up as that functionality isn't part of route 53
Anyone have any suggestions?
Thanks for all your help
CloudFront + Lambda#Edge + S3 can do this "serverless."
Lambda#Edge is a CloudFront enhancement that allows attributes of requests and responses to be represented and manipulated as simple JavaScript objects. Triggers can be provisioned to fire during request processing, either before the cache is checked ("viewer request" trigger) or before the request proceeds to the back-end ("origin server", an S3 web site hosting endpoint, in this case) following a cache miss ("origin request" trigger)... or during response processing, after the response is received from the origin but before it is considered for storing in the CloudFront cache ("origin response" trigger), or when finalizing the response to the browser ("viewer response" trigger). Response triggers can also examine the original request object.
The following snippet is something I originally posted at the AWS Forums. It is an Origin Request trigger which compares the original hostname to your pattern (e.g. the domain must match *.example.com) and if it does, the hostname prefix subdomain-here.example.com is request is served from a folder named for the subdomain.
lol.example.com/cat.jpg -> my-bucket/lol/cat.jpg
funny-pics.example.com/cat.jpg -> my-bucket/funny-pics/cat.jpg
In this way, static content from as many subdomains as you like can all be served from a single bucket.
In order to access the original incoming Host header, CloudFront needs to be configured to whitelist the Host header for forwarding to the origin even though the net result of the Lambda function's execution will be to modify that value before the origin acually sees it.
The code is actually very simple -- most of the following is explanatory comments.
'use strict';
// if the end of incoming Host header matches this string,
// strip this part and prepend the remaining characters onto the request path,
// along with a new leading slash (otherwise, the request will be handled
// with an unmodified path, at the root of the bucket)
const remove_suffix = '.example.com';
// provide the correct origin hostname here so that we send the correct
// Host header to the S3 website endpoint
const origin_hostname = 'example-bucket.s3-website.us-east-2.amazonaws.com'; // see comments, below
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
const host_header = headers.host[0].value;
if(host_header.endsWith(remove_suffix))
{
// prepend '/' + the subdomain onto the existing request path ("uri")
request.uri = '/' + host_header.substring(0,host_header.length - remove_suffix.length) + request.uri;
}
// fix the host header so that S3 understands the request
headers.host[0].value = origin_hostname;
// return control to CloudFront with the modified request
return callback(null,request);
};
Note that index documents and redirects from S3 may also require an Origin Response trigger to normalize the Location header against the original request. This will depend on exactly which S3 website features you use. But the above is a working example that illustrates the general idea.
Note that const origin_hostname needs to be set to the bucket's endpoint hostname as configured in the CloudFront origin settings. In this example, the bucket is in us-east-2 with the web site hosting feature active.
Create a Cloudfront distribution
Add all the Alternate CNAMEs records in the cloudfront distribution
Add a custom origin as the EC2 server.
Set behaviours as per your requirements.
Configure nginx virtualhosts in the server to route to specific folders.

AWS S3 Static hosing: Routing rules doesn't work with cloudfront

I am using AWS S3 static web hosting for my VueJs SPA app. I have setup routing rules in S3 and it works perfectly fine when I access it using S3 static hosting url. But, I also have configured CloudFront to use it with my custom domain. Since single page apps need to be routed via index.html, I have setup custom error page in cloudfront to redirect 404 errors to index.html. So now routing rules I have setup in S3 no longer works.
What is the best way to get S3 routing rules to work along with CloudFront custom error page setup for SPA?
I think I am a bit late but here goes anyway,
Apparently you can't do that if you are using S3 REST_API endpoints (example-bucket.s3.amazonaws.com) as your origin for your CloudFront distribution, you have to use the S3 website url provided by S3 as the origin (example-bucket.s3-website-[region].amazonaws.com). Also, objects must be public you can't lock your bucket to the distribution by origin policy.
So,
Objects must be public.
S3 bucket website option must be turned on.
Distribution origin has to come from the S3 website url, not the rest api endpoint.
EDIT:
I was mistaking, actually, you can do it with the REST_API endpoint too, you only have to create a Custom Error Response inside your CloudFront distribution, probably only for the 404 and 403 error codes, set the "Customize Error Response" option to "yes", Response Page Path to "/index.html" and HTTP Response Code to "200". You can find that option inside your distribution and the error pages tab if you are using the console.

Only allow S3 files on my site

I want to allow s3 files only on my site. They should be able to download files from my site ony. Not from any other site. Please tell me how i can do that.
I tried bucket policy and cros configuration but it doesn't work.
You could restrict origin using CORS headers https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html this would only block cross-site resources directly displayed or processed, not files to be downloaded
As a solution - make the S3 bucket private (not public) and your site can generate signed expiring (temporary) url for S3 resources
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html
there are multiple blogs and exampled just search for it
You could code your Bucket Policy to Restrict Access to a Specific HTTP Referrer:
{
"Version":"2012-10-17",
"Id":"http referer policy example",
"Statement":[
{
"Sid":"Allow get requests originating from www.example.com and example.com.",
"Effect":"Allow",
"Principal":"*",
"Action":"s3:GetObject",
"Resource":"arn:aws:s3:::examplebucket/*",
"Condition":{
"StringLike":{"aws:Referer":["http://www.example.com/*","http://example.com/*"]}
}
}
]
}
However, this can be faked so it is not a guaranteed method of controlling access.
If you really wish to control who can access your content, your application should verify the identity of the user (eg username/password) and then generate time-limited Amazon S3 Pre-Signed URLs. This is the way you would normally serve private content, such as personal photos and PDF files.

Resolve S3 static page from custom path with CloudFront

I've got an S3 bucket configured to host a static website, and if I browse to the bucket URL it shows the index.html file. I also have a CloudFront distribution to show another S3 bucket under a custom domain. Is there any chance I could configure CloudFront to serve one bucket from the root and another from a custom path? So:
mydomain.com -> bucket1/index.html
mydomain.come/some-path -> bucket2/index.html
I already created an origin for the bucket and set up a path pattern for it and some-path, but I'm getting 403 Forbidden, even though if I browse to the origin directly I can see the webpage.
This configuration works fine, but it requires that the object in bucket2 be located at some-path/index.html inside bucket2.
The path pattern you configured in the cache behavior is still part of the request path, so it is forwarded to the origin.
CloudFront does not support removing part of the request path before forwarding the request to the origin.