-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
static-site.ts
112 lines (98 loc) · 4.28 KB
/
static-site.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env node
import * as route53 from 'aws-cdk-lib/aws-route53';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import * as targets from 'aws-cdk-lib/aws-route53-targets';
import * as cloudfront_origins from 'aws-cdk-lib/aws-cloudfront-origins';
import { CfnOutput, Duration, RemovalPolicy, Stack } from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
import path = require('path');
export interface StaticSiteProps {
domainName: string;
siteSubDomain: string;
}
/**
* Static site infrastructure, which deploys site content to an S3 bucket.
*
* The site redirects from HTTP to HTTPS, using a CloudFront distribution,
* Route53 alias record, and ACM certificate.
*/
export class StaticSite extends Construct {
constructor(parent: Stack, name: string, props: StaticSiteProps) {
super(parent, name);
const zone = route53.HostedZone.fromLookup(this, 'Zone', { domainName: props.domainName });
const siteDomain = props.siteSubDomain + '.' + props.domainName;
const cloudfrontOAI = new cloudfront.OriginAccessIdentity(this, 'cloudfront-OAI', {
comment: `OAI for ${name}`
});
new CfnOutput(this, 'Site', { value: 'https://' + siteDomain });
// Content bucket
const siteBucket = new s3.Bucket(this, 'SiteBucket', {
bucketName: siteDomain,
publicReadAccess: false,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
/**
* The default removal policy is RETAIN, which means that cdk destroy will not attempt to delete
* the new bucket, and it will remain in your account until manually deleted. By setting the policy to
* DESTROY, cdk destroy will attempt to delete the bucket, but will error if the bucket is not empty.
*/
removalPolicy: RemovalPolicy.DESTROY, // NOT recommended for production code
/**
* For sample purposes only, if you create an S3 bucket then populate it, stack destruction fails. This
* setting will enable full cleanup of the demo.
*/
autoDeleteObjects: true, // NOT recommended for production code
});
// Grant access to cloudfront
siteBucket.addToResourcePolicy(new iam.PolicyStatement({
actions: ['s3:GetObject'],
resources: [siteBucket.arnForObjects('*')],
principals: [new iam.CanonicalUserPrincipal(cloudfrontOAI.cloudFrontOriginAccessIdentityS3CanonicalUserId)]
}));
new CfnOutput(this, 'Bucket', { value: siteBucket.bucketName });
// TLS certificate
const certificate = new acm.Certificate(this, 'SiteCertificate', {
domainName: siteDomain,
validation: acm.CertificateValidation.fromDns(zone),
});
new CfnOutput(this, 'Certificate', { value: certificate.certificateArn });
// CloudFront distribution
const distribution = new cloudfront.Distribution(this, 'SiteDistribution', {
certificate: certificate,
defaultRootObject: "index.html",
domainNames: [siteDomain],
minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
errorResponses:[
{
httpStatus: 403,
responseHttpStatus: 403,
responsePagePath: '/error.html',
ttl: Duration.minutes(30),
}
],
defaultBehavior: {
origin: new cloudfront_origins.S3Origin(siteBucket, {originAccessIdentity: cloudfrontOAI}),
compress: true,
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
}
})
new CfnOutput(this, 'DistributionId', { value: distribution.distributionId });
// Route53 alias record for the CloudFront distribution
new route53.ARecord(this, 'SiteAliasRecord', {
recordName: siteDomain,
target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),
zone
});
// Deploy site contents to S3 bucket
new s3deploy.BucketDeployment(this, 'DeployWithInvalidation', {
sources: [s3deploy.Source.asset(path.join(__dirname, './site-contents'))],
destinationBucket: siteBucket,
distribution,
distributionPaths: ['/*'],
});
}
}