Skip to content

Commit d7170fa

Browse files
committed
feat: DNS updates to shared infra
1 parent 4b6abd1 commit d7170fa

2 files changed

Lines changed: 90 additions & 30 deletions

File tree

shared/infra/aws/lib/network.ts

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ import {
4141
LoadBalancerV2Origin,
4242
S3BucketOrigin,
4343
} from "aws-cdk-lib/aws-cloudfront-origins";
44+
import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
45+
import { getPrimaryDomainName } from "./shared-resources-stack";
4446

4547
export interface NetworkProps {
4648
env: string;
4749
account: string;
50+
certificate?: ICertificate;
4851
}
4952

5053
export class Network extends Construct {
@@ -96,16 +99,16 @@ export class Network extends Construct {
9699
allowAllOutbound: true,
97100
description: "No inbound / all outbound",
98101
securityGroupName: "noInboundAllOutboundSecurityGroup",
99-
}
102+
},
100103
);
101104
this.noInboundAllOutboundSecurityGroup.addIngressRule(
102105
this.noInboundAllOutboundSecurityGroup,
103106
Port.tcp(8080),
104-
"allow self"
107+
"allow self",
105108
);
106109
this.noInboundAllOutboundSecurityGroup.addIngressRule(
107110
Peer.ipv4(this.vpc.vpcCidrBlock),
108-
Port.tcp(8080)
111+
Port.tcp(8080),
109112
);
110113

111114
this.vpcLink = new VpcLink(this, "HttpApiVpcLink", {
@@ -128,7 +131,7 @@ export class Network extends Construct {
128131
{
129132
stringValue: this.noInboundAllOutboundSecurityGroup.securityGroupId,
130133
parameterName: `/stickerlandia/${props.env}/shared/vpc-link-sg-id`,
131-
}
134+
},
132135
);
133136

134137
this.httpApi = new HttpApi(this, "StickerlandiaHttpApi", {
@@ -159,28 +162,37 @@ export class Network extends Construct {
159162
});
160163
webFrontendBucket.grantRead(originIdentity);
161164

165+
// If the certificate is passed in, create a domain for that specific environment.
166+
const primaryDomainName = getPrimaryDomainName(
167+
props.certificate,
168+
props.env,
169+
);
170+
const domainName = primaryDomainName ? [primaryDomainName] : undefined;
171+
162172
this.distribution = new Distribution(this, `Stickerlandia-${props.env}`, {
173+
certificate: props.certificate,
174+
domainNames: domainName,
163175
minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2_2021,
164176
defaultRootObject: "index.html",
177+
// SPA routing: return index.html for any unmatched routes on the frontend
178+
// S3 with OAI returns 403 for non-existent files, so we handle both 403 and 404
179+
// This only applies to the default behavior (S3/frontend), not API routes
180+
errorResponses: [
181+
{
182+
httpStatus: 403,
183+
responseHttpStatus: 200,
184+
responsePagePath: "/index.html",
185+
},
186+
{
187+
httpStatus: 404,
188+
responseHttpStatus: 200,
189+
responsePagePath: "/index.html",
190+
},
191+
],
165192
defaultBehavior: {
166193
origin: S3BucketOrigin.withOriginAccessIdentity(webFrontendBucket, {
167194
originAccessIdentity: originIdentity,
168195
}),
169-
// SPA routing: return index.html for any unmatched routes on the frontend
170-
// S3 with OAI returns 403 for non-existent files, so we handle both 403 and 404
171-
// This only applies to the default behavior (S3/frontend), not API routes
172-
errorResponses: [
173-
{
174-
httpStatus: 403,
175-
responseHttpStatus: 200,
176-
responsePagePath: "/index.html",
177-
},
178-
{
179-
httpStatus: 404,
180-
responseHttpStatus: 200,
181-
responsePagePath: "/index.html",
182-
},
183-
],
184196
},
185197
});
186198

@@ -192,15 +204,15 @@ export class Network extends Construct {
192204
`${this.httpApi.apiId}.execute-api.${region}.amazonaws.com`,
193205
{
194206
protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
195-
}
207+
},
196208
),
197209
{
198210
cachePolicy: CachePolicy.CACHING_DISABLED,
199211
originRequestPolicy: OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
200212
responseHeadersPolicy:
201213
ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT_AND_SECURITY_HEADERS,
202214
allowedMethods: AllowedMethods.ALLOW_ALL,
203-
}
215+
},
204216
);
205217

206218
this.distribution.addBehavior(
@@ -209,15 +221,15 @@ export class Network extends Construct {
209221
`${this.httpApi.apiId}.execute-api.${region}.amazonaws.com`,
210222
{
211223
protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
212-
}
224+
},
213225
),
214226
{
215227
cachePolicy: CachePolicy.CACHING_DISABLED,
216228
originRequestPolicy: OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
217229
responseHeadersPolicy:
218230
ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT_AND_SECURITY_HEADERS,
219231
allowedMethods: AllowedMethods.ALLOW_ALL,
220-
}
232+
},
221233
);
222234

