@fjall/components-infrastructure 0.100.0 → 1.1.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.
Files changed (74) 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 +49 -1
  4. package/dist/lib/patterns/aws/clickhouseDatabase.js +137 -20
  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/computeEcs.d.ts +13 -1
  9. package/dist/lib/patterns/aws/computeEcs.js +88 -8
  10. package/dist/lib/patterns/aws/interfaces/database.d.ts +32 -1
  11. package/dist/lib/patterns/aws/interfaces/database.js +1 -1
  12. package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +21 -0
  13. package/dist/lib/resources/aws/database/clickhouseConstants.js +21 -0
  14. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +2 -0
  15. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +2 -0
  16. package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +21 -0
  17. package/dist/lib/resources/aws/database/clickhouseUserData.js +48 -3
  18. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +1 -1
  19. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +1 -1
  20. package/dist/lib/resources/aws/secrets/index.d.ts +2 -0
  21. package/dist/lib/resources/aws/secrets/index.js +2 -0
  22. package/dist/lib/resources/aws/secrets/tlsCaSecret.d.ts +13 -0
  23. package/dist/lib/resources/aws/secrets/tlsCaSecret.js +15 -0
  24. package/dist/lib/resources/aws/secrets/tlsServerSecret.d.ts +15 -0
  25. package/dist/lib/resources/aws/secrets/tlsServerSecret.js +17 -0
  26. package/dist/lib/resources/aws/utilities/index.d.ts +1 -0
  27. package/dist/lib/resources/aws/utilities/index.js +1 -0
  28. package/dist/lib/resources/aws/utilities/tlsCertGenerator.d.ts +33 -0
  29. package/dist/lib/resources/aws/utilities/tlsCertGenerator.js +67 -0
  30. package/package.json +7 -5
  31. package/dist/lib/config/aws/__t17fixture.js +0 -3
  32. package/dist/lib/config/aws/__t17fixtureType.d.ts +0 -2
  33. package/dist/lib/config/aws/__t17fixtureType.js +0 -1
  34. package/dist/lib/config/aws/eventBus.d.ts +0 -7
  35. package/dist/lib/config/aws/eventBus.js +0 -21
  36. package/dist/lib/config/aws/identityCenterGroupMembership.d.ts +0 -10
  37. package/dist/lib/config/aws/identityCenterGroupMembership.js +0 -102
  38. package/dist/lib/config/aws/securityBaseline.d.ts +0 -15
  39. package/dist/lib/config/aws/securityBaseline.js +0 -27
  40. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +0 -1
  41. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +0 -4
  42. package/dist/lib/patterns/aws/managedIdentityCenter.d.ts +0 -4
  43. package/dist/lib/patterns/aws/managedIdentityCenter.js +0 -19
  44. package/dist/lib/patterns/aws/subdomainHostedZone.d.ts +0 -9
  45. package/dist/lib/patterns/aws/subdomainHostedZone.js +0 -34
  46. package/dist/lib/resources/aws/analytics/clickhouse.d.ts +0 -15
  47. package/dist/lib/resources/aws/analytics/clickhouse.js +0 -310
  48. package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +0 -49
  49. package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +0 -140
  50. package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +0 -73
  51. package/dist/lib/resources/aws/analytics/clickhouseConstants.js +0 -89
  52. package/dist/lib/resources/aws/analytics/clickhouseSecurityGroup.d.ts +0 -13
  53. package/dist/lib/resources/aws/analytics/clickhouseSecurityGroup.js +0 -28
  54. package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +0 -59
  55. package/dist/lib/resources/aws/analytics/clickhouseTypes.js +0 -1
  56. package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +0 -6
  57. package/dist/lib/resources/aws/analytics/clickhouseUserData.js +0 -299
  58. package/dist/lib/resources/aws/analytics/index.d.ts +0 -4
  59. package/dist/lib/resources/aws/analytics/index.js +0 -2
  60. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +0 -2
  61. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +0 -11
  62. package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +0 -7
  63. package/dist/lib/resources/aws/messaging/defaultEventBus.js +0 -21
  64. package/dist/lib/resources/aws/networking/domain.d.ts +0 -13
  65. package/dist/lib/resources/aws/networking/domain.js +0 -100
  66. package/dist/lib/synth_dump.d.ts +0 -1
  67. package/dist/lib/synth_dump.js +0 -42
  68. package/dist/lib/utils/bastionFactory.d.ts +0 -10
  69. package/dist/lib/utils/bastionFactory.js +0 -29
  70. package/dist/lib/utils/constructMap.d.ts +0 -33
  71. package/dist/lib/utils/constructMap.js +0 -154
  72. package/dist/lib/utils/dnsRecords.d.ts +0 -4
  73. package/dist/lib/utils/dnsRecords.js +0 -104
  74. /package/dist/lib/{config/aws/__t17fixture.d.ts → patterns/aws/clickhouseTls/types.js} +0 -0
