Last active
August 29, 2022 23:23
-
-
Save btakita/2572df4fae440dc065eb888f53316bdd to your computer and use it in GitHub Desktop.
AWS CDK for a static https website
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
// See https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/static-site/ | |
import { Stack, App, StackProps } from '@aws-cdk/core' | |
import { CONTEXT_ENV } from '@aws-cdk/cx-api' | |
import { StaticSite } from './static-site' | |
type Context = { | |
domain: string, | |
subdomain: string, | |
} | |
const ctx = JSON.parse(process.env[CONTEXT_ENV] as string) as Context | |
export const stage = ctx.subdomain || 'prod' | |
export const base_id = `mystaticsite-${stage}` | |
/** | |
* This stack relies on getting the domain name from CDK context. | |
* Use 'cdk synth -c domain=mystaticsite.com -c subdomain=www' | |
* Or add the following to cdk.json: | |
* { | |
* "context": { | |
* "domain": "mystaticsite.com", | |
* "subdomain": "www" | |
* } | |
* } | |
**/ | |
class MyStaticSiteStack extends Stack { | |
constructor(parent:App, name:string, props:StackProps) { | |
super(parent, name, props) | |
new StaticSite(this, base_id, { | |
domainName: this.node.tryGetContext('domain'), | |
siteSubDomain: this.node.tryGetContext('subdomain'), | |
}) | |
} | |
} | |
const app = new App() | |
new MyStaticSiteStack(app, `${base_id}-stack`, { | |
env: { | |
account: process.env.CDK_DEFAULT_ACCOUNT, | |
// Stack must be in us-east-1, because the ACM certificate for a | |
// global CloudFront distribution must be requested in us-east-1. | |
region: process.env.CDK_DEFAULT_REGION || 'us-east-1', | |
}, | |
tags: { | |
property: 'mystaticsite', | |
} | |
}) | |
app.synth() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
CloudFrontWebDistribution, | |
SSLMethod, | |
SecurityPolicyProtocol, | |
LambdaEdgeEventType, | |
} from '@aws-cdk/aws-cloudfront' | |
// import { | |
// HostedZone, | |
// ARecord, | |
// RecordTarget, | |
// } from '@aws-cdk/aws-route53' | |
import { Bucket } from '@aws-cdk/aws-s3' | |
import { | |
BucketDeployment, | |
Source, | |
} from '@aws-cdk/aws-s3-deployment' | |
// import { DnsValidatedCertificate } from '@aws-cdk/aws-certificatemanager' | |
import { CfnOutput, RemovalPolicy, Construct } from '@aws-cdk/core' | |
// import { CloudFrontTarget } from '@aws-cdk/aws-route53-targets/lib' | |
import iam = require('@aws-cdk/aws-iam') | |
import lambda = require('@aws-cdk/aws-lambda') | |
import { base_id } from './index' | |
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:Construct, name:string, props:StaticSiteProps) { | |
super(parent, name) | |
const { domainName, siteSubDomain } = props | |
// const zone = HostedZone.fromLookup(this, 'Zone', { domainName }) | |
const siteDomain = siteSubDomain ? `${siteSubDomain}.${domainName}` : domainName | |
// Content bucket | |
const siteBucket = new Bucket(this, | |
`${base_id}-SiteBucket`, { | |
bucketName: siteDomain, | |
websiteIndexDocument: 'index.html', | |
websiteErrorDocument: 'error.html', | |
publicReadAccess: true, | |
// 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 | |
}) | |
// TLS certificate | |
// Use an existing certificate | |
const certificateArn = 'arn:aws:acm:us-east-1:rest-of-your-certificateArn' | |
// or create a new certificate | |
// const certificateArn = new DnsValidatedCertificate( | |
// this, | |
// `${base_id}-SiteCertificate`, | |
// { | |
// domainName: siteDomain, | |
// hostedZone: zone | |
// } | |
// ).certificateArn | |
const rewriteUrl = new lambda.Function(this, `${base_id}-rewriteUrl`, { | |
functionName: `${base_id}-rewriteUrl`, | |
runtime: lambda.Runtime.NODEJS_10_X, | |
handler: 'rewrite-url.trigger', | |
code: lambda.Code.fromAsset('./lambda'), | |
role: new iam.Role(this, `${base_id}-EdgeRole--rewriteUrl`, { | |
assumedBy: new iam.CompositePrincipal( | |
new iam.ServicePrincipal('lambda.amazonaws.com'), | |
new iam.ServicePrincipal('edgelambda.amazonaws.com'), | |
), | |
}) | |
}) | |
const version__rewriteUrl = new lambda.Version(this, `${base_id}-version--rewriteUrl`, { | |
lambda: rewriteUrl | |
}) | |
// CloudFront distribution that provides HTTPS | |
const distribution = new CloudFrontWebDistribution(this, | |
`${base_id}-SiteDistribution`, { | |
aliasConfiguration: { | |
acmCertRef: certificateArn, | |
names: [siteDomain], | |
sslMethod: SSLMethod.SNI, | |
securityPolicy: SecurityPolicyProtocol.TLS_V1_1_2016, | |
}, | |
originConfigs: [ | |
{ | |
s3OriginSource: { | |
s3BucketSource: siteBucket | |
}, | |
behaviors: [{ | |
isDefaultBehavior: true, | |
lambdaFunctionAssociations: [ | |
{ | |
eventType: LambdaEdgeEventType.ORIGIN_REQUEST, | |
lambdaFunction: version__rewriteUrl | |
} | |
] | |
}], | |
} | |
], | |
}) | |
// Use Route53 if you want to host your DNS on AWS. | |
// If you are using another DNS service, you can create a CNAME to the Cloudfront url | |
// Route53 alias record for the CloudFront distribution | |
// new ARecord(this, 'SiteAliasRecord', { | |
// recordName: siteDomain, | |
// target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)), | |
// zone | |
// }) | |
// Deploy site contents to S3 bucket | |
new BucketDeployment(this, | |
`${base_id}-DeployWithInvalidation`, { | |
sources: [ | |
Source.asset(`path/to/your/site/html`) | |
], | |
destinationBucket: siteBucket, | |
distribution, | |
distributionPaths: ['/*'], | |
}) | |
new CfnOutput(this, | |
'Site', { | |
value: `https://${siteDomain}` | |
}) | |
new CfnOutput(this, | |
'bucketName', { | |
value: siteBucket.bucketName | |
}) | |
new CfnOutput(this, | |
'bucketRegionalDomainName', { | |
value: siteBucket.bucketRegionalDomainName | |
}) | |
new CfnOutput(this, | |
'Certificate', { | |
value: certificateArn | |
}) | |
new CfnOutput(this, | |
'DistributionId', | |
{ | |
value: distribution.distributionId | |
}) | |
new CfnOutput(this, | |
'CloudFrontDomainName', | |
{ | |
value: distribution.domainName | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment