Skip to content

Instantly share code, notes, and snippets.

@btakita
Last active August 29, 2022 23:23
Show Gist options
  • Save btakita/2572df4fae440dc065eb888f53316bdd to your computer and use it in GitHub Desktop.
Save btakita/2572df4fae440dc065eb888f53316bdd to your computer and use it in GitHub Desktop.
AWS CDK for a static https website
#!/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()
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