CloudFront function to redirect empty document requests to index.html
URL rewrite to append index.html to the URI for single page applications
If you set up an s3 website serving content generated by Hugo or Jekyll, everything works fine, until you decide to use CloudFront to add SSL for example. The website breaks (Access Denied errors) as soon as you try to open the posts. The reason being that when you open https://website.com/blog
, you are actually loading https://website.com/blog/index.html
. You don’t have to specify that document as the index.html is just default assumption that any web server should be dealing with automatically. Problem is, it doesn’t work with CloudFront (because it is not a web server, neither is s3 and CF talks to the s3 bucket through REST API calls, not HTTP/S). Desperate users open their s3 buckets to the public, set up s3 website endpoint and accellerate that through CloudFront. But there is a better option. Amazon suggests to use a new shiny thing (as usual) - CloudFront functions and specifically url-rewrite-function.
But wait, I can hear you say ‘but there is the default root object setting in CF!’ - yes, there is, but it only applies to the root document (so https://website.com/index.html not website.com/…/index.html) default root object
Deployment steps
Clone the official AWS repo
git clone https://github.com/aws-samples/amazon-cloudfront-functions.git
Create function
cd amazon-cloudfront-functions
aws cloudfront create-function --name url-rewrite-single-page-apps --function-config Comment="Function to redirect empty doc requests to index.html",Runtime=cloudfront-js-1.0 --function-code fileb://url-rewrite-single-page-apps/index.js
If the function was created correctly, the JSON output should look similar to this (make note of the ETag value, you’ll need it in a second!):
{
"Location": "https://cloudfront.amazonaws.com/2020-05-31/function/arn:aws:cloudfront::<account id>:function/url-rewrite-single-page-apps",
"ETag": "EXXXXXXXXXXXX",
"FunctionSummary": {
"Name": "url-rewrite-single-page-apps",
"Status": "UNPUBLISHED",
"FunctionConfig": {
"Comment": "Function to redirect empty doc requests to index.html",
"Runtime": "cloudfront-js-1.0"
},
"FunctionMetadata": {
"FunctionARN": "arn:aws:cloudfront::<account id>:function/url-rewrite-single-page-apps",
"Stage": "DEVELOPMENT",
"CreatedTime": "2021-12-26T08:43:50.950Z",
"LastModifiedTime": "2021-12-26T08:43:50.950Z"
}
}
}
Test the function
To validate that the function is working as expected, you can use the JSON test objects in the test-objects
directory. To test, use the test-function
CLI command as shown in the following example:
$ aws cloudfront test-function --if-match <ETag> --name url-rewrite-single-page-apps --event-object fileb://url-rewrite-single-page-apps/test-objects/file-name-no-extension.json
If the function has been set up correctly, you should see the uri
being updated to index.html
in the FunctionOutput
JSON object:
{
"TestResult": {
"FunctionSummary": {
"Name": "url-rewrite-single-page-apps",
"Status": "UNPUBLISHED",
"FunctionConfig": {
"Comment": "",
"Runtime": "cloudfront-js-1.0"
},
"FunctionMetadata": {
"FunctionARN": "arn:aws:cloudfront::1234567890:function/url-rewrite-single-page-apps",
"Stage": "DEVELOPMENT",
"CreatedTime": "2021-04-09T21:53:20.882000+00:00",
"LastModifiedTime": "2021-04-09T21:53:21.001000+00:00"
}
},
"ComputeUtilization": "14",
"FunctionExecutionLogs": [],
"FunctionErrorMessage": "",
"FunctionOutput": "{\"request\":{\"headers\":{\"host\":{\"value\":\"www.example.com\"},\"accept\":{\"value\":\"text/html\"}},\"method\":\"GET\",\"querystring\":{\"test\":{\"value\":\"true\"},\"arg\":{\"value\":\"val1\"}},\"uri\":\"/blog/index.html\",\"cookies\":{\"loggedIn\":{\"value\":\"false\"},\"id\":{\"value\":\"CookeIdValue\"}}}}"
}
}
Publish the function.
Please note that the JSON response states "Status" : "UNPUBLISHED"
, so the next step is to publish the function.
aws cloudfront publish-function --name url-rewrite-single-page-apps --if-match <ETag>
And - if successful - JSON response should look similar to:
{
"FunctionSummary": {
"Name": "url-rewrite-single-page-apps",
"Status": "UNASSOCIATED",
"FunctionConfig": {
"Comment": "Function to redirect empty doc requests to index.html",
"Runtime": "cloudfront-js-1.0"
},
"FunctionMetadata": {
"FunctionARN": "arn:aws:cloudfront::<account id>:function/url-rewrite-single-page-apps",
"Stage": "LIVE",
"CreatedTime": "2021-12-26T08:47:42.111Z",
"LastModifiedTime": "2021-12-26T08:47:42.111Z"
}
}
}
Configuration of the function.
Since it’s created and published, now it needs to be configured.
aws cloudfront get-distribution-config --id <distribution name, NOT ETag!> --output json > dist-cfg.json
Edit the dist-cfg.json
:
- Change the
ETag
key toIfMatch
- Modify
FunctionAssociation
to the following:
"FunctionAssociations": {
"Quantity": 1,
"Items" : [
{
"EventType" : "viewer-request",
"FunctionARN":"arn:aws:cloudfront::<account id>:function/url-rewrite-single-page-apps"
}
]
},
Modify CloudFront distribution by adding ETag
.
Distributions->Distribution ID->Origins->Edit->Add custom header - optional Add ETag
, value EXXXXXXXXXXXX
Update the distribution
aws cloudfront update-distribution --id <CF Distribution ID> --cli-input-json fileb://dist-cfg.json
Other considerations.
Somewhere along the way you should have locked down your s3 bucket so it’s not public, and add a policy (can be done automatically through CloudFront->Distributions->
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity <ID>"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::<bucket name>/*"
}
]
}
(c) Dawid Krysiak https://itisoktoask.me/ http://www.krysiak.biz/