@fjall/components-infrastructure 21.0.0 → 22.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 +20 -1
- package/dist/lib/patterns/aws/database.js +2 -2
- 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/dist/lib/utils/validationLogger.js +20 -9
- 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,13 @@ 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
|
+
Tags.of(this).add("fjall:description", Lazy.string({
|
|
53
|
+
produce: () => GOVERNANCE_TIER_DESCRIPTIONS[this.organisationType]
|
|
54
|
+
}));
|
|
36
55
|
this.resolvedRegion = region ?? this.region;
|
|
37
56
|
const orgId = this.node.tryGetContext(CDK_CONTEXT_KEYS.ORG_ID);
|
|
38
57
|
if (orgId) {
|
|
@@ -24,8 +24,8 @@ export const DATABASE_ENGINE_CONFIG = Object.freeze({
|
|
|
24
24
|
sslParameters: { "rds.force_ssl": "1" }
|
|
25
25
|
},
|
|
26
26
|
mysql: {
|
|
27
|
-
// majorVersion must be Aurora version (3.
|
|
28
|
-
aurora: { version: "8.0.mysql_aurora.3.
|
|
27
|
+
// majorVersion must be Aurora version (3.13.0), NOT MySQL version (8.0)
|
|
28
|
+
aurora: { version: "8.0.mysql_aurora.3.13.0", majorVersion: "3.13.0" },
|
|
29
29
|
instance: { version: "8.4.9", majorVersion: "8.4" },
|
|
30
30
|
defaultUsername: "admin",
|
|
31
31
|
sslParameters: { require_secure_transport: "ON" }
|
|
@@ -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) {
|
|
@@ -4,20 +4,29 @@
|
|
|
4
4
|
* Also writes to JSONL file for debugging and support.
|
|
5
5
|
*/
|
|
6
6
|
import { appendFileSync, existsSync, mkdirSync } from "fs";
|
|
7
|
-
import { homedir } from "os";
|
|
8
7
|
import { join } from "path";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
import { fjallLogDir } from "@fjall/util";
|
|
9
|
+
import { FileRotator, LOG_ROTATION_DEFAULTS } from "@fjall/util/logRotation";
|
|
10
|
+
const LOG_FILENAME = "infrastructure.jsonl";
|
|
11
|
+
/**
|
|
12
|
+
* Shared with the CLI's own logs, so this file cannot outgrow the budget
|
|
13
|
+
* the rest of Fjall's logging keeps to. Without it this log had no bound of
|
|
14
|
+
* any kind: it is appended to on every CDK synth, and one developer machine
|
|
15
|
+
* reached 267MB of it inside a single release cycle.
|
|
16
|
+
*/
|
|
17
|
+
const rotator = new FileRotator({
|
|
18
|
+
maxFiles: LOG_ROTATION_DEFAULTS.MAX_FILES
|
|
19
|
+
});
|
|
14
20
|
/**
|
|
15
21
|
* Write a log entry to the infrastructure JSONL file
|
|
16
22
|
*/
|
|
17
23
|
function writeToLog(level, message) {
|
|
18
24
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
// Resolved per write, not at module load: the log dir is an env var and
|
|
26
|
+
// CDK imports this module long before a command has read its environment.
|
|
27
|
+
const logDir = fjallLogDir();
|
|
28
|
+
if (!existsSync(logDir)) {
|
|
29
|
+
mkdirSync(logDir, { recursive: true });
|
|
21
30
|
}
|
|
22
31
|
const entry = JSON.stringify({
|
|
23
32
|
timestamp: new Date().toISOString(),
|
|
@@ -26,7 +35,9 @@ function writeToLog(level, message) {
|
|
|
26
35
|
message,
|
|
27
36
|
source: "cdk-subprocess"
|
|
28
37
|
});
|
|
29
|
-
|
|
38
|
+
const logPath = join(logDir, LOG_FILENAME);
|
|
39
|
+
rotator.rotateIfNeeded(logPath, LOG_ROTATION_DEFAULTS.MAX_FILE_SIZE);
|
|
40
|
+
appendFileSync(logPath, entry + "\n");
|
|
30
41
|
}
|
|
31
42
|
catch (err) {
|
|
32
43
|
console.debug("[Fjall] writeToLog failed:", err instanceof Error ? err.message : String(err));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "22.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": "^22.0.0",
|
|
84
|
+
"@fjall/util": "^22.0.0",
|
|
85
85
|
"constructs": "^10.7.2"
|
|
86
86
|
},
|
|
87
87
|
"overrides": {
|