@@ -23,7 +23,7 @@ import { type Secret } from "../../../resources/aws/secrets/index.js";
23
23
  import { type SnapshotTarget } from "../../../utils/databaseTypes.js";
24
24
  import { type IMigrationContributor } from "./migrationContributor.js";
25
25
  export { type SnapshotTarget } from "../../../utils/databaseTypes.js";
26
- export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV } from "@fjall/util";
26
+ export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, EXPECTED_CH_SCHEMA_VERSION_ENV } from "@fjall/util";
27
27
  /**
28
28
  * Declarative migration configuration on a relational database. Drives:
29
29
  * - the schema-version resolver at synth (filesystem scan of `dir`)
@@ -42,6 +42,21 @@ export type MigrationsConfig = {
42
42
  readonly dir: string;
43
43
  readonly versionResolver: (dir: string) => string;
44
44
  };
45
+ /**
46
+ * Declarative migration configuration on a ClickHouse database. Drives:
47
+ * - the schema-version resolver at synth (filesystem scan of `dir`)
48
+ * - the `EXPECTED_CH_SCHEMA_VERSION` auto-injection through every
49
+ * connected service's containers
50
+ *
51
+ * The default resolver picks the lexicographically-latest `*.sql` filename
52
+ * under `dir` (skipping `*.dev.sql`), matching what `runSqlMigrations`
53
+ * records in `_schema_migrations.ch_version`. `versionResolver` overrides
54
+ * the default for projects with a different file convention.
55
+ */
56
+ export type ClickHouseMigrationsConfig = {
57
+ readonly dir: string;
58
+ readonly versionResolver?: (dir: string) => string;
59
+ };
45
60
  /**
46
61
  * Database type discriminator.
47
62
  * Relational types share the IRelationalDatabase interface.
@@ -267,6 +282,22 @@ export interface IClickHouseDatabase extends IDatabase, IConnectable, IMigration
267
282
  getBackupBucket(): IBucket;
268
283
  /** Get the cold-tier bucket, or `undefined` when cold-tier storage is disabled. */
269
284
  getColdTierBucket(): IBucket | undefined;
285
+ /**
286
+ * Returns the migration configuration declared on this ClickHouse database,
287
+ * or `undefined` when the user did not declare one. When set, every
288
+ * container of every service in this database's `connections:` graph
289
+ * receives an `EXPECTED_CH_SCHEMA_VERSION` env entry (unless service-level
290
+ * `schemaGate: false`).
291
+ */
292
+ getMigrationsConfig(): ClickHouseMigrationsConfig | undefined;
293
+ /**
294
+ * Resolves `migrations.dir` via the resolver (default
295
+ * `pickLatestClickHouseMigration`) at synth time. Returns `undefined`
296
+ * when no `migrations:` was declared. Throws when declared but the
297
+ * directory is empty / unreadable / contains no matching entries — fail
298
+ * loud, never silent.
299
+ */
300
+ getExpectedSchemaVersion(): string | undefined;
270
301
  /**
271
302
  * Grant connect permissions to a grantee.
272
303
  * Adds the grantee to the database security group on both ports.
@@ -13,7 +13,7 @@
13
13
  * dynamo.getTableName(); // ✓ Available on IDynamoDBDatabase
14
14
  * dynamo.getHostEndpoint(); // ✗ Compile error - not on IDynamoDBDatabase
15
15
  */
16
- export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV } from "@fjall/util";
16
+ export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, EXPECTED_CH_SCHEMA_VERSION_ENV } from "@fjall/util";
17
17
  /**
18
18
  * Relational database type discriminator.
19
19
  */
@@ -53,6 +53,27 @@ export declare const CLICKHOUSE_TASK_MEMORY_MIB = 3072;
53
53
  export declare const CLICKHOUSE_HTTP_PORT = 8123;
