@fjall/components-infrastructure 0.102.0 → 2.1.1

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.
Files changed (72) hide show
  1. package/dist/lib/lambda-assets/cert-generator/asset/index.js +17948 -0
  2. package/dist/lib/lambda-assets/cert-generator/asset/package.json +4 -0
  3. package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +45 -2
  4. package/dist/lib/patterns/aws/clickhouseDatabase.js +156 -32
  5. package/dist/lib/patterns/aws/clickhouseTls/index.d.ts +1 -0
  6. package/dist/lib/patterns/aws/clickhouseTls/index.js +1 -0
  7. package/dist/lib/patterns/aws/clickhouseTls/types.d.ts +48 -0
  8. package/dist/lib/patterns/aws/interfaces/database.d.ts +7 -5
  9. package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +21 -0
  10. package/dist/lib/resources/aws/database/clickhouseConstants.js +21 -0
  11. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +2 -0
  12. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +2 -0
  13. package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +21 -0
  14. package/dist/lib/resources/aws/database/clickhouseUserData.js +68 -3
  15. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +1 -1
  16. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +1 -1
  17. package/dist/lib/resources/aws/secrets/index.d.ts +2 -0
  18. package/dist/lib/resources/aws/secrets/index.js +2 -0
  19. package/dist/lib/resources/aws/secrets/tlsCaSecret.d.ts +13 -0
  20. package/dist/lib/resources/aws/secrets/tlsCaSecret.js +15 -0
  21. package/dist/lib/resources/aws/secrets/tlsServerSecret.d.ts +15 -0
  22. package/dist/lib/resources/aws/secrets/tlsServerSecret.js +17 -0
  23. package/dist/lib/resources/aws/utilities/index.d.ts +1 -0
  24. package/dist/lib/resources/aws/utilities/index.js +1 -0
  25. package/dist/lib/resources/aws/utilities/tlsCertGenerator.d.ts +33 -0
  26. package/dist/lib/resources/aws/utilities/tlsCertGenerator.js +67 -0
  27. package/dist/lib/utils/manifestWriter.js +6 -13
  28. package/package.json +8 -6
  29. package/dist/lib/config/aws/__t17fixture.js +0 -3
  30. package/dist/lib/config/aws/__t17fixtureType.d.ts +0 -2
  31. package/dist/lib/config/aws/__t17fixtureType.js +0 -1
  32. package/dist/lib/config/aws/eventBus.d.ts +0 -7
  33. package/dist/lib/config/aws/eventBus.js +0 -21
  34. package/dist/lib/config/aws/identityCenterGroupMembership.d.ts +0 -10
  35. package/dist/lib/config/aws/identityCenterGroupMembership.js +0 -102
  36. package/dist/lib/config/aws/securityBaseline.d.ts +0 -15
  37. package/dist/lib/config/aws/securityBaseline.js +0 -27
  38. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +0 -1
  39. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +0 -4
  40. package/dist/lib/patterns/aws/managedIdentityCenter.d.ts +0 -4
  41. package/dist/lib/patterns/aws/managedIdentityCenter.js +0 -19
  42. package/dist/lib/patterns/aws/subdomainHostedZone.d.ts +0 -9
  43. package/dist/lib/patterns/aws/subdomainHostedZone.js +0 -34
  44. package/dist/lib/resources/aws/analytics/clickhouse.d.ts +0 -15
  45. package/dist/lib/resources/aws/analytics/clickhouse.js +0 -310
  46. package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +0 -49
  47. package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +0 -140
  48. package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +0 -73
  49. package/dist/lib/resources/aws/analytics/clickhouseConstants.js +0 -89
  50. package/dist/lib/resources/aws/analytics/clickhouseSecurityGroup.d.ts +0 -13
  51. package/dist/lib/resources/aws/analytics/clickhouseSecurityGroup.js +0 -28
  52. package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +0 -59
  53. package/dist/lib/resources/aws/analytics/clickhouseTypes.js +0 -1
  54. package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +0 -6
  55. package/dist/lib/resources/aws/analytics/clickhouseUserData.js +0 -299
  56. package/dist/lib/resources/aws/analytics/index.d.ts +0 -4
  57. package/dist/lib/resources/aws/analytics/index.js +0 -2
  58. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +0 -2
  59. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +0 -11
  60. package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +0 -7
  61. package/dist/lib/resources/aws/messaging/defaultEventBus.js +0 -21
  62. package/dist/lib/resources/aws/networking/domain.d.ts +0 -13
  63. package/dist/lib/resources/aws/networking/domain.js +0 -100
  64. package/dist/lib/synth_dump.d.ts +0 -1
  65. package/dist/lib/synth_dump.js +0 -42
  66. package/dist/lib/utils/bastionFactory.d.ts +0 -10
  67. package/dist/lib/utils/bastionFactory.js +0 -29
  68. package/dist/lib/utils/constructMap.d.ts +0 -33
  69. package/dist/lib/utils/constructMap.js +0 -154
  70. package/dist/lib/utils/dnsRecords.d.ts +0 -4
  71. package/dist/lib/utils/dnsRecords.js +0 -104
  72. /package/dist/lib/{config/aws/__t17fixture.d.ts → patterns/aws/clickhouseTls/types.js} +0 -0
