Skip to content

Commit

Permalink
Merge pull request #62 from unstubbable/aws-custom-domain
Browse files Browse the repository at this point in the history
Use a custom domain for AWS app
  • Loading branch information
unstubbable authored Mar 16, 2024
2 parents ad3e8ca + fbe28cc commit 171e7ca
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 145 deletions.
24 changes: 8 additions & 16 deletions apps/aws-app/cdk/app.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import * as cdk from 'aws-cdk-lib';
import {MainStack} from './main-stack.js';
import {WafStack} from './waf-stack.js';
import './env.js';
import {Stack} from './stack.js';

const app = new cdk.App();

const wafStack = new WafStack(app, `mfng-waf`, {
crossRegionReferences: true,
new Stack(app, `mfng-app`, {
env: {
// For a web ACL with CLOUDFRONT scope, the WAF resources must be created in
// the US East (N. Virginia) Region, us-east-1.
account: process.env.CDK_DEFAULT_ACCOUNT,
// A certificate for CloudFront must be created in the US East (N. Virginia)
// Region, us-east-1.
region: `us-east-1`,
},
});

new MainStack(app, `mfng-app`, {
crossRegionReferences: true,
env: {
// Cross stack/region references are only supported for stacks with an
// explicit region defined.
region: process.env.AWS_REGION,
},
webAcl: wafStack.webAcl,
bucketName: `mfng-app-assets`,
customDomain: {domainName: `strict.software`, subdomainName: `mfng`},
});
16 changes: 16 additions & 0 deletions apps/aws-app/cdk/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {z} from 'zod';

declare global {
namespace NodeJS {
interface ProcessEnv extends z.infer<typeof envVariables> {}
}
}

const envVariables = z.object({
AWS_ACCESS_KEY_ID: z.string(),
AWS_HANDLER_VERIFY_HEADER: z.string(),
AWS_REGION: z.string(),
AWS_SECRET_ACCESS_KEY: z.string(),
});

envVariables.parse(process.env);
97 changes: 0 additions & 97 deletions apps/aws-app/cdk/main-stack.ts

This file was deleted.

165 changes: 165 additions & 0 deletions apps/aws-app/cdk/stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import path from 'path';
import * as cdk from 'aws-cdk-lib';
import type {Construct} from 'constructs';

const distDirname = path.join(import.meta.dirname, `../dist/`);

export interface StackProps extends cdk.StackProps {
readonly bucketName: string;
readonly customDomain?: {
readonly domainName: string;
readonly subdomainName: string;
};
}

export class Stack extends cdk.Stack {
constructor(scope: Construct, id: string, props: StackProps) {
const {bucketName, customDomain, ...otherProps} = props;
super(scope, id, otherProps);

const lambdaFunction = new cdk.aws_lambda_nodejs.NodejsFunction(
this,
`function`,
{
entry: path.join(distDirname, `handler/index.js`),
runtime: cdk.aws_lambda.Runtime.NODEJS_20_X,
bundling: {format: cdk.aws_lambda_nodejs.OutputFormat.ESM},
timeout: cdk.Duration.minutes(1),
environment: {
AWS_HANDLER_VERIFY_HEADER: process.env.AWS_HANDLER_VERIFY_HEADER,
},
},
);

const functionUrl = new cdk.aws_lambda.FunctionUrl(this, `function-url`, {
function: lambdaFunction,
authType: cdk.aws_lambda.FunctionUrlAuthType.NONE,
invokeMode: cdk.aws_lambda.InvokeMode.RESPONSE_STREAM,
});

const bucket = new cdk.aws_s3.Bucket(this, `assets-bucket`, {
bucketName,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});

const customDomainName =
customDomain &&
`${customDomain.subdomainName}.${customDomain.domainName}`;

const hostedZone =
customDomain &&
cdk.aws_route53.HostedZone.fromLookup(this, `hosted-zone-lookup`, {
domainName: customDomain.domainName,
});

const distribution = new cdk.aws_cloudfront.Distribution(this, `cdn`, {
certificate:
customDomainName && hostedZone
? new cdk.aws_certificatemanager.Certificate(this, `certificate`, {
domainName: customDomainName,
validation:
cdk.aws_certificatemanager.CertificateValidation.fromDns(
hostedZone,
),
})
: undefined,
domainNames: customDomainName ? [customDomainName] : undefined,
defaultBehavior: {
origin: new cdk.aws_cloudfront_origins.FunctionUrlOrigin(functionUrl, {
customHeaders: {
'X-Origin-Verify': process.env.AWS_HANDLER_VERIFY_HEADER,
},
}),
allowedMethods: cdk.aws_cloudfront.AllowedMethods.ALLOW_ALL,
cachePolicy: new cdk.aws_cloudfront.CachePolicy(this, `cache-policy`, {
enableAcceptEncodingGzip: true,
enableAcceptEncodingBrotli: true,
queryStringBehavior:
cdk.aws_cloudfront.CacheQueryStringBehavior.all(),
headerBehavior:
cdk.aws_cloudfront.CacheHeaderBehavior.allowList(`accept`),
}),
originRequestPolicy:
cdk.aws_cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
viewerProtocolPolicy:
cdk.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
responseHeadersPolicy: new cdk.aws_cloudfront.ResponseHeadersPolicy(
this,
`response-headers-policy`,
{
securityHeadersBehavior: {
frameOptions: {
frameOption: cdk.aws_cloudfront.HeadersFrameOption.DENY,
override: true,
},
strictTransportSecurity: {
accessControlMaxAge: cdk.Duration.days(365),
includeSubdomains: true,
override: true,
},
},
},
),
},
additionalBehaviors: {
'/client/*': {
origin: new cdk.aws_cloudfront_origins.S3Origin(bucket),
cachePolicy: cdk.aws_cloudfront.CachePolicy.CACHING_OPTIMIZED,
viewerProtocolPolicy:
cdk.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
},
priceClass: cdk.aws_cloudfront.PriceClass.PRICE_CLASS_100,
});

if (customDomain && hostedZone) {
new cdk.aws_route53.ARecord(this, `a-record`, {
zone: hostedZone,
recordName: customDomain.subdomainName,
target: cdk.aws_route53.RecordTarget.fromAlias(
new cdk.aws_route53_targets.CloudFrontTarget(distribution),
),
});

new cdk.aws_route53.AaaaRecord(this, `aaaa-record`, {
zone: hostedZone,
recordName: customDomain.subdomainName,
target: cdk.aws_route53.RecordTarget.fromAlias(
new cdk.aws_route53_targets.CloudFrontTarget(distribution),
),
});
}

new cdk.aws_s3_deployment.BucketDeployment(this, `assets-deployment`, {
destinationBucket: bucket,
destinationKeyPrefix: `client`,
sources: [
cdk.aws_s3_deployment.Source.asset(
path.join(distDirname, `static/client`),
),
],
distribution,
distributionPaths: [`/client/*`],
cacheControl: [
cdk.aws_s3_deployment.CacheControl.setPublic(),
cdk.aws_s3_deployment.CacheControl.maxAge(cdk.Duration.days(365)),
cdk.aws_s3_deployment.CacheControl.immutable(),
],
});

new cdk.CfnOutput(this, `function-url-output`, {
value: functionUrl.url,
});

new cdk.CfnOutput(this, `cdn-cloudfront-url-output`, {
value: `https://${distribution.domainName}`,
});

if (customDomainName) {
new cdk.CfnOutput(this, `cdn-custom-domain-url-output`, {
value: `https://${customDomainName}`,
});
}
}
}
27 changes: 0 additions & 27 deletions apps/aws-app/cdk/waf-stack.ts

This file was deleted.

3 changes: 2 additions & 1 deletion apps/aws-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"tsx": "^4.7.1",
"webpack": "^5.77.0",
"webpack-cli": "^5.0.1",
"webpack-manifest-plugin": "^5.0.0"
"webpack-manifest-plugin": "^5.0.0",
"zod": "^3.22.4"
}
}
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 171e7ca

Please sign in to comment.