@fjall/components-infrastructure 21.1.0 → 23.0.0
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/dist/lib/config/aws/ipam.js +6 -2
- package/dist/lib/patterns/aws/account.js +28 -1
- package/dist/lib/patterns/aws/cdn.d.ts +2 -4
- package/dist/lib/patterns/aws/cdn.js +15 -2
- package/dist/lib/patterns/aws/interfaces/pattern.d.ts +4 -1
- package/dist/lib/patterns/aws/staticSite.d.ts +1 -1
- package/dist/lib/patterns/aws/staticSite.js +6 -1
- package/dist/lib/resources/aws/cdn/cloudFront.d.ts +22 -7
- package/dist/lib/resources/aws/cdn/cloudFront.js +47 -14
- package/dist/lib/resources/aws/compute/ecsNetworking.js +4 -6
- package/dist/lib/resources/aws/iam/delegationRole.js +7 -5
- package/dist/lib/resources/aws/networking/domainCertificate.d.ts +1 -9
- package/dist/lib/resources/aws/networking/domainCertificate.js +1 -2
- package/dist/lib/resources/aws/networking/vpcPeeringAccepterRole.d.ts +1 -1
- package/dist/lib/resources/aws/networking/vpcPeeringAccepterRole.js +6 -4
- package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +6 -4
- package/dist/lib/utils/costAllocationTags.d.ts +9 -8
- package/dist/lib/utils/costAllocationTags.js +0 -3
- package/package.json +3 -3
|
@@ -2,6 +2,7 @@ import { CfnOutput } from "aws-cdk-lib";
|
|
|
2
2
|
import { Construct } from "constructs";
|
|
3
3
|
import { Ipam as IpamClass } from "../../resources/aws/networking/ipam.js";
|
|
4
4
|
import { getConfig } from "../../utils/getConfig.js";
|
|
5
|
+
import { COST_ALLOCATION_TAGS, DEFAULT_COST_ALLOCATION_ENVIRONMENT } from "../../utils/costAllocationTags.js";
|
|
5
6
|
import { IPAM_PRIVATE_DEFAULT_SCOPE_EXPORT_NAME } from "../../utils/exportNaming.js";
|
|
6
7
|
export class Ipam extends Construct {
|
|
7
8
|
privateDefaultScopeId;
|
|
@@ -18,8 +19,11 @@ export class Ipam extends Construct {
|
|
|
18
19
|
})),
|
|
19
20
|
tags: [
|
|
20
21
|
{
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// Explicit claim, not a fallback: the IPAM is an org-wide pool that
|
|
23
|
+
// genuinely lives in the management estate regardless of which app
|
|
24
|
+
// synthesises it.
|
|
25
|
+
key: COST_ALLOCATION_TAGS.ENVIRONMENT,
|
|
26
|
+
value: DEFAULT_COST_ALLOCATION_ENVIRONMENT
|
|
23
27
|
}
|
|
24
28
|
]
|
|
25
29
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CfnOutput, Stack, Tags } from "aws-cdk-lib";
|
|
1
|
+
import { CfnOutput, Lazy, Stack, Tags } from "aws-cdk-lib";
|
|
2
2
|
import { EcrDefaultImage, SharedAlarmTopic } from "../../config/aws/index.js";
|
|
3
3
|
import { ManagementEventsTrail } from "../../config/aws/cloudTrail.js";
|
|
4
4
|
import { OidcConnector } from "../../config/aws/oidcConnector.js";
|
|
@@ -16,6 +16,18 @@ import { SecurityHubHub } from "../../config/aws/securityHubHub.js";
|
|
|
16
16
|
import { ConfigRecorder } from "../../config/aws/configRecorder.js";
|
|
17
17
|
import { AccountAccessAnalyser } from "../../config/aws/accessAnalyser.js";
|
|
18
18
|
import { InspectorEnablement } from "../../config/aws/inspectorEnablement.js";
|
|
19
|
+
/**
|
|
20
|
+
* `fjall:description` values for the governance tiers — the same user-facing
|
|
21
|
+
* gold-plating the domain-side constructs carry, so a governance stack's
|
|
22
|
+
* resources explain themselves in the console exactly like a zone or
|
|
23
|
+
* certificate does. Keyed by `organisationType` so each tier self-describes
|
|
24
|
+
* from ONE stamp in this base class.
|
|
25
|
+
*/
|
|
26
|
+
const GOVERNANCE_TIER_DESCRIPTIONS = {
|
|
27
|
+
organisation: "Fjall-managed organisation governance: org root, member accounts and organisation-wide policy",
|
|
28
|
+
platform: "Fjall-managed platform governance: shared platform services and CI trust",
|
|
29
|
+
account: "Fjall-managed account governance: account security and operations baseline"
|
|
30
|
+
};
|
|
19
31
|
export class Account extends Stack {
|
|
20
32
|
organisationType = "account";
|
|
21
33
|
resolvedRegion;
|
|
@@ -33,6 +45,21 @@ export class Account extends Stack {
|
|
|
33
45
|
// read "FjallApp"; the stack-scope tag wins over App scope and names the
|
|
34
46
|
// governance tier instead ("Organisation"/"Account"/"Platform").
|
|
35
47
|
Tags.of(this).add(COST_ALLOCATION_TAGS.SERVICE, id);
|
|
48
|
+
// Lazy, NOT a direct read: subclass field initialisers run after super(),
|
|
49
|
+
// so `this.organisationType` here would say "account" for every tier (the
|
|
50
|
+
// same trap the connector-mode note below documents). By synth the field
|
|
51
|
+
// is final and the tag renders the true tier.
|
|
52
|
+
//
|
|
53
|
+
// excludeResourceTypes is load-bearing: stack-level tags travel on the
|
|
54
|
+
// CloudFormation API call as literal strings, so a Lazy token there
|
|
55
|
+
// aborts every governance deploy at changeset creation ("Tag
|
|
56
|
+
// [fjall:description] contained invalid characters"). Resources render
|
|
57
|
+
// the resolved value in-template; only the stack's own tag set is
|
|
58
|
+
// skipped — CloudFormation would re-propagate a stack tag to those same
|
|
59
|
+
// resources anyway.
|
|
60
|
+
Tags.of(this).add("fjall:description", Lazy.string({
|
|
61
|
+
produce: () => GOVERNANCE_TIER_DESCRIPTIONS[this.organisationType]
|
|
62
|
+
}), { excludeResourceTypes: ["aws:cdk:stack"] });
|
|
36
63
|
this.resolvedRegion = region ?? this.region;
|
|
37
64
|
const orgId = this.node.tryGetContext(CDK_CONTEXT_KEYS.ORG_ID);
|
|
38
65
|
if (orgId) {
|
|
@@ -98,7 +98,8 @@ export interface S3CdnProps extends BaseCdnProps {
|
|
|
98
98
|
originAccess?: "oai" | "oac";
|
|
99
99
|
/** Object served for `/` (static sites: "index.html"). */
|
|
100
100
|
defaultRootObject?: string;
|
|
101
|
-
/** "multipage" →
|
|
101
|
+
/** "multipage" (/about → /about.html), "directory" (/about →
|
|
102
|
+
* /about/index.html) or "spa" (403/404 fall back to /index.html). */
|
|
102
103
|
routing?: StaticSiteRouting;
|
|
103
104
|
/** Security-headers policy (built by the caller, e.g. SecurityHeadersPolicy). */
|
|
104
105
|
responseHeadersPolicy?: IResponseHeadersPolicy;
|
|
@@ -193,9 +194,6 @@ export interface SmartCdnBehaviour {
|
|
|
193
194
|
*/
|
|
194
195
|
originRecord?: "alias" | "none";
|
|
195
196
|
}
|
|
196
|
-
/**
|
|
197
|
-
* CDN wrapper class that extends CloudFrontDistribution with Fjall patterns.
|
|
198
|
-
*/
|
|
199
197
|
export declare class Cdn extends CloudFrontDistribution implements ICdn {
|
|
200
198
|
constructor(scope: Construct, id: string, props: ICdnProps);
|
|
201
199
|
private static createEcsOriginResolver;
|
|
@@ -208,6 +208,19 @@ function validateCdnProps(props) {
|
|
|
208
208
|
/**
|
|
209
209
|
* CDN wrapper class that extends CloudFrontDistribution with Fjall patterns.
|
|
210
210
|
*/
|
|
211
|
+
/**
|
|
212
|
+
* What each routing mode asks of the distribution, total over the routing
|
|
213
|
+
* vocabulary — a mode added to `STATIC_SITE_ROUTING_VALUES` fails to compile
|
|
214
|
+
* here until it declares its CDN behaviour, instead of silently synthesising
|
|
215
|
+
* a distribution that ignores it. `multipage` and `directory` rewrite URIs in
|
|
216
|
+
* a viewer-request function; `spa` skips rewriting and relies on 403/404
|
|
217
|
+
* error responses falling back to `/index.html`.
|
|
218
|
+
*/
|
|
219
|
+
const ROUTING_CDN_BEHAVIOUR = {
|
|
220
|
+
multipage: { uriRewrite: "html" },
|
|
221
|
+
directory: { uriRewrite: "directory-index" },
|
|
222
|
+
spa: { spaFallback: true }
|
|
223
|
+
};
|
|
211
224
|
export class Cdn extends CloudFrontDistribution {
|
|
212
225
|
constructor(scope, id, props) {
|
|
213
226
|
validateCdnProps(props);
|
|
@@ -429,8 +442,8 @@ export class Cdn extends CloudFrontDistribution {
|
|
|
429
442
|
const s3Routing = props.originType === "s3"
|
|
430
443
|
? {
|
|
431
444
|
defaultRootObject: props.defaultRootObject,
|
|
432
|
-
|
|
433
|
-
|
|
445
|
+
...(props.routing !== undefined &&
|
|
446
|
+
ROUTING_CDN_BEHAVIOUR[props.routing]),
|
|
434
447
|
responseHeadersPolicy: props.responseHeadersPolicy,
|
|
435
448
|
originRequestPolicy: props.originRequestPolicy
|
|
436
449
|
}
|
|
@@ -452,7 +452,10 @@ export interface IStaticSiteProps {
|
|
|
452
452
|
source: string;
|
|
453
453
|
/** Build command + output directory (single source of truth). */
|
|
454
454
|
build: StaticSiteBuildConfig;
|
|
455
|
-
/** "multipage" (
|
|
455
|
+
/** "multipage" (/about → /about.html), "directory" (/about →
|
|
456
|
+
* /about/index.html — Astro's default layout) or "spa" (unknown paths fall
|
|
457
|
+
* back to /index.html). Match it to the build's emitted layout.
|
|
458
|
+
* Default: "multipage". */
|
|
456
459
|
routing?: StaticSiteRouting;
|
|
457
460
|
/** Security-header configuration. */
|
|
458
461
|
security?: StaticSiteSecurityConfig;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Resources created:
|
|
9
9
|
* - Private S3 bucket + BucketDeployment (asset upload)
|
|
10
|
-
* - CloudFront distribution (OAC,
|
|
10
|
+
* - CloudFront distribution (OAC, routing-mode viewer function, security-headers policy)
|
|
11
11
|
* - ACM certificate + Route53 alias record (when `domain` is set)
|
|
12
12
|
* - Contact-form Lambda + Function URL (when `forms` is set)
|
|
13
13
|
*
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Resources created:
|
|
9
9
|
* - Private S3 bucket + BucketDeployment (asset upload)
|
|
10
|
-
* - CloudFront distribution (OAC,
|
|
10
|
+
* - CloudFront distribution (OAC, routing-mode viewer function, security-headers policy)
|
|
11
11
|
* - ACM certificate + Route53 alias record (when `domain` is set)
|
|
12
12
|
* - Contact-form Lambda + Function URL (when `forms` is set)
|
|
13
13
|
*
|
|
@@ -182,6 +182,11 @@ export class StaticSite extends Construct {
|
|
|
182
182
|
bucket: this._bucket,
|
|
183
183
|
originAccess: "oac",
|
|
184
184
|
defaultRootObject: "index.html",
|
|
185
|
+
// The default VALUE stays "multipage" for configs that omit `routing`,
|
|
186
|
+
// but multipage's own semantics changed with the mode split: the old
|
|
187
|
+
// hybrid fragment served trailing-slash links from `…/index.html`. A
|
|
188
|
+
// deployed directory-layout site that relied on that must now state
|
|
189
|
+
// routing: "directory" — the default cannot protect it.
|
|
185
190
|
routing: this.props.routing ?? "multipage",
|
|
186
191
|
cachePolicy: "CACHING_OPTIMIZED",
|
|
187
192
|
priceClass: "PriceClass_100",
|
|
@@ -38,6 +38,8 @@ export interface AccessGateConfig {
|
|
|
38
38
|
username: string;
|
|
39
39
|
password: string;
|
|
40
40
|
}
|
|
41
|
+
/** See {@link CloudFrontDistributionProps.uriRewrite}. */
|
|
42
|
+
export type UriRewriteMode = "html" | "directory-index";
|
|
41
43
|
export interface CloudFrontDistributionProps {
|
|
42
44
|
/**
|
|
43
45
|
* App this CDN fronts. Keys the cross-stack export a `Domain` resolves via
|
|
@@ -67,9 +69,22 @@ export interface CloudFrontDistributionProps {
|
|
|
67
69
|
accessGate?: false | AccessGateConfig;
|
|
68
70
|
/** Object served for a bare `/` request (static sites: "index.html"). */
|
|
69
71
|
defaultRootObject?: string;
|
|
70
|
-
/**
|
|
71
|
-
*
|
|
72
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Rewrites pretty URLs onto the objects the build actually emitted, via a
|
|
74
|
+
* CloudFront Function. Each mode serves BOTH link shapes (`/about` and
|
|
75
|
+
* `/about/`) from its layout's object:
|
|
76
|
+
*
|
|
77
|
+
* - `"html"` → `/about.html`: builds that emit sibling `.html` files
|
|
78
|
+
* (Astro `build.format: "file"`, Next.js default export).
|
|
79
|
+
* - `"directory-index"` → `/about/index.html`: builds that emit a
|
|
80
|
+
* directory per page (Astro's default `build.format: "directory"`).
|
|
81
|
+
*
|
|
82
|
+
* Dynamic `/api/` paths are carved out so POSTs are not rewritten.
|
|
83
|
+
* Replaces the former `cleanUrls: boolean`, whose single fragment rewrote
|
|
84
|
+
* trailing-slash requests to `…/index.html` and bare requests to `….html`
|
|
85
|
+
* — a hybrid that served neither layout completely.
|
|
86
|
+
*/
|
|
87
|
+
uriRewrite?: UriRewriteMode;
|
|
73
88
|
/** SPA fallback: 403/404 → `/index.html` @200 for client-side routing. */
|
|
74
89
|
spaFallback?: boolean;
|
|
75
90
|
/** Security-headers policy applied to the default + every additional behaviour. */
|
|
@@ -98,14 +113,14 @@ export declare class CloudFrontDistribution extends Construct {
|
|
|
98
113
|
constructor(scope: Construct, id: string, props: CloudFrontDistributionProps);
|
|
99
114
|
/**
|
|
100
115
|
* Build a composable CloudFront Function for VIEWER_REQUEST.
|
|
101
|
-
* Merges accessGate, forwardHostHeader and
|
|
102
|
-
* (CloudFront allows only one function per event type).
|
|
116
|
+
* Merges accessGate, forwardHostHeader and uriRewrite into a single
|
|
117
|
+
* function (CloudFront allows only one function per event type).
|
|
103
118
|
*
|
|
104
119
|
* ORDER IS LOAD-BEARING. The access gate is emitted first because it is the
|
|
105
120
|
* only fragment that can reject a request, and a fragment that returns early
|
|
106
121
|
* ahead of it would silently un-gate whatever paths it short-circuits. The
|
|
107
|
-
*
|
|
108
|
-
*
|
|
122
|
+
* uriRewrite fragments carve `/api/*` out of the rewriting — that carve-out
|
|
123
|
+
* must therefore be a guard around the rewrite, never an early
|
|
109
124
|
* `return request`, or the whole `/api/*` surface leaves the gate behind it.
|
|
110
125
|
*/
|
|
111
126
|
private buildViewerRequestFunction;
|
|
@@ -4,6 +4,43 @@ import { toPascalCase } from "../../../utils/capitaliseString.js";
|
|
|
4
4
|
import { cdnDomainExportName } from "@fjall/util";
|
|
5
5
|
import { Distribution, PriceClass, ViewerProtocolPolicy, CachePolicy, OriginRequestPolicy, AllowedMethods, OriginProtocolPolicy, Function as CloudFrontFunction, FunctionCode, FunctionRuntime, FunctionEventType, OriginAccessIdentity, S3OriginAccessControl } from "aws-cdk-lib/aws-cloudfront";
|
|
6
6
|
import { S3BucketOrigin, LoadBalancerV2Origin, HttpOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
7
|
+
/**
|
|
8
|
+
* The viewer-request rewrite for each `UriRewriteMode`, keyed on the mode so
|
|
9
|
+
* a new mode fails to compile until it states its rewrite.
|
|
10
|
+
*
|
|
11
|
+
* Both fragments keep the `/api/*` carve-out as a GUARD around the rewrite,
|
|
12
|
+
* never an early `return request` — see the ordering contract on
|
|
13
|
+
* `buildViewerRequestFunction`: a fragment that returns early would let every
|
|
14
|
+
* path it short-circuits leave later fragments behind it.
|
|
15
|
+
*
|
|
16
|
+
* `"html"`: `/about` and `/about/` both → `/about.html`. The trailing slash
|
|
17
|
+
* is stripped before `.html` is appended — the build emitted a FILE, so the
|
|
18
|
+
* old hybrid's `/about/` → `/about/index.html` named an object that does not
|
|
19
|
+
* exist in this layout. The root `/` becomes `/index.html` (both layouts
|
|
20
|
+
* emit one), so neither mode depends on `defaultRootObject` being set.
|
|
21
|
+
*
|
|
22
|
+
* `"directory-index"`: `/about` and `/about/` both → `/about/index.html`,
|
|
23
|
+
* matching the directory-per-page layout (the root `/` likewise becomes
|
|
24
|
+
* `/index.html`).
|
|
25
|
+
*
|
|
26
|
+
* Requests whose last segment carries an extension (`/assets/app.css`) pass
|
|
27
|
+
* through untouched in both modes, and so does any path containing a
|
|
28
|
+
* dot-directory (`/.well-known/apple-app-site-association` must be served
|
|
29
|
+
* byte-for-byte — dot-paths are literal files, never pretty URLs).
|
|
30
|
+
*/
|
|
31
|
+
const URI_REWRITE_FRAGMENTS = {
|
|
32
|
+
html: " var uri = request.uri;" +
|
|
33
|
+
" if (!uri.startsWith('/api/') && uri.indexOf('/.') === -1) {" +
|
|
34
|
+
" if (uri === '/') { request.uri = '/index.html'; }" +
|
|
35
|
+
" else if (uri.endsWith('/')) { request.uri = uri.substring(0, uri.length - 1) + '.html'; }" +
|
|
36
|
+
" else { var seg = uri.substring(uri.lastIndexOf('/') + 1);" +
|
|
37
|
+
" if (seg.indexOf('.') === -1) { request.uri = uri + '.html'; } } }",
|
|
38
|
+
"directory-index": " var uri = request.uri;" +
|
|
39
|
+
" if (!uri.startsWith('/api/') && uri.indexOf('/.') === -1) {" +
|
|
40
|
+
" if (uri.endsWith('/')) { request.uri = uri + 'index.html'; }" +
|
|
41
|
+
" else { var seg = uri.substring(uri.lastIndexOf('/') + 1);" +
|
|
42
|
+
" if (seg.indexOf('.') === -1) { request.uri = uri + '/index.html'; } } }"
|
|
43
|
+
};
|
|
7
44
|
export class CloudFrontDistribution extends Construct {
|
|
8
45
|
id;
|
|
9
46
|
distribution;
|
|
@@ -20,8 +57,8 @@ export class CloudFrontDistribution extends Construct {
|
|
|
20
57
|
const defaultOrigin = this.createOrigin(props.defaultOrigin);
|
|
21
58
|
const defaultCachePolicy = this.resolveCachePolicy(props.defaultCachePolicy);
|
|
22
59
|
// Build composable viewer request function (accessGate + forwardHostHeader
|
|
23
|
-
// +
|
|
24
|
-
const viewerRequestFunction = this.buildViewerRequestFunction(id, props.forwardHostHeader, props.accessGate, props.
|
|
60
|
+
// + uriRewrite)
|
|
61
|
+
const viewerRequestFunction = this.buildViewerRequestFunction(id, props.forwardHostHeader, props.accessGate, props.uriRewrite);
|
|
25
62
|
const functionAssociations = viewerRequestFunction
|
|
26
63
|
? [
|
|
27
64
|
{
|
|
@@ -96,18 +133,18 @@ export class CloudFrontDistribution extends Construct {
|
|
|
96
133
|
}
|
|
97
134
|
/**
|
|
98
135
|
* Build a composable CloudFront Function for VIEWER_REQUEST.
|
|
99
|
-
* Merges accessGate, forwardHostHeader and
|
|
100
|
-
* (CloudFront allows only one function per event type).
|
|
136
|
+
* Merges accessGate, forwardHostHeader and uriRewrite into a single
|
|
137
|
+
* function (CloudFront allows only one function per event type).
|
|
101
138
|
*
|
|
102
139
|
* ORDER IS LOAD-BEARING. The access gate is emitted first because it is the
|
|
103
140
|
* only fragment that can reject a request, and a fragment that returns early
|
|
104
141
|
* ahead of it would silently un-gate whatever paths it short-circuits. The
|
|
105
|
-
*
|
|
106
|
-
*
|
|
142
|
+
* uriRewrite fragments carve `/api/*` out of the rewriting — that carve-out
|
|
143
|
+
* must therefore be a guard around the rewrite, never an early
|
|
107
144
|
* `return request`, or the whole `/api/*` surface leaves the gate behind it.
|
|
108
145
|
*/
|
|
109
|
-
buildViewerRequestFunction(id, forwardHostHeader, accessGate,
|
|
110
|
-
if (!accessGate && !forwardHostHeader &&
|
|
146
|
+
buildViewerRequestFunction(id, forwardHostHeader, accessGate, uriRewrite) {
|
|
147
|
+
if (!accessGate && !forwardHostHeader && uriRewrite === undefined) {
|
|
111
148
|
return undefined;
|
|
112
149
|
}
|
|
113
150
|
const fragments = [];
|
|
@@ -125,12 +162,8 @@ export class CloudFrontDistribution extends Construct {
|
|
|
125
162
|
if (forwardHostHeader) {
|
|
126
163
|
fragments.push(" request.headers['x-forwarded-host'] = { value: request.headers.host.value };");
|
|
127
164
|
}
|
|
128
|
-
if (
|
|
129
|
-
fragments.push(
|
|
130
|
-
" if (!uri.startsWith('/api/')) {" +
|
|
131
|
-
" if (uri.endsWith('/')) { request.uri = uri + 'index.html'; }" +
|
|
132
|
-
" else { var seg = uri.substring(uri.lastIndexOf('/') + 1);" +
|
|
133
|
-
" if (seg.indexOf('.') === -1) { request.uri = uri + '.html'; } } }");
|
|
165
|
+
if (uriRewrite !== undefined) {
|
|
166
|
+
fragments.push(URI_REWRITE_FRAGMENTS[uriRewrite]);
|
|
134
167
|
}
|
|
135
168
|
const functionBody = "function handler(event) { var request = event.request;" +
|
|
136
169
|
fragments.join("") +
|
|
@@ -303,13 +303,11 @@ export function addHostedZone(ctx, loadBalancer) {
|
|
|
303
303
|
domainName,
|
|
304
304
|
...(subjectAlternativeNames.length > 0 && { subjectAlternativeNames }),
|
|
305
305
|
hostedZone,
|
|
306
|
-
//
|
|
306
|
+
// No costAllocationEnvironment: the cluster's app already tags the
|
|
307
307
|
// account-stage-resolved environment tree-wide (App.globalTags via
|
|
308
|
-
// getConfig)
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
// cascade, and did on the first live plan.
|
|
312
|
-
inheritCostAllocationEnvironment: true,
|
|
308
|
+
// getConfig) and the mint inherits it. No local re-resolution
|
|
309
|
+
// (context/env-var channels) — it can diverge from the app cascade,
|
|
310
|
+
// and did on the first live plan.
|
|
313
311
|
// The cert is consumed in-stack by the ALB listener; exporting would
|
|
314
312
|
// collide with a managed-domain/apex stack that owns the domain's
|
|
315
313
|
// export names.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Construct } from "constructs";
|
|
3
|
-
import { ArnFormat, CfnOutput, Stack
|
|
3
|
+
import { ArnFormat, CfnOutput, Stack } from "aws-cdk-lib";
|
|
4
4
|
import { OrganizationPrincipal, PolicyDocument, PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
5
5
|
import { getDomainExportNames } from "@fjall/util";
|
|
6
6
|
import { Role } from "./role.js";
|
|
7
7
|
import { capitaliseString, toPascalCase, getSafeZoneName } from "../../../utils/capitaliseString.js";
|
|
8
|
-
import {
|
|
8
|
+
import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
|
|
9
9
|
// The suffix is a deployed contract: the SCP dev-isolation carve-out
|
|
10
10
|
// wildcards arn:aws:iam::*:role/*DelegateHostedZoneRole (scpPreset).
|
|
11
11
|
const ROLE_NAME_SUFFIX = "DelegateHostedZoneRole";
|
|
@@ -86,9 +86,11 @@ export class DelegationRole extends Construct {
|
|
|
86
86
|
props.hostedZone.grantDelegation(role);
|
|
87
87
|
this.role = role;
|
|
88
88
|
this.roleArn = role.roleArn;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
applyCostAllocationTags(this, {
|
|
90
|
+
service: "delegationRole",
|
|
91
|
+
domain: props.costAllocationDomain ?? props.zoneName,
|
|
92
|
+
environment: props.costAllocationEnvironment
|
|
93
|
+
});
|
|
92
94
|
const exportName = getDomainExportNames(props.zoneName).delegationRoleArn;
|
|
93
95
|
this.exportName = exportName;
|
|
94
96
|
new CfnOutput(this, `${safeZone}DelegationRoleArn`, {
|
|
@@ -7,16 +7,8 @@ export interface DomainCertificateProps {
|
|
|
7
7
|
readonly hostedZone: IHostedZone;
|
|
8
8
|
readonly description?: string;
|
|
9
9
|
readonly transparencyLogging?: boolean;
|
|
10
|
+
/** Omit to inherit the App cascade's environment tag (see CostAllocationTagsArgs). */
|
|
10
11
|
readonly costAllocationEnvironment?: string;
|
|
11
|
-
/**
|
|
12
|
-
* Inherit the environment tag from an outer cascade (the fjall App's
|
|
13
|
-
* global tags) instead of stamping the "management" default when
|
|
14
|
-
* `costAllocationEnvironment` is not given. In-stack certificates minted
|
|
15
|
-
* inside an application (the ECS fallthrough mint) must set this: the
|
|
16
|
-
* cluster's app already tags the resolved environment tree-wide, and the
|
|
17
|
-
* resource-level default would out-tag it (deeper scope wins).
|
|
18
|
-
*/
|
|
19
|
-
readonly inheritCostAllocationEnvironment?: boolean;
|
|
20
12
|
readonly costAllocationDomain?: string;
|
|
21
13
|
/**
|
|
22
14
|
* Publish the `${domain}-certificate-arn` CloudFormation export. Default true.
|
|
@@ -40,8 +40,7 @@ export class DomainCertificate extends Certificate {
|
|
|
40
40
|
applyCostAllocationTags(this, {
|
|
41
41
|
service: "certificate",
|
|
42
42
|
domain: sanitiseAcmTagValue(props.costAllocationDomain ?? props.domainName),
|
|
43
|
-
environment: props.costAllocationEnvironment
|
|
44
|
-
inheritEnvironment: props.inheritCostAllocationEnvironment
|
|
43
|
+
environment: props.costAllocationEnvironment
|
|
45
44
|
});
|
|
46
45
|
if (props.exportCertificateArn !== false) {
|
|
47
46
|
const safeKey = toPascalCase(props.domainName.split(".").join(""));
|
|
@@ -6,7 +6,7 @@ export interface VpcPeeringAccepterRoleProps {
|
|
|
6
6
|
readonly requesterAccountIds: string[];
|
|
7
7
|
/** The local VPC this role is scoped to. */
|
|
8
8
|
readonly localVpc: IVpc;
|
|
9
|
-
/**
|
|
9
|
+
/** Omit to inherit the App cascade's environment tag (see CostAllocationTagsArgs). */
|
|
10
10
|
readonly costAllocationEnvironment?: string;
|
|
11
11
|
/** Cost-allocation override — defaults to the local VPC's construct id. */
|
|
12
12
|
readonly costAllocationDomain?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { ArnFormat, Stack, Tags } from "aws-cdk-lib";
|
|
3
3
|
import { AccountPrincipal, CompositePrincipal, PolicyDocument, PolicyStatement, Role } from "aws-cdk-lib/aws-iam";
|
|
4
|
-
import {
|
|
4
|
+
import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
|
|
5
5
|
export class VpcPeeringAccepterRole extends Construct {
|
|
6
6
|
role;
|
|
7
7
|
roleArn;
|
|
@@ -54,8 +54,10 @@ export class VpcPeeringAccepterRole extends Construct {
|
|
|
54
54
|
});
|
|
55
55
|
this.roleArn = this.role.roleArn;
|
|
56
56
|
Tags.of(this).add("fjall:resource:type", "vpc-peering-accepter");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
applyCostAllocationTags(this, {
|
|
58
|
+
service: "vpcPeeringAccepter",
|
|
59
|
+
domain: props.costAllocationDomain ?? props.localVpc.node.id,
|
|
60
|
+
environment: props.costAllocationEnvironment
|
|
61
|
+
});
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -3,7 +3,7 @@ import { Stack, Tags } from "aws-cdk-lib";
|
|
|
3
3
|
import { CfnRoute, CfnVPCPeeringConnection, SubnetType } from "aws-cdk-lib/aws-ec2";
|
|
4
4
|
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
5
5
|
import { AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";
|
|
6
|
-
import {
|
|
6
|
+
import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
|
|
7
7
|
import { AwsCustomResource } from "../utilities/awsCustomResource.js";
|
|
8
8
|
import { CrossAccountReturnRoutes } from "./crossAccountReturnRoutes.js";
|
|
9
9
|
export class VpcPeeringConnection extends Construct {
|
|
@@ -100,8 +100,10 @@ export class VpcPeeringConnection extends Construct {
|
|
|
100
100
|
if (props.peerAppName) {
|
|
101
101
|
Tags.of(this).add("fjall:peering:peer-app", props.peerAppName);
|
|
102
102
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
applyCostAllocationTags(this, {
|
|
104
|
+
service: "vpcPeering",
|
|
105
|
+
domain: props.costAllocationDomain ?? props.peerAppName ?? props.peerVpcId,
|
|
106
|
+
environment: props.costAllocationEnvironment
|
|
107
|
+
});
|
|
106
108
|
}
|
|
107
109
|
}
|
|
@@ -4,16 +4,17 @@ export { COST_ALLOCATION_TAGS, DEFAULT_COST_ALLOCATION_ENVIRONMENT };
|
|
|
4
4
|
export interface CostAllocationTagsArgs {
|
|
5
5
|
readonly service: string;
|
|
6
6
|
readonly domain: string;
|
|
7
|
-
readonly environment?: string;
|
|
8
7
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* Omit to INHERIT: the fjall App root always stamps the account-stage-
|
|
9
|
+
* resolved environment tree-wide ("unknown" when unresolvable — getConfig
|
|
10
|
+
* never leaves it blank), and CDK resolves tag conflicts deeper-scope-wins,
|
|
11
|
+
* so a construct-level fallback here would out-tag the truth. That is how
|
|
12
|
+
* live zones were mis-tagged "management" inside staged apps. A construct
|
|
13
|
+
* stamps an environment ONLY when its caller makes an explicit claim —
|
|
14
|
+
* including an explicit `DEFAULT_COST_ALLOCATION_ENVIRONMENT` for genuinely
|
|
15
|
+
* stage-less, management-estate resources.
|
|
15
16
|
*/
|
|
16
|
-
readonly
|
|
17
|
+
readonly environment?: string;
|
|
17
18
|
readonly owner?: string;
|
|
18
19
|
}
|
|
19
20
|
export declare function applyCostAllocationTags(scope: IConstruct, args: CostAllocationTagsArgs): void;
|
|
@@ -8,9 +8,6 @@ export function applyCostAllocationTags(scope, args) {
|
|
|
8
8
|
if (args.environment !== undefined) {
|
|
9
9
|
Tags.of(scope).add(COST_ALLOCATION_TAGS.ENVIRONMENT, args.environment);
|
|
10
10
|
}
|
|
11
|
-
else if (args.inheritEnvironment !== true) {
|
|
12
|
-
Tags.of(scope).add(COST_ALLOCATION_TAGS.ENVIRONMENT, DEFAULT_COST_ALLOCATION_ENVIRONMENT);
|
|
13
|
-
}
|
|
14
11
|
Tags.of(scope).add(COST_ALLOCATION_TAGS.SERVICE, args.service);
|
|
15
12
|
Tags.of(scope).add(COST_ALLOCATION_TAGS.DOMAIN, args.domain);
|
|
16
13
|
if (args.owner !== undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "23.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/fjall-tech/fjall.git",
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@aws-sdk/client-organizations": "^3.1098.0",
|
|
83
|
-
"@fjall/generator": "^
|
|
84
|
-
"@fjall/util": "^
|
|
83
|
+
"@fjall/generator": "^23.0.0",
|
|
84
|
+
"@fjall/util": "^23.0.0",
|
|
85
85
|
"constructs": "^10.7.2"
|
|
86
86
|
},
|
|
87
87
|
"overrides": {
|