@@ -1,7 +1,8 @@
1
- import { CLICKHOUSE_DATA_MOUNT_PATH, CLICKHOUSE_EBS_DEVICE_NAME, CLICKHOUSE_CONFIG_SUBDIR, CLICKHOUSE_USERS_SUBDIR, CLICKHOUSE_HTTP_PORT, CLICKHOUSE_PROMETHEUS_PORT, clickHousePasswordSha256Snippet } from "./clickhouseConstants.js";
1
+ import { CLICKHOUSE_DATA_MOUNT_PATH, CLICKHOUSE_EBS_DEVICE_NAME, CLICKHOUSE_CONFIG_SUBDIR, CLICKHOUSE_USERS_SUBDIR, CLICKHOUSE_HTTP_PORT, CLICKHOUSE_HTTPS_PORT, CLICKHOUSE_TCP_SECURE_PORT, CLICKHOUSE_TLS_CERT_MOUNT_PATH, CLICKHOUSE_PROMETHEUS_PORT, clickHousePasswordSha256Snippet } from "./clickhouseConstants.js";
2
2
  import { renderUsersXml } from "./clickhouseXmlRenderer.js";
3
3
  export function generateServerConfigXml(options) {
4
4
  const { backupBucketName, backupBucketRegion, coldTier } = options;
5
+ const tlsActive = options.tlsActive ?? false;
5
6
  const storageBlock = coldTier !== undefined
6
7
  ? ` <storage_configuration>
7
8
  <!-- Same CH 26 rule as the no-cold-tier branch: a <local_ssd> disk
@@ -145,7 +146,19 @@ export function generateServerConfigXml(options) {
145
146
  <number_of_free_entries_in_pool_to_lower_max_size_of_merge>1</number_of_free_entries_in_pool_to_lower_max_size_of_merge>
146
147
  <number_of_free_entries_in_pool_to_execute_optimize_entire_partition>1</number_of_free_entries_in_pool_to_execute_optimize_entire_partition>
147
148
  </merge_tree>
148
- <http_port>${CLICKHOUSE_HTTP_PORT}</http_port>
149
+ ${tlsActive
150
+ ? ` <https_port>${CLICKHOUSE_HTTPS_PORT}</https_port>
151
+ <tcp_port_secure>${CLICKHOUSE_TCP_SECURE_PORT}</tcp_port_secure>
152
+ <openSSL>
153
+ <server>
154
+ <certificateFile>${CLICKHOUSE_TLS_CERT_MOUNT_PATH}/server.crt</certificateFile>
155
+ <privateKeyFile>${CLICKHOUSE_TLS_CERT_MOUNT_PATH}/server.key</privateKeyFile>
156
+ <verificationMode>relaxed</verificationMode>
157
+ <disableProtocols>sslv2,sslv3,tlsv1,tlsv1_1</disableProtocols>
158
+ <preferServerCiphers>true</preferServerCiphers>
159
+ </server>
160
+ </openSSL>`
161
+ : ` <http_port>${CLICKHOUSE_HTTP_PORT}</http_port>`}
149
162
  <custom_settings_prefixes>current_</custom_settings_prefixes>
150
163
  <!-- HTTP keep-alive window. Must exceed @clickhouse/client idle_socket_ttl (15 s)
151
164
  so the client always closes the socket first. Prevents ECONNRESET on reuse. -->
@@ -247,6 +260,58 @@ function compactUserDataScript(script) {
247
260
  */
248
261
  export function buildClickHouseUserData(options) {
249
262
  const serverConfigXml = generateServerConfigXml(options);
263
+ const tlsActive = options.tlsActive ?? false;
264
+ if (tlsActive &&
265
+ (options.caSecretArn === undefined || options.serverSecretArn === undefined)) {
266
+ throw new Error("buildClickHouseUserData: tlsActive=true requires both caSecretArn and serverSecretArn");
267
+ }
268
+ const tlsBootstrap = tlsActive
269
+ ? `
270
+ # Materialise TLS certs from Secrets Manager. The EC2 instance role carries
271
+ # the secretsmanager:GetSecretValue grant; certs land on the EBS-backed mount
272
+ # at \`$MOUNT_POINT/server-certs/\` and are bind-mounted read-only into the
273
+ # CH container at /etc/clickhouse-server/certs. Cert rotation triggers task
274
+ # replacement via the CA_CERT_SHA256 env on dependent services (the cert
275
+ # generator emits the digest as a CFN Data attribute).
276
+ mkdir -p "$MOUNT_POINT/server-certs"
277
+
278
+ # jq is needed to extract cert + key fields from the server bundle JSON.
279
+ # AL2023 ECS-optimised AMIs ship without jq; install it if missing.
280
+ command -v jq >/dev/null 2>&1 || dnf install -y jq || yum install -y jq
281
+
282
+ # Fetch CA (raw PEM SecretString). Quoting prevents word-splitting on multi-line PEM.
283
+ CA_PEM=$(aws secretsmanager get-secret-value --secret-id "${options.caSecretArn ?? ""}" --query SecretString --output text)
284
+ printf '%s\\n' "$CA_PEM" > "$MOUNT_POINT/server-certs/ca.crt"
285
+
286
+ # Fetch server bundle (JSON { "cert": "<pem>", "key": "<pem>" }).
287
+ SERVER_JSON=$(aws secretsmanager get-secret-value --secret-id "${options.serverSecretArn ?? ""}" --query SecretString --output text)
288
+ printf '%s' "$SERVER_JSON" | jq -r .cert > "$MOUNT_POINT/server-certs/server.crt"
289
+ printf '%s' "$SERVER_JSON" | jq -r .key > "$MOUNT_POINT/server-certs/server.key"
290
+
291
+ # Strict-verify client config consumed by docker-exec reload SSM script and any
292
+ # in-container clickhouse-client invocation. RejectCertificateHandler + strict
293
+ # verificationMode together close the trust-anchor-skipped class.
294
+ cat > "$MOUNT_POINT/server-certs/client.xml" <<'CLIENTXML'
295
+ <?xml version="1.0"?>
296
+ <config>
297
+ <openSSL>
298
+ <client>
299
+ <caConfig>/etc/clickhouse-server/certs/ca.crt</caConfig>
300
+ <verificationMode>strict</verificationMode>
301
+ <loadDefaultCAFile>false</loadDefaultCAFile>
302
+ <invalidCertificateHandler>
303
+ <name>RejectCertificateHandler</name>
304
+ </invalidCertificateHandler>
305
+ </client>
306
+ </openSSL>
307
+ </config>
308
+ CLIENTXML
309
+
310
+ chown -R 101:101 "$MOUNT_POINT/server-certs"
311
+ chmod 600 "$MOUNT_POINT/server-certs/server.key"
312
+ chmod 644 "$MOUNT_POINT/server-certs/client.xml"
313
+ `
314
+ : "";
250
315
  const script = `#!/bin/bash
251
316
  set -euo pipefail
252
317
 
@@ -333,7 +398,7 @@ if [ "$USERS_CONFIG_RETRIEVED" != "1" ]; then
333
398
  fi
334
399
 
335
400
  chown -R 101:101 "$MOUNT_POINT"
336
- `;
401
+ ${tlsBootstrap}`;
337
402
  return compactUserDataScript(script);
338
403
  }
