@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.
- package/dist/lib/lambda-assets/cert-generator/asset/index.js +17948 -0
- package/dist/lib/lambda-assets/cert-generator/asset/package.json +4 -0
- package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +49 -1
- package/dist/lib/patterns/aws/clickhouseDatabase.js +137 -20
- package/dist/lib/patterns/aws/clickhouseTls/index.d.ts +1 -0
- package/dist/lib/patterns/aws/clickhouseTls/index.js +1 -0
- package/dist/lib/patterns/aws/clickhouseTls/types.d.ts +48 -0
- package/dist/lib/patterns/aws/computeEcs.d.ts +13 -1
- package/dist/lib/patterns/aws/computeEcs.js +88 -8
- package/dist/lib/patterns/aws/interfaces/database.d.ts +32 -1
- package/dist/lib/patterns/aws/interfaces/database.js +1 -1
- package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +21 -0
- package/dist/lib/resources/aws/database/clickhouseConstants.js +21 -0
- package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +2 -0
- package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +2 -0
- package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +21 -0
- package/dist/lib/resources/aws/database/clickhouseUserData.js +48 -3
- package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +1 -1
- package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +1 -1
- package/dist/lib/resources/aws/secrets/index.d.ts +2 -0
- package/dist/lib/resources/aws/secrets/index.js +2 -0
- package/dist/lib/resources/aws/secrets/tlsCaSecret.d.ts +13 -0
- package/dist/lib/resources/aws/secrets/tlsCaSecret.js +15 -0
- package/dist/lib/resources/aws/secrets/tlsServerSecret.d.ts +15 -0
- package/dist/lib/resources/aws/secrets/tlsServerSecret.js +17 -0
- package/dist/lib/resources/aws/utilities/index.d.ts +1 -0
- package/dist/lib/resources/aws/utilities/index.js +1 -0
- package/dist/lib/resources/aws/utilities/tlsCertGenerator.d.ts +33 -0
- package/dist/lib/resources/aws/utilities/tlsCertGenerator.js +67 -0
- package/package.json +7 -5
- package/dist/lib/config/aws/__t17fixture.js +0 -3
- package/dist/lib/config/aws/__t17fixtureType.d.ts +0 -2
- package/dist/lib/config/aws/__t17fixtureType.js +0 -1
- package/dist/lib/config/aws/eventBus.d.ts +0 -7
- package/dist/lib/config/aws/eventBus.js +0 -21
- package/dist/lib/config/aws/identityCenterGroupMembership.d.ts +0 -10
- package/dist/lib/config/aws/identityCenterGroupMembership.js +0 -102
- package/dist/lib/config/aws/securityBaseline.d.ts +0 -15
- package/dist/lib/config/aws/securityBaseline.js +0 -27
- package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +0 -1
- package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +0 -4
- package/dist/lib/patterns/aws/managedIdentityCenter.d.ts +0 -4
- package/dist/lib/patterns/aws/managedIdentityCenter.js +0 -19
- package/dist/lib/patterns/aws/subdomainHostedZone.d.ts +0 -9
- package/dist/lib/patterns/aws/subdomainHostedZone.js +0 -34
- package/dist/lib/resources/aws/analytics/clickhouse.d.ts +0 -15
- package/dist/lib/resources/aws/analytics/clickhouse.js +0 -310
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +0 -49
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +0 -140
- package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +0 -73
- package/dist/lib/resources/aws/analytics/clickhouseConstants.js +0 -89
- package/dist/lib/resources/aws/analytics/clickhouseSecurityGroup.d.ts +0 -13
- package/dist/lib/resources/aws/analytics/clickhouseSecurityGroup.js +0 -28
- package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +0 -59
- package/dist/lib/resources/aws/analytics/clickhouseTypes.js +0 -1
- package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +0 -6
- package/dist/lib/resources/aws/analytics/clickhouseUserData.js +0 -299
- package/dist/lib/resources/aws/analytics/index.d.ts +0 -4
- package/dist/lib/resources/aws/analytics/index.js +0 -2
- package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +0 -2
- package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +0 -11
- package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +0 -7
- package/dist/lib/resources/aws/messaging/defaultEventBus.js +0 -21
- package/dist/lib/resources/aws/networking/domain.d.ts +0 -13
- package/dist/lib/resources/aws/networking/domain.js +0 -100
- package/dist/lib/synth_dump.d.ts +0 -1
- package/dist/lib/synth_dump.js +0 -42
- package/dist/lib/utils/bastionFactory.d.ts +0 -10
- package/dist/lib/utils/bastionFactory.js +0 -29
- package/dist/lib/utils/constructMap.d.ts +0 -33
- package/dist/lib/utils/constructMap.js +0 -154
- package/dist/lib/utils/dnsRecords.d.ts +0 -4
- package/dist/lib/utils/dnsRecords.js +0 -104
- /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
|
-
|
|
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
|
|
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
|
|
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
|
*
|
|
@@ -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
|
+
}
|
|
@@ -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": "
|
|
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": "
|
|
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": "^
|
|
65
|
-
"@fjall/util": "^
|
|
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": "
|
|
82
|
+
"gitHead": "437ec726425bd7610b83a57c12c1a4e1980bbb01"
|
|
81
83
|
}
|
|
@@ -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 {};
|