54
54
  export declare const CLICKHOUSE_NATIVE_PORT = 9000;
55
55
  export declare const CLICKHOUSE_PROMETHEUS_PORT = 9363;
56
+ /** TLS-mode ClickHouse ports.
57
+ * HTTPS replaces 8123; secure native protocol replaces 9000.
58
+ * See aiDocs/patterns/clickhouse-tls-pattern.md § "The contract". */
59
+ export declare const CLICKHOUSE_HTTPS_PORT = 8443;
60
+ export declare const CLICKHOUSE_TCP_SECURE_PORT = 9440;
61
+ /** Mount path inside the main ClickHouse container for the materialised
62
+ * TLS cert + key. The init container writes to a shared task-scoped volume
63
+ * mounted here read-only on the main container. */
64
+ export declare const CLICKHOUSE_TLS_CERT_MOUNT_PATH = "/etc/clickhouse-server/certs";
65
+ /** Task-scoped Docker volume name shared between the TLS init container
66
+ * (writer) and the main ClickHouse container (reader). */
67
+ export declare const CLICKHOUSE_TLS_CERT_VOLUME_NAME = "tls-certs";
68
+ /** UID:GID the official ClickHouse image runs as. Init container `chown`s
69
+ * the materialised cert files to this UID so the server can read them. */
70
+ export declare const CLICKHOUSE_UID = 101;
71
+ /** Digest-pinned alpine image used by the TLS init container. The init
72
+ * container needs `jq` (extracts `cert`+`key` from the server-cert JSON
73
+ * secret); alpine ships it via `apk add` — but pinning the digest avoids
74
+ * fetching `:latest` on every deploy. Bump in lockstep with renovate
75
+ * alerts; CI verifies the digest resolves. */
76
+ export declare const ALPINE_INIT_CONTAINER_IMAGE = "public.ecr.aws/docker/library/alpine:3.20@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d";
56
77
  /** EBS device name for the data volume (must match user data script). */
57
78
  export declare const CLICKHOUSE_EBS_DEVICE_NAME = "/dev/xvdf";
58
79
  /** EBS mount path on the EC2 host. */
@@ -53,6 +53,27 @@ export const CLICKHOUSE_TASK_MEMORY_MIB = 3072;
53
53
  export const CLICKHOUSE_HTTP_PORT = 8123;
54
54
  export const CLICKHOUSE_NATIVE_PORT = 9000;
55
55
  export const CLICKHOUSE_PROMETHEUS_PORT = 9363;
56
+ /** TLS-mode ClickHouse ports.
57
+ * HTTPS replaces 8123; secure native protocol replaces 9000.
58
+ * See aiDocs/patterns/clickhouse-tls-pattern.md § "The contract". */
59
+ export const CLICKHOUSE_HTTPS_PORT = 8443;
60
+ export const CLICKHOUSE_TCP_SECURE_PORT = 9440;
61
+ /** Mount path inside the main ClickHouse container for the materialised
62
+ * TLS cert + key. The init container writes to a shared task-scoped volume
63
+ * mounted here read-only on the main container. */
64
+ export const CLICKHOUSE_TLS_CERT_MOUNT_PATH = "/etc/clickhouse-server/certs";
65
+ /** Task-scoped Docker volume name shared between the TLS init container
66
+ * (writer) and the main ClickHouse container (reader). */
67
+ export const CLICKHOUSE_TLS_CERT_VOLUME_NAME = "tls-certs";
68
+ /** UID:GID the official ClickHouse image runs as. Init container `chown`s
69
+ * the materialised cert files to this UID so the server can read them. */
70
+ export const CLICKHOUSE_UID = 101;
71
+ /** Digest-pinned alpine image used by the TLS init container. The init
72
+ * container needs `jq` (extracts `cert`+`key` from the server-cert JSON
73
+ * secret); alpine ships it via `apk add` — but pinning the digest avoids
74
+ * fetching `:latest` on every deploy. Bump in lockstep with renovate
75
+ * alerts; CI verifies the digest resolves. */
76
+ export const ALPINE_INIT_CONTAINER_IMAGE = "public.ecr.aws/docker/library/alpine:3.20@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d";
56
77
  /** EBS device name for the data volume (must match user data script). */
