aws-cloudfront-s3 - support for s3 websites
When using your CloudFrontToS3 construct it creates an S3 bucket and CloudFront very easily. https://docs.aws.amazon.com/solutions/latest/constructs/aws-cloudfront-s3.html
This configuration works really well for hosting individual files. However when you want to host a static site on s3 the it requires a lot of manual overrides.
S3 Bucket props for switching on website hosting seems simple:
const construct = new CloudFrontToS3(this, 'my-website', {
bucketProps: {
websiteErrorDocument: '/404/index.html',
websiteIndexDocument: 'index.html'
}
});
But then when deployed, the url defined in the CloudFront Origin is the S3 bucket REST url not the Website url:
REST API endpoints use this format: DOC-EXAMPLE-BUCKET.s3.amazonaws.com
Website endpoints use this format: DOC-EXAMPLE-BUCKET.s3-website-us-east-1.amazonaws.com
Then there are further requirements for s3 bucket website hosting:
- Objects in the bucket must be publicly accessible.
- Objects in the bucket can't be encrypted by AWS Key Management Service (AWS KMS).
- The bucket policy must allow access to s3:GetObject.
- If the bucket policy grants public read access, then the AWS account that owns the bucket must also own the object.
- The requested objects must exist in the bucket.
- Amazon S3 Block Public Access must be disabled on the bucket.
- If Requester Pays is enabled, then the request must include the request-payer parameter.
- If you're using a Referrer header to restrict access from CloudFront to your S3 origin, then review the custom header.
https://aws.amazon.com/premiumsupport/knowledge-center/s3-website-cloudfront-error-403/
All this adds up to hours of work per developer to find the correct configuration overrides, to get static hosting working with CloudFront.
Use Case
It's a common use-case to host static sites on S3, not just hosting static files. So let's make it even easier to use CDK to deploy a static site, and also give developers an AWS opinionated way to securely deploy static sites!
Proposed Solution
Set a type, to automatically configure the settings between file hosting and website hosting:
const construct = new CloudFrontToS3(this, 'my-website', {
type: 'website'
});
Other
Related issues others have faced trying to implement these settings themselves: https://stackoverflow.com/questions/34060394/cloudfront-s3-website-the-specified-key-does-not-exist-when-an-implicit-ind/64397720
I am also keen to see this, especially in a production context where the deployment of new versions require zero downtime: eg. Blue-Green etc.
A construct showing the way in this regard would be a key resource!
Looks like there is a recommended approach for static sites using CloudFront and private S3 buckets (set using Origin Access Identities): https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/
It involves adding a Lambda function to redirect requests (instead of using s3 website redirects).
However when I try adding the Lambda function it means you have to override the default originConfigs (S3 bucket and security headers Lambda). This removes helpful features which are already supported.
This is as far as I got before things started breaking:
import { CloudFrontToS3 } from '@aws-solutions-constructs/aws-cloudfront-s3';
import * as lambda from '@aws-cdk/aws-lambda';
import { LambdaEdgeEventType } from '@aws-cdk/aws-cloudfront';
const lambdaFunction = new lambda.Function(this, 'Function', {
runtime: lambda.Runtime.NODEJS_12_X,
code: lambda.Code.fromAsset('./lambdas'),
handler: 'index.handler',
});
const construct = new CloudFrontToS3(this, 'my-website', {
cloudFrontDistributionProps: {
originConfigs: {
behaviors: [
{
isDefaultBehavior: true,
lambdaFunctionAssociations: [
{
eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
lambdaFunction: lambdaFunction.latestVersion
}
]
}
]
}
},
});
I wonder if the security header lambda could be extended to offer the redirect logic needed? since it's already created and supported. It's only five lines of code:
exports.handler = (event, context, callback) => {
var request = event.Records[0].cf.request;
var olduri = request.uri;
var newuri = olduri.replace(/\/$/, '\/index.html');
request.uri = newuri;
return callback(null, request);
};
Look like there was a changed made in the latest release:
v1.67.0
public readonly cloudFrontWebDistribution: cloudfront.CloudFrontWebDistribution;
https://github.com/awslabs/aws-solutions-constructs/blob/6af666f367c90a665534907bac9b4604bc134bdd/source/patterns/%40aws-solutions-constructs/aws-cloudfront-s3/lib/index.ts#L52
v1.68.0
public readonly cloudFrontWebDistribution: cloudfront.Distribution;
https://github.com/awslabs/aws-solutions-constructs/blob/master/source/patterns/%40aws-solutions-constructs/aws-cloudfront-s3/lib/index.ts#L52
This allows you to use the new method addBehavior() after the stack has been created:
import { CloudFrontToS3 } from '@aws-solutions-constructs/aws-cloudfront-s3';
import { IBucket } from '@aws-cdk/aws-s3';
import * as lambda from '@aws-cdk/aws-lambda';
import { LambdaEdgeEventType } from '@aws-cdk/aws-cloudfront';
import { S3Origin } from '@aws-cdk/aws-cloudfront-origins';
const construct = new CloudFrontToS3(this as any, 'my-website');
const lambdaFunction = new lambda.Function(this, 'Function', {
runtime: lambda.Runtime.NODEJS_12_X,
code: lambda.Code.fromAsset('./lambdas'),
handler: 'redirect.handler',
});
construct.cloudFrontWebDistribution.addBehavior('/*', new S3Origin(construct.s3Bucket as IBucket), {
edgeLambdas: [
{
functionVersion: lambdaFunction.currentVersion,
eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
},
],
});
/lambdas/redirect.js
exports.handler = (event, context, callback) => {
var request = event.Records[0].cf.request;
var olduri = request.uri;
var newuri = olduri.replace(/\/$/, '\/index.html');
request.uri = newuri;
return callback(null, request);
};
kmturley@ Are you still facing any issue with the updates rolled out in v1.67.0+ ?
@hnishar In 1.68+ It's now working as expected, I used the addBehavior method to extend the functionality and support static websites with redirects from '/' to '/index.html'. As mentioned in the blog post: https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/
However I think this feature is so common, it should be supported by either this construct, or a new construct specifically for static websites.
This example also shows the number of steps we have to repeat to setup a static site: https://github.com/aws-samples/aws-cdk-examples/blob/master/typescript/static-site/static-site.ts
I'd like to strongly agree with @kmturley
it's very surprising that isn't hanlded by a key like website.