@aws/nx-plugin 1.0.0-rc.85 → 1.0.0-rc.87
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/migrations.json +11 -1
- package/package.json +1 -1
- package/src/infra/app/__snapshots__/generator.spec.ts.snap +34 -8
- package/src/infra/app/files/app/README.md.template +14 -0
- package/src/infra/app/generator.js +15 -2
- package/src/infra/app/generator.js.map +1 -1
- package/src/mcp-server/tools/general-guidance.js +1 -1
- package/src/mcp-server/tools/general-guidance.js.map +1 -1
- package/src/migrations/latest/add-destroy-sandbox-target/metadata.json +3 -0
- package/src/migrations/latest/add-destroy-sandbox-target/migration.d.ts +6 -0
- package/src/migrations/latest/add-destroy-sandbox-target/migration.js +150 -0
- package/src/migrations/latest/add-destroy-sandbox-target/migration.js.map +1 -0
- package/src/migrations/latest/terraform-rdb-proxy-iam-connect-grant/__snapshots__/migration.spec.ts.snap +23 -0
- package/src/migrations/latest/terraform-rdb-proxy-iam-connect-grant/metadata.json +3 -0
- package/src/migrations/latest/terraform-rdb-proxy-iam-connect-grant/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-rdb-proxy-iam-connect-grant/migration.js +88 -0
- package/src/migrations/latest/terraform-rdb-proxy-iam-connect-grant/migration.js.map +1 -0
- package/src/open-api/ts-client/generator.js.map +1 -1
- package/src/open-api/ts-hooks/generator.spec.tsx +1 -1
- package/src/py/project/generator.js +5 -5
- package/src/py/project/generator.js.map +1 -1
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +21 -9
- package/src/py/rdb/files/__name__/utils.py.template +17 -1
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +8 -8
- package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +16 -16
- package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +3 -3
- package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +3 -3
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +132 -261
- package/src/ts/rdb/files/src/utils.ts.template +28 -1
- package/src/ts/react-website/cognito-auth/generator.js +1 -1
- package/src/ts/react-website/cognito-auth/generator.js.map +1 -1
- package/src/ts/react-website/runtime-config/generator.js +1 -1
- package/src/ts/react-website/runtime-config/generator.js.map +1 -1
- package/src/utils/agent-core-constructs/agent-core-constructs.js +3 -2
- package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
- package/src/utils/agent-core-constructs/files/cdk/core/agentcore-gateway/agentcore-gateway.ts.template +1 -1
- package/src/utils/api-constructs/api-constructs.js +7 -4
- package/src/utils/api-constructs/api-constructs.js.map +1 -1
- package/src/utils/api-constructs/files/cdk/app/apis/http/__apiNameKebabCase__.ts.template +4 -4
- package/src/utils/api-constructs/files/cdk/app/apis/rest/__apiNameKebabCase__.ts.template +4 -4
- package/src/utils/api-constructs/files/cdk/core/api/rest/api-gateway-account.ts.template +1 -1
- package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +4 -4
- package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +4 -4
- package/src/utils/ast.d.ts +13 -0
- package/src/utils/ast.js +36 -0
- package/src/utils/ast.js.map +1 -1
- package/src/utils/bundle/bundle.js +5 -2
- package/src/utils/bundle/bundle.js.map +1 -1
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.js +5 -3
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.js.map +1 -1
- package/src/utils/dcr-proxy-constructs/files/cdk/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.ts.template +1 -1
- package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +2 -2
- package/src/utils/files/common/scripts/src/infra/stage-credentials/cdk-command.ts.template +7 -3
- package/src/utils/function-constructs/function-constructs.js +3 -3
- package/src/utils/function-constructs/function-constructs.js.map +1 -1
- package/src/utils/lambda-runtime-resolution.d.ts +74 -0
- package/src/utils/lambda-runtime-resolution.js +97 -0
- package/src/utils/lambda-runtime-resolution.js.map +1 -0
- package/src/utils/rdb-constructs/files/cdk/app/dbs/__nameKebabCase__.ts.template +3 -3
- package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +10 -29
- package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +28 -116
- package/src/utils/rdb-constructs/rdb-constructs.js +5 -3
- package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
- package/src/utils/version-upgrade-migration/sync-lambda-runtimes.d.ts +28 -0
- package/src/utils/version-upgrade-migration/sync-lambda-runtimes.js +148 -0
- package/src/utils/version-upgrade-migration/sync-lambda-runtimes.js.map +1 -0
- package/src/utils/version-upgrade-migration/sync-python-language-version.d.ts +31 -0
- package/src/utils/version-upgrade-migration/sync-python-language-version.js +110 -0
- package/src/utils/version-upgrade-migration/sync-python-language-version.js.map +1 -0
- package/src/utils/version-upgrade-migration/sync-vended-versions.js +36 -7
- package/src/utils/version-upgrade-migration/sync-vended-versions.js.map +1 -1
- package/src/utils/versions.d.ts +60 -0
- package/src/utils/versions.js +51 -0
- package/src/utils/versions.js.map +1 -1
|
@@ -10,7 +10,7 @@ export class TestProjectTestFunction extends Function {
|
|
|
10
10
|
constructor(scope: Construct, id: string) {
|
|
11
11
|
super(scope, id, {
|
|
12
12
|
timeout: Duration.seconds(30),
|
|
13
|
-
runtime: Runtime.
|
|
13
|
+
runtime: Runtime.NODEJS_24_X,
|
|
14
14
|
handler: 'index.handler',
|
|
15
15
|
code: Code.fromAsset(
|
|
16
16
|
url.fileURLToPath(
|
|
@@ -282,7 +282,7 @@ resource "aws_lambda_function" "lambda_function" {
|
|
|
282
282
|
role = aws_iam_role.lambda_role.arn
|
|
283
283
|
handler = "index.handler"
|
|
284
284
|
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
285
|
-
runtime = "
|
|
285
|
+
runtime = "nodejs24.x"
|
|
286
286
|
timeout = 30
|
|
287
287
|
|
|
288
288
|
tracing_config {
|
|
@@ -467,7 +467,7 @@ resource "aws_lambda_function" "lambda_function" {
|
|
|
467
467
|
role = aws_iam_role.lambda_role.arn
|
|
468
468
|
handler = "index.handler"
|
|
469
469
|
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
470
|
-
runtime = "
|
|
470
|
+
runtime = "nodejs24.x"
|
|
471
471
|
timeout = 30
|
|
472
472
|
|
|
473
473
|
tracing_config {
|
|
@@ -225,6 +225,24 @@ export type DatabaseSecret = {
|
|
|
225
225
|
port: number;
|
|
226
226
|
};
|
|
227
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Connection details for the admin credentials secret. RDS-managed secrets hold
|
|
230
|
+
* only \`username\` and \`password\`, so the host, port and database name are taken
|
|
231
|
+
* from the environment when the secret omits them.
|
|
232
|
+
*/
|
|
233
|
+
type SecretConnectionOverrides = Partial<
|
|
234
|
+
Omit<DatabaseSecret, 'username' | 'password'>
|
|
235
|
+
>;
|
|
236
|
+
|
|
237
|
+
const connectionOverridesFromEnv = (): SecretConnectionOverrides => {
|
|
238
|
+
const { DATABASE_HOST, DATABASE_PORT, DATABASE_NAME } = process.env;
|
|
239
|
+
return {
|
|
240
|
+
...(DATABASE_HOST ? { host: DATABASE_HOST } : {}),
|
|
241
|
+
...(DATABASE_PORT ? { port: Number(DATABASE_PORT) } : {}),
|
|
242
|
+
...(DATABASE_NAME ? { dbname: DATABASE_NAME } : {}),
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
|
|
228
246
|
const databaseConfigPromises: Record<string, Promise<DatabaseConfig>> = {};
|
|
229
247
|
|
|
230
248
|
const getSecretValue = async (secretArn: string): Promise<string> => {
|
|
@@ -251,7 +269,18 @@ export const getDatabaseSecret = async (): Promise<DatabaseSecret> => {
|
|
|
251
269
|
);
|
|
252
270
|
}
|
|
253
271
|
|
|
254
|
-
|
|
272
|
+
const secret = JSON.parse(await getSecretValue(secretArn)) as DatabaseSecret;
|
|
273
|
+
const secretWithConnection = { ...secret, ...connectionOverridesFromEnv() };
|
|
274
|
+
|
|
275
|
+
for (const key of ['host', 'port', 'dbname'] as const) {
|
|
276
|
+
if (secretWithConnection[key] == null) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
\`Database secret is missing \${key} and no override was provided in the environment.\`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return secretWithConnection;
|
|
255
284
|
};
|
|
256
285
|
|
|
257
286
|
const loadDatabaseConfig = async (
|
|
@@ -810,7 +839,8 @@ export default defineConfig({
|
|
|
810
839
|
|
|
811
840
|
exports[`ts#rdb generator > should generate terraform modules when iac is Terraform 1`] = `
|
|
812
841
|
"# Core Aurora module
|
|
813
|
-
# This module creates an Aurora cluster
|
|
842
|
+
# This module creates an Aurora cluster whose admin credentials are generated
|
|
843
|
+
# and rotated by RDS in Secrets Manager.
|
|
814
844
|
|
|
815
845
|
terraform {
|
|
816
846
|
required_version = ">= 1.0"
|
|
@@ -854,11 +884,6 @@ variable "subnet_ids" {
|
|
|
854
884
|
type = list(string)
|
|
855
885
|
}
|
|
856
886
|
|
|
857
|
-
variable "lambda_subnet_ids" {
|
|
858
|
-
description = "Subnet IDs for the credential-rotation Lambda. Must have outbound egress to AWS service endpoints (e.g. Secrets Manager) — use private subnets with a NAT gateway."
|
|
859
|
-
type = list(string)
|
|
860
|
-
}
|
|
861
|
-
|
|
862
887
|
variable "engine" {
|
|
863
888
|
description = "Aurora engine to use."
|
|
864
889
|
type = string
|
|
@@ -928,12 +953,6 @@ variable "enable_rds_proxy" {
|
|
|
928
953
|
default = true
|
|
929
954
|
}
|
|
930
955
|
|
|
931
|
-
variable "enable_credential_rotation" {
|
|
932
|
-
description = "Whether to enable automatic credential rotation for the admin secret."
|
|
933
|
-
type = bool
|
|
934
|
-
default = true
|
|
935
|
-
}
|
|
936
|
-
|
|
937
956
|
variable "enable_cloudwatch_logs" {
|
|
938
957
|
description = "Whether to export Aurora engine logs to CloudWatch. PostgreSQL logs DDL statements only (log_statement=ddl); MySQL enables Advanced Auditing scoped to connections and DDL (server_audit_events=CONNECT,QUERY_DDL). Neither logs statement parameter values, to avoid leaking PII into log data."
|
|
939
958
|
type = bool
|
|
@@ -1055,6 +1074,20 @@ resource "aws_kms_key" "database" {
|
|
|
1055
1074
|
"kms:CreateGrant"
|
|
1056
1075
|
]
|
|
1057
1076
|
Resource = "*"
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
Sid = "AllowSecretsManagerService"
|
|
1080
|
+
Effect = "Allow"
|
|
1081
|
+
Principal = {
|
|
1082
|
+
Service = "secretsmanager.amazonaws.com"
|
|
1083
|
+
}
|
|
1084
|
+
Action = [
|
|
1085
|
+
"kms:Encrypt",
|
|
1086
|
+
"kms:Decrypt",
|
|
1087
|
+
"kms:GenerateDataKey*",
|
|
1088
|
+
"kms:DescribeKey"
|
|
1089
|
+
]
|
|
1090
|
+
Resource = "*"
|
|
1058
1091
|
}
|
|
1059
1092
|
]
|
|
1060
1093
|
})
|
|
@@ -1109,35 +1142,6 @@ resource "aws_db_subnet_group" "database" {
|
|
|
1109
1142
|
})
|
|
1110
1143
|
}
|
|
1111
1144
|
|
|
1112
|
-
resource "random_password" "master_password" {
|
|
1113
|
-
length = 32
|
|
1114
|
-
special = true
|
|
1115
|
-
override_special = "!#$%&*()-_=+[]{}<>:?"
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
resource "aws_secretsmanager_secret" "credentials" {
|
|
1119
|
-
#checkov:skip=CKV2_AWS_57:Rotation is configured via aws_cloudformation_stack.credentials_rotation using the AWS::SecretsManager-2024-09-16 transform; Checkov cannot resolve rotation schedules created by CloudFormation transforms
|
|
1120
|
-
name_prefix = "\${var.name}-aurora-credentials-"
|
|
1121
|
-
kms_key_id = aws_kms_key.database.arn
|
|
1122
|
-
|
|
1123
|
-
tags = merge(var.tags, {
|
|
1124
|
-
Name = "\${var.name}-aurora-credentials"
|
|
1125
|
-
})
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
resource "aws_secretsmanager_secret_version" "credentials" {
|
|
1129
|
-
secret_id = aws_secretsmanager_secret.credentials.id
|
|
1130
|
-
secret_string = jsonencode({
|
|
1131
|
-
engine = var.engine == "aurora-mysql" ? "mysql" : "postgres"
|
|
1132
|
-
username = var.admin_user
|
|
1133
|
-
password = random_password.master_password.result
|
|
1134
|
-
host = aws_rds_cluster.database.endpoint
|
|
1135
|
-
port = aws_rds_cluster.database.port
|
|
1136
|
-
dbname = var.database_name
|
|
1137
|
-
dbClusterIdentifier = aws_rds_cluster.database.cluster_identifier
|
|
1138
|
-
})
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
1145
|
resource "aws_rds_cluster" "database" {
|
|
1142
1146
|
#checkov:skip=CKV2_AWS_27:Query logging is enabled by default via enable_cloudwatch_logs; checkov cannot resolve the conditional count expression on aws_rds_cluster_parameter_group.database
|
|
1143
1147
|
#checkov:skip=CKV_AWS_139:Deletion protection is enabled but checkov is unable to detect it correctly
|
|
@@ -1146,7 +1150,8 @@ resource "aws_rds_cluster" "database" {
|
|
|
1146
1150
|
engine_version = coalesce(var.engine_version, local.default_engine_version)
|
|
1147
1151
|
database_name = var.database_name
|
|
1148
1152
|
master_username = var.admin_user
|
|
1149
|
-
|
|
1153
|
+
manage_master_user_password = true
|
|
1154
|
+
master_user_secret_kms_key_id = aws_kms_key.database.arn
|
|
1150
1155
|
db_subnet_group_name = aws_db_subnet_group.database.name
|
|
1151
1156
|
db_cluster_parameter_group_name = var.enable_cloudwatch_logs ? aws_rds_cluster_parameter_group.database[0].name : null
|
|
1152
1157
|
vpc_security_group_ids = [aws_security_group.database.id]
|
|
@@ -1198,75 +1203,6 @@ resource "aws_rds_cluster_instance" "database" {
|
|
|
1198
1203
|
})
|
|
1199
1204
|
}
|
|
1200
1205
|
|
|
1201
|
-
resource "aws_security_group" "rotation" {
|
|
1202
|
-
#checkov:skip=CKV2_AWS_5:Security group is attached to the hosted rotation Lambda created by AWS::SecretsManager::RotationSchedule; Checkov cannot resolve resources created by the CloudFormation transform
|
|
1203
|
-
count = var.enable_credential_rotation ? 1 : 0
|
|
1204
|
-
name_prefix = "\${var.name}-aurora-rotation-"
|
|
1205
|
-
description = "Security group for Aurora secret rotation \${var.name}"
|
|
1206
|
-
vpc_id = var.vpc_id
|
|
1207
|
-
|
|
1208
|
-
tags = merge(var.tags, {
|
|
1209
|
-
Name = "\${var.name}-aurora-rotation"
|
|
1210
|
-
})
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
resource "aws_vpc_security_group_egress_rule" "rotation_to_database" {
|
|
1214
|
-
count = var.enable_credential_rotation ? 1 : 0
|
|
1215
|
-
security_group_id = aws_security_group.rotation[0].id
|
|
1216
|
-
referenced_security_group_id = aws_security_group.database.id
|
|
1217
|
-
from_port = coalesce(var.port, local.default_port)
|
|
1218
|
-
to_port = coalesce(var.port, local.default_port)
|
|
1219
|
-
ip_protocol = "tcp"
|
|
1220
|
-
description = "Allow outbound database traffic from secret rotation Lambda"
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
resource "aws_vpc_security_group_ingress_rule" "rotation_to_database" {
|
|
1224
|
-
count = var.enable_credential_rotation ? 1 : 0
|
|
1225
|
-
security_group_id = aws_security_group.database.id
|
|
1226
|
-
referenced_security_group_id = aws_security_group.rotation[0].id
|
|
1227
|
-
from_port = coalesce(var.port, local.default_port)
|
|
1228
|
-
to_port = coalesce(var.port, local.default_port)
|
|
1229
|
-
ip_protocol = "tcp"
|
|
1230
|
-
description = "Allow inbound database traffic from secret rotation Lambda"
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
resource "aws_cloudformation_stack" "credentials_rotation" {
|
|
1234
|
-
#checkov:skip=CKV_AWS_124:SNS event notifications are not required for the credentials rotation stack
|
|
1235
|
-
count = var.enable_credential_rotation ? 1 : 0
|
|
1236
|
-
name = "\${local.cluster_name}-credentials-rotation"
|
|
1237
|
-
|
|
1238
|
-
capabilities = ["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"]
|
|
1239
|
-
|
|
1240
|
-
template_body = jsonencode({
|
|
1241
|
-
AWSTemplateFormatVersion = "2010-09-09"
|
|
1242
|
-
Transform = "AWS::SecretsManager-2024-09-16"
|
|
1243
|
-
Resources = {
|
|
1244
|
-
CredentialsRotationSchedule = {
|
|
1245
|
-
Type = "AWS::SecretsManager::RotationSchedule"
|
|
1246
|
-
Properties = {
|
|
1247
|
-
SecretId = aws_secretsmanager_secret.credentials.arn
|
|
1248
|
-
HostedRotationLambda = {
|
|
1249
|
-
RotationType = var.engine == "aurora-mysql" ? "MySQLSingleUser" : "PostgreSQLSingleUser"
|
|
1250
|
-
RotationLambdaName = "\${local.cluster_name}-credentials-rotation"
|
|
1251
|
-
VpcSecurityGroupIds = aws_security_group.rotation[0].id
|
|
1252
|
-
VpcSubnetIds = join(",", var.lambda_subnet_ids)
|
|
1253
|
-
}
|
|
1254
|
-
RotationRules = {
|
|
1255
|
-
AutomaticallyAfterDays = 30
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
})
|
|
1261
|
-
|
|
1262
|
-
depends_on = [
|
|
1263
|
-
aws_secretsmanager_secret_version.credentials,
|
|
1264
|
-
aws_rds_cluster_instance.database,
|
|
1265
|
-
aws_vpc_security_group_egress_rule.rotation_to_database,
|
|
1266
|
-
aws_vpc_security_group_ingress_rule.rotation_to_database,
|
|
1267
|
-
]
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
1206
|
resource "aws_iam_role" "proxy" {
|
|
1271
1207
|
count = var.enable_rds_proxy ? 1 : 0
|
|
1272
1208
|
|
|
@@ -1505,6 +1441,11 @@ output "cluster_endpoint" {
|
|
|
1505
1441
|
value = var.enable_rds_proxy ? aws_db_proxy.aurora[0].endpoint : aws_rds_cluster.database.endpoint
|
|
1506
1442
|
}
|
|
1507
1443
|
|
|
1444
|
+
output "writer_endpoint" {
|
|
1445
|
+
description = "Writer endpoint of the Aurora cluster itself, bypassing the RDS Proxy. Used by the admin-credentialled handlers, which connect directly to the cluster."
|
|
1446
|
+
value = aws_rds_cluster.database.endpoint
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1508
1449
|
output "reader_endpoint" {
|
|
1509
1450
|
description = "Reader endpoint of the Aurora cluster."
|
|
1510
1451
|
value = aws_rds_cluster.database.reader_endpoint
|
|
@@ -1531,12 +1472,12 @@ output "proxy_role_name" {
|
|
|
1531
1472
|
}
|
|
1532
1473
|
|
|
1533
1474
|
output "secret_arn" {
|
|
1534
|
-
description = "ARN of the
|
|
1535
|
-
value =
|
|
1475
|
+
description = "ARN of the RDS-managed admin credentials secret. Its value is a JSON object with username and password only — connection details come from this module's other outputs."
|
|
1476
|
+
value = one(aws_rds_cluster.database.master_user_secret[*].secret_arn)
|
|
1536
1477
|
}
|
|
1537
1478
|
|
|
1538
1479
|
output "kms_key_arn" {
|
|
1539
|
-
description = "ARN of the KMS key protecting Aurora and
|
|
1480
|
+
description = "ARN of the KMS key protecting Aurora and its admin credentials secret."
|
|
1540
1481
|
value = aws_kms_key.database.arn
|
|
1541
1482
|
}
|
|
1542
1483
|
|
|
@@ -1561,8 +1502,8 @@ output "database_name" {
|
|
|
1561
1502
|
}
|
|
1562
1503
|
|
|
1563
1504
|
output "admin_user" {
|
|
1564
|
-
description = "Admin username stored in the
|
|
1565
|
-
value =
|
|
1505
|
+
description = "Admin username stored in the RDS-managed credentials secret."
|
|
1506
|
+
value = aws_rds_cluster.database.master_username
|
|
1566
1507
|
}
|
|
1567
1508
|
"
|
|
1568
1509
|
`;
|
|
@@ -1658,12 +1599,6 @@ variable "enable_rds_proxy" {
|
|
|
1658
1599
|
default = true
|
|
1659
1600
|
}
|
|
1660
1601
|
|
|
1661
|
-
variable "enable_credential_rotation" {
|
|
1662
|
-
description = "Whether to enable automatic credential rotation for the admin secret."
|
|
1663
|
-
type = bool
|
|
1664
|
-
default = true
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
1602
|
variable "enable_cloudwatch_logs" {
|
|
1668
1603
|
description = "Whether to export Aurora engine logs to CloudWatch. PostgreSQL logs DDL statements only (log_statement=ddl); MySQL enables Advanced Auditing scoped to connections and DDL (server_audit_events=CONNECT,QUERY_DDL). Neither logs statement parameter values, to avoid leaking PII into log data."
|
|
1669
1604
|
type = bool
|
|
@@ -1736,7 +1671,6 @@ module "aurora" {
|
|
|
1736
1671
|
name = "db"
|
|
1737
1672
|
vpc_id = var.vpc_id
|
|
1738
1673
|
subnet_ids = var.database_subnet_ids
|
|
1739
|
-
lambda_subnet_ids = var.lambda_subnet_ids
|
|
1740
1674
|
engine = "aurora-postgresql"
|
|
1741
1675
|
engine_version = var.engine_version
|
|
1742
1676
|
database_name = "database_name"
|
|
@@ -1748,7 +1682,6 @@ module "aurora" {
|
|
|
1748
1682
|
deletion_protection = var.deletion_protection
|
|
1749
1683
|
skip_final_snapshot = var.skip_final_snapshot
|
|
1750
1684
|
enable_rds_proxy = var.enable_rds_proxy
|
|
1751
|
-
enable_credential_rotation = var.enable_credential_rotation
|
|
1752
1685
|
enable_cloudwatch_logs = var.enable_cloudwatch_logs
|
|
1753
1686
|
enable_performance_insights = var.enable_performance_insights
|
|
1754
1687
|
performance_insights_retention_period = var.performance_insights_retention_period
|
|
@@ -2117,6 +2050,9 @@ resource "aws_lambda_function" "create_db_user" {
|
|
|
2117
2050
|
environment {
|
|
2118
2051
|
variables = {
|
|
2119
2052
|
DATABASE_SECRET_ARN = module.aurora.secret_arn
|
|
2053
|
+
DATABASE_HOST = module.aurora.writer_endpoint
|
|
2054
|
+
DATABASE_PORT = module.aurora.cluster_port
|
|
2055
|
+
DATABASE_NAME = module.aurora.database_name
|
|
2120
2056
|
NODE_EXTRA_CA_CERTS = "/var/runtime/ca-cert.pem"
|
|
2121
2057
|
}
|
|
2122
2058
|
}
|
|
@@ -2194,6 +2130,9 @@ resource "aws_lambda_function" "migration_handler" {
|
|
|
2194
2130
|
environment {
|
|
2195
2131
|
variables = {
|
|
2196
2132
|
DATABASE_SECRET_ARN = module.aurora.secret_arn
|
|
2133
|
+
DATABASE_HOST = module.aurora.writer_endpoint
|
|
2134
|
+
DATABASE_PORT = module.aurora.cluster_port
|
|
2135
|
+
DATABASE_NAME = module.aurora.database_name
|
|
2197
2136
|
}
|
|
2198
2137
|
}
|
|
2199
2138
|
|
|
@@ -2313,7 +2252,8 @@ output "database_runtime_user" {
|
|
|
2313
2252
|
|
|
2314
2253
|
exports[`ts#rdb generator > should generate terraform modules with MySQL engine 1`] = `
|
|
2315
2254
|
"# Core Aurora module
|
|
2316
|
-
# This module creates an Aurora cluster
|
|
2255
|
+
# This module creates an Aurora cluster whose admin credentials are generated
|
|
2256
|
+
# and rotated by RDS in Secrets Manager.
|
|
2317
2257
|
|
|
2318
2258
|
terraform {
|
|
2319
2259
|
required_version = ">= 1.0"
|
|
@@ -2357,11 +2297,6 @@ variable "subnet_ids" {
|
|
|
2357
2297
|
type = list(string)
|
|
2358
2298
|
}
|
|
2359
2299
|
|
|
2360
|
-
variable "lambda_subnet_ids" {
|
|
2361
|
-
description = "Subnet IDs for the credential-rotation Lambda. Must have outbound egress to AWS service endpoints (e.g. Secrets Manager) — use private subnets with a NAT gateway."
|
|
2362
|
-
type = list(string)
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
2300
|
variable "engine" {
|
|
2366
2301
|
description = "Aurora engine to use."
|
|
2367
2302
|
type = string
|
|
@@ -2431,12 +2366,6 @@ variable "enable_rds_proxy" {
|
|
|
2431
2366
|
default = true
|
|
2432
2367
|
}
|
|
2433
2368
|
|
|
2434
|
-
variable "enable_credential_rotation" {
|
|
2435
|
-
description = "Whether to enable automatic credential rotation for the admin secret."
|
|
2436
|
-
type = bool
|
|
2437
|
-
default = true
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
2369
|
variable "enable_cloudwatch_logs" {
|
|
2441
2370
|
description = "Whether to export Aurora engine logs to CloudWatch. PostgreSQL logs DDL statements only (log_statement=ddl); MySQL enables Advanced Auditing scoped to connections and DDL (server_audit_events=CONNECT,QUERY_DDL). Neither logs statement parameter values, to avoid leaking PII into log data."
|
|
2442
2371
|
type = bool
|
|
@@ -2558,6 +2487,20 @@ resource "aws_kms_key" "database" {
|
|
|
2558
2487
|
"kms:CreateGrant"
|
|
2559
2488
|
]
|
|
2560
2489
|
Resource = "*"
|
|
2490
|
+
},
|
|
2491
|
+
{
|
|
2492
|
+
Sid = "AllowSecretsManagerService"
|
|
2493
|
+
Effect = "Allow"
|
|
2494
|
+
Principal = {
|
|
2495
|
+
Service = "secretsmanager.amazonaws.com"
|
|
2496
|
+
}
|
|
2497
|
+
Action = [
|
|
2498
|
+
"kms:Encrypt",
|
|
2499
|
+
"kms:Decrypt",
|
|
2500
|
+
"kms:GenerateDataKey*",
|
|
2501
|
+
"kms:DescribeKey"
|
|
2502
|
+
]
|
|
2503
|
+
Resource = "*"
|
|
2561
2504
|
}
|
|
2562
2505
|
]
|
|
2563
2506
|
})
|
|
@@ -2612,35 +2555,6 @@ resource "aws_db_subnet_group" "database" {
|
|
|
2612
2555
|
})
|
|
2613
2556
|
}
|
|
2614
2557
|
|
|
2615
|
-
resource "random_password" "master_password" {
|
|
2616
|
-
length = 32
|
|
2617
|
-
special = true
|
|
2618
|
-
override_special = "!#$%&*()-_=+[]{}<>:?"
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
resource "aws_secretsmanager_secret" "credentials" {
|
|
2622
|
-
#checkov:skip=CKV2_AWS_57:Rotation is configured via aws_cloudformation_stack.credentials_rotation using the AWS::SecretsManager-2024-09-16 transform; Checkov cannot resolve rotation schedules created by CloudFormation transforms
|
|
2623
|
-
name_prefix = "\${var.name}-aurora-credentials-"
|
|
2624
|
-
kms_key_id = aws_kms_key.database.arn
|
|
2625
|
-
|
|
2626
|
-
tags = merge(var.tags, {
|
|
2627
|
-
Name = "\${var.name}-aurora-credentials"
|
|
2628
|
-
})
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
|
-
resource "aws_secretsmanager_secret_version" "credentials" {
|
|
2632
|
-
secret_id = aws_secretsmanager_secret.credentials.id
|
|
2633
|
-
secret_string = jsonencode({
|
|
2634
|
-
engine = var.engine == "aurora-mysql" ? "mysql" : "postgres"
|
|
2635
|
-
username = var.admin_user
|
|
2636
|
-
password = random_password.master_password.result
|
|
2637
|
-
host = aws_rds_cluster.database.endpoint
|
|
2638
|
-
port = aws_rds_cluster.database.port
|
|
2639
|
-
dbname = var.database_name
|
|
2640
|
-
dbClusterIdentifier = aws_rds_cluster.database.cluster_identifier
|
|
2641
|
-
})
|
|
2642
|
-
}
|
|
2643
|
-
|
|
2644
2558
|
resource "aws_rds_cluster" "database" {
|
|
2645
2559
|
#checkov:skip=CKV2_AWS_27:Query logging is enabled by default via enable_cloudwatch_logs; checkov cannot resolve the conditional count expression on aws_rds_cluster_parameter_group.database
|
|
2646
2560
|
#checkov:skip=CKV_AWS_139:Deletion protection is enabled but checkov is unable to detect it correctly
|
|
@@ -2649,7 +2563,8 @@ resource "aws_rds_cluster" "database" {
|
|
|
2649
2563
|
engine_version = coalesce(var.engine_version, local.default_engine_version)
|
|
2650
2564
|
database_name = var.database_name
|
|
2651
2565
|
master_username = var.admin_user
|
|
2652
|
-
|
|
2566
|
+
manage_master_user_password = true
|
|
2567
|
+
master_user_secret_kms_key_id = aws_kms_key.database.arn
|
|
2653
2568
|
db_subnet_group_name = aws_db_subnet_group.database.name
|
|
2654
2569
|
db_cluster_parameter_group_name = var.enable_cloudwatch_logs ? aws_rds_cluster_parameter_group.database[0].name : null
|
|
2655
2570
|
vpc_security_group_ids = [aws_security_group.database.id]
|
|
@@ -2701,75 +2616,6 @@ resource "aws_rds_cluster_instance" "database" {
|
|
|
2701
2616
|
})
|
|
2702
2617
|
}
|
|
2703
2618
|
|
|
2704
|
-
resource "aws_security_group" "rotation" {
|
|
2705
|
-
#checkov:skip=CKV2_AWS_5:Security group is attached to the hosted rotation Lambda created by AWS::SecretsManager::RotationSchedule; Checkov cannot resolve resources created by the CloudFormation transform
|
|
2706
|
-
count = var.enable_credential_rotation ? 1 : 0
|
|
2707
|
-
name_prefix = "\${var.name}-aurora-rotation-"
|
|
2708
|
-
description = "Security group for Aurora secret rotation \${var.name}"
|
|
2709
|
-
vpc_id = var.vpc_id
|
|
2710
|
-
|
|
2711
|
-
tags = merge(var.tags, {
|
|
2712
|
-
Name = "\${var.name}-aurora-rotation"
|
|
2713
|
-
})
|
|
2714
|
-
}
|
|
2715
|
-
|
|
2716
|
-
resource "aws_vpc_security_group_egress_rule" "rotation_to_database" {
|
|
2717
|
-
count = var.enable_credential_rotation ? 1 : 0
|
|
2718
|
-
security_group_id = aws_security_group.rotation[0].id
|
|
2719
|
-
referenced_security_group_id = aws_security_group.database.id
|
|
2720
|
-
from_port = coalesce(var.port, local.default_port)
|
|
2721
|
-
to_port = coalesce(var.port, local.default_port)
|
|
2722
|
-
ip_protocol = "tcp"
|
|
2723
|
-
description = "Allow outbound database traffic from secret rotation Lambda"
|
|
2724
|
-
}
|
|
2725
|
-
|
|
2726
|
-
resource "aws_vpc_security_group_ingress_rule" "rotation_to_database" {
|
|
2727
|
-
count = var.enable_credential_rotation ? 1 : 0
|
|
2728
|
-
security_group_id = aws_security_group.database.id
|
|
2729
|
-
referenced_security_group_id = aws_security_group.rotation[0].id
|
|
2730
|
-
from_port = coalesce(var.port, local.default_port)
|
|
2731
|
-
to_port = coalesce(var.port, local.default_port)
|
|
2732
|
-
ip_protocol = "tcp"
|
|
2733
|
-
description = "Allow inbound database traffic from secret rotation Lambda"
|
|
2734
|
-
}
|
|
2735
|
-
|
|
2736
|
-
resource "aws_cloudformation_stack" "credentials_rotation" {
|
|
2737
|
-
#checkov:skip=CKV_AWS_124:SNS event notifications are not required for the credentials rotation stack
|
|
2738
|
-
count = var.enable_credential_rotation ? 1 : 0
|
|
2739
|
-
name = "\${local.cluster_name}-credentials-rotation"
|
|
2740
|
-
|
|
2741
|
-
capabilities = ["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"]
|
|
2742
|
-
|
|
2743
|
-
template_body = jsonencode({
|
|
2744
|
-
AWSTemplateFormatVersion = "2010-09-09"
|
|
2745
|
-
Transform = "AWS::SecretsManager-2024-09-16"
|
|
2746
|
-
Resources = {
|
|
2747
|
-
CredentialsRotationSchedule = {
|
|
2748
|
-
Type = "AWS::SecretsManager::RotationSchedule"
|
|
2749
|
-
Properties = {
|
|
2750
|
-
SecretId = aws_secretsmanager_secret.credentials.arn
|
|
2751
|
-
HostedRotationLambda = {
|
|
2752
|
-
RotationType = var.engine == "aurora-mysql" ? "MySQLSingleUser" : "PostgreSQLSingleUser"
|
|
2753
|
-
RotationLambdaName = "\${local.cluster_name}-credentials-rotation"
|
|
2754
|
-
VpcSecurityGroupIds = aws_security_group.rotation[0].id
|
|
2755
|
-
VpcSubnetIds = join(",", var.lambda_subnet_ids)
|
|
2756
|
-
}
|
|
2757
|
-
RotationRules = {
|
|
2758
|
-
AutomaticallyAfterDays = 30
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2761
|
-
}
|
|
2762
|
-
}
|
|
2763
|
-
})
|
|
2764
|
-
|
|
2765
|
-
depends_on = [
|
|
2766
|
-
aws_secretsmanager_secret_version.credentials,
|
|
2767
|
-
aws_rds_cluster_instance.database,
|
|
2768
|
-
aws_vpc_security_group_egress_rule.rotation_to_database,
|
|
2769
|
-
aws_vpc_security_group_ingress_rule.rotation_to_database,
|
|
2770
|
-
]
|
|
2771
|
-
}
|
|
2772
|
-
|
|
2773
2619
|
resource "aws_iam_role" "proxy" {
|
|
2774
2620
|
count = var.enable_rds_proxy ? 1 : 0
|
|
2775
2621
|
|
|
@@ -3008,6 +2854,11 @@ output "cluster_endpoint" {
|
|
|
3008
2854
|
value = var.enable_rds_proxy ? aws_db_proxy.aurora[0].endpoint : aws_rds_cluster.database.endpoint
|
|
3009
2855
|
}
|
|
3010
2856
|
|
|
2857
|
+
output "writer_endpoint" {
|
|
2858
|
+
description = "Writer endpoint of the Aurora cluster itself, bypassing the RDS Proxy. Used by the admin-credentialled handlers, which connect directly to the cluster."
|
|
2859
|
+
value = aws_rds_cluster.database.endpoint
|
|
2860
|
+
}
|
|
2861
|
+
|
|
3011
2862
|
output "reader_endpoint" {
|
|
3012
2863
|
description = "Reader endpoint of the Aurora cluster."
|
|
3013
2864
|
value = aws_rds_cluster.database.reader_endpoint
|
|
@@ -3034,12 +2885,12 @@ output "proxy_role_name" {
|
|
|
3034
2885
|
}
|
|
3035
2886
|
|
|
3036
2887
|
output "secret_arn" {
|
|
3037
|
-
description = "ARN of the
|
|
3038
|
-
value =
|
|
2888
|
+
description = "ARN of the RDS-managed admin credentials secret. Its value is a JSON object with username and password only — connection details come from this module's other outputs."
|
|
2889
|
+
value = one(aws_rds_cluster.database.master_user_secret[*].secret_arn)
|
|
3039
2890
|
}
|
|
3040
2891
|
|
|
3041
2892
|
output "kms_key_arn" {
|
|
3042
|
-
description = "ARN of the KMS key protecting Aurora and
|
|
2893
|
+
description = "ARN of the KMS key protecting Aurora and its admin credentials secret."
|
|
3043
2894
|
value = aws_kms_key.database.arn
|
|
3044
2895
|
}
|
|
3045
2896
|
|
|
@@ -3064,8 +2915,8 @@ output "database_name" {
|
|
|
3064
2915
|
}
|
|
3065
2916
|
|
|
3066
2917
|
output "admin_user" {
|
|
3067
|
-
description = "Admin username stored in the
|
|
3068
|
-
value =
|
|
2918
|
+
description = "Admin username stored in the RDS-managed credentials secret."
|
|
2919
|
+
value = aws_rds_cluster.database.master_username
|
|
3069
2920
|
}
|
|
3070
2921
|
"
|
|
3071
2922
|
`;
|
|
@@ -3161,12 +3012,6 @@ variable "enable_rds_proxy" {
|
|
|
3161
3012
|
default = true
|
|
3162
3013
|
}
|
|
3163
3014
|
|
|
3164
|
-
variable "enable_credential_rotation" {
|
|
3165
|
-
description = "Whether to enable automatic credential rotation for the admin secret."
|
|
3166
|
-
type = bool
|
|
3167
|
-
default = true
|
|
3168
|
-
}
|
|
3169
|
-
|
|
3170
3015
|
variable "enable_cloudwatch_logs" {
|
|
3171
3016
|
description = "Whether to export Aurora engine logs to CloudWatch. PostgreSQL logs DDL statements only (log_statement=ddl); MySQL enables Advanced Auditing scoped to connections and DDL (server_audit_events=CONNECT,QUERY_DDL). Neither logs statement parameter values, to avoid leaking PII into log data."
|
|
3172
3017
|
type = bool
|
|
@@ -3239,7 +3084,6 @@ module "aurora" {
|
|
|
3239
3084
|
name = "db"
|
|
3240
3085
|
vpc_id = var.vpc_id
|
|
3241
3086
|
subnet_ids = var.database_subnet_ids
|
|
3242
|
-
lambda_subnet_ids = var.lambda_subnet_ids
|
|
3243
3087
|
engine = "aurora-mysql"
|
|
3244
3088
|
engine_version = var.engine_version
|
|
3245
3089
|
database_name = "database_name"
|
|
@@ -3251,7 +3095,6 @@ module "aurora" {
|
|
|
3251
3095
|
deletion_protection = var.deletion_protection
|
|
3252
3096
|
skip_final_snapshot = var.skip_final_snapshot
|
|
3253
3097
|
enable_rds_proxy = var.enable_rds_proxy
|
|
3254
|
-
enable_credential_rotation = var.enable_credential_rotation
|
|
3255
3098
|
enable_cloudwatch_logs = var.enable_cloudwatch_logs
|
|
3256
3099
|
enable_performance_insights = var.enable_performance_insights
|
|
3257
3100
|
performance_insights_retention_period = var.performance_insights_retention_period
|
|
@@ -3270,17 +3113,10 @@ resource "aws_iam_role_policy" "proxy_db_user_connect" {
|
|
|
3270
3113
|
Statement = [
|
|
3271
3114
|
{
|
|
3272
3115
|
Effect = "Allow"
|
|
3273
|
-
Action = [
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
Resource = [module.aurora.secret_arn]
|
|
3277
|
-
},
|
|
3278
|
-
{
|
|
3279
|
-
Effect = "Allow"
|
|
3280
|
-
Action = [
|
|
3281
|
-
"kms:Decrypt"
|
|
3116
|
+
Action = ["rds-db:connect"]
|
|
3117
|
+
Resource = [
|
|
3118
|
+
"arn:aws:rds-db:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:dbuser:\${module.aurora.cluster_resource_id}/\${local.database_runtime_user}"
|
|
3282
3119
|
]
|
|
3283
|
-
Resource = [module.aurora.kms_key_arn]
|
|
3284
3120
|
}
|
|
3285
3121
|
]
|
|
3286
3122
|
})
|
|
@@ -3627,6 +3463,9 @@ resource "aws_lambda_function" "create_db_user" {
|
|
|
3627
3463
|
environment {
|
|
3628
3464
|
variables = {
|
|
3629
3465
|
DATABASE_SECRET_ARN = module.aurora.secret_arn
|
|
3466
|
+
DATABASE_HOST = module.aurora.writer_endpoint
|
|
3467
|
+
DATABASE_PORT = module.aurora.cluster_port
|
|
3468
|
+
DATABASE_NAME = module.aurora.database_name
|
|
3630
3469
|
NODE_EXTRA_CA_CERTS = "/var/runtime/ca-cert.pem"
|
|
3631
3470
|
}
|
|
3632
3471
|
}
|
|
@@ -3704,6 +3543,9 @@ resource "aws_lambda_function" "migration_handler" {
|
|
|
3704
3543
|
environment {
|
|
3705
3544
|
variables = {
|
|
3706
3545
|
DATABASE_SECRET_ARN = module.aurora.secret_arn
|
|
3546
|
+
DATABASE_HOST = module.aurora.writer_endpoint
|
|
3547
|
+
DATABASE_PORT = module.aurora.cluster_port
|
|
3548
|
+
DATABASE_NAME = module.aurora.database_name
|
|
3707
3549
|
}
|
|
3708
3550
|
}
|
|
3709
3551
|
|
|
@@ -4070,6 +3912,24 @@ export type DatabaseSecret = {
|
|
|
4070
3912
|
port: number;
|
|
4071
3913
|
};
|
|
4072
3914
|
|
|
3915
|
+
/**
|
|
3916
|
+
* Connection details for the admin credentials secret. RDS-managed secrets hold
|
|
3917
|
+
* only \`username\` and \`password\`, so the host, port and database name are taken
|
|
3918
|
+
* from the environment when the secret omits them.
|
|
3919
|
+
*/
|
|
3920
|
+
type SecretConnectionOverrides = Partial<
|
|
3921
|
+
Omit<DatabaseSecret, 'username' | 'password'>
|
|
3922
|
+
>;
|
|
3923
|
+
|
|
3924
|
+
const connectionOverridesFromEnv = (): SecretConnectionOverrides => {
|
|
3925
|
+
const { DATABASE_HOST, DATABASE_PORT, DATABASE_NAME } = process.env;
|
|
3926
|
+
return {
|
|
3927
|
+
...(DATABASE_HOST ? { host: DATABASE_HOST } : {}),
|
|
3928
|
+
...(DATABASE_PORT ? { port: Number(DATABASE_PORT) } : {}),
|
|
3929
|
+
...(DATABASE_NAME ? { dbname: DATABASE_NAME } : {}),
|
|
3930
|
+
};
|
|
3931
|
+
};
|
|
3932
|
+
|
|
4073
3933
|
const databaseConfigPromises: Record<string, Promise<DatabaseConfig>> = {};
|
|
4074
3934
|
|
|
4075
3935
|
const getSecretValue = async (secretArn: string): Promise<string> => {
|
|
@@ -4096,7 +3956,18 @@ export const getDatabaseSecret = async (): Promise<DatabaseSecret> => {
|
|
|
4096
3956
|
);
|
|
4097
3957
|
}
|
|
4098
3958
|
|
|
4099
|
-
|
|
3959
|
+
const secret = JSON.parse(await getSecretValue(secretArn)) as DatabaseSecret;
|
|
3960
|
+
const secretWithConnection = { ...secret, ...connectionOverridesFromEnv() };
|
|
3961
|
+
|
|
3962
|
+
for (const key of ['host', 'port', 'dbname'] as const) {
|
|
3963
|
+
if (secretWithConnection[key] == null) {
|
|
3964
|
+
throw new Error(
|
|
3965
|
+
\`Database secret is missing \${key} and no override was provided in the environment.\`,
|
|
3966
|
+
);
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
return secretWithConnection;
|
|
4100
3971
|
};
|
|
4101
3972
|
|
|
4102
3973
|
const loadDatabaseConfig = async (
|
|
@@ -4692,7 +4563,7 @@ export class Db extends AuroraDatabase {
|
|
|
4692
4563
|
...baseProps,
|
|
4693
4564
|
code: Code.fromAsset(createDbUserBundleDir),
|
|
4694
4565
|
handler: 'index.handler',
|
|
4695
|
-
runtime: Runtime.
|
|
4566
|
+
runtime: Runtime.NODEJS_24_X,
|
|
4696
4567
|
environment: {
|
|
4697
4568
|
NODE_EXTRA_CA_CERTS: '/var/runtime/ca-cert.pem',
|
|
4698
4569
|
},
|