57
78
  export const CLICKHOUSE_EBS_DEVICE_NAME = "/dev/xvdf";
58
79
  /** EBS mount path on the EC2 host. */
@@ -10,5 +10,7 @@ import { SecurityGroup } from "../networking/securityGroup.js";
10
10
  *
11
11
  * Self-referencing native-port ingress is kept so the optimise/backup
12
12
  * scheduled tasks (which share this SG) can reach the ClickHouse server.
13
+ * Under TLS modes, the secure native port (9440) is the self-referencing
14
+ * port; under `mode: "none"` the plaintext native port stays.
13
15
  */
14
16
  export declare function createClickHouseSecurityGroup(scope: Construct, vpc: IVpc, nativePort: number): SecurityGroup;
@@ -9,6 +9,8 @@ import { SecurityGroup } from "../networking/securityGroup.js";
9
9
  *
10
10
  * Self-referencing native-port ingress is kept so the optimise/backup
11
11
  * scheduled tasks (which share this SG) can reach the ClickHouse server.
12
+ * Under TLS modes, the secure native port (9440) is the self-referencing
13
+ * port; under `mode: "none"` the plaintext native port stays.
12
14
  */
13
15
  export function createClickHouseSecurityGroup(scope, vpc, nativePort) {
14
16
  const sg = new SecurityGroup(scope, "ClickHouseSecurityGroup", {
@@ -19,6 +19,27 @@ export interface BuildClickHouseUserDataOptions {
19
19
  bucketName: string;
20
20
  region: string;
21
21
  };
22
+ /**
23
+ * When true, the server config emits `<https_port>`, `<tcp_port_secure>`,
24
+ * and an `<openSSL><server>` block referencing the cert files materialised
25
+ * at `CLICKHOUSE_TLS_CERT_MOUNT_PATH` by the user-data bootstrap step.
26
+ * Plaintext `<http_port>` is omitted to avoid mixed-protocol exposure.
27
+ * Default: false. When true, `caSecretArn` and `serverSecretArn` MUST be
28
+ * supplied so the bootstrap step can materialise the cert PEMs.
29
+ */
30
+ tlsActive?: boolean;
31
+ /**
32
+ * Secrets Manager ARN holding the CA cert as raw PEM. Required when
33
+ * `tlsActive` is true. Fetched at instance boot via the EC2 instance role
34
+ * and written to `${dataMount}/server-certs/ca.crt`.
35
+ */
36
+ caSecretArn?: string;
37
+ /**
38
+ * Secrets Manager ARN holding the server cert + key as JSON
39
+ * `{ "cert": "<pem>", "key": "<pem>" }`. Required when `tlsActive` is true.
40
+ * Fetched at instance boot and written to `${dataMount}/server-certs/server.{crt,key}`.
41
+ */
42
+ serverSecretArn?: string;
22
43
  }
23
44
  export declare function generateServerConfigXml(options: BuildClickHouseUserDataOptions): string;
24
45
  export interface GenerateUsersConfigXmlOptions {
@@ -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,38 @@ 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
+ chown -R 101:101 "$MOUNT_POINT/server-certs"
292
+ chmod 600 "$MOUNT_POINT/server-certs/server.key"
293
+ `
294
+ : "";
250
295
  const script = `#!/bin/bash
251
296
  set -euo pipefail
252
297
 
@@ -333,7 +378,7 @@ if [ "$USERS_CONFIG_RETRIEVED" != "1" ]; then
333
378
  fi
334
379
 
335
380
  chown -R 101:101 "$MOUNT_POINT"
336
- `;
381
+ ${tlsBootstrap}`;
337
382
  return compactUserDataScript(script);
338
383
  }
339
384
  /**
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "0.100.0",
3
+ "version": "1.1.0",
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",
@@ -61,8 +63,8 @@
61
63
  },
62
64
  "dependencies": {
63
65
  "@aws-sdk/client-organizations": "^3.1038.0",
64
- "@fjall/generator": "^0.100.0",
65
- "@fjall/util": "^0.100.0",
66
+ "@fjall/generator": "^1.1.0",
67
+ "@fjall/util": "^1.1.0",
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": "9eb16c56de49e6757cfd6352ad860dd125c6c21e"
82
+ "gitHead": "437ec726425bd7610b83a57c12c1a4e1980bbb01"
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 {};