223235
this.distribution.addBehavior(
@@ -226,15 +238,15 @@ export class Network extends Construct {
226238
`${this.httpApi.apiId}.execute-api.${region}.amazonaws.com`,
227239
{
228240
protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
229-
}
241+
},
230242
),
231243
{
232244
cachePolicy: CachePolicy.CACHING_DISABLED,
233245
originRequestPolicy: OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
234246
responseHeadersPolicy:
235247
ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT_AND_SECURITY_HEADERS,
236248
allowedMethods: AllowedMethods.ALLOW_ALL,
237-
}
249+
},
238250
);
239251

240252
this.distribution.addBehavior(
@@ -243,15 +255,15 @@ export class Network extends Construct {
243255
`${this.httpApi.apiId}.execute-api.${region}.amazonaws.com`,
244256
{
245257
protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
246-
}
258+
},
247259
),
248260
{
249261
cachePolicy: CachePolicy.CACHING_DISABLED,
250262
originRequestPolicy: OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
251263
responseHeadersPolicy:
252264
ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT_AND_SECURITY_HEADERS,
253265
allowedMethods: AllowedMethods.ALLOW_ALL,
254-
}
266+
},
255267
);
256268
}
257269
}

shared/infra/aws/lib/shared-resources-stack.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,45 @@ import { PrivateDnsNamespace } from "aws-cdk-lib/aws-servicediscovery";
1212
import { StringParameter } from "aws-cdk-lib/aws-ssm";
1313
import { EventBus } from "aws-cdk-lib/aws-events";
1414
import { Persistence } from "./persistence";
15+
import { CnameRecord, PublicHostedZone } from "aws-cdk-lib/aws-route53";
16+
import { Certificate, ICertificate } from "aws-cdk-lib/aws-certificatemanager";
1517
// import * as sqs from 'aws-cdk-lib/aws-sqs';
1618

1719
export class StickerlandiaSharedResourcesStack extends cdk.Stack {
1820
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
1921
super(scope, id, props);
2022

2123
const env = process.env.ENV || "dev";
24+
const certificateArn = process.env.CERTIFICATE_ARN;
25+
const hostedZoneId = process.env.HOSTED_ZONE_ID;
26+
27+
// Only deploy shared resources for 'prod' and 'dev' environments
28+
if (env !== "prod" && env !== "dev") {
29+
return;
30+
}
31+
32+
const hostedZone = hostedZoneId
33+
? PublicHostedZone.fromPublicHostedZoneAttributes(
34+
this,
35+
"StickerlandiaHostedZone",
36+
{
37+
hostedZoneId: hostedZoneId,
38+
zoneName: "stickerlandia.dev",
39+
},
40+
)
41+
: undefined;
42+
43+
const cert = certificateArn
44+
? Certificate.fromCertificateArn(this, "Certificate", certificateArn)
45+
: undefined;
46+
47+
const domainName = getPrimaryDomainName(cert, env);
48+
const primaryDomainName = domainName ? `https://${domainName}` : undefined;
2249

2350
const network = new Network(this, "Network", {
2451
env,
2552
account: this.account,
53+
certificate: cert,
2654
});
2755

2856
const persistence = new Persistence(this, "Persistence", {
@@ -31,6 +59,15 @@ export class StickerlandiaSharedResourcesStack extends cdk.Stack {
3159
vpc: network.vpc,
3260
});
3361

62+
if (hostedZone) {
63+
const cNameRecord = new CnameRecord(this, "CnameRecord", {
64+
zone: hostedZone,
65+
domainName: network.distribution.domainName,
66+
recordName: env,
67+
ttl: cdk.Duration.minutes(5),
68+
});
69+
}
70+
3471
const dnsNamespace = new PrivateDnsNamespace(this, "PrivateDnsNamespace", {
3572
name: `${env}.stickerlandia.local`,
3673
vpc: network.vpc,
@@ -62,9 +99,9 @@ export class StickerlandiaSharedResourcesStack extends cdk.Stack {
6299
this,
63100
"CloudFrontEndpointParam",
64101
{
65-
stringValue: network.distribution.domainName,
102+
stringValue: primaryDomainName ?? network.distribution.domainName,
66103
parameterName: `/stickerlandia/${env}/shared/cloudfront-endpoint`,
67-
}
104+
},
68105
);
69106

70107
const cloudfrontId = new StringParameter(this, "CloudFrontIdParam", {
@@ -88,7 +125,7 @@ export class StickerlandiaSharedResourcesStack extends cdk.Stack {
88125

89126
// CDK Outputs
90127
new cdk.CfnOutput(this, "BaseUrl", {
91-
value: `https://${network.distribution.domainName}`,
128+
value: primaryDomainName ?? network.distribution.domainName,
92129
description: "Base URL for Stickerlandia (CloudFront distribution)",
93130
exportName: `stickerlandia-${env}-base-url`,
94131
});
@@ -100,3 +137,14 @@ export class StickerlandiaSharedResourcesStack extends cdk.Stack {
100137
});
101138
}
102139
}
140+
141+
export function getPrimaryDomainName(
142+
cert: ICertificate | undefined,
143+
env: string,
144+
): string | undefined {
145+
return cert
146+
? env === "prod"
147+
? "stickerlandia.dev"
148+
: `${env}.stickerlandia.dev`
149+
: undefined;
150+
}

0 commit comments

Comments
 (0)