339
404
  /**
@@ -45,7 +45,7 @@ export interface RenderUsersXmlOptions {
45
45
  *
46
46
  * **Schema-admin-only emission.** Only the schema admin lands in the XML.
47
47
  * Managed-password workload users are created at runtime by the migration
48
- * helper (`@fjall/clickhouse-migrations § provisionUsersFromEnv`) — they
48
+ * helper (`@fjall/clickhouse § provisionUsersFromEnv`) — they
49
49
  * authenticate via plaintext from `USER_<NAME>_PASSWORD` env vars at runtime
50
50
  * and bind their profiles via customer SQL.
51
51
  *
@@ -73,7 +73,7 @@ ${body}
73
73
  *
74
74
  * **Schema-admin-only emission.** Only the schema admin lands in the XML.
75
75
  * Managed-password workload users are created at runtime by the migration
76
- * helper (`@fjall/clickhouse-migrations § provisionUsersFromEnv`) — they
76
+ * helper (`@fjall/clickhouse § provisionUsersFromEnv`) — they
77
77
  * authenticate via plaintext from `USER_<NAME>_PASSWORD` env vars at runtime
78
78
  * and bind their profiles via customer SQL.
79
79
  *
@@ -2,3 +2,5 @@ export * from "./alias.js";
2
2
  export * from "./kms.js";
3
3
  export * from "./parameter.js";
4
4
  export * from "./secret.js";
5
+ export * from "./tlsCaSecret.js";
6
+ export * from "./tlsServerSecret.js";
@@ -2,3 +2,5 @@ export * from "./alias.js";
2
2
  export * from "./kms.js";
3
3
  export * from "./parameter.js";
4
4
  export * from "./secret.js";
5
+ export * from "./tlsCaSecret.js";
6
+ export * from "./tlsServerSecret.js";
@@ -0,0 +1,13 @@
1
+ import type { Construct } from "constructs";
2
+ import { Secret } from "./secret.js";
3
+ export interface TlsCaSecretProps {
4
+ /** Drives the deterministic secret name `fjall-<appName>-clickhouse-tls-ca`. */
5
+ readonly appName: string;
6
+ }
7
+ /**
8
+ * ClickHouse TLS CA cert (public — clients pin against this). Retained on
9
+ * delete so client-side trust anchors survive accidental stack teardown.
10
+ */
11
+ export declare class TlsCaSecret extends Secret {
12
+ constructor(scope: Construct, id: string, props: TlsCaSecretProps);
13
+ }
@@ -0,0 +1,15 @@
1
+ import { RemovalPolicy } from "aws-cdk-lib";
2
+ import { Secret } from "./secret.js";
3
+ /**
4
+ * ClickHouse TLS CA cert (public — clients pin against this). Retained on
5
+ * delete so client-side trust anchors survive accidental stack teardown.
6
+ */
7
+ export class TlsCaSecret extends Secret {
8
+ constructor(scope, id, props) {
9
+ super(scope, id, {
10
+ secretName: `fjall-${props.appName}-clickhouse-tls-ca`,
11
+ description: "ClickHouse TLS CA cert (public — clients pin against this)"
12
+ });
13
+ this.secret.applyRemovalPolicy(RemovalPolicy.RETAIN);
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ import type { Construct } from "constructs";
2
+ import { Secret } from "./secret.js";
3
+ export interface TlsServerSecretProps {
4
+ /** Drives the deterministic secret name `fjall-<appName>-clickhouse-tls-server`. */
5
+ readonly appName: string;
6
+ }
7
+ /**
8
+ * ClickHouse TLS server cert + key (JSON: `{ cert, key }`). Retained on
9
+ * delete so cert material survives accidental stack teardown; the private
10
+ * key only leaves Secrets Manager via ECS executionRole + the TLS init
11
+ * container that materialises it onto the shared volume.
12
+ */
13
+ export declare class TlsServerSecret extends Secret {
14
+ constructor(scope: Construct, id: string, props: TlsServerSecretProps);
15
+ }
@@ -0,0 +1,17 @@
1
+ import { RemovalPolicy } from "aws-cdk-lib";
2
+ import { Secret } from "./secret.js";
3
+ /**
4
+ * ClickHouse TLS server cert + key (JSON: `{ cert, key }`). Retained on
5
+ * delete so cert material survives accidental stack teardown; the private
6
+ * key only leaves Secrets Manager via ECS executionRole + the TLS init
7
+ * container that materialises it onto the shared volume.
8
+ */
9
+ export class TlsServerSecret extends Secret {
10
+ constructor(scope, id, props) {
11
+ super(scope, id, {
12
+ secretName: `fjall-${props.appName}-clickhouse-tls-server`,
13
+ description: "ClickHouse TLS server cert + key (JSON: { cert, key })"
14
+ });
15
+ this.secret.applyRemovalPolicy(RemovalPolicy.RETAIN);
16
+ }
17
+ }
@@ -3,3 +3,4 @@ export * from "./codeBuild.js";
3
3
  export * from "./customResource.js";
4
4
  export * from "./customResourceProvider.js";
5
5
  export * from "./resourceShare.js";
6
+ export * from "./tlsCertGenerator.js";
@@ -3,3 +3,4 @@ export * from "./codeBuild.js";
3
3
  export * from "./customResource.js";
4
4
  export * from "./customResourceProvider.js";
5
5
  export * from "./resourceShare.js";
6
+ export * from "./tlsCertGenerator.js";
@@ -0,0 +1,33 @@
1
+ import { Construct } from "constructs";
2
+ import { TlsCaSecret } from "../secrets/tlsCaSecret.js";
3
+ import { TlsServerSecret } from "../secrets/tlsServerSecret.js";
4
+ export interface TlsCertGeneratorProps {
5
+ /** Drives the deterministic Secrets Manager names of the two cert secrets. */
6
+ readonly appName: string;
7
+ /**
8
+ * Hostname embedded as CN + SAN dns entry on the leaf cert. Typically the
9
+ * Cloud Map SRV name the ClickHouse service answers on.
10
+ */
11
+ readonly hostname: string;
12
+ /** CA cert validity in years (default 10). Bumping forces re-issuance on next deploy. */
13
+ readonly caValidityYears?: number;
14
+ /** Leaf cert validity in years (default 5). Bumping forces re-issuance on next deploy. */
15
+ readonly leafValidityYears?: number;
16
+ }
17
+ /**
18
+ * Custom-resource Lambda that mints a self-signed CA + leaf cert on stack
19
+ * deploy and persists them to Secrets Manager. The CA secret carries the
20
+ * raw PEM (public — clients pin against it); the server secret carries a
21
+ * JSON `{ cert, key }` payload consumed by the TLS init container.
22
+ *
23
+ * `caCertSha256` is exposed for downstream consumers to wire into their
24
+ * container `environment` block as `CA_CERT_SHA256` — its CFN value
25
+ * changes whenever the cert regenerates, forcing task-def replacement so
26
+ * services pick up the new trust anchor.
27
+ */
28
+ export declare class TlsCertGenerator extends Construct {
29
+ readonly caSecret: TlsCaSecret;
30
+ readonly serverSecret: TlsServerSecret;
31
+ readonly caCertSha256: string;
32
+ constructor(scope: Construct, id: string, props: TlsCertGeneratorProps);
33
+ }
@@ -0,0 +1,67 @@
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { Duration, Stack } from "aws-cdk-lib";
4
+ import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
5
+ import { Runtime } from "aws-cdk-lib/aws-lambda";
6
+ import { Construct } from "constructs";
7
+ import { TlsCaSecret } from "../secrets/tlsCaSecret.js";
8
+ import { TlsServerSecret } from "../secrets/tlsServerSecret.js";
9
+ import { CustomResource } from "./customResource.js";
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ /**
12
+ * Resolves relative to the compiled location too — the build step copies
13
+ * the asset directory into dist/lib/lambda-assets/cert-generator/asset/,
14
+ * mirroring the source tree, so the same relative walk works post-build.
15
+ */
16
+ const LAMBDA_ASSET_DIR = join(__dirname, "../../../lambda-assets/cert-generator/asset");
17
+ const DEFAULT_CA_VALIDITY_YEARS = 10;
18
+ const DEFAULT_LEAF_VALIDITY_YEARS = 5;
19
+ const LAMBDA_TIMEOUT = Duration.minutes(2);
20
+ /**
21
+ * Custom-resource Lambda that mints a self-signed CA + leaf cert on stack
22
+ * deploy and persists them to Secrets Manager. The CA secret carries the
23
+ * raw PEM (public — clients pin against it); the server secret carries a
24
+ * JSON `{ cert, key }` payload consumed by the TLS init container.
25
+ *
26
+ * `caCertSha256` is exposed for downstream consumers to wire into their
27
+ * container `environment` block as `CA_CERT_SHA256` — its CFN value
28
+ * changes whenever the cert regenerates, forcing task-def replacement so
29
+ * services pick up the new trust anchor.
30
+ */
31
+ export class TlsCertGenerator extends Construct {
32
+ caSecret;
33
+ serverSecret;
34
+ caCertSha256;
35
+ constructor(scope, id, props) {
36
+ super(scope, id);
37
+ this.caSecret = new TlsCaSecret(this, "Ca", { appName: props.appName });
38
+ this.serverSecret = new TlsServerSecret(this, "Server", {
39
+ appName: props.appName
40
+ });
41
+ const stack = Stack.of(this);
42
+ const caSecretArnPattern = `arn:${stack.partition}:secretsmanager:${stack.region}:${stack.account}:secret:fjall-${props.appName}-clickhouse-tls-ca-*`;
43
+ const serverSecretArnPattern = `arn:${stack.partition}:secretsmanager:${stack.region}:${stack.account}:secret:fjall-${props.appName}-clickhouse-tls-server-*`;
44
+ const writePolicy = new PolicyStatement({
45
+ effect: Effect.ALLOW,
46
+ actions: ["secretsmanager:PutSecretValue"],
47
+ resources: [caSecretArnPattern, serverSecretArnPattern]
48
+ });
49
+ const cr = new CustomResource(this, "Generator", {
50
+ runtime: Runtime.NODEJS_22_X,
51
+ timeout: LAMBDA_TIMEOUT,
52
+ codePath: LAMBDA_ASSET_DIR,
53
+ handler: "index.handler",
54
+ lambdaDescription: `${id} ClickHouse TLS cert generator`,
55
+ roleDescription: `Execution role for ${id} cert generator (PutSecretValue on two cert secrets)`,
56
+ inlinePolicy: [writePolicy],
57
+ properties: {
58
+ caSecretArn: this.caSecret.secret.secretArn,
59
+ serverSecretArn: this.serverSecret.secret.secretArn,
60
+ hostname: props.hostname,
61
+ caValidityYears: String(props.caValidityYears ?? DEFAULT_CA_VALIDITY_YEARS),
62
+ leafValidityYears: String(props.leafValidityYears ?? DEFAULT_LEAF_VALIDITY_YEARS)
63
+ }
64
+ });
65
+ this.caCertSha256 = cr.resource.getAttString("CaCertSha256");
66
+ }
67
+ }
@@ -17,7 +17,7 @@ import { writeFileSync, readFileSync, existsSync, readdirSync, renameSync, unlin
17
17
  import { join, basename } from "path";
18
18
  import { createHash } from "crypto";
19
19
  import { buildConstructMap, constructMapToRecord, ACCOUNT_CONSTRUCT_GROUPS } from "@fjall/util/constructMap";
20
- import { maskSensitiveOutput } from "@fjall/util";
20
+ import { getErrorMessage, maskSensitiveOutput } from "@fjall/util";
21
21
  import { FJALL_MANIFEST_FILENAME, MANIFEST_SCHEMA_VERSION, FjallManifestSchema } from "@fjall/util/manifest/schemas";
22
22
  import { FjallLogger } from "./validationLogger.js";
23
23
  /**
@@ -117,7 +117,7 @@ function computeTemplateHash(templatePath) {
117
117
  return createHash("sha256").update(normalised).digest("hex");
118
118
  }
119
119
  catch (error) {
120
- const maskedMessage = maskSensitiveOutput(error instanceof Error ? error.message : String(error));
120
+ const maskedMessage = maskSensitiveOutput(getErrorMessage(error));
121
121
  FjallLogger.warn(`Failed to compute hash for ${templatePath}: ${maskedMessage}`);
122
122
  return null;
123
123
  }
@@ -144,7 +144,7 @@ function computeStackHashes(cdkOutPath) {
144
144
  }
145
145
  }
146
146
  catch (error) {
147
- const maskedMessage = maskSensitiveOutput(error instanceof Error ? error.message : String(error));
147
+ const maskedMessage = maskSensitiveOutput(getErrorMessage(error));
148
148
  FjallLogger.warn(`Failed to read cdk.out for hash computation: ${maskedMessage}`);
149
149
  }
150
150
  return stacks;
@@ -194,16 +194,10 @@ export function writeManifest(assembly, collector) {
194
194
  unlinkSync(tmpPath);
195
195
  }
196
196
  catch (cleanupError) {
197
- const cleanupMessage = cleanupError instanceof Error
198
- ? cleanupError.message
199
- : String(cleanupError);
200
- FjallLogger.warn(`Failed to clean up tmp manifest at ${tmpPath}: ${maskSensitiveOutput(cleanupMessage)}`);
197
+ FjallLogger.warn(`Failed to clean up tmp manifest at ${tmpPath}: ${maskSensitiveOutput(getErrorMessage(cleanupError))}`);
201
198
  }
202
199
  }
203
- const errorMessage = error instanceof Error ? error.message : String(error);
204
- throw new Error(`Failed to write Fjall manifest: ${errorMessage}`, {
205
- cause: error
206
- });
200
+ throw new Error(`Failed to write Fjall manifest: ${getErrorMessage(error)}`, { cause: error });
207
201
  }
208
202
  }
209
203
  /**
@@ -224,8 +218,7 @@ export function readExistingManifest(cdkOutPath) {
224
218
  return result.data;
225
219
  }
226
220
  catch (error) {
227
- const message = error instanceof Error ? error.message : String(error);
228
- FjallLogger.warn(`Failed to parse existing manifest: ${maskSensitiveOutput(message)}`);
221
+ FjallLogger.warn(`Failed to parse existing manifest: ${maskSensitiveOutput(getErrorMessage(error))}`);
229
222
  return null;
230
223
  }
231
224
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "0.102.0",
3
+ "version": "2.1.1",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,8 @@
33
33
  "scripts": {
34
34
  "clean": "rm -rf ./dist",
35
35
  "clean:node": "rm -rf ./node_modules",
36
- "build": "tsc && cp -r lib/layers dist/lib/layers && cp lib/resources/aws/compute/lifecycleHookLambda.source.cjs dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs && cp lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs && cp lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs",
36
+ "build:cert-gen-lambda": "node lib/lambda-assets/cert-generator/src/build.mjs",
37
+ "build": "npm run build:cert-gen-lambda && tsc && cp -r lib/layers dist/lib/layers && mkdir -p dist/lib/lambda-assets/cert-generator/asset && cp lib/lambda-assets/cert-generator/asset/index.js dist/lib/lambda-assets/cert-generator/asset/index.js && cp lib/lambda-assets/cert-generator/asset/package.json dist/lib/lambda-assets/cert-generator/asset/package.json && cp lib/resources/aws/compute/lifecycleHookLambda.source.cjs dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs && cp lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs && cp lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs",
37
38
  "watch": "tsc -w",
38
39
  "watch:only": "tsc -w",
39
40
  "test": "vitest run",
@@ -49,6 +50,7 @@
49
50
  },
50
51
  "devDependencies": {
51
52
  "@aws-sdk/client-elastic-load-balancing-v2": "^3.1045.0",
53
+ "@peculiar/x509": "1.14.0",
52
54
  "@types/aws-lambda": "^8.10.161",
53
55
  "@types/node": "^25.6.0",
54
56
  "@types/uuid": "^11.0.0",
@@ -57,12 +59,12 @@
57
59
  "eslint": "^10.2.1",
58
60
  "prettier": "^3.8.3",
59
61
  "typescript": "^6.0.3",
60
- "vitest": "^4.1.5"
62
+ "vitest": "^4.1.7"
61
63
  },
62
64
  "dependencies": {
63
65
  "@aws-sdk/client-organizations": "^3.1038.0",
64
- "@fjall/generator": "^0.102.0",
65
- "@fjall/util": "^0.102.0",
66
+ "@fjall/generator": "^2.1.1",
67
+ "@fjall/util": "^2.1.1",
66
68
  "constructs": "^10.0.0",
67
69
  "uuid": "^14.0.0"
68
70
  },
@@ -77,5 +79,5 @@
77
79
  "engines": {
78
80
  "node": ">=18.0.0"
79
81
  },
80
- "gitHead": "04a4f13181c261cc063786eae527fa82c90a610e"
82
+ "gitHead": "971d9ed50c6a21d24e67c9c83b9c471a632bf284"
81
83
  }
@@ -1,3 +0,0 @@
1
- import { Rule } from "aws-cdk-lib/aws-events";
2
- import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
3
- console.log(Rule, LambdaFunction);
@@ -1,2 +0,0 @@
1
- import { type IRule } from "aws-cdk-lib/aws-events";
2
- export type T = IRule;
@@ -1 +0,0 @@
1
- export {};
@@ -1,7 +0,0 @@
1
- import { CfnOutput } from "aws-cdk-lib";
2
- import { Construct } from "constructs";
3
- export declare class DefaultEventBus extends Construct {
4
- readonly defaultEventBusName: CfnOutput;
5
- readonly defaultEventBusArn: CfnOutput;
6
- constructor(scope: Construct, id: string);
7
- }
@@ -1,21 +0,0 @@
1
- import { CfnOutput } from "aws-cdk-lib";
2
- import * as Events from "aws-cdk-lib/aws-events";
3
- import { Construct } from "constructs";
4
- export class DefaultEventBus extends Construct {
5
- defaultEventBusName;
6
- defaultEventBusArn;
7
- constructor(scope, id) {
8
- super(scope, id);
9
- const eventBridge = Events.EventBus.fromEventBusName(this, "defaultEventBus", "default");
10
- this.defaultEventBusName = new CfnOutput(this, "defaultEventBusName", {
11
- key: `defaultEventBusName`,
12
- value: eventBridge.eventBusName,
13
- exportName: "defaultEventBusName"
14
- });
15
- this.defaultEventBusArn = new CfnOutput(this, "defaultEventBusArn", {
16
- key: `defaultEventBusArn`,
17
- value: eventBridge.eventBusArn,
18
- exportName: "defaultEventBusArn"
19
- });
20
- }
21
- }
@@ -1,10 +0,0 @@
1
- import { NestedStack, type StackProps } from "aws-cdk-lib";
2
- import { type Construct } from "constructs";
3
- interface IdentityCenterGroupMembershipProps extends StackProps {
4
- groupName: string;
5
- groupMembers: string[];
6
- }
7
- export declare class IdentityCenterGroupMembership extends NestedStack {
8
- constructor(scope: Construct, id: string, props: IdentityCenterGroupMembershipProps);
9
- }
10
- export {};
@@ -1,102 +0,0 @@
1
- import { Fn, NestedStack } from "aws-cdk-lib";
2
- import * as customResources from "aws-cdk-lib/custom-resources";
3
- import { AwsCustomResource } from "../../resources/aws/utilities/awsCustomResource.js";
4
- const IDENTITY_STORE_SERVICE = "identityStore";
5
- const IDENTITY_CENTER_USERS_RESOURCE_TYPE = "Custom::IdentityCenterUsers";
6
- // TODO: This requires a deletion and recreation to update
7
- export class IdentityCenterGroupMembership extends NestedStack {
8
- constructor(scope, id, props) {
9
- super(scope, id);
10
- const identityStoreId = Fn.importValue("identityStoreId");
11
- const groupId = Fn.importValue(`${props.groupName}GroupId`);
12
- for (const member of props.groupMembers) {
13
- const memberSuffix = (member.split("@")[0] ?? member)
14
- .split(/[^a-zA-Z0-9]/)
15
- .filter((part) => part.length > 0)
16
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
17
- .join("") +
18
- props.groupName.charAt(0).toUpperCase() +
19
- props.groupName.slice(1);
20
- const listUsersCall = {
21
- service: IDENTITY_STORE_SERVICE,
22
- action: "listUsers",
23
- parameters: {
24
- IdentityStoreId: identityStoreId,
25
- Filters: [
26
- {
27
- AttributePath: "UserName",
28
- AttributeValue: member
29
- }
30
- ]
31
- },
32
- physicalResourceId: customResources.PhysicalResourceId.of(`listUsers${memberSuffix}`)
33
- };
34
- const listUser = new AwsCustomResource(this, `ListUsersResource${memberSuffix}`, {
35
- onCreate: listUsersCall,
36
- onUpdate: listUsersCall
37
- });
38
- const userId = listUser.getResponseField("Users.0.UserId");
39
- const groupMembershipId = new AwsCustomResource(this, `CreateGroupMembershipResource${memberSuffix}`, {
40
- onCreate: {
41
- service: IDENTITY_STORE_SERVICE,
42
- action: "createGroupMembership",
43
- parameters: {
44
- GroupId: groupId,
45
- IdentityStoreId: identityStoreId,
46
- MemberId: {
47
- UserId: userId
48
- }
49
- },
50
- physicalResourceId: customResources.PhysicalResourceId.of(`createGroupMembership${memberSuffix}`)
51
- },
52
- resourceType: IDENTITY_CENTER_USERS_RESOURCE_TYPE
53
- });
54
- const refreshMembership = new AwsCustomResource(this, `RefreshMembershipResource${memberSuffix}`, {
55
- onUpdate: {
56
- service: IDENTITY_STORE_SERVICE,
57
- action: "deleteGroupMembership",
58
- parameters: {
59
- IdentityStoreId: identityStoreId,
60
- MembershipId: groupMembershipId.getResponseField("MembershipId")
61
- },
62
- physicalResourceId: customResources.PhysicalResourceId.of(`refreshGroupMembership${memberSuffix}`)
63
- },
64
- resourceType: IDENTITY_CENTER_USERS_RESOURCE_TYPE
65
- });
66
- const recreateMembership = new AwsCustomResource(this, `RecreateGroupMembershipResource${memberSuffix}`, {
67
- onUpdate: {
68
- service: IDENTITY_STORE_SERVICE,
69
- action: "createGroupMembership",
70
- parameters: {
71
- GroupId: groupId,
72
- IdentityStoreId: identityStoreId,
73
- MemberId: {
74
- UserId: userId
75
- }
76
- },
77
- physicalResourceId: customResources.PhysicalResourceId.of(`recreateGroupMembership${memberSuffix}`),
78
- // createGroupMembership throws ConflictException when the
79
- // membership already exists — keeps onUpdate idempotent.
80
- ignoreErrorCodesMatching: "ConflictException"
81
- },
82
- resourceType: IDENTITY_CENTER_USERS_RESOURCE_TYPE
83
- });
84
- refreshMembership.node.addDependency(recreateMembership);
85
- const deleteMembership = new AwsCustomResource(this, `DeleteGroupMembershipResource${memberSuffix}`, {
86
- onDelete: {
87
- service: IDENTITY_STORE_SERVICE,
88
- action: "deleteGroupMembership",
89
- parameters: {
90
- IdentityStoreId: identityStoreId,
91
- MembershipId: groupMembershipId.getResponseField("MembershipId")
92
- },
93
- // deleteGroupMembership throws ResourceNotFoundException when
94
- // the membership is already gone — keeps onDelete idempotent.
95
- ignoreErrorCodesMatching: "ResourceNotFoundException"
96
- },
97
- resourceType: IDENTITY_CENTER_USERS_RESOURCE_TYPE
98
- });
99
- deleteMembership.node.addDependency(groupMembershipId);
100
- }
101
- }
102
- }
@@ -1,15 +0,0 @@
1
- import { Construct } from "constructs";
2
- export interface SecurityBaselineProps {
3
- /** Controls which services are instantiated. "off" creates nothing. */
4
- level: "off" | "baseline" | "compliance";
5
- }
6
- /**
7
- * Convenience orchestrator for per-account security services.
8
- * Instantiates child constructs based on the selected level.
9
- *
10
- * Does NOT include ConfigRulePreset -- Config Rules are applied independently
11
- * at the Account stack level with environment-aware presets.
12
- */
13
- export declare class SecurityBaseline extends Construct {
14
- constructor(scope: Construct, id: string, props: SecurityBaselineProps);
15
- }
@@ -1,27 +0,0 @@
1
- import { Construct } from "constructs";
2
- import { GuardDutyDetector } from "./guardDutyDetector.js";
3
- import { SecurityHubHub } from "./securityHubHub.js";
4
- import { ConfigRecorder } from "./configRecorder.js";
5
- import { AccountAccessAnalyser } from "./accessAnalyser.js";
6
- import { InspectorEnablement } from "./inspectorEnablement.js";
7
- /**
8
- * Convenience orchestrator for per-account security services.
9
- * Instantiates child constructs based on the selected level.
10
- *
11
- * Does NOT include ConfigRulePreset -- Config Rules are applied independently
12
- * at the Account stack level with environment-aware presets.
13
- */
14
- export class SecurityBaseline extends Construct {
15
- constructor(scope, id, props) {
16
- super(scope, id);
17
- if (props.level === "off")
18
- return;
19
- new GuardDutyDetector(this, "GuardDuty");
20
- new SecurityHubHub(this, "SecurityHub");
21
- new ConfigRecorder(this, "Config");
22
- new AccountAccessAnalyser(this, "AccessAnalyser");
23
- if (props.level === "compliance") {
24
- new InspectorEnablement(this, "Inspector");
25
- }
26
- }
27
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,4 +0,0 @@
1
- import { Port } from "aws-cdk-lib/aws-ec2";
2
- import { PrivateDnsNamespace } from "aws-cdk-lib/aws-servicediscovery";
3
- const _x = Port.tcp(9000);
4
- const _y = PrivateDnsNamespace;
@@ -1,4 +0,0 @@
1
- import { Stack } from "aws-cdk-lib";
2
- export declare class ManagedIdentityCenter extends Stack {
3
- constructor(id: string);
4
- }