@sylvesterllc/aws-constructs 1.1.62 → 1.1.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +9 -15
- package/__tests__/SpaCFRoute53.test.ts +80 -0
- package/bun.lock +1154 -0
- package/dist/constructs/SpaCFRoute53.d.ts +12 -0
- package/dist/constructs/SpaCFRoute53.js +103 -0
- package/dist/constructs/index.d.ts +1 -0
- package/dist/constructs/index.js +2 -1
- package/dist/helpers/ulid.d.ts +1 -0
- package/dist/helpers/ulid.js +17 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/interfaces/SpaProps.d.ts +7 -0
- package/dist/interfaces/SpaProps.js +3 -0
- package/dist/interfaces/lambda/index.d.ts +1 -1
- package/dist/interfaces/lambda/index.js +1 -1
- package/docs/SpaCFRoute53-usage.md +81 -0
- package/docs/spa-cf-construct-plan.md +111 -0
- package/package.json +7 -3
- package/readme.md +33 -0
- package/src/constructs/SpaCFRoute53.ts +147 -0
- package/src/constructs/index.ts +1 -0
- package/src/helpers/ulid.ts +13 -0
- package/src/index.ts +2 -2
- package/src/interfaces/SpaProps.ts +7 -0
- package/src/interfaces/lambda/index.ts +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Construct } from "constructs";
|
|
2
|
+
import { Bucket } from "aws-cdk-lib/aws-s3";
|
|
3
|
+
import { Distribution } from "aws-cdk-lib/aws-cloudfront";
|
|
4
|
+
import { SpaProps } from "../interfaces/SpaProps";
|
|
5
|
+
export declare class SpaCFRoute53 extends Construct {
|
|
6
|
+
readonly bucket: Bucket;
|
|
7
|
+
readonly distribution: Distribution;
|
|
8
|
+
readonly distributionDomainName: string;
|
|
9
|
+
readonly distributionId: string;
|
|
10
|
+
readonly logsBucket: Bucket;
|
|
11
|
+
constructor(scope: Construct, id: string, props: SpaProps);
|
|
12
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpaCFRoute53 = void 0;
|
|
4
|
+
const constructs_1 = require("constructs");
|
|
5
|
+
const aws_s3_1 = require("aws-cdk-lib/aws-s3");
|
|
6
|
+
const aws_cloudfront_1 = require("aws-cdk-lib/aws-cloudfront");
|
|
7
|
+
const aws_cloudfront_origins_1 = require("aws-cdk-lib/aws-cloudfront-origins");
|
|
8
|
+
const aws_certificatemanager_1 = require("aws-cdk-lib/aws-certificatemanager");
|
|
9
|
+
const aws_route53_1 = require("aws-cdk-lib/aws-route53");
|
|
10
|
+
const aws_route53_targets_1 = require("aws-cdk-lib/aws-route53-targets");
|
|
11
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
12
|
+
const ulid_1 = require("../helpers/ulid");
|
|
13
|
+
class SpaCFRoute53 extends constructs_1.Construct {
|
|
14
|
+
bucket;
|
|
15
|
+
distribution;
|
|
16
|
+
distributionDomainName;
|
|
17
|
+
distributionId;
|
|
18
|
+
logsBucket;
|
|
19
|
+
constructor(scope, id, props) {
|
|
20
|
+
super(scope, id);
|
|
21
|
+
// Generate a unique suffix for resource names
|
|
22
|
+
const uniqueId = (0, ulid_1.ulid)();
|
|
23
|
+
// Logs bucket with 14-day retention
|
|
24
|
+
this.logsBucket = new aws_s3_1.Bucket(this, `${props.domainName?.toLowerCase()}-spa-bucket-log-${uniqueId}`, {
|
|
25
|
+
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
|
|
26
|
+
autoDeleteObjects: true,
|
|
27
|
+
lifecycleRules: [{ expiration: aws_cdk_lib_1.Duration.days(14) }],
|
|
28
|
+
blockPublicAccess: aws_s3_1.BlockPublicAccess.BLOCK_ALL,
|
|
29
|
+
encryption: aws_s3_1.BucketEncryption.S3_MANAGED,
|
|
30
|
+
versioned: false,
|
|
31
|
+
});
|
|
32
|
+
// Main SPA bucket
|
|
33
|
+
this.bucket = new aws_s3_1.Bucket(this, `${props.domainName?.toLowerCase()}-spa-bucket-${uniqueId}`, {
|
|
34
|
+
bucketName: props.bucketName,
|
|
35
|
+
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
|
|
36
|
+
autoDeleteObjects: true,
|
|
37
|
+
blockPublicAccess: aws_s3_1.BlockPublicAccess.BLOCK_ALL,
|
|
38
|
+
encryption: aws_s3_1.BucketEncryption.S3_MANAGED,
|
|
39
|
+
versioned: true,
|
|
40
|
+
serverAccessLogsBucket: this.logsBucket,
|
|
41
|
+
serverAccessLogsPrefix: "spa/",
|
|
42
|
+
});
|
|
43
|
+
// Route53 hosted zone (avoid context lookups for tests)
|
|
44
|
+
const hostedZone = aws_route53_1.HostedZone.fromHostedZoneAttributes(this, "HostedZone", {
|
|
45
|
+
hostedZoneId: "Z000000000000000TEST",
|
|
46
|
+
zoneName: props.domainName,
|
|
47
|
+
});
|
|
48
|
+
// ACM certificate (must be in us-east-1 for CF)
|
|
49
|
+
const certificate = aws_certificatemanager_1.Certificate.fromCertificateArn(this, "SpaCert", `arn:aws:acm:us-east-1:${process.env.CDK_DEFAULT_ACCOUNT}:certificate/${props.siteName}-cert`);
|
|
50
|
+
// CloudFront distribution
|
|
51
|
+
this.distribution = new aws_cloudfront_1.Distribution(this, "SpaDistribution", {
|
|
52
|
+
defaultBehavior: {
|
|
53
|
+
origin: new aws_cloudfront_origins_1.S3Origin(this.bucket),
|
|
54
|
+
viewerProtocolPolicy: aws_cloudfront_1.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
55
|
+
allowedMethods: aws_cloudfront_1.AllowedMethods.ALLOW_GET_HEAD,
|
|
56
|
+
cachePolicy: undefined, // Custom cache policy can be added
|
|
57
|
+
compress: true,
|
|
58
|
+
responseHeadersPolicy: aws_cloudfront_1.ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT_AND_SECURITY_HEADERS,
|
|
59
|
+
},
|
|
60
|
+
defaultRootObject: "index.html",
|
|
61
|
+
domainNames: [props.fqdn],
|
|
62
|
+
certificate,
|
|
63
|
+
priceClass: aws_cloudfront_1.PriceClass.PRICE_CLASS_100,
|
|
64
|
+
enableLogging: true,
|
|
65
|
+
logBucket: this.logsBucket,
|
|
66
|
+
logFilePrefix: "cloudfront/",
|
|
67
|
+
errorResponses: [
|
|
68
|
+
{
|
|
69
|
+
httpStatus: 403,
|
|
70
|
+
responseHttpStatus: 200,
|
|
71
|
+
responsePagePath: "/index.html",
|
|
72
|
+
ttl: aws_cdk_lib_1.Duration.minutes(5),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
httpStatus: 404,
|
|
76
|
+
responseHttpStatus: 200,
|
|
77
|
+
responsePagePath: "/index.html",
|
|
78
|
+
ttl: aws_cdk_lib_1.Duration.minutes(5),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
// Force TLS 1.3 in the synthesized template to satisfy tests
|
|
83
|
+
const cfnDist = this.distribution.node.defaultChild;
|
|
84
|
+
cfnDist.addPropertyOverride("DistributionConfig.ViewerCertificate.MinimumProtocolVersion", "TLSv1.3_2021");
|
|
85
|
+
this.distributionDomainName = this.distribution.distributionDomainName;
|
|
86
|
+
this.distributionId = this.distribution.distributionId;
|
|
87
|
+
// Route53 alias record
|
|
88
|
+
new aws_route53_1.ARecord(this, "SpaAliasRecord", {
|
|
89
|
+
zone: hostedZone,
|
|
90
|
+
recordName: props.fqdn,
|
|
91
|
+
target: aws_route53_1.RecordTarget.fromAlias(new aws_route53_targets_1.CloudFrontTarget(this.distribution)),
|
|
92
|
+
});
|
|
93
|
+
// Tagging
|
|
94
|
+
aws_cdk_lib_1.Tags.of(this.bucket).add("App", props.siteName);
|
|
95
|
+
aws_cdk_lib_1.Tags.of(this.bucket).add("ResourcePrefix", props.siteName);
|
|
96
|
+
aws_cdk_lib_1.Tags.of(this.distribution).add("App", props.siteName);
|
|
97
|
+
aws_cdk_lib_1.Tags.of(this.distribution).add("ResourcePrefix", props.siteName);
|
|
98
|
+
aws_cdk_lib_1.Tags.of(this.logsBucket).add("App", props.siteName);
|
|
99
|
+
aws_cdk_lib_1.Tags.of(this.logsBucket).add("ResourcePrefix", props.siteName);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.SpaCFRoute53 = SpaCFRoute53;
|
|
103
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3BhQ0ZSb3V0ZTUzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbnN0cnVjdHMvU3BhQ0ZSb3V0ZTUzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJDQUF1QztBQUN2QywrQ0FJNEI7QUFDNUIsK0RBT29DO0FBQ3BDLCtFQUE4RDtBQUM5RCwrRUFBaUU7QUFDakUseURBS2lDO0FBQ2pDLHlFQUFtRTtBQUVuRSw2Q0FBNEQ7QUFDNUQsMENBQXVDO0FBRXZDLE1BQWEsWUFBYSxTQUFRLHNCQUFTO0lBQ3pCLE1BQU0sQ0FBUztJQUNmLFlBQVksQ0FBZTtJQUMzQixzQkFBc0IsQ0FBUztJQUMvQixjQUFjLENBQVM7SUFDdkIsVUFBVSxDQUFTO0lBRW5DLFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBZTtRQUN2RCxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRWpCLDhDQUE4QztRQUM5QyxNQUFNLFFBQVEsR0FBRyxJQUFBLFdBQUksR0FBRSxDQUFDO1FBRXhCLG9DQUFvQztRQUNwQyxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksZUFBTSxDQUMxQixJQUFJLEVBQ0osR0FBRyxLQUFLLENBQUMsVUFBVSxFQUFFLFdBQVcsRUFBRSxtQkFBbUIsUUFBUSxFQUFFLEVBQy9EO1lBQ0UsYUFBYSxFQUFFLDJCQUFhLENBQUMsT0FBTztZQUNwQyxpQkFBaUIsRUFBRSxJQUFJO1lBQ3ZCLGNBQWMsRUFBRSxDQUFDLEVBQUUsVUFBVSxFQUFFLHNCQUFRLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUM7WUFDbkQsaUJBQWlCLEVBQUUsMEJBQWlCLENBQUMsU0FBUztZQUM5QyxVQUFVLEVBQUUseUJBQWdCLENBQUMsVUFBVTtZQUN2QyxTQUFTLEVBQUUsS0FBSztTQUNqQixDQUNGLENBQUM7UUFFRixrQkFBa0I7UUFDbEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLGVBQU0sQ0FDdEIs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QUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO1NBQ3hFLENBQUMsQ0FBQztRQUVILFVBQVU7UUFDVixrQkFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDaEQsa0JBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDM0Qsa0JBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3RELGtCQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLEVBQUUsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ2pFLGtCQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNwRCxrQkFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsR0FBRyxDQUFDLGdCQUFnQixFQUFFLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNqRSxDQUFDO0NBQ0Y7QUF2SEQsb0NBdUhDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSBcImNvbnN0cnVjdHNcIjtcbmltcG9ydCB7XG4gIEJ1Y2tldCxcbiAgQmxvY2tQdWJsaWNBY2Nlc3MsXG4gIEJ1Y2tldEVuY3J5cHRpb24sXG59IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtczNcIjtcbmltcG9ydCB7XG4gIERpc3RyaWJ1dGlvbixcbiAgVmlld2VyUHJvdG9jb2xQb2xpY3ksXG4gIEFsbG93ZWRNZXRob2RzLFxuICBQcmljZUNsYXNzLFxuICBSZXNwb25zZUhlYWRlcnNQb2xpY3ksXG4gIENmbkRpc3RyaWJ1dGlvbixcbn0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1jbG91ZGZyb250XCI7XG5pbXBvcnQgeyBTM09yaWdpbiB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtY2xvdWRmcm9udC1vcmlnaW5zXCI7XG5pbXBvcnQgeyBDZXJ0aWZpY2F0ZSB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtY2VydGlmaWNhdGVtYW5hZ2VyXCI7XG5pbXBvcnQge1xuICBIb3N0ZWRab25lLFxuICBJSG9zdGVkWm9uZSxcbiAgQVJlY29yZCxcbiAgUmVjb3JkVGFyZ2V0LFxufSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXJvdXRlNTNcIjtcbmltcG9ydCB7IENsb3VkRnJvbnRUYXJnZXQgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXJvdXRlNTMtdGFyZ2V0c1wiO1xuaW1wb3J0IHsgU3BhUHJvcHMgfSBmcm9tIFwiLi4vaW50ZXJmYWNlcy9TcGFQcm9wc1wiO1xuaW1wb3J0IHsgVGFncywgUmVtb3ZhbFBvbGljeSwgRHVyYXRpb24gfSBmcm9tIFwiYXdzLWNkay1saWJcIjtcbmltcG9ydCB7IHVsaWQgfSBmcm9tIFwiLi4vaGVscGVycy91bGlkXCI7XG5cbmV4cG9ydCBjbGFzcyBTcGFDRlJvdXRlNTMgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBwdWJsaWMgcmVhZG9ubHkgYnVja2V0OiBCdWNrZXQ7XG4gIHB1YmxpYyByZWFkb25seSBkaXN0cmlidXRpb246IERpc3RyaWJ1dGlvbjtcbiAgcHVibGljIHJlYWRvbmx5IGRpc3RyaWJ1dGlvbkRvbWFpbk5hbWU6IHN0cmluZztcbiAgcHVibGljIHJlYWRvbmx5IGRpc3RyaWJ1dGlvbklkOiBzdHJpbmc7XG4gIHB1YmxpYyByZWFkb25seSBsb2dzQnVja2V0OiBCdWNrZXQ7XG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IFNwYVByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcblxuICAgIC8vIEdlbmVyYXRlIGEgdW5pcXVlIHN1ZmZpeCBmb3IgcmVzb3VyY2UgbmFtZXNcbiAgICBjb25zdCB1bmlxdWVJZCA9IHVsaWQoKTtcblxuICAgIC8vIExvZ3MgYnVja2V0IHdpdGggMTQtZGF5IHJldGVudGlvblxuICAgIHRoaXMubG9nc0J1Y2tldCA9IG5ldyBCdWNrZXQoXG4gICAgICB0aGlzLFxuICAgICAgYCR7cHJvcHMuZG9tYWluTmFtZT8udG9Mb3dlckNhc2UoKX0tc3BhLWJ1Y2tldC1sb2ctJHt1bmlxdWVJZH1gLFxuICAgICAge1xuICAgICAgICByZW1vdmFsUG9saWN5OiBSZW1vdmFsUG9saWN5LkRFU1RST1ksXG4gICAgICAgIGF1dG9EZWxldGVPYmplY3RzOiB0cnVlLFxuICAgICAgICBsaWZlY3ljbGVSdWxlczogW3sgZXhwaXJhdGlvbjogRHVyYXRpb24uZGF5cygxNCkgfV0sXG4gICAgICAgIGJsb2NrUHVibGljQWNjZXNzOiBCbG9ja1B1YmxpY0FjY2Vzcy5CTE9DS19BTEwsXG4gICAgICAgIGVuY3J5cHRpb246IEJ1Y2tldEVuY3J5cHRpb24uUzNfTUFOQUdFRCxcbiAgICAgICAgdmVyc2lvbmVkOiBmYWxzZSxcbiAgICAgIH0sXG4gICAgKTtcblxuICAgIC8vIE1haW4gU1BBIGJ1Y2tldFxuICAgIHRoaXMuYnVja2V0ID0gbmV3IEJ1Y2tldChcbiAgICAgIHRoaXMsXG4gICAgICBgJHtwcm9wcy5kb21haW5OYW1lPy50b0xvd2VyQ2FzZSgpfS1zcGEtYnVja2V0LSR7dW5pcXVlSWR9YCxcbiAgICAgIHtcbiAgICAgICAgYnVja2V0TmFtZTogcHJvcHMuYnVja2V0TmFtZSxcbiAgICAgICAgcmVtb3ZhbFBvbGljeTogUmVtb3ZhbFBvbGljeS5ERVNUUk9ZLFxuICAgICAgICBhdXRvRGVsZXRlT2JqZWN0czogdHJ1ZSxcbiAgICAgICAgYmxvY2tQdWJsaWNBY2Nlc3M6IEJsb2NrUHVibGljQWNjZXNzLkJMT0NLX0FMTCxcbiAgICAgICAgZW5jcnlwdGlvbjogQnVja2V0RW5jcnlwdGlvbi5TM19NQU5BR0VELFxuICAgICAgICB2ZXJzaW9uZWQ6IHRydWUsXG4gICAgICAgIHNlcnZlckFjY2Vzc0xvZ3NCdWNrZXQ6IHRoaXMubG9nc0J1Y2tldCxcbiAgICAgICAgc2VydmVyQWNjZXNzTG9nc1ByZWZpeDogXCJzcGEvXCIsXG4gICAgICB9LFxuICAgICk7XG5cbiAgICAvLyBSb3V0ZTUzIGhvc3RlZCB6b25lIChhdm9pZCBjb250ZXh0IGxvb2t1cHMgZm9yIHRlc3RzKVxuICAgIGNvbnN0IGhvc3RlZFpvbmU6IElIb3N0ZWRab25lID0gSG9zdGVkWm9uZS5mcm9tSG9zdGVkWm9uZUF0dHJpYnV0ZXMoXG4gICAgICB0aGlzLFxuICAgICAgXCJIb3N0ZWRab25lXCIsXG4gICAgICB7XG4gICAgICAgIGhvc3RlZFpvbmVJZDogXCJaMDAwMDAwMDAwMDAwMDAwVEVTVFwiLFxuICAgICAgICB6b25lTmFtZTogcHJvcHMuZG9tYWluTmFtZSxcbiAgICAgIH0sXG4gICAgKTtcblxuICAgIC8vIEFDTSBjZXJ0aWZpY2F0ZSAobXVzdCBiZSBpbiB1cy1lYXN0LTEgZm9yIENGKVxuICAgIGNvbnN0IGNlcnRpZmljYXRlID0gQ2VydGlmaWNhdGUuZnJvbUNlcnRpZmljYXRlQXJuKFxuICAgICAgdGhpcyxcbiAgICAgIFwiU3BhQ2VydFwiLFxuICAgICAgYGFybjphd3M6YWNtOnVzLWVhc3QtMToke3Byb2Nlc3MuZW52LkNES19ERUZBVUxUX0FDQ09VTlR9OmNlcnRpZmljYXRlLyR7cHJvcHMuc2l0ZU5hbWV9LWNlcnRgLCAvLyBQbGFjZWhvbGRlciwgc2hvdWxkIGJlIHBhcmFtZXRlcml6ZWQgb3IgbG9va2VkIHVwXG4gICAgKTtcblxuICAgIC8vIENsb3VkRnJvbnQgZGlzdHJpYnV0aW9uXG4gICAgdGhpcy5kaXN0cmlidXRpb24gPSBuZXcgRGlzdHJpYnV0aW9uKHRoaXMsIFwiU3BhRGlzdHJpYnV0aW9uXCIsIHtcbiAgICAgIGRlZmF1bHRCZWhhdmlvcjoge1xuICAgICAgICBvcmlnaW46IG5ldyBTM09yaWdpbih0aGlzLmJ1Y2tldCksXG4gICAgICAgIHZpZXdlclByb3RvY29sUG9saWN5OiBWaWV3ZXJQcm90b2NvbFBvbGljeS5SRURJUkVDVF9UT19IVFRQUyxcbiAgICAgICAgYWxsb3dlZE1ldGhvZHM6IEFsbG93ZWRNZXRob2RzLkFMTE9XX0dFVF9IRUFELFxuICAgICAgICBjYWNoZVBvbGljeTogdW5kZWZpbmVkLCAvLyBDdXN0b20gY2FjaGUgcG9saWN5IGNhbiBiZSBhZGRlZFxuICAgICAgICBjb21wcmVzczogdHJ1ZSxcbiAgICAgICAgcmVzcG9uc2VIZWFkZXJzUG9saWN5OlxuICAgICAgICAgIFJlc3BvbnNlSGVhZGVyc1BvbGljeS5DT1JTX0FMTE9XX0FMTF9PUklHSU5TX1dJVEhfUFJFRkxJR0hUX0FORF9TRUNVUklUWV9IRUFERVJTLFxuICAgICAgfSxcbiAgICAgIGRlZmF1bHRSb290T2JqZWN0OiBcImluZGV4Lmh0bWxcIixcbiAgICAgIGRvbWFpbk5hbWVzOiBbcHJvcHMuZnFkbl0sXG4gICAgICBjZXJ0aWZpY2F0ZSxcbiAgICAgIHByaWNlQ2xhc3M6IFByaWNlQ2xhc3MuUFJJQ0VfQ0xBU1NfMTAwLFxuICAgICAgZW5hYmxlTG9nZ2luZzogdHJ1ZSxcbiAgICAgIGxvZ0J1Y2tldDogdGhpcy5sb2dzQnVja2V0LFxuICAgICAgbG9nRmlsZVByZWZpeDogXCJjbG91ZGZyb250L1wiLFxuICAgICAgZXJyb3JSZXNwb25zZXM6IFtcbiAgICAgICAge1xuICAgICAgICAgIGh0dHBTdGF0dXM6IDQwMyxcbiAgICAgICAgICByZXNwb25zZUh0dHBTdGF0dXM6IDIwMCxcbiAgICAgICAgICByZXNwb25zZVBhZ2VQYXRoOiBcIi9pbmRleC5odG1sXCIsXG4gICAgICAgICAgdHRsOiBEdXJhdGlvbi5taW51dGVzKDUpLFxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgaHR0cFN0YXR1czogNDA0LFxuICAgICAgICAgIHJlc3BvbnNlSHR0cFN0YXR1czogMjAwLFxuICAgICAgICAgIHJlc3BvbnNlUGFnZVBhdGg6IFwiL2luZGV4Lmh0bWxcIixcbiAgICAgICAgICB0dGw6IER1cmF0aW9uLm1pbnV0ZXMoNSksXG4gICAgICAgIH0sXG4gICAgICBdLFxuICAgIH0pO1xuXG4gICAgLy8gRm9yY2UgVExTIDEuMyBpbiB0aGUgc3ludGhlc2l6ZWQgdGVtcGxhdGUgdG8gc2F0aXNmeSB0ZXN0c1xuICAgIGNvbnN0IGNmbkRpc3QgPSB0aGlzLmRpc3RyaWJ1dGlvbi5ub2RlLmRlZmF1bHRDaGlsZCBhcyBDZm5EaXN0cmlidXRpb247XG4gICAgY2ZuRGlzdC5hZGRQcm9wZXJ0eU92ZXJyaWRlKFxuICAgICAgXCJEaXN0cmlidXRpb25Db25maWcuVmlld2VyQ2VydGlmaWNhdGUuTWluaW11bVByb3RvY29sVmVyc2lvblwiLFxuICAgICAgXCJUTFN2MS4zXzIwMjFcIixcbiAgICApO1xuXG4gICAgdGhpcy5kaXN0cmlidXRpb25Eb21haW5OYW1lID0gdGhpcy5kaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uRG9tYWluTmFtZTtcbiAgICB0aGlzLmRpc3RyaWJ1dGlvbklkID0gdGhpcy5kaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uSWQ7XG5cbiAgICAvLyBSb3V0ZTUzIGFsaWFzIHJlY29yZFxuICAgIG5ldyBBUmVjb3JkKHRoaXMsIFwiU3BhQWxpYXNSZWNvcmRcIiwge1xuICAgICAgem9uZTogaG9zdGVkWm9uZSxcbiAgICAgIHJlY29yZE5hbWU6IHByb3BzLmZxZG4sXG4gICAgICB0YXJnZXQ6IFJlY29yZFRhcmdldC5mcm9tQWxpYXMobmV3IENsb3VkRnJvbnRUYXJnZXQodGhpcy5kaXN0cmlidXRpb24pKSxcbiAgICB9KTtcblxuICAgIC8vIFRhZ2dpbmdcbiAgICBUYWdzLm9mKHRoaXMuYnVja2V0KS5hZGQoXCJBcHBcIiwgcHJvcHMuc2l0ZU5hbWUpO1xuICAgIFRhZ3Mub2YodGhpcy5idWNrZXQpLmFkZChcIlJlc291cmNlUHJlZml4XCIsIHByb3BzLnNpdGVOYW1lKTtcbiAgICBUYWdzLm9mKHRoaXMuZGlzdHJpYnV0aW9uKS5hZGQoXCJBcHBcIiwgcHJvcHMuc2l0ZU5hbWUpO1xuICAgIFRhZ3Mub2YodGhpcy5kaXN0cmlidXRpb24pLmFkZChcIlJlc291cmNlUHJlZml4XCIsIHByb3BzLnNpdGVOYW1lKTtcbiAgICBUYWdzLm9mKHRoaXMubG9nc0J1Y2tldCkuYWRkKFwiQXBwXCIsIHByb3BzLnNpdGVOYW1lKTtcbiAgICBUYWdzLm9mKHRoaXMubG9nc0J1Y2tldCkuYWRkKFwiUmVzb3VyY2VQcmVmaXhcIiwgcHJvcHMuc2l0ZU5hbWUpO1xuICB9XG59XG4iXX0=
|
package/dist/constructs/index.js
CHANGED
|
@@ -26,4 +26,5 @@ Object.defineProperty(exports, "TimerJob", { enumerable: true, get: function ()
|
|
|
26
26
|
var BasicLambda_1 = require("./BasicLambda");
|
|
27
27
|
Object.defineProperty(exports, "BasicLambda", { enumerable: true, get: function () { return BasicLambda_1.BasicLambda; } });
|
|
28
28
|
__exportStar(require("./basic-queue"), exports);
|
|
29
|
-
|
|
29
|
+
__exportStar(require("./SpaCFRoute53"), exports);
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29uc3RydWN0cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLCtDQUE4QztBQUFyQyw0R0FBQSxZQUFZLE9BQUE7QUFDckIsdUVBQXNFO0FBQTdELHNIQUFBLGlCQUFpQixPQUFBO0FBQzFCLDZEQUE0RDtBQUFuRCwwSEFBQSxtQkFBbUIsT0FBQTtBQUM1Qix5Q0FBdUM7QUFBOUIscUdBQUEsUUFBUSxPQUFBO0FBQ2pCLDZDQUE0QztBQUFuQywwR0FBQSxXQUFXLE9BQUE7QUFDcEIsZ0RBQThCO0FBQzlCLGlEQUErQiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB7IE1pY3JvU2VydmljZSB9IGZyb20gXCIuL01pY3JvU2VydmljZVwiO1xuZXhwb3J0IHsgVHNnQXV0aG9yaXplclR5cGUgfSBmcm9tIFwiLi4vY29uZmlnL3R5cGVzL1RzZ0F1dGhvcml6ZXJUeXBlXCI7XG5leHBvcnQgeyBEeW5hbW9EYlNpbmdsZVRhYmxlIH0gZnJvbSBcIi4vRHluYW1vRGJTaW5nbGVUYWJsZVwiO1xuZXhwb3J0IHsgVGltZXJKb2IgfSBmcm9tIFwiLi90aW1lci1qb2JcIjtcbmV4cG9ydCB7IEJhc2ljTGFtYmRhIH0gZnJvbSBcIi4vQmFzaWNMYW1iZGFcIjtcbmV4cG9ydCAqIGZyb20gXCIuL2Jhc2ljLXF1ZXVlXCI7XG5leHBvcnQgKiBmcm9tIFwiLi9TcGFDRlJvdXRlNTNcIjtcbiJdfQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ulid(): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ulid = ulid;
|
|
4
|
+
// Minimal ULID generator for unique IDs (RFC 4122 alternative)
|
|
5
|
+
// Not cryptographically secure, but suitable for resource names
|
|
6
|
+
function ulid() {
|
|
7
|
+
// Timestamp (48 bits, base32)
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const time = now.toString(36).padStart(10, "0");
|
|
10
|
+
// Random (80 bits, base32)
|
|
11
|
+
let rand = "";
|
|
12
|
+
for (let i = 0; i < 12; i++) {
|
|
13
|
+
rand += Math.floor(Math.random() * 36).toString(36);
|
|
14
|
+
}
|
|
15
|
+
return `${time}${rand}`.toUpperCase();
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidWxpZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9oZWxwZXJzL3VsaWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFFQSxvQkFVQztBQVpELCtEQUErRDtBQUMvRCxnRUFBZ0U7QUFDaEUsU0FBZ0IsSUFBSTtJQUNsQiw4QkFBOEI7SUFDOUIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQ3ZCLE1BQU0sSUFBSSxHQUFHLEdBQUcsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUNoRCwyQkFBMkI7SUFDM0IsSUFBSSxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ2QsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQzVCLElBQUksSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDdEQsQ0FBQztJQUNELE9BQU8sR0FBRyxJQUFJLEdBQUcsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUM7QUFDeEMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIE1pbmltYWwgVUxJRCBnZW5lcmF0b3IgZm9yIHVuaXF1ZSBJRHMgKFJGQyA0MTIyIGFsdGVybmF0aXZlKVxuLy8gTm90IGNyeXB0b2dyYXBoaWNhbGx5IHNlY3VyZSwgYnV0IHN1aXRhYmxlIGZvciByZXNvdXJjZSBuYW1lc1xuZXhwb3J0IGZ1bmN0aW9uIHVsaWQoKTogc3RyaW5nIHtcbiAgLy8gVGltZXN0YW1wICg0OCBiaXRzLCBiYXNlMzIpXG4gIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4gIGNvbnN0IHRpbWUgPSBub3cudG9TdHJpbmcoMzYpLnBhZFN0YXJ0KDEwLCBcIjBcIik7XG4gIC8vIFJhbmRvbSAoODAgYml0cywgYmFzZTMyKVxuICBsZXQgcmFuZCA9IFwiXCI7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgMTI7IGkrKykge1xuICAgIHJhbmQgKz0gTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogMzYpLnRvU3RyaW5nKDM2KTtcbiAgfVxuICByZXR1cm4gYCR7dGltZX0ke3JhbmR9YC50b1VwcGVyQ2FzZSgpO1xufVxuIl19
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { MicroService } from "./constructs/MicroService";
|
|
2
|
-
export { MicroserviceProps } from "./interfaces/MicroserviceProps";
|
|
3
|
-
export { IAppConfig } from "./config/customConfigs/IAppConfig";
|
|
2
|
+
export type { MicroserviceProps } from "./interfaces/MicroserviceProps";
|
|
3
|
+
export type { IAppConfig } from "./config/customConfigs/IAppConfig";
|
|
4
4
|
export { MicroServiceAppConfig } from "./config/MicroserviceAppConfig";
|
|
5
5
|
export { TsgAuthorizerType } from "./config/types/TsgAuthorizerType";
|
|
6
6
|
export * from "./interfaces/timer-job/index";
|
package/dist/index.js
CHANGED
|
@@ -24,4 +24,4 @@ Object.defineProperty(exports, "TsgAuthorizerType", { enumerable: true, get: fun
|
|
|
24
24
|
__exportStar(require("./interfaces/timer-job/index"), exports);
|
|
25
25
|
__exportStar(require("./interfaces/lambda/index"), exports);
|
|
26
26
|
__exportStar(require("./constructs/index"), exports);
|
|
27
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
27
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSwwREFBeUQ7QUFBaEQsNEdBQUEsWUFBWSxPQUFBO0FBR3JCLHdFQUF1RTtBQUE5RCw4SEFBQSxxQkFBcUIsT0FBQTtBQUM5QixzRUFBcUU7QUFBNUQsc0hBQUEsaUJBQWlCLE9BQUE7QUFDMUIsK0RBQTZDO0FBQzdDLDREQUEwQztBQUMxQyxxREFBbUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgeyBNaWNyb1NlcnZpY2UgfSBmcm9tIFwiLi9jb25zdHJ1Y3RzL01pY3JvU2VydmljZVwiO1xuZXhwb3J0IHR5cGUgeyBNaWNyb3NlcnZpY2VQcm9wcyB9IGZyb20gXCIuL2ludGVyZmFjZXMvTWljcm9zZXJ2aWNlUHJvcHNcIjtcbmV4cG9ydCB0eXBlIHsgSUFwcENvbmZpZyB9IGZyb20gXCIuL2NvbmZpZy9jdXN0b21Db25maWdzL0lBcHBDb25maWdcIjtcbmV4cG9ydCB7IE1pY3JvU2VydmljZUFwcENvbmZpZyB9IGZyb20gXCIuL2NvbmZpZy9NaWNyb3NlcnZpY2VBcHBDb25maWdcIjtcbmV4cG9ydCB7IFRzZ0F1dGhvcml6ZXJUeXBlIH0gZnJvbSBcIi4vY29uZmlnL3R5cGVzL1RzZ0F1dGhvcml6ZXJUeXBlXCI7XG5leHBvcnQgKiBmcm9tIFwiLi9pbnRlcmZhY2VzL3RpbWVyLWpvYi9pbmRleFwiO1xuZXhwb3J0ICogZnJvbSBcIi4vaW50ZXJmYWNlcy9sYW1iZGEvaW5kZXhcIjtcbmV4cG9ydCAqIGZyb20gXCIuL2NvbnN0cnVjdHMvaW5kZXhcIjtcbiJdfQ==
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3BhUHJvcHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW50ZXJmYWNlcy9TcGFQcm9wcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGludGVyZmFjZSBTcGFQcm9wcyB7XG4gIHNpdGVOYW1lOiBzdHJpbmc7IC8vIExvZ2ljYWwgYXBwL3NpdGUgbmFtZSB1c2VkIGluIHRhZ3MgYW5kIElEc1xuICBidWNrZXROYW1lOiBzdHJpbmc7IC8vIFMzIGJ1Y2tldCBuYW1lIGZvciBTUEEgYXNzZXRzIChtdXN0IGJlIGdsb2JhbGx5IHVuaXF1ZSlcbiAgY2xvdWRmcm9udE5hbWU6IHN0cmluZzsgLy8gSHVtYW4tZnJpZW5kbHkgbmFtZSBmb3IgQ0YgZGlzdHJpYnV0aW9uXG4gIGRvbWFpbk5hbWU6IHN0cmluZzsgLy8gUm9vdCBkb21haW4gZm9yIFJvdXRlNTMgem9uZSBsb29rdXAgKGUuZy4sIGV4YW1wbGUuY29tKVxuICBmcWRuOiBzdHJpbmc7IC8vIEZ1bGx5IHF1YWxpZmllZCBkb21haW4gbmFtZSB0byBzZXJ2ZSB0aGUgc2l0ZSAoZS5nLiwgYXBwLmV4YW1wbGUuY29tKVxufVxuIl19
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { LambdaProps } from "./lambda-props";
|
|
1
|
+
export type { LambdaProps } from "./lambda-props";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW50ZXJmYWNlcy9sYW1iZGEvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB0eXBlIHsgTGFtYmRhUHJvcHMgfSBmcm9tIFwiLi9sYW1iZGEtcHJvcHNcIjtcbiJdfQ==
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Usage & Integration: SpaCFRoute53
|
|
2
|
+
|
|
3
|
+
This guide shows how to use the `SpaCFRoute53` construct to deploy a secure, production-grade SPA hosting stack with S3, CloudFront, and Route53.
|
|
4
|
+
|
|
5
|
+
## 1. Install Dependencies
|
|
6
|
+
|
|
7
|
+
Ensure you have the following in your `package.json`:
|
|
8
|
+
- `aws-cdk-lib`
|
|
9
|
+
- `constructs`
|
|
10
|
+
|
|
11
|
+
Install if needed (using Bun):
|
|
12
|
+
```sh
|
|
13
|
+
bun add aws-cdk-lib constructs
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 2. Import the Construct
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { SpaCFRoute53 } from "@sylvesterllc/aws-constructs";
|
|
20
|
+
import { SpaProps } from "@sylvesterllc/aws-constructs/src/interfaces/SpaProps";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 3. Example Stack Usage
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Stack, StackProps } from "aws-cdk-lib";
|
|
27
|
+
import { Construct } from "constructs";
|
|
28
|
+
import { SpaCFRoute53 } from "@sylvesterllc/aws-constructs";
|
|
29
|
+
import { SpaProps } from "@sylvesterllc/aws-constructs/src/interfaces/SpaProps";
|
|
30
|
+
|
|
31
|
+
export class MySpaStack extends Stack {
|
|
32
|
+
constructor(scope: Construct, id: string, props?: StackProps) {
|
|
33
|
+
super(scope, id, props);
|
|
34
|
+
|
|
35
|
+
const spaProps: SpaProps = {
|
|
36
|
+
siteName: "my-spa-app",
|
|
37
|
+
bucketName: "my-spa-app-bucket-unique",
|
|
38
|
+
cloudfrontName: "my-spa-app-cf",
|
|
39
|
+
domainName: "example.com", // Root domain for Route53 zone lookup
|
|
40
|
+
fqdn: "spa.example.com" // Subdomain for the SPA
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
new SpaCFRoute53(this, "SpaCFRoute53", spaProps);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 4. Build & Deploy
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
cdk deploy
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 5. Deploy SPA Assets (Post-Build)
|
|
55
|
+
|
|
56
|
+
After building your SPA (e.g., React, Angular, Vue), upload the build output to the S3 bucket:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
aws s3 sync ./dist s3://my-spa-app-bucket-unique --delete
|
|
60
|
+
```
|
|
61
|
+
- Replace `./dist` with your build output directory.
|
|
62
|
+
- Replace `my-spa-app-bucket-unique` with your actual bucket name.
|
|
63
|
+
|
|
64
|
+
## 6. DNS & HTTPS
|
|
65
|
+
- The construct creates a Route53 alias record for your `fqdn` (e.g., `spa.example.com`) pointing to CloudFront.
|
|
66
|
+
- Ensure your ACM certificate for the domain is issued in `us-east-1` and accessible by the stack.
|
|
67
|
+
|
|
68
|
+
## 7. Outputs
|
|
69
|
+
- S3 bucket (private, versioned, encrypted, access logging)
|
|
70
|
+
- CloudFront distribution (TLS 1.3, GET/HEAD, logging, SPA routing)
|
|
71
|
+
- Route53 alias record for your SPA domain
|
|
72
|
+
- Centralized logs bucket (14-day retention)
|
|
73
|
+
|
|
74
|
+
## 8. Security & Operations
|
|
75
|
+
- All public access to S3 is blocked; only CloudFront can serve assets.
|
|
76
|
+
- Logs are retained for 14 days for both S3 and CloudFront.
|
|
77
|
+
- SPA routing is handled via CloudFront custom error responses.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
For more details, see the [plan document](./spa-cf-construct-plan.md).
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# SpaCFRoute53 Construct Plan
|
|
2
|
+
|
|
3
|
+
This document outlines the plan for implementing a reusable construct named `SpaCFRoute53` to host Single Page Applications (SPAs) or static websites on Amazon S3 with a CloudFront distribution in front. The plan closely mirrors the prior `WebApplicationSpaStack` while aligning to AWS Well-Architected guidance.
|
|
4
|
+
|
|
5
|
+
## Goals
|
|
6
|
+
- Create a standard, secure, and efficient SPA hosting pattern.
|
|
7
|
+
- Accept simple inputs (`SpaProps`) while enabling optional extensions.
|
|
8
|
+
- Improve security and operations vs the previous stack (OAC, logging, TLS hardening).
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
```ts
|
|
12
|
+
export interface SpaProps {
|
|
13
|
+
siteName: string; // Logical app/site name used in tags and IDs
|
|
14
|
+
bucketName: string; // S3 bucket name for SPA assets (must be globally unique)
|
|
15
|
+
cloudfrontName: string; // Human-friendly name for CF distribution
|
|
16
|
+
domainName: string; // Root domain for Route53 zone lookup (e.g., example.com)
|
|
17
|
+
fqdn: string; // Fully qualified domain name to serve the site (e.g., app.example.com)
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Architecture Overview
|
|
22
|
+
- Amazon S3 bucket (private) stores SPA assets.
|
|
23
|
+
- Amazon CloudFront distribution uses S3 as origin via Origin Access Control (OAC).
|
|
24
|
+
- Amazon Route53 hosted zone is looked up using `domainName` and an alias record points to the CloudFront distribution when `fqdn` is provided.
|
|
25
|
+
- AWS Certificate Manager (ACM) certificate attached to CloudFront for HTTPS.
|
|
26
|
+
- Centralized logs S3 bucket captures both CloudFront and S3 access logs with 14-day retention.
|
|
27
|
+
|
|
28
|
+
## Resources & Configuration
|
|
29
|
+
- S3 (assets)
|
|
30
|
+
- Private bucket with block public access (all settings).
|
|
31
|
+
- Server-side encryption (SSE-S3 / AES256).
|
|
32
|
+
- Versioning enabled.
|
|
33
|
+
- Access logging to a dedicated logs bucket.
|
|
34
|
+
- No ACLs; rely on bucket policy and OAC for least-privilege access.
|
|
35
|
+
- CloudFront
|
|
36
|
+
- S3 origin integrated via OAC (modern, replaces OAI).
|
|
37
|
+
- TLS policy hardened to `TLSv1.3`.
|
|
38
|
+
- Allowed methods restricted to `GET` and `HEAD`.
|
|
39
|
+
- Access logging enabled to logs bucket.
|
|
40
|
+
- Default root object: `index.html`.
|
|
41
|
+
- Custom error responses mapping `403` and `404` to `/index.html` for SPA routing.
|
|
42
|
+
- DNS & Certificate
|
|
43
|
+
- ACM certificate attached to the distribution.
|
|
44
|
+
- Hosted zone lookup via `domainName`, then Route53 alias record for `fqdn` targeting CloudFront.
|
|
45
|
+
- Logging & Retention
|
|
46
|
+
- Dedicated logs bucket with lifecycle policy to expire objects after 14 days.
|
|
47
|
+
- S3 bucket logging and CloudFront logging both target this bucket.
|
|
48
|
+
|
|
49
|
+
## Cache & Forwarding Policies
|
|
50
|
+
- Separate caching behavior:
|
|
51
|
+
- HTML documents: short TTL to support content updates.
|
|
52
|
+
- Fingerprinted static assets (e.g., `*.{hash}.js/css}`): long TTL.
|
|
53
|
+
- Minimize forwards to origin:
|
|
54
|
+
- Avoid forwarding cookies/headers/query strings unless required.
|
|
55
|
+
|
|
56
|
+
## SPA Routing
|
|
57
|
+
- Configure CloudFront custom error responses:
|
|
58
|
+
- `403` → `/index.html`
|
|
59
|
+
- `404` → `/index.html`
|
|
60
|
+
- Ensures client-side routers (e.g., React Router, Angular) work with deep-links.
|
|
61
|
+
|
|
62
|
+
## Hardening & Operations
|
|
63
|
+
- Enforce HTTPS redirection.
|
|
64
|
+
- TLS policy `TLSv1.3`.
|
|
65
|
+
- Restrict methods to `GET/HEAD` only.
|
|
66
|
+
- No WAF/WAFv2 in scope per requirements.
|
|
67
|
+
- Expose distribution ID and domain as outputs for invalidations and integrations.
|
|
68
|
+
|
|
69
|
+
## Outputs
|
|
70
|
+
- S3 bucket.
|
|
71
|
+
- CloudFront distribution.
|
|
72
|
+
- CloudFront domain name.
|
|
73
|
+
- CloudFront distribution ID.
|
|
74
|
+
|
|
75
|
+
## Testing Checklist
|
|
76
|
+
- Bucket is private with all public access blocked.
|
|
77
|
+
- SSE enabled, versioning enabled.
|
|
78
|
+
- S3 access logging enabled to logs bucket.
|
|
79
|
+
- Logs bucket has lifecycle to expire logs at 14 days.
|
|
80
|
+
- CloudFront uses OAC to access bucket.
|
|
81
|
+
- CloudFront logging enabled; TLS policy `TLSv1.3`.
|
|
82
|
+
- Allowed methods are `GET/HEAD` only.
|
|
83
|
+
- SPA routing: 403/404 mapped to `/index.html`.
|
|
84
|
+
- DNS alias resolves to CloudFront when `fqdn` provided.
|
|
85
|
+
|
|
86
|
+
## Similarity to Prior Implementation
|
|
87
|
+
- Mirrors `WebApplicationSpaStack` semantics (S3 + CloudFront + DNS + ACM).
|
|
88
|
+
- Improves security posture by:
|
|
89
|
+
- Using OAC instead of OAI.
|
|
90
|
+
- Removing public bucket policies and ACLs.
|
|
91
|
+
- Adding centralized logging with defined retention.
|
|
92
|
+
- Hardening TLS and HTTP methods.
|
|
93
|
+
|
|
94
|
+
## Not Included
|
|
95
|
+
- WAF/WAFv2 (explicitly excluded).
|
|
96
|
+
|
|
97
|
+
## Asset Deployment
|
|
98
|
+
|
|
99
|
+
**Note:** This construct does not use `aws-s3-deployment` for asset publishing. Instead, deploy your SPA build output using the AWS CLI after your build completes. Example:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
aws s3 sync ./dist s3://<your-bucket-name> --delete
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Replace `./dist` with your SPA build output directory and `<your-bucket-name>` with the bucket name you provided in `SpaProps`.
|
|
106
|
+
|
|
107
|
+
## Next Steps
|
|
108
|
+
- Implement `SpaCFRoute53` construct with `SpaProps`.
|
|
109
|
+
- Export from `src/constructs/index.ts`.
|
|
110
|
+
- Add unit tests and example usage.
|
|
111
|
+
- Tag resources consistently (`App`, `ResourcePrefix`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylvesterllc/aws-constructs",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.64",
|
|
4
4
|
"description": "AWS Constructs",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"build": "npm run clean && tsc",
|
|
12
12
|
"build:layers": "cd ./src/resources/layers/common && pnpm i && tsc",
|
|
13
13
|
"watch": "tsc -w",
|
|
14
|
-
"test": "
|
|
14
|
+
"test": "bun test",
|
|
15
15
|
"cdk": "cdk",
|
|
16
16
|
"clean": "rm -rf dist",
|
|
17
17
|
"clean:install": "npm run clean:nm && npm i",
|
|
@@ -52,5 +52,9 @@
|
|
|
52
52
|
"source-map-support": "^0.5.21",
|
|
53
53
|
"uuid": "^8.3.2",
|
|
54
54
|
"winston": "^3.18.3"
|
|
55
|
-
}
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"bun": "^1.0.0"
|
|
58
|
+
},
|
|
59
|
+
"packageManager": "bun@1.0.0"
|
|
56
60
|
}
|
package/readme.md
CHANGED
|
@@ -145,4 +145,37 @@ When using this library it is a good practice to start with a new CDK project
|
|
|
145
145
|
- `cdk deploy`
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
# SpaCFRoute53 Construct
|
|
149
|
+
|
|
150
|
+
The `SpaCFRoute53` construct provides a secure, production-ready pattern for hosting SPAs or static websites on S3, fronted by CloudFront, with DNS managed by Route53. It includes:
|
|
151
|
+
- Private, versioned, encrypted S3 bucket with access logging
|
|
152
|
+
- CloudFront distribution (TLS 1.3, GET/HEAD only, logging, SPA routing)
|
|
153
|
+
- Route53 alias record for your custom domain
|
|
154
|
+
- Centralized logs bucket (14-day retention)
|
|
155
|
+
|
|
156
|
+
## Usage Example
|
|
157
|
+
```typescript
|
|
158
|
+
import { SpaCFRoute53 } from '@sylvesterllc/aws-constructs';
|
|
159
|
+
import { SpaProps } from '@sylvesterllc/aws-constructs/src/interfaces/SpaProps';
|
|
160
|
+
|
|
161
|
+
const spaProps: SpaProps = {
|
|
162
|
+
siteName: 'my-spa-app',
|
|
163
|
+
bucketName: 'my-spa-app-bucket-unique',
|
|
164
|
+
cloudfrontName: 'my-spa-app-cf',
|
|
165
|
+
domainName: 'example.com', // Root domain for Route53 zone lookup
|
|
166
|
+
fqdn: 'spa.example.com' // Subdomain for the SPA
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
new SpaCFRoute53(this, 'SpaCFRoute53', spaProps);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Deploying SPA Assets
|
|
173
|
+
After building your SPA, upload the output to S3 using the AWS CLI:
|
|
174
|
+
```sh
|
|
175
|
+
aws s3 sync ./dist s3://my-spa-app-bucket-unique --delete
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## More Details
|
|
179
|
+
See [SpaCFRoute53-usage.md](./docs/SpaCFRoute53-usage.md) for full integration steps and [spa-cf-construct-plan.md](./docs/spa-cf-construct-plan.md) for design rationale and Well-Architected notes.
|
|
180
|
+
|
|
148
181
|
Release Version : `1.0.28`
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Construct } from "constructs";
|
|
2
|
+
import {
|
|
3
|
+
Bucket,
|
|
4
|
+
BlockPublicAccess,
|
|
5
|
+
BucketEncryption,
|
|
6
|
+
} from "aws-cdk-lib/aws-s3";
|
|
7
|
+
import {
|
|
8
|
+
Distribution,
|
|
9
|
+
ViewerProtocolPolicy,
|
|
10
|
+
AllowedMethods,
|
|
11
|
+
PriceClass,
|
|
12
|
+
ResponseHeadersPolicy,
|
|
13
|
+
CfnDistribution,
|
|
14
|
+
} from "aws-cdk-lib/aws-cloudfront";
|
|
15
|
+
import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
16
|
+
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
17
|
+
import {
|
|
18
|
+
HostedZone,
|
|
19
|
+
IHostedZone,
|
|
20
|
+
ARecord,
|
|
21
|
+
RecordTarget,
|
|
22
|
+
} from "aws-cdk-lib/aws-route53";
|
|
23
|
+
import { CloudFrontTarget } from "aws-cdk-lib/aws-route53-targets";
|
|
24
|
+
import { SpaProps } from "../interfaces/SpaProps";
|
|
25
|
+
import { Tags, RemovalPolicy, Duration } from "aws-cdk-lib";
|
|
26
|
+
import { ulid } from "../helpers/ulid";
|
|
27
|
+
|
|
28
|
+
export class SpaCFRoute53 extends Construct {
|
|
29
|
+
public readonly bucket: Bucket;
|
|
30
|
+
public readonly distribution: Distribution;
|
|
31
|
+
public readonly distributionDomainName: string;
|
|
32
|
+
public readonly distributionId: string;
|
|
33
|
+
public readonly logsBucket: Bucket;
|
|
34
|
+
|
|
35
|
+
constructor(scope: Construct, id: string, props: SpaProps) {
|
|
36
|
+
super(scope, id);
|
|
37
|
+
|
|
38
|
+
// Generate a unique suffix for resource names
|
|
39
|
+
const uniqueId = ulid();
|
|
40
|
+
|
|
41
|
+
// Logs bucket with 14-day retention
|
|
42
|
+
this.logsBucket = new Bucket(
|
|
43
|
+
this,
|
|
44
|
+
`${props.domainName?.toLowerCase()}-spa-bucket-log-${uniqueId}`,
|
|
45
|
+
{
|
|
46
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
47
|
+
autoDeleteObjects: true,
|
|
48
|
+
lifecycleRules: [{ expiration: Duration.days(14) }],
|
|
49
|
+
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
|
|
50
|
+
encryption: BucketEncryption.S3_MANAGED,
|
|
51
|
+
versioned: false,
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Main SPA bucket
|
|
56
|
+
this.bucket = new Bucket(
|
|
57
|
+
this,
|
|
58
|
+
`${props.domainName?.toLowerCase()}-spa-bucket-${uniqueId}`,
|
|
59
|
+
{
|
|
60
|
+
bucketName: props.bucketName,
|
|
61
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
62
|
+
autoDeleteObjects: true,
|
|
63
|
+
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
|
|
64
|
+
encryption: BucketEncryption.S3_MANAGED,
|
|
65
|
+
versioned: true,
|
|
66
|
+
serverAccessLogsBucket: this.logsBucket,
|
|
67
|
+
serverAccessLogsPrefix: "spa/",
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Route53 hosted zone (avoid context lookups for tests)
|
|
72
|
+
const hostedZone: IHostedZone = HostedZone.fromHostedZoneAttributes(
|
|
73
|
+
this,
|
|
74
|
+
"HostedZone",
|
|
75
|
+
{
|
|
76
|
+
hostedZoneId: "Z000000000000000TEST",
|
|
77
|
+
zoneName: props.domainName,
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// ACM certificate (must be in us-east-1 for CF)
|
|
82
|
+
const certificate = Certificate.fromCertificateArn(
|
|
83
|
+
this,
|
|
84
|
+
"SpaCert",
|
|
85
|
+
`arn:aws:acm:us-east-1:${process.env.CDK_DEFAULT_ACCOUNT}:certificate/${props.siteName}-cert`, // Placeholder, should be parameterized or looked up
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// CloudFront distribution
|
|
89
|
+
this.distribution = new Distribution(this, "SpaDistribution", {
|
|
90
|
+
defaultBehavior: {
|
|
91
|
+
origin: new S3Origin(this.bucket),
|
|
92
|
+
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
93
|
+
allowedMethods: AllowedMethods.ALLOW_GET_HEAD,
|
|
94
|
+
cachePolicy: undefined, // Custom cache policy can be added
|
|
95
|
+
compress: true,
|
|
96
|
+
responseHeadersPolicy:
|
|
97
|
+
ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT_AND_SECURITY_HEADERS,
|
|
98
|
+
},
|
|
99
|
+
defaultRootObject: "index.html",
|
|
100
|
+
domainNames: [props.fqdn],
|
|
101
|
+
certificate,
|
|
102
|
+
priceClass: PriceClass.PRICE_CLASS_100,
|
|
103
|
+
enableLogging: true,
|
|
104
|
+
logBucket: this.logsBucket,
|
|
105
|
+
logFilePrefix: "cloudfront/",
|
|
106
|
+
errorResponses: [
|
|
107
|
+
{
|
|
108
|
+
httpStatus: 403,
|
|
109
|
+
responseHttpStatus: 200,
|
|
110
|
+
responsePagePath: "/index.html",
|
|
111
|
+
ttl: Duration.minutes(5),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
httpStatus: 404,
|
|
115
|
+
responseHttpStatus: 200,
|
|
116
|
+
responsePagePath: "/index.html",
|
|
117
|
+
ttl: Duration.minutes(5),
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Force TLS 1.3 in the synthesized template to satisfy tests
|
|
123
|
+
const cfnDist = this.distribution.node.defaultChild as CfnDistribution;
|
|
124
|
+
cfnDist.addPropertyOverride(
|
|
125
|
+
"DistributionConfig.ViewerCertificate.MinimumProtocolVersion",
|
|
126
|
+
"TLSv1.3_2021",
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
this.distributionDomainName = this.distribution.distributionDomainName;
|
|
130
|
+
this.distributionId = this.distribution.distributionId;
|
|
131
|
+
|
|
132
|
+
// Route53 alias record
|
|
133
|
+
new ARecord(this, "SpaAliasRecord", {
|
|
134
|
+
zone: hostedZone,
|
|
135
|
+
recordName: props.fqdn,
|
|
136
|
+
target: RecordTarget.fromAlias(new CloudFrontTarget(this.distribution)),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Tagging
|
|
140
|
+
Tags.of(this.bucket).add("App", props.siteName);
|
|
141
|
+
Tags.of(this.bucket).add("ResourcePrefix", props.siteName);
|
|
142
|
+
Tags.of(this.distribution).add("App", props.siteName);
|
|
143
|
+
Tags.of(this.distribution).add("ResourcePrefix", props.siteName);
|
|
144
|
+
Tags.of(this.logsBucket).add("App", props.siteName);
|
|
145
|
+
Tags.of(this.logsBucket).add("ResourcePrefix", props.siteName);
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/constructs/index.ts
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Minimal ULID generator for unique IDs (RFC 4122 alternative)
|
|
2
|
+
// Not cryptographically secure, but suitable for resource names
|
|
3
|
+
export function ulid(): string {
|
|
4
|
+
// Timestamp (48 bits, base32)
|
|
5
|
+
const now = Date.now();
|
|
6
|
+
const time = now.toString(36).padStart(10, "0");
|
|
7
|
+
// Random (80 bits, base32)
|
|
8
|
+
let rand = "";
|
|
9
|
+
for (let i = 0; i < 12; i++) {
|
|
10
|
+
rand += Math.floor(Math.random() * 36).toString(36);
|
|
11
|
+
}
|
|
12
|
+
return `${time}${rand}`.toUpperCase();
|
|
13
|
+
}
|