@aws/nx-plugin 0.110.0 → 0.112.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/README.md +1 -0
- package/generators.json +6 -0
- package/package.json +1 -1
- package/src/connection/agent-serve-local.js +7 -8
- package/src/connection/agent-serve-local.js.map +1 -1
- package/src/connection/serve-local.js +7 -8
- package/src/connection/serve-local.js.map +1 -1
- package/src/infra/app/generator.js +2 -5
- package/src/infra/app/generator.js.map +1 -1
- package/src/preset/__snapshots__/generator.spec.ts.snap +2 -0
- package/src/py/fast-api/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/py/fast-api/files/app/__name__/init.py.template +2 -2
- package/src/py/project/generator.js +17 -1
- package/src/py/project/generator.js.map +1 -1
- package/src/py/strands-agent/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/py/strands-agent/a2a-connection/generator.js +4 -8
- package/src/py/strands-agent/a2a-connection/generator.js.map +1 -1
- package/src/py/strands-agent/files/http/init.py.template +2 -2
- package/src/py/strands-agent/mcp-connection/__snapshots__/generator.spec.ts.snap +8 -4
- package/src/py/strands-agent/mcp-connection/generator.js +4 -8
- package/src/py/strands-agent/mcp-connection/generator.js.map +1 -1
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +4822 -0
- package/src/ts/rdb/files/Dockerfile.template +15 -0
- package/src/ts/rdb/files/prisma/models/example.prisma.template +5 -0
- package/src/ts/rdb/files/prisma/schema.prisma.template +9 -0
- package/src/ts/rdb/files/prisma.config.ts.template +24 -0
- package/src/ts/rdb/files/scripts/docker-pull.ts.template +15 -0
- package/src/ts/rdb/files/scripts/docker-start.ts.template +81 -0
- package/src/ts/rdb/files/scripts/wait-for-db.ts.template +55 -0
- package/src/ts/rdb/files/src/constants.ts.template +8 -0
- package/src/ts/rdb/files/src/create-db-user-handler.ts.template +128 -0
- package/src/ts/rdb/files/src/index.ts.template +2 -0
- package/src/ts/rdb/files/src/migration-handler.ts.template +54 -0
- package/src/ts/rdb/files/src/prisma.ts.template +104 -0
- package/src/ts/rdb/files/src/utils.ts.template +145 -0
- package/src/ts/rdb/generator.d.ts +10 -0
- package/src/ts/rdb/generator.js +206 -0
- package/src/ts/rdb/generator.js.map +1 -0
- package/src/ts/rdb/schema.d.ts +17 -0
- package/src/ts/rdb/schema.json +73 -0
- package/src/ts/strands-agent/a2a-connection/generator.js +4 -8
- package/src/ts/strands-agent/a2a-connection/generator.js.map +1 -1
- package/src/ts/strands-agent/mcp-connection/generator.js +4 -8
- package/src/ts/strands-agent/mcp-connection/generator.js.map +1 -1
- package/src/utils/agent-connection/files/py-core-mcp/agentcore_mcp_client.py.template +9 -3
- package/src/utils/agent-connection/files/py-core-runtime-config/runtime_config.py.template +3 -2
- package/src/utils/api-constructs/api-constructs.js +2 -10
- package/src/utils/api-constructs/api-constructs.js.map +1 -1
- package/src/utils/api-constructs/open-api-metadata.js +2 -7
- package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
- package/src/utils/function-constructs/function-constructs.js +2 -10
- package/src/utils/function-constructs/function-constructs.js.map +1 -1
- package/src/utils/nx.d.ts +14 -1
- package/src/utils/nx.js +27 -1
- package/src/utils/nx.js.map +1 -1
- package/src/utils/rdb-constructs/files/cdk/app/dbs/__nameKebabCase__.ts.template +43 -0
- package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +380 -0
- package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +732 -0
- package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +742 -0
- package/src/utils/rdb-constructs/rdb-constructs.d.ts +23 -0
- package/src/utils/rdb-constructs/rdb-constructs.js +59 -0
- package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -0
- package/src/utils/versions.d.ts +15 -3
- package/src/utils/versions.js +14 -2
- package/src/utils/versions.js.map +1 -1
- package/src/utils/website-constructs/website-constructs.js +2 -10
- package/src/utils/website-constructs/website-constructs.js.map +1 -1
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
# Core Aurora module
|
|
2
|
+
# This module creates an Aurora cluster and a generated admin secret.
|
|
3
|
+
|
|
4
|
+
terraform {
|
|
5
|
+
required_version = ">= 1.0"
|
|
6
|
+
|
|
7
|
+
required_providers {
|
|
8
|
+
aws = {
|
|
9
|
+
source = "hashicorp/aws"
|
|
10
|
+
version = "~> 6.33"
|
|
11
|
+
}
|
|
12
|
+
null = {
|
|
13
|
+
source = "hashicorp/null"
|
|
14
|
+
version = ">= 3.0"
|
|
15
|
+
}
|
|
16
|
+
random = {
|
|
17
|
+
source = "hashicorp/random"
|
|
18
|
+
version = ">= 3.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
data "aws_caller_identity" "current" {}
|
|
24
|
+
data "aws_partition" "current" {}
|
|
25
|
+
data "aws_region" "current" {}
|
|
26
|
+
|
|
27
|
+
data "aws_iam_policy" "enhanced_monitoring" {
|
|
28
|
+
arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
variable "name" {
|
|
32
|
+
description = "Base name applied to Aurora resources."
|
|
33
|
+
type = string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
variable "vpc_id" {
|
|
37
|
+
description = "VPC where the Aurora cluster will be deployed."
|
|
38
|
+
type = string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
variable "subnet_ids" {
|
|
42
|
+
description = "Subnet IDs for the Aurora DB subnet group and RDS Proxy. These subnets do not need outbound egress — private isolated subnets are fine."
|
|
43
|
+
type = list(string)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
variable "lambda_subnet_ids" {
|
|
47
|
+
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."
|
|
48
|
+
type = list(string)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
variable "engine" {
|
|
52
|
+
description = "Aurora engine to use."
|
|
53
|
+
type = string
|
|
54
|
+
default = "aurora-postgresql"
|
|
55
|
+
|
|
56
|
+
validation {
|
|
57
|
+
condition = contains(["aurora-postgresql", "aurora-mysql"], var.engine)
|
|
58
|
+
error_message = "engine must be aurora-postgresql or aurora-mysql."
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
variable "engine_version" {
|
|
63
|
+
description = "Aurora engine version."
|
|
64
|
+
type = string
|
|
65
|
+
default = null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
variable "database_name" {
|
|
69
|
+
description = "Initial database created in the cluster."
|
|
70
|
+
type = string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
variable "admin_user" {
|
|
74
|
+
description = "Admin username stored in the generated Secrets Manager secret."
|
|
75
|
+
type = string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
variable "port" {
|
|
79
|
+
description = "Database port for the selected Aurora engine."
|
|
80
|
+
type = number
|
|
81
|
+
default = null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
variable "serverless_min_capacity" {
|
|
85
|
+
description = "Minimum Aurora Serverless v2 ACUs."
|
|
86
|
+
type = number
|
|
87
|
+
default = 0.5
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
variable "serverless_max_capacity" {
|
|
91
|
+
description = "Maximum Aurora Serverless v2 ACUs."
|
|
92
|
+
type = number
|
|
93
|
+
default = 4
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
variable "instance_count" {
|
|
97
|
+
description = "Number of Aurora instances to create."
|
|
98
|
+
type = number
|
|
99
|
+
default = 1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
variable "deletion_protection" {
|
|
103
|
+
description = "Whether deletion protection is enabled."
|
|
104
|
+
type = bool
|
|
105
|
+
default = true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
variable "skip_final_snapshot" {
|
|
109
|
+
description = "Whether to skip the final snapshot on deletion."
|
|
110
|
+
type = bool
|
|
111
|
+
default = false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
variable "enable_rds_proxy" {
|
|
115
|
+
description = "Whether to provision an RDS Proxy in front of the Aurora cluster."
|
|
116
|
+
type = bool
|
|
117
|
+
default = true
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
variable "enable_credential_rotation" {
|
|
121
|
+
description = "Whether to enable automatic credential rotation for the admin secret."
|
|
122
|
+
type = bool
|
|
123
|
+
default = true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
variable "enable_cloudwatch_logs" {
|
|
127
|
+
description = "Whether to export Aurora engine logs to CloudWatch. PostgreSQL also enables verbose statement logging."
|
|
128
|
+
type = bool
|
|
129
|
+
default = false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
variable "enable_performance_insights" {
|
|
133
|
+
description = "Whether to enable Performance Insights on Aurora cluster instances."
|
|
134
|
+
type = bool
|
|
135
|
+
default = true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
variable "performance_insights_retention_period" {
|
|
139
|
+
description = "Retention period, in days, for Performance Insights data when enabled."
|
|
140
|
+
type = number
|
|
141
|
+
default = 7
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
variable "enable_backup" {
|
|
145
|
+
description = "Whether to provision an AWS Backup plan for the Aurora cluster."
|
|
146
|
+
type = bool
|
|
147
|
+
default = false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
variable "enable_key_rotation" {
|
|
151
|
+
description = "Whether to enable automatic key rotation on the KMS key used to encrypt the Aurora cluster and its credentials secret."
|
|
152
|
+
type = bool
|
|
153
|
+
default = true
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
variable "tags" {
|
|
157
|
+
description = "Tags to apply to all resources."
|
|
158
|
+
type = map(string)
|
|
159
|
+
default = {}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
locals {
|
|
163
|
+
default_port = var.engine == "aurora-mysql" ? 3306 : 5432
|
|
164
|
+
default_engine_version = var.engine == "aurora-mysql" ? "8.0.mysql_aurora.3.12.0" : "17.7"
|
|
165
|
+
parameter_group_family = var.engine == "aurora-mysql" ? "aurora-mysql8.0" : "aurora-postgresql${split(".", coalesce(var.engine_version, local.default_engine_version))[0]}"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
resource "aws_rds_cluster_parameter_group" "database" {
|
|
169
|
+
count = var.enable_cloudwatch_logs ? 1 : 0
|
|
170
|
+
|
|
171
|
+
name_prefix = "${var.name}-"
|
|
172
|
+
family = local.parameter_group_family
|
|
173
|
+
description = "Parameter group for ${var.name} Aurora cluster"
|
|
174
|
+
|
|
175
|
+
dynamic "parameter" {
|
|
176
|
+
for_each = var.engine == "aurora-postgresql" ? [1] : []
|
|
177
|
+
content {
|
|
178
|
+
name = "log_statement"
|
|
179
|
+
value = "all"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
tags = var.tags
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
resource "aws_kms_key" "database" {
|
|
187
|
+
description = "KMS key for Aurora cluster ${var.name}"
|
|
188
|
+
enable_key_rotation = var.enable_key_rotation
|
|
189
|
+
|
|
190
|
+
policy = jsonencode({
|
|
191
|
+
Version = "2012-10-17"
|
|
192
|
+
Statement = [
|
|
193
|
+
{
|
|
194
|
+
Sid = "EnableRootAccess"
|
|
195
|
+
Effect = "Allow"
|
|
196
|
+
Principal = {
|
|
197
|
+
AWS = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"
|
|
198
|
+
}
|
|
199
|
+
Action = "kms:*"
|
|
200
|
+
Resource = "*"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
Sid = "AllowRDSService"
|
|
204
|
+
Effect = "Allow"
|
|
205
|
+
Principal = {
|
|
206
|
+
Service = "rds.amazonaws.com"
|
|
207
|
+
}
|
|
208
|
+
Action = [
|
|
209
|
+
"kms:Encrypt",
|
|
210
|
+
"kms:Decrypt",
|
|
211
|
+
"kms:ReEncrypt*",
|
|
212
|
+
"kms:GenerateDataKey*",
|
|
213
|
+
"kms:DescribeKey",
|
|
214
|
+
"kms:CreateGrant"
|
|
215
|
+
]
|
|
216
|
+
Resource = "*"
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
tags = merge(var.tags, {
|
|
222
|
+
Name = "${var.name}-aurora"
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
resource "aws_iam_role" "enhanced_monitoring" {
|
|
227
|
+
name_prefix = "${var.name}-aurora-monitoring-"
|
|
228
|
+
|
|
229
|
+
assume_role_policy = jsonencode({
|
|
230
|
+
Version = "2012-10-17"
|
|
231
|
+
Statement = [
|
|
232
|
+
{
|
|
233
|
+
Effect = "Allow"
|
|
234
|
+
Principal = {
|
|
235
|
+
Service = "monitoring.rds.amazonaws.com"
|
|
236
|
+
}
|
|
237
|
+
Action = "sts:AssumeRole"
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
tags = merge(var.tags, {
|
|
243
|
+
Name = "${var.name}-aurora-monitoring"
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
resource "aws_iam_role_policy_attachment" "enhanced_monitoring" {
|
|
248
|
+
role = aws_iam_role.enhanced_monitoring.name
|
|
249
|
+
policy_arn = data.aws_iam_policy.enhanced_monitoring.arn
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
resource "aws_security_group" "database" {
|
|
253
|
+
name_prefix = "${var.name}-aurora-"
|
|
254
|
+
description = "Security group for Aurora cluster ${var.name}"
|
|
255
|
+
vpc_id = var.vpc_id
|
|
256
|
+
|
|
257
|
+
tags = merge(var.tags, {
|
|
258
|
+
Name = "${var.name}-aurora"
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
resource "aws_db_subnet_group" "database" {
|
|
263
|
+
name = "${var.name}-aurora"
|
|
264
|
+
subnet_ids = var.subnet_ids
|
|
265
|
+
|
|
266
|
+
tags = merge(var.tags, {
|
|
267
|
+
Name = "${var.name}-aurora"
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
resource "random_password" "master_password" {
|
|
272
|
+
length = 32
|
|
273
|
+
special = true
|
|
274
|
+
override_special = "!#$%&*()-_=+[]{}<>:?"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
resource "aws_secretsmanager_secret" "credentials" {
|
|
278
|
+
#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
|
|
279
|
+
name_prefix = "${var.name}-aurora-credentials-"
|
|
280
|
+
kms_key_id = aws_kms_key.database.arn
|
|
281
|
+
|
|
282
|
+
tags = merge(var.tags, {
|
|
283
|
+
Name = "${var.name}-aurora-credentials"
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
resource "aws_secretsmanager_secret_version" "credentials" {
|
|
288
|
+
secret_id = aws_secretsmanager_secret.credentials.id
|
|
289
|
+
secret_string = jsonencode({
|
|
290
|
+
engine = var.engine == "aurora-mysql" ? "mysql" : "postgres"
|
|
291
|
+
username = var.admin_user
|
|
292
|
+
password = random_password.master_password.result
|
|
293
|
+
host = aws_rds_cluster.database.endpoint
|
|
294
|
+
port = aws_rds_cluster.database.port
|
|
295
|
+
dbname = var.database_name
|
|
296
|
+
dbClusterIdentifier = aws_rds_cluster.database.cluster_identifier
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
resource "aws_rds_cluster" "database" {
|
|
301
|
+
#checkov:skip=CKV2_AWS_27:Query logging can be enabled with enable_cloudwatch_logs; this module defaults to CDK-equivalent behavior unless logging is requested
|
|
302
|
+
#checkov:skip=CKV_AWS_139:Deletion protection is enabled but checkov is unable to detect it correctly
|
|
303
|
+
cluster_identifier = "${var.name}-aurora"
|
|
304
|
+
engine = var.engine
|
|
305
|
+
engine_version = coalesce(var.engine_version, local.default_engine_version)
|
|
306
|
+
database_name = var.database_name
|
|
307
|
+
master_username = var.admin_user
|
|
308
|
+
master_password = random_password.master_password.result
|
|
309
|
+
db_subnet_group_name = aws_db_subnet_group.database.name
|
|
310
|
+
db_cluster_parameter_group_name = var.enable_cloudwatch_logs ? aws_rds_cluster_parameter_group.database[0].name : null
|
|
311
|
+
vpc_security_group_ids = [aws_security_group.database.id]
|
|
312
|
+
port = coalesce(var.port, local.default_port)
|
|
313
|
+
storage_encrypted = true
|
|
314
|
+
kms_key_id = aws_kms_key.database.arn
|
|
315
|
+
deletion_protection = var.deletion_protection
|
|
316
|
+
skip_final_snapshot = var.skip_final_snapshot
|
|
317
|
+
final_snapshot_identifier = var.skip_final_snapshot ? null : "${var.name}-aurora-final-snapshot"
|
|
318
|
+
iam_database_authentication_enabled = true
|
|
319
|
+
monitoring_interval = 5
|
|
320
|
+
monitoring_role_arn = aws_iam_role.enhanced_monitoring.arn
|
|
321
|
+
copy_tags_to_snapshot = true
|
|
322
|
+
enabled_cloudwatch_logs_exports = var.enable_cloudwatch_logs ? (var.engine == "aurora-mysql" ? ["audit", "error", "general", "slowquery"] : ["postgresql"]) : null
|
|
323
|
+
|
|
324
|
+
serverlessv2_scaling_configuration {
|
|
325
|
+
min_capacity = var.serverless_min_capacity
|
|
326
|
+
max_capacity = var.serverless_max_capacity
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
tags = merge(var.tags, {
|
|
330
|
+
Name = "${var.name}-aurora"
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
resource "aws_rds_cluster_instance" "database" {
|
|
335
|
+
count = var.instance_count
|
|
336
|
+
|
|
337
|
+
identifier = "${var.name}-aurora-${count.index + 1}"
|
|
338
|
+
cluster_identifier = aws_rds_cluster.database.id
|
|
339
|
+
instance_class = "db.serverless"
|
|
340
|
+
engine = aws_rds_cluster.database.engine
|
|
341
|
+
engine_version = aws_rds_cluster.database.engine_version
|
|
342
|
+
db_subnet_group_name = aws_db_subnet_group.database.name
|
|
343
|
+
|
|
344
|
+
auto_minor_version_upgrade = true
|
|
345
|
+
monitoring_interval = 5
|
|
346
|
+
monitoring_role_arn = aws_iam_role.enhanced_monitoring.arn
|
|
347
|
+
performance_insights_enabled = var.enable_performance_insights
|
|
348
|
+
performance_insights_kms_key_id = var.enable_performance_insights ? aws_kms_key.database.arn : null
|
|
349
|
+
performance_insights_retention_period = var.enable_performance_insights ? var.performance_insights_retention_period : null
|
|
350
|
+
|
|
351
|
+
tags = merge(var.tags, {
|
|
352
|
+
Name = "${var.name}-aurora-${count.index + 1}"
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
resource "aws_security_group" "rotation" {
|
|
357
|
+
#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
|
|
358
|
+
count = var.enable_credential_rotation ? 1 : 0
|
|
359
|
+
name_prefix = "${var.name}-aurora-rotation-"
|
|
360
|
+
description = "Security group for Aurora secret rotation ${var.name}"
|
|
361
|
+
vpc_id = var.vpc_id
|
|
362
|
+
|
|
363
|
+
tags = merge(var.tags, {
|
|
364
|
+
Name = "${var.name}-aurora-rotation"
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
resource "aws_vpc_security_group_egress_rule" "rotation_to_database" {
|
|
369
|
+
count = var.enable_credential_rotation ? 1 : 0
|
|
370
|
+
security_group_id = aws_security_group.rotation[0].id
|
|
371
|
+
referenced_security_group_id = aws_security_group.database.id
|
|
372
|
+
from_port = coalesce(var.port, local.default_port)
|
|
373
|
+
to_port = coalesce(var.port, local.default_port)
|
|
374
|
+
ip_protocol = "tcp"
|
|
375
|
+
description = "Allow outbound database traffic from secret rotation Lambda"
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
resource "aws_vpc_security_group_ingress_rule" "rotation_to_database" {
|
|
379
|
+
count = var.enable_credential_rotation ? 1 : 0
|
|
380
|
+
security_group_id = aws_security_group.database.id
|
|
381
|
+
referenced_security_group_id = aws_security_group.rotation[0].id
|
|
382
|
+
from_port = coalesce(var.port, local.default_port)
|
|
383
|
+
to_port = coalesce(var.port, local.default_port)
|
|
384
|
+
ip_protocol = "tcp"
|
|
385
|
+
description = "Allow inbound database traffic from secret rotation Lambda"
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
resource "aws_cloudformation_stack" "credentials_rotation" {
|
|
389
|
+
#checkov:skip=CKV_AWS_124:SNS event notifications are not required for the credentials rotation stack
|
|
390
|
+
count = var.enable_credential_rotation ? 1 : 0
|
|
391
|
+
name = "${var.name}-aurora-credentials-rotation"
|
|
392
|
+
|
|
393
|
+
capabilities = ["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"]
|
|
394
|
+
|
|
395
|
+
template_body = jsonencode({
|
|
396
|
+
AWSTemplateFormatVersion = "2010-09-09"
|
|
397
|
+
Transform = "AWS::SecretsManager-2024-09-16"
|
|
398
|
+
Resources = {
|
|
399
|
+
CredentialsRotationSchedule = {
|
|
400
|
+
Type = "AWS::SecretsManager::RotationSchedule"
|
|
401
|
+
Properties = {
|
|
402
|
+
SecretId = aws_secretsmanager_secret.credentials.arn
|
|
403
|
+
HostedRotationLambda = {
|
|
404
|
+
RotationType = var.engine == "aurora-mysql" ? "MySQLSingleUser" : "PostgreSQLSingleUser"
|
|
405
|
+
RotationLambdaName = "${var.name}-aurora-credentials-rotation"
|
|
406
|
+
VpcSecurityGroupIds = aws_security_group.rotation[0].id
|
|
407
|
+
VpcSubnetIds = join(",", var.lambda_subnet_ids)
|
|
408
|
+
}
|
|
409
|
+
RotationRules = {
|
|
410
|
+
AutomaticallyAfterDays = 30
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
depends_on = [
|
|
418
|
+
aws_secretsmanager_secret_version.credentials,
|
|
419
|
+
aws_rds_cluster_instance.database,
|
|
420
|
+
aws_vpc_security_group_egress_rule.rotation_to_database,
|
|
421
|
+
aws_vpc_security_group_ingress_rule.rotation_to_database,
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
resource "aws_iam_role" "proxy" {
|
|
426
|
+
count = var.enable_rds_proxy ? 1 : 0
|
|
427
|
+
|
|
428
|
+
name_prefix = "${var.name}-aurora-proxy-"
|
|
429
|
+
|
|
430
|
+
assume_role_policy = jsonencode({
|
|
431
|
+
Version = "2012-10-17"
|
|
432
|
+
Statement = [
|
|
433
|
+
{
|
|
434
|
+
Effect = "Allow"
|
|
435
|
+
Principal = {
|
|
436
|
+
Service = "rds.amazonaws.com"
|
|
437
|
+
}
|
|
438
|
+
Action = "sts:AssumeRole"
|
|
439
|
+
}
|
|
440
|
+
]
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
tags = merge(var.tags, {
|
|
444
|
+
Name = "${var.name}-aurora-proxy"
|
|
445
|
+
})
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
resource "aws_iam_role_policy" "proxy_secret_access" {
|
|
449
|
+
count = var.enable_rds_proxy ? 1 : 0
|
|
450
|
+
role = aws_iam_role.proxy[0].name
|
|
451
|
+
|
|
452
|
+
policy = jsonencode({
|
|
453
|
+
Version = "2012-10-17"
|
|
454
|
+
Statement = [
|
|
455
|
+
{
|
|
456
|
+
Effect = "Allow"
|
|
457
|
+
Action = ["secretsmanager:GetSecretValue"]
|
|
458
|
+
Resource = [aws_secretsmanager_secret.credentials.arn]
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
Effect = "Allow"
|
|
462
|
+
Action = ["kms:Decrypt"]
|
|
463
|
+
Resource = [aws_kms_key.database.arn]
|
|
464
|
+
}
|
|
465
|
+
]
|
|
466
|
+
})
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
resource "aws_security_group" "proxy" {
|
|
470
|
+
#checkov:skip=CKV2_AWS_5:Security group is attached to aws_db_proxy.aurora; Checkov cannot resolve count-conditional cross-resource references
|
|
471
|
+
count = var.enable_rds_proxy ? 1 : 0
|
|
472
|
+
|
|
473
|
+
name_prefix = "${var.name}-aurora-proxy-"
|
|
474
|
+
description = "Security group for Aurora proxy ${var.name}"
|
|
475
|
+
vpc_id = var.vpc_id
|
|
476
|
+
|
|
477
|
+
tags = merge(var.tags, {
|
|
478
|
+
Name = "${var.name}-aurora-proxy"
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
resource "aws_db_proxy" "aurora" {
|
|
483
|
+
count = var.enable_rds_proxy ? 1 : 0
|
|
484
|
+
|
|
485
|
+
name = "${var.name}-proxy"
|
|
486
|
+
default_auth_scheme = "IAM_AUTH"
|
|
487
|
+
engine_family = var.engine == "aurora-mysql" ? "MYSQL" : "POSTGRESQL"
|
|
488
|
+
role_arn = aws_iam_role.proxy[0].arn
|
|
489
|
+
vpc_subnet_ids = var.subnet_ids
|
|
490
|
+
vpc_security_group_ids = [aws_security_group.proxy[0].id]
|
|
491
|
+
require_tls = true
|
|
492
|
+
|
|
493
|
+
auth {
|
|
494
|
+
auth_scheme = "SECRETS"
|
|
495
|
+
iam_auth = "REQUIRED"
|
|
496
|
+
secret_arn = aws_secretsmanager_secret.credentials.arn
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
depends_on = [
|
|
500
|
+
aws_iam_role_policy.proxy_secret_access,
|
|
501
|
+
aws_secretsmanager_secret_version.credentials,
|
|
502
|
+
]
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
resource "aws_db_proxy_default_target_group" "default" {
|
|
506
|
+
count = var.enable_rds_proxy ? 1 : 0
|
|
507
|
+
|
|
508
|
+
db_proxy_name = aws_db_proxy.aurora[0].name
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
resource "aws_db_proxy_target" "default" {
|
|
512
|
+
count = var.enable_rds_proxy ? 1 : 0
|
|
513
|
+
|
|
514
|
+
db_proxy_name = aws_db_proxy.aurora[0].name
|
|
515
|
+
target_group_name = aws_db_proxy_default_target_group.default[0].name
|
|
516
|
+
db_cluster_identifier = aws_rds_cluster.database.cluster_identifier
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
resource "null_resource" "proxy_target_ready" {
|
|
520
|
+
count = var.enable_rds_proxy ? 1 : 0
|
|
521
|
+
|
|
522
|
+
triggers = {
|
|
523
|
+
db_proxy_name = aws_db_proxy.aurora[0].name
|
|
524
|
+
target_group_name = aws_db_proxy_default_target_group.default[0].name
|
|
525
|
+
db_cluster_identifier = aws_rds_cluster.database.cluster_identifier
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
provisioner "local-exec" {
|
|
529
|
+
command = <<-EOT
|
|
530
|
+
start_time=$(date +%s)
|
|
531
|
+
deadline=$((start_time + 600))
|
|
532
|
+
while true; do
|
|
533
|
+
target_state=$(aws rds describe-db-proxy-targets \
|
|
534
|
+
--region ${data.aws_region.current.region} \
|
|
535
|
+
--db-proxy-name ${self.triggers.db_proxy_name} \
|
|
536
|
+
--target-group-name ${self.triggers.target_group_name} \
|
|
537
|
+
--query "Targets[?Type=='RDS_INSTANCE'] | [0].TargetHealth.State" \
|
|
538
|
+
--output text)
|
|
539
|
+
|
|
540
|
+
if [ "$target_state" = "AVAILABLE" ]; then
|
|
541
|
+
exit 0
|
|
542
|
+
fi
|
|
543
|
+
|
|
544
|
+
current_time=$(date +%s)
|
|
545
|
+
if [ "$current_time" -ge "$deadline" ]; then
|
|
546
|
+
echo "RDS Proxy target ${self.triggers.db_cluster_identifier} did not become AVAILABLE within 10 minutes. Last state: $target_state"
|
|
547
|
+
exit 1
|
|
548
|
+
fi
|
|
549
|
+
|
|
550
|
+
sleep 10
|
|
551
|
+
done
|
|
552
|
+
EOT
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
depends_on = [
|
|
556
|
+
aws_db_proxy_target.default,
|
|
557
|
+
aws_rds_cluster_instance.database,
|
|
558
|
+
aws_vpc_security_group_ingress_rule.proxy_to_database,
|
|
559
|
+
aws_vpc_security_group_egress_rule.proxy_to_database
|
|
560
|
+
]
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
resource "null_resource" "cluster_ready" {
|
|
564
|
+
count = var.enable_rds_proxy ? 0 : 1
|
|
565
|
+
|
|
566
|
+
triggers = {
|
|
567
|
+
cluster_identifier = aws_rds_cluster.database.cluster_identifier
|
|
568
|
+
instance_ids = join(",", aws_rds_cluster_instance.database[*].id)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
depends_on = [aws_rds_cluster_instance.database]
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
resource "aws_vpc_security_group_ingress_rule" "proxy_to_database" {
|
|
575
|
+
count = var.enable_rds_proxy ? 1 : 0
|
|
576
|
+
|
|
577
|
+
security_group_id = aws_security_group.database.id
|
|
578
|
+
referenced_security_group_id = aws_security_group.proxy[0].id
|
|
579
|
+
from_port = coalesce(var.port, local.default_port)
|
|
580
|
+
to_port = coalesce(var.port, local.default_port)
|
|
581
|
+
ip_protocol = "tcp"
|
|
582
|
+
description = "Allow inbound database traffic from RDS Proxy"
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
resource "aws_vpc_security_group_egress_rule" "proxy_to_database" {
|
|
586
|
+
count = var.enable_rds_proxy ? 1 : 0
|
|
587
|
+
|
|
588
|
+
security_group_id = aws_security_group.proxy[0].id
|
|
589
|
+
referenced_security_group_id = aws_security_group.database.id
|
|
590
|
+
from_port = coalesce(var.port, local.default_port)
|
|
591
|
+
to_port = coalesce(var.port, local.default_port)
|
|
592
|
+
ip_protocol = "tcp"
|
|
593
|
+
description = "Allow outbound database traffic from RDS Proxy to Aurora cluster"
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
data "aws_iam_policy" "backup" {
|
|
597
|
+
count = var.enable_backup ? 1 : 0
|
|
598
|
+
|
|
599
|
+
arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
resource "aws_iam_role" "backup" {
|
|
603
|
+
count = var.enable_backup ? 1 : 0
|
|
604
|
+
|
|
605
|
+
name_prefix = "${var.name}-backup-"
|
|
606
|
+
|
|
607
|
+
assume_role_policy = jsonencode({
|
|
608
|
+
Version = "2012-10-17"
|
|
609
|
+
Statement = [
|
|
610
|
+
{
|
|
611
|
+
Effect = "Allow"
|
|
612
|
+
Principal = { Service = "backup.amazonaws.com" }
|
|
613
|
+
Action = "sts:AssumeRole"
|
|
614
|
+
}
|
|
615
|
+
]
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
tags = var.tags
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
resource "aws_iam_role_policy_attachment" "backup" {
|
|
622
|
+
count = var.enable_backup ? 1 : 0
|
|
623
|
+
|
|
624
|
+
role = aws_iam_role.backup[0].name
|
|
625
|
+
policy_arn = data.aws_iam_policy.backup[0].arn
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
resource "aws_backup_vault" "database" {
|
|
629
|
+
count = var.enable_backup ? 1 : 0
|
|
630
|
+
|
|
631
|
+
name = "${var.name}-aurora-backup"
|
|
632
|
+
kms_key_arn = aws_kms_key.database.arn
|
|
633
|
+
tags = var.tags
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
resource "aws_backup_plan" "database" {
|
|
637
|
+
count = var.enable_backup ? 1 : 0
|
|
638
|
+
|
|
639
|
+
name = "${var.name}-aurora-backup"
|
|
640
|
+
|
|
641
|
+
rule {
|
|
642
|
+
rule_name = "daily-backup"
|
|
643
|
+
target_vault_name = aws_backup_vault.database[0].name
|
|
644
|
+
schedule = "cron(0 5 ? * * *)"
|
|
645
|
+
|
|
646
|
+
lifecycle {
|
|
647
|
+
delete_after = 35
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
tags = var.tags
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
resource "aws_backup_selection" "database" {
|
|
655
|
+
count = var.enable_backup ? 1 : 0
|
|
656
|
+
|
|
657
|
+
name = "${var.name}-aurora"
|
|
658
|
+
plan_id = aws_backup_plan.database[0].id
|
|
659
|
+
iam_role_arn = aws_iam_role.backup[0].arn
|
|
660
|
+
|
|
661
|
+
resources = [aws_rds_cluster.database.arn]
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
output "cluster_arn" {
|
|
665
|
+
description = "ARN of the Aurora cluster."
|
|
666
|
+
value = aws_rds_cluster.database.arn
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
output "cluster_resource_id" {
|
|
670
|
+
description = "Resource ID of the Aurora cluster used for IAM database authentication."
|
|
671
|
+
value = aws_rds_cluster.database.cluster_resource_id
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
output "proxy_resource_id" {
|
|
675
|
+
description = "Resource identifier of the RDS Proxy (prx-XXXX), used for IAM rds-db:connect policies when connecting through the proxy. Null if RDS Proxy is disabled."
|
|
676
|
+
value = var.enable_rds_proxy ? split(":", aws_db_proxy.aurora[0].arn)[6] : null
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
output "cluster_endpoint" {
|
|
680
|
+
description = "Writer endpoint of the Aurora cluster or proxy."
|
|
681
|
+
value = var.enable_rds_proxy ? aws_db_proxy.aurora[0].endpoint : aws_rds_cluster.database.endpoint
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
output "reader_endpoint" {
|
|
685
|
+
description = "Reader endpoint of the Aurora cluster."
|
|
686
|
+
value = aws_rds_cluster.database.reader_endpoint
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
output "cluster_port" {
|
|
690
|
+
description = "Port exposed by the Aurora cluster."
|
|
691
|
+
value = aws_rds_cluster.database.port
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
output "security_group_id" {
|
|
695
|
+
description = "Security group protecting the Aurora proxy or cluster."
|
|
696
|
+
value = var.enable_rds_proxy ? aws_security_group.proxy[0].id : aws_security_group.database.id
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
output "database_security_group_id" {
|
|
700
|
+
description = "Security group protecting the Aurora cluster directly."
|
|
701
|
+
value = aws_security_group.database.id
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
output "proxy_role_name" {
|
|
705
|
+
description = "Name of the IAM role used by the RDS Proxy, or null if proxy is disabled."
|
|
706
|
+
value = var.enable_rds_proxy ? aws_iam_role.proxy[0].name : null
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
output "secret_arn" {
|
|
710
|
+
description = "ARN of the generated admin credentials secret."
|
|
711
|
+
value = aws_secretsmanager_secret.credentials.arn
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
output "kms_key_arn" {
|
|
715
|
+
description = "ARN of the KMS key protecting Aurora and the generated secret."
|
|
716
|
+
value = aws_kms_key.database.arn
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
output "database_host" {
|
|
720
|
+
description = "Hostname of the Aurora cluster writer endpoint or proxy endpoint."
|
|
721
|
+
value = var.enable_rds_proxy ? aws_db_proxy.aurora[0].endpoint : aws_rds_cluster.database.endpoint
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
output "database_ready" {
|
|
725
|
+
description = "Dependency marker that is resolved after the selected database connection path is ready."
|
|
726
|
+
value = true
|
|
727
|
+
|
|
728
|
+
depends_on = [
|
|
729
|
+
null_resource.proxy_target_ready,
|
|
730
|
+
null_resource.cluster_ready
|
|
731
|
+
]
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
output "database_name" {
|
|
735
|
+
description = "Initial database created in the cluster."
|
|
736
|
+
value = var.database_name
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
output "admin_user" {
|
|
740
|
+
description = "Admin username stored in the generated secret."
|
|
741
|
+
value = jsondecode(aws_secretsmanager_secret_version.credentials.secret_string).username
|
|
742
|
+
}
|