Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add alternate domain names to cdn site host construct #84

Merged
merged 15 commits into from
Oct 27, 2023
42 changes: 42 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ jobs:
- run:
name: Build and deploy authenticated api example code
command: cd examples/simple-authenticated-api && npm install && npm run build && npx cdk --require-approval never deploy
deploy-cdn-site-hosting:
executor: default_executor
steps:
- add_ssh_keys:
fingerprints:
- "ee:b4:00:e6:23:5b:55:bb:fd:07:bc:73:9e:f7:89:9c" # [email protected] 'talis-cdk-constructs Deploy Key'
- restore_root_build_number
- set_aws_prefix
- checkout
- node_install_packages
- run:
name: Build and deploy cdn site hosting example code
command: cd examples/simple-cdn-site-hosting-construct && npm install && npm run build && npx cdk --require-approval never deploy
test:
executor: default_executor
steps:
Expand Down Expand Up @@ -146,6 +159,19 @@ jobs:
- run:
name: Destroy authenticated api example code
command: cd examples/simple-authenticated-api && npm install && npm run build && npx cdk --require-approval never destroy --force
destroy-cdn-site-hosting:
executor: default_executor
steps:
- add_ssh_keys:
fingerprints:
- "ee:b4:00:e6:23:5b:55:bb:fd:07:bc:73:9e:f7:89:9c" # [email protected] 'talis-cdk-constructs Deploy Key'
- restore_root_build_number
- set_aws_prefix
- checkout
- node_install_packages
- run:
name: Destroy cdn site hosting example code
command: cd examples/simple-cdn-site-hosting-construct && npm install && npm run build && npx cdk --require-approval never destroy --force
semantic-release:
executor: default_executor
steps:
Expand Down Expand Up @@ -197,10 +223,18 @@ workflows:
- cdk-deploy-to-shared-aspire-20211202
- aws-region-eu
- talis-cdk-constructs-build
- deploy-cdn-site-hosting:
requires:
- build
context:
- cdk-deploy-to-shared-aspire-20211202
- aws-region-eu
- talis-cdk-constructs-build
- test:
requires:
- deploy-lambda-worker
- deploy-authenticated-api
- deploy-cdn-site-hosting
context:
- cdk-deploy-to-shared-aspire-20211202
- aws-region-eu
Expand All @@ -219,10 +253,18 @@ workflows:
- cdk-deploy-to-shared-aspire-20211202
- aws-region-eu
- talis-cdk-constructs-build
- destroy-cdn-site-hosting:
requires:
- test
context:
- cdk-deploy-to-shared-aspire-20211202
- aws-region-eu
- talis-cdk-constructs-build
- semantic-release:
requires:
- destroy-lambda-worker
- destroy-authenticated-api
- destroy-cdn-site-hosting
context:
- cdk-deploy-to-shared-aspire-20211202
- aws-region-eu
Expand Down
8 changes: 8 additions & 0 deletions examples/simple-cdn-site-hosting-construct/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
6 changes: 6 additions & 0 deletions examples/simple-cdn-site-hosting-construct/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
119 changes: 119 additions & 0 deletions examples/simple-cdn-site-hosting-construct/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# CdnSiteHostingConstruct Example: SimpleCdnSiteHostingConstructStack

This is an example of using the CdnSiteHostingConstruct and the CdnSiteHostingWithDnsConstruct in a simple stack to host a static website.

The CdnSiteHostingConstruct:

- creates a bucket to host the static html
- creates a cloudfront distribution to server the website

Optionionally, the CdnSiteHostingWithDnsConstruct:

- sets up a DNS entry int Route 53 pointing at the cloudfront distribution

## This Example

To build and deploy this example:

- `export AWS_PREFIX=development-XX-` where XX are your initials
- This is used in the name of the stack and resources created, so that they do not clash with anyone elses stack in AWS
- `source awsenv <profile>` to set your credentials to the shared account
- `npm install`
- `npm run build`
- `cdk deploy`

After you have finished with the example, remove your stack in AWS using:

- `cdk destroy`

## Best Practice Using this Construct

It's best practice NOT to create a DNS entry for a production website within a cloud formation stack. In production
best practice is to be bring up a cloud formation stack and manually point a route 53 entry to the cloud front distribution.

Why? We want the ability, if necessary, to bring up a new cloud formation stack and when we are happy that it is working
as expected, switch the DNS manually to point to the new stack.

Therefore, production deployments should use the CdnSiteHostingConstruct and set a watermarked sub domain name. For example:

```typescript
new CdnSiteHostingConstruct(
this,
`TalisAppCdnSiteHostingConstruct`,
{
domainName: "talis.io",
siteSubDomain: `production-eu-20231025-talis-app`,
aliasSubDomains: ['talis-app'],
...
},
);
```

The above will create a cloud front distribution with two aliases:

- `production-eu-20231025-talis-app.talis.io`
- `talis-app.talis.io`

Outside of the cloud formation stack, DNS entries can be manually created for both the aliases and either can be used to access the website.

Unfortunately, cloud front can NOT have the same alias on two different distributions. This means we can not always set the `talis-app` alias from
the above example on a new cloud front distribution. i.e. Attempting to create:

```typescript
new CdnSiteHostingConstruct(
this,
`TalisAppCdnSiteHostingConstruct`,
{
domainName: "talis.io",
siteSubDomain: `production-eu-20231026-talis-app`,
aliasSubDomains: ['talis-app'],
...
},
);
```

will fail. Despite the cloud formation stack having a different watermark, 20231026 instead of 20231025, and the siteSubDomain being `production-eu-20231026-talis-app.talis.io` instead of `production-eu-20231025-talis-app.talis.io` - the creation of the stack will fail due to the duplicate alias `talis-app.talis.io`.

