@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.
- 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 +45 -2
- package/dist/lib/patterns/aws/clickhouseDatabase.js +156 -32
- 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/interfaces/database.d.ts +7 -5
- 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 +68 -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/dist/lib/utils/manifestWriter.js +6 -13
- package/package.json +8 -6
- 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
|
@@ -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,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
|
|
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
|
+
}
|
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
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": "
|
|
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.
|
|
62
|
+
"vitest": "^4.1.7"
|
|
61
63
|
},
|
|
62
64
|
"dependencies": {
|
|
63
65
|
"@aws-sdk/client-organizations": "^3.1038.0",
|
|
64
|
-
"@fjall/generator": "^
|
|
65
|
-
"@fjall/util": "^
|
|
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": "
|
|
82
|
+
"gitHead": "971d9ed50c6a21d24e67c9c83b9c471a632bf284"
|
|
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 {};
|
|
@@ -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 {};
|