Therefore - when bringing up a new watermarked stack alongside an already live production stack, the alias sub domain must not initially be set:

```typescript
new CdnSiteHostingConstruct(
this,
`TalisAppCdnSiteHostingConstruct`,
{
domainName: "talis.io",
siteSubDomain: `production-eu-20231026-talis-app`,
aliasSubDomains: [],
...
},
);
```

This will mean the new stack can be deployed and tested at the watermarked DNS name, e.g. `production-eu-20231026-talis-app.talis.io`.

When ready to switch the stacks in production, the `talis-app.talis.io` alias needs to be deleted from the old stack and added to the new stack manually.
The DNS is then switched from the old cloud front distribution to the new one.

If this was a simple DNS change, there would be no downtime. But due to the need to delete and recreate the alias on the cloud front distribution, there is a small
period where the website is in accessible. Therefore, for a production site, ONLY when switching to a new cloud formation stack, NOT on every release, a maintenance
window is required for the switch over.

Subsequent deployments after the stack switch over DO need to add in the `talis-app` alias to the aliasSubDomains property of the construct. If they do not,
the alias will be deleted and an outage will occur.

This process should be managed via the hercules deployment script - setting up the `aliasSubDomain` for a standard deploy to the live watermark to include the
additional alias. When a different watermark is specified, to bring up a new none live stack, hercules should ensure the `aliasSubDomain` property does not
contain the additional alias.

This will require logic in both the hercules script and the projects CDK deployment code to ensure this property is set correctly.

LINK TO EXAMPLE HERE WHEN WE HAVE ONE

## Useful commands

- `npm run build` compile typescript to js
- `npm run watch` watch for changes and compile
- `npm run test` perform the jest unit tests
- `cdk deploy` deploy this stack to your default AWS account/region
- `cdk diff` compare deployed stack with current state
- `cdk synth` emits the synthesized CloudFormation template
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { SimpleCdnSiteHostingConstructStack } from "../lib/simple-cdn-site-hosting-construct-stack";

const app = new cdk.App();
new SimpleCdnSiteHostingConstructStack(
app,
`${process.env.AWS_PREFIX}simple-cdn-site-hosting-construct-stack`,
{
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
},
);
6 changes: 6 additions & 0 deletions examples/simple-cdn-site-hosting-construct/cdk.context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"hosted-zone:account=302477901552:domainName=talis.io:region=eu-west-1": {
"Id": "/hostedzone/ZQLU3GTNLTUB",
"Name": "talis.io."
}
}
55 changes: 55 additions & 0 deletions examples/simple-cdn-site-hosting-construct/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"app": "npx ts-node --prefer-ts-exts bin/simple-cdn-site-hosting-construct.ts",
"watch": {
"include": ["**"],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true
}
}
8 changes: 8 additions & 0 deletions examples/simple-cdn-site-hosting-construct/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as cdk from "aws-cdk-lib";
import { aws_s3_deployment as s3deploy } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as path from "path";

import {
CdnSiteHostingConstruct,
CdnSiteHostingWithDnsConstruct,
} from "../../../lib";

export const STAGING_TALIS_IO_TLS_CERT_ARN =
"arn:aws:acm:us-east-1:302477901552:certificate/7973186a-2f4f-4d4e-92c7-d50f92f32718";

export class SimpleCdnSiteHostingConstructStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// Use AWS_PREFIX to give all resources in this sample
// a unique name. This is usually `development-xx` where xx are your initials.
// If you do not set AWS_PREFIX, when you deploy this stack, it may conflict
// with someone elses stack who has also not set AWS_PREFIX
const prefix = process.env.AWS_PREFIX
? process.env.AWS_PREFIX
: "development-xx-";

// Create a site without setting DNS
/* const cdnSiteHostingConstruct = */ new CdnSiteHostingConstruct(
this,
`${prefix}CdnSiteHostingConstruct`,
{
domainName: "talis.io",
removalPolicy: cdk.RemovalPolicy.DESTROY,
// siteDomain: This would be the watermarked domain name e.g. elevate-20231025 from https://elevate-20231025.talis.com
siteSubDomain: `${prefix}cdn-site-hosting-construct`,
// aliasSubDomains: The production stack would supply this alias, e.g. elevate.talis.com
aliasSubDomains: [`${prefix}cdn-site-hosting-construct-alias`],
sources: [
s3deploy.Source.asset(path.resolve(__dirname, "./static-site")),
],
websiteIndexDocument: "index.html",
certificateArn: STAGING_TALIS_IO_TLS_CERT_ARN,
},
);

// Create a site with DNS
/* const cdnSiteHostingWithDnsConstruct = */ new CdnSiteHostingWithDnsConstruct(
this,
`${prefix}CdnSiteHostingWithDnsConstruct`,
{
domainName: "talis.io",
removalPolicy: cdk.RemovalPolicy.DESTROY,
// siteSubDomain: This would be the watermarked domain name e.g. elevate-20231025 from https://elevate-20231025.talis.com
siteSubDomain: `${prefix}cdn-site-hosting-with-dns-construct`,
// aliasSubDomains: The production stack would supply this alias, e.g. elevate.talis.com
aliasSubDomains: [`${prefix}cdn-site-hosting-with-dns-construct-alias`],
sources: [
s3deploy.Source.asset(path.resolve(__dirname, "./static-site")),
],
websiteIndexDocument: "index.html",
certificateArn: STAGING_TALIS_IO_TLS_CERT_ARN,
},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Test Deployment</h1>
Loading
Loading