@aws/nx-plugin 1.0.0-rc.82 → 1.0.0-rc.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/migrations.json +41 -1
  2. package/package.json +1 -1
  3. package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +51 -2
  4. package/src/agentcore-gateway/generator.d.ts +7 -6
  5. package/src/agentcore-gateway/generator.js +2 -12
  6. package/src/agentcore-gateway/generator.js.map +1 -1
  7. package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +4 -5
  8. package/src/agentcore-harness/files/project/README.md.template +4 -5
  9. package/src/migrations/latest/agentcore-harness-no-tools-by-default/__snapshots__/migration.spec.ts.snap +79 -0
  10. package/src/migrations/latest/agentcore-harness-no-tools-by-default/metadata.json +3 -0
  11. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.d.ts +6 -0
  12. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js +87 -0
  13. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +1 -0
  14. package/src/migrations/latest/dynamodb-terraform-app-module-outputs/metadata.json +3 -0
  15. package/src/migrations/latest/dynamodb-terraform-app-module-outputs/migration.d.ts +6 -0
  16. package/src/migrations/latest/dynamodb-terraform-app-module-outputs/migration.js +69 -0
  17. package/src/migrations/latest/dynamodb-terraform-app-module-outputs/migration.js.map +1 -0
  18. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/__snapshots__/migration.spec.ts.snap +54 -0
  19. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/metadata.json +3 -0
  20. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.d.ts +6 -0
  21. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js +86 -0
  22. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
  23. package/src/migrations/latest/terraform-data-resource-destroy-protection/__snapshots__/migration.spec.ts.snap +75 -0
  24. package/src/migrations/latest/terraform-data-resource-destroy-protection/metadata.json +3 -0
  25. package/src/migrations/latest/terraform-data-resource-destroy-protection/migration.d.ts +6 -0
  26. package/src/migrations/latest/terraform-data-resource-destroy-protection/migration.js +78 -0
  27. package/src/migrations/latest/terraform-data-resource-destroy-protection/migration.js.map +1 -0
  28. package/src/migrations/latest/terraform-declare-used-providers/metadata.json +3 -0
  29. package/src/migrations/latest/terraform-declare-used-providers/migration.d.ts +6 -0
  30. package/src/migrations/latest/terraform-declare-used-providers/migration.js +147 -0
  31. package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +1 -0
  32. package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/metadata.json +3 -0
  33. package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration.d.ts +6 -0
  34. package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration.js +38 -0
  35. package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration.js.map +1 -0
  36. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/__snapshots__/migration.spec.ts.snap +521 -0
  37. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/metadata.json +3 -0
  38. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.d.ts +6 -0
  39. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js +504 -0
  40. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
  41. package/src/migrations/latest/waf-log-group-cmk-encryption/__snapshots__/migration.spec.ts.snap +286 -0
  42. package/src/migrations/latest/waf-log-group-cmk-encryption/metadata.json +3 -0
  43. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.d.ts +6 -0
  44. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js +238 -0
  45. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +1 -0
  46. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +12 -0
  47. package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +21 -0
  48. package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +231 -24
  49. package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  50. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  51. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +31 -2
  52. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +216 -25
  53. package/src/terraform/project/files/application/bootstrap/providers.tf.template +9 -0
  54. package/src/terraform/project/files/application/src/providers.tf.template +4 -0
  55. package/src/terraform/project/files/library/src/main.tf.template +9 -0
  56. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +231 -24
  57. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +8 -0
  58. package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +4 -0
  59. package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +21 -0
  60. package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  61. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  62. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +74 -4
  63. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +4 -0
  64. package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +35 -8
  65. package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +4 -0
  66. package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +9 -2
  67. package/src/utils/agent-core-constructs/agent-core-constructs.js +22 -8
  68. package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
  69. package/src/utils/agent-core-constructs/files/cdk/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.ts.template +39 -1
  70. package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +8 -1
  71. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +57 -2
  72. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +258 -24
  73. package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +4 -0
  74. package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +8 -0
  75. package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +9 -1
  76. package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +60 -7
  77. package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  78. package/src/utils/dynamodb-constructs/files/terraform/app/dynamodb/__nameKebabCase__/__nameKebabCase__.tf.template +15 -0
  79. package/src/utils/dynamodb-constructs/files/terraform/core/dynamodb/dynamodb.tf.template +6 -0
  80. package/src/utils/files/terraform/src/metrics/providers.tf.template +12 -0
  81. package/src/utils/function-constructs/files/terraform/app/lambda-functions/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  82. package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +33 -8
  83. package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +4 -0
  84. package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +51 -2
  85. package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +18 -0
  86. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +15 -4
  87. package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +8 -2
  88. package/src/utils/rdb-constructs/rdb-constructs.d.ts +12 -0
  89. package/src/utils/rdb-constructs/rdb-constructs.js +24 -3
  90. package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
  91. package/src/utils/shared-constructs-constants.d.ts +5 -0
  92. package/src/utils/shared-constructs-constants.js +4 -0
  93. package/src/utils/shared-constructs-constants.js.map +1 -1
  94. package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +4 -0
@@ -19,6 +19,84 @@ variable "model_id" {
19
19
  default = "global.anthropic.claude-sonnet-4-6"
20
20
  }
21
21
 
22
+ variable "allowed_tools" {
23
+ description = "Tools the Harness may use. Defaults to none; set [\"@builtin\"] for every built-in tool, or name individual tools. Always sent explicitly, since the service treats an absent value as every tool."
24
+ type = list(string)
25
+ default = []
26
+ }
27
+
28
+ variable "memory" {
29
+ description = "Memory configuration for the Harness. Null leaves the service to provision managed memory with default strategies. Set exactly one field: managed_memory_configuration to tune that managed memory, agentcore_memory_configuration to use a memory resource you own, or disabled to turn memory off."
30
+ type = object({
31
+ managed_memory_configuration = optional(object({
32
+ strategies = optional(set(string))
33
+ event_expiry_duration = optional(number)
34
+ encryption_key_arn = optional(string)
35
+ }))
36
+ agentcore_memory_configuration = optional(object({
37
+ arn = string
38
+ actor_id = optional(string)
39
+ messages_count = optional(number)
40
+ }))
41
+ disabled = optional(bool, false)
42
+ })
43
+ default = null
44
+
45
+ validation {
46
+ condition = var.memory == null || length([
47
+ for configured in [
48
+ var.memory.managed_memory_configuration != null,
49
+ var.memory.agentcore_memory_configuration != null,
50
+ var.memory.disabled,
51
+ ] : configured if configured
52
+ ]) == 1
53
+ error_message = "Set exactly one of memory.managed_memory_configuration, memory.agentcore_memory_configuration or memory.disabled."
54
+ }
55
+ }
56
+
57
+ variable "environment_variables" {
58
+ description = "Environment variables available to the Harness."
59
+ type = map(string)
60
+ default = null
61
+ sensitive = true
62
+ }
63
+
64
+ variable "max_iterations" {
65
+ description = "Maximum agent loop iterations per invocation. Uses the service default when null."
66
+ type = number
67
+ default = null
68
+ }
69
+
70
+ variable "timeout_seconds" {
71
+ description = "Maximum seconds an invocation may run. Uses the service default when null."
72
+ type = number
73
+ default = null
74
+ }
75
+
76
+ variable "create_execution_role" {
77
+ description = "Create the execution role with the baseline permissions below. Set false alongside execution_role_arn to use a role you already have, which is then used as-is."
78
+ type = bool
79
+ default = true
80
+
81
+ validation {
82
+ condition = var.create_execution_role || var.execution_role_arn != null
83
+ error_message = "execution_role_arn must be set when create_execution_role is false."
84
+ }
85
+
86
+ validation {
87
+ condition = var.create_execution_role || (
88
+ var.model_resource_arns == null && length(var.additional_execution_role_policy_statements) == 0
89
+ )
90
+ error_message = "model_resource_arns and additional_execution_role_policy_statements configure the generated execution role, which is not created when create_execution_role is false. Grant those permissions on the supplied role instead."
91
+ }
92
+ }
93
+
94
+ variable "execution_role_arn" {
95
+ description = "ARN of an existing IAM role for the Harness to assume. Requires create_execution_role = false."
96
+ type = string
97
+ default = null
98
+ }
99
+
22
100
  variable "model_resource_arns" {
23
101
  description = "Bedrock model and inference-profile ARNs the execution role may invoke. Defaults to every foundation model plus this account's Bedrock resources in the deployment region; replace with narrower ARNs to restrict baseline model access."
24
102
  type = set(string)
@@ -37,6 +115,36 @@ variable "additional_execution_role_policy_statements" {
37
115
  default = []
38
116
  }
39
117
 
118
+ # VPC Configuration (optional)
119
+ variable "enable_vpc" {
120
+ description = "Run the Harness inside a VPC so it can reach private resources. Set alongside vpc_id/subnet_ids."
121
+ type = bool
122
+ default = false
123
+ }
124
+
125
+ variable "vpc_id" {
126
+ description = "VPC ID to run the Harness in. Required when enable_vpc is true."
127
+ type = string
128
+ default = null
129
+
130
+ validation {
131
+ condition = !var.enable_vpc || (var.vpc_id != null && var.subnet_ids != null)
132
+ error_message = "vpc_id and subnet_ids must be set when enable_vpc is true."
133
+ }
134
+ }
135
+
136
+ variable "subnet_ids" {
137
+ description = "Subnet IDs to run the Harness in. Required when enable_vpc is true."
138
+ type = list(string)
139
+ default = null
140
+ }
141
+
142
+ variable "tags" {
143
+ description = "Tags to apply to resources"
144
+ type = map(string)
145
+ default = {}
146
+ }
147
+
40
148
  data "aws_caller_identity" "current" {}
41
149
  data "aws_partition" "current" {}
42
150
  data "aws_region" "current" {}
@@ -60,9 +168,17 @@ locals {
60
168
  "arn:${local.partition}:bedrock:*::foundation-model/*",
61
169
  "arn:${local.partition}:bedrock:${local.region}:${local.account_id}:*",
62
170
  ]
171
+
172
+ execution_role_arn = var.create_execution_role ? aws_iam_role.execution_role[0].arn : var.execution_role_arn
173
+
174
+ # The service provisions managed memory unless var.memory redirects it
175
+ # elsewhere or turns it off.
176
+ has_managed_memory = var.memory == null || var.memory.managed_memory_configuration != null
63
177
  }
64
178
 
65
179
  resource "aws_iam_role" "execution_role" {
180
+ count = var.create_execution_role ? 1 : 0
181
+
66
182
  name = "${local.harness_name_prefix}-HarnessRole-${random_id.unique_suffix.hex}"
67
183
 
68
184
  assume_role_policy = jsonencode({
@@ -83,14 +199,18 @@ resource "aws_iam_role" "execution_role" {
83
199
  }
84
200
  }]
85
201
  })
202
+
203
+ tags = var.tags
86
204
  }
87
205
 
88
206
  # Baseline permissions per the AgentCore Harness security guidance; extend for
89
207
  # customer-owned resources via var.additional_execution_role_policy_statements.
90
208
  # https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-security.html
91
209
  resource "aws_iam_role_policy" "execution_role" {
210
+ count = var.create_execution_role ? 1 : 0
211
+
92
212
  name = "${local.harness_name_prefix}-HarnessPolicy"
93
- role = aws_iam_role.execution_role.id
213
+ role = aws_iam_role.execution_role[0].id
94
214
 
95
215
  policy = jsonencode({
96
216
  Version = "2012-10-17"
@@ -184,22 +304,6 @@ resource "aws_iam_role_policy" "execution_role" {
184
304
  "arn:${local.partition}:bedrock-agentcore:${local.region}:${local.account_id}:workload-identity-directory/default/workload-identity/harness_${local.harness_name}-*",
185
305
  ]
186
306
  },
187
- # The service names the managed memory from the harness name plus a
188
- # generated suffix, so this scopes to that prefix.
189
- {
190
- Sid = "AgentCoreManagedMemory"
191
- Effect = "Allow"
192
- Action = [
193
- "bedrock-agentcore:CreateEvent",
194
- "bedrock-agentcore:DeleteEvent",
195
- "bedrock-agentcore:GetEvent",
196
- "bedrock-agentcore:ListEvents",
197
- "bedrock-agentcore:RetrieveMemoryRecords",
198
- ]
199
- Resource = [
200
- "arn:${local.partition}:bedrock-agentcore:${local.region}:${local.account_id}:memory/${local.harness_name}-*",
201
- ]
202
- },
203
307
  {
204
308
  Sid = "AgentCoreBrowserDefault"
205
309
  Effect = "Allow"
@@ -230,19 +334,70 @@ resource "aws_iam_role_policy" "execution_role" {
230
334
  "arn:${local.partition}:bedrock-agentcore:${local.region}:aws:code-interpreter/*",
231
335
  ]
232
336
  },
233
- ], [
234
- # Null Sid/Condition fields are dropped from the rendered policy.
235
- for statement in var.additional_execution_role_policy_statements :
236
- { for key, value in statement : key => value if value != null }
337
+ ],
338
+ # In VPC mode the Harness pulls its managed container from a private ECR
339
+ # repository in the region rather than ECR Public, so it needs pull
340
+ # permissions on it. The repository is service-owned, hence the wildcard
341
+ # account.
342
+ var.enable_vpc ? [
343
+ {
344
+ Sid = "EcrManagedImagePull"
345
+ Effect = "Allow"
346
+ Action = [
347
+ "ecr:BatchGetImage",
348
+ "ecr:GetDownloadUrlForLayer",
349
+ "ecr:BatchCheckLayerAvailability",
350
+ ]
351
+ Resource = ["arn:${local.partition}:ecr:${local.region}:*:repository/harness-*"]
352
+ },
353
+ {
354
+ #checkov:skip=CKV_AWS_355:EcrManagedImageToken requires a wildcard resource; ecr:GetAuthorizationToken has no resource-level permission
355
+ Sid = "EcrManagedImageToken"
356
+ Effect = "Allow"
357
+ Action = ["ecr:GetAuthorizationToken"]
358
+ Resource = ["*"]
359
+ },
360
+ ] : [],
361
+ [
362
+ # Null Sid/Condition fields are dropped from the rendered policy.
363
+ for statement in var.additional_execution_role_policy_statements :
364
+ { for key, value in statement : key => value if value != null }
237
365
  ])
238
366
  })
239
367
  }
240
368
 
369
+ # Security group for the Harness, used when running inside a VPC
370
+ resource "aws_security_group" "harness" {
371
+ count = var.enable_vpc ? 1 : 0
372
+
373
+ #checkov:skip=CKV2_AWS_5:Attached to the Harness via its network configuration; Checkov cannot resolve this reference
374
+ name_prefix = "${local.harness_name_prefix}-harness-"
375
+ description = "Security group for the ${local.harness_name_prefix} Harness"
376
+ vpc_id = var.vpc_id
377
+ tags = var.tags
378
+ }
379
+
380
+ resource "aws_vpc_security_group_egress_rule" "harness_https" {
381
+ count = var.enable_vpc ? 1 : 0
382
+
383
+ security_group_id = aws_security_group.harness[0].id
384
+ cidr_ipv4 = "0.0.0.0/0"
385
+ from_port = 443
386
+ to_port = 443
387
+ ip_protocol = "tcp"
388
+ description = "Allow outbound HTTPS to AWS service endpoints"
389
+ }
390
+
241
391
  # Omitting authorizer_configuration uses IAM inbound authorization; add a
242
392
  # custom_jwt_authorizer block to change that.
243
393
  resource "aws_bedrockagentcore_harness" "this" {
244
394
  harness_name = local.harness_name
245
- execution_role_arn = aws_iam_role.execution_role.arn
395
+ execution_role_arn = local.execution_role_arn
396
+
397
+ allowed_tools = var.allowed_tools
398
+ environment_variables = var.environment_variables
399
+ max_iterations = var.max_iterations
400
+ timeout_seconds = var.timeout_seconds
246
401
 
247
402
  model {
248
403
  bedrock_model_config {
@@ -255,9 +410,83 @@ resource "aws_bedrockagentcore_harness" "this" {
255
410
  text = file("${path.module}/../../../../../../../<%- promptPathFromTerraform %>")
256
411
  }
257
412
 
413
+ dynamic "memory" {
414
+ for_each = var.memory != null ? [var.memory] : []
415
+ content {
416
+ dynamic "managed_memory_configuration" {
417
+ for_each = memory.value.managed_memory_configuration != null ? [memory.value.managed_memory_configuration] : []
418
+ content {
419
+ strategies = managed_memory_configuration.value.strategies
420
+ event_expiry_duration = managed_memory_configuration.value.event_expiry_duration
421
+ encryption_key_arn = managed_memory_configuration.value.encryption_key_arn
422
+ }
423
+ }
424
+
425
+ dynamic "agentcore_memory_configuration" {
426
+ for_each = memory.value.agentcore_memory_configuration != null ? [memory.value.agentcore_memory_configuration] : []
427
+ content {
428
+ arn = agentcore_memory_configuration.value.arn
429
+ actor_id = agentcore_memory_configuration.value.actor_id
430
+ messages_count = agentcore_memory_configuration.value.messages_count
431
+ }
432
+ }
433
+
434
+ dynamic "disabled" {
435
+ for_each = memory.value.disabled ? [1] : []
436
+ content {}
437
+ }
438
+ }
439
+ }
440
+
441
+ dynamic "environment" {
442
+ for_each = var.enable_vpc ? [1] : []
443
+ content {
444
+ agentcore_runtime_environment {
445
+ network_configuration {
446
+ network_mode = "VPC"
447
+ network_mode_config {
448
+ security_groups = [aws_security_group.harness[0].id]
449
+ subnets = var.subnet_ids
450
+ }
451
+ }
452
+ }
453
+ }
454
+ }
455
+
456
+ tags = var.tags
457
+
258
458
  depends_on = [aws_iam_role_policy.execution_role]
259
459
  }
260
460
 
461
+ # Scoped to the memory ARN the service assigns, which is only readable after the
462
+ # Harness exists, so this is a policy of its own rather than a baseline
463
+ # statement. Skipped when the Harness uses a role or memory the module did not
464
+ # create.
465
+ resource "aws_iam_role_policy" "managed_memory" {
466
+ count = var.create_execution_role && local.has_managed_memory ? 1 : 0
467
+
468
+ name = "${local.harness_name_prefix}-HarnessManagedMemoryPolicy"
469
+ role = aws_iam_role.execution_role[0].id
470
+
471
+ policy = jsonencode({
472
+ Version = "2012-10-17"
473
+ Statement = [{
474
+ Sid = "AgentCoreManagedMemory"
475
+ Effect = "Allow"
476
+ Action = [
477
+ "bedrock-agentcore:CreateEvent",
478
+ "bedrock-agentcore:DeleteEvent",
479
+ "bedrock-agentcore:GetEvent",
480
+ "bedrock-agentcore:ListEvents",
481
+ "bedrock-agentcore:RetrieveMemoryRecords",
482
+ ]
483
+ Resource = [
484
+ aws_bedrockagentcore_harness.this.memory_actual[0].managed_memory_configuration[0].arn,
485
+ ]
486
+ }]
487
+ })
488
+ }
489
+
261
490
  module "add_harness_arn_to_runtime_config" {
262
491
  source = "../../../core/runtime-config/entry"
263
492
 
@@ -277,6 +506,11 @@ output "harness_arn" {
277
506
  }
278
507
 
279
508
  output "execution_role_arn" {
280
- description = "ARN of the IAM role assumed by the Harness"
281
- value = aws_iam_role.execution_role.arn
509
+ description = "ARN of the IAM role assumed by the Harness, whether generated or supplied via var.execution_role_arn"
510
+ value = local.execution_role_arn
511
+ }
512
+
513
+ output "security_group_id" {
514
+ description = "Security group ID of the Harness, for use in ingress rules on resources it must reach (e.g. a database). Null unless enable_vpc is true."
515
+ value = var.enable_vpc ? aws_security_group.harness[0].id : null
282
516
  }
@@ -6,6 +6,10 @@ terraform {
6
6
  source = "hashicorp/aws"
7
7
  version = "<%- awsProviderVersion %>"
8
8
  }
9
+ external = {
10
+ source = "hashicorp/external"
11
+ version = "<%- externalProviderVersion %>"
12
+ }
9
13
  null = {
10
14
  source = "hashicorp/null"
11
15
  version = "<%- nullProviderVersion %>"
@@ -2,10 +2,18 @@ terraform {
2
2
  required_version = ">= 1.0"
3
3
 
4
4
  required_providers {
5
+ archive = {
6
+ source = "hashicorp/archive"
7
+ version = "<%- archiveProviderVersion %>"
8
+ }
5
9
  aws = {
6
10
  source = "hashicorp/aws"
7
11
  version = "<%- awsProviderVersion %>"
8
12
  }
13
+ random = {
14
+ source = "hashicorp/random"
15
+ version = "<%- randomProviderVersion %>"
16
+ }
9
17
  }
10
18
  }
11
19
 
@@ -2,10 +2,18 @@ terraform {
2
2
  required_version = ">= 1.0"
3
3
 
4
4
  required_providers {
5
+ archive = {
6
+ source = "hashicorp/archive"
7
+ version = "<%- archiveProviderVersion %>"
8
+ }
5
9
  aws = {
6
10
  source = "hashicorp/aws"
7
11
  version = "<%- awsProviderVersion %>"
8
12
  }
13
+ random = {
14
+ source = "hashicorp/random"
15
+ version = "<%- randomProviderVersion %>"
16
+ }
9
17
  }
10
18
  }
11
19
 
@@ -785,7 +793,7 @@ module "add_url_to_runtime_config" {
785
793
 
786
794
  namespace = "connection"
787
795
  key = "apis"
788
- value = { "<%- apiNameClassName %>" = aws_api_gateway_stage.api_stage.invoke_url }
796
+ value = { "<%- apiNameClassName %>" = "${aws_api_gateway_stage.api_stage.invoke_url}/" }
789
797
 
790
798
  depends_on = [aws_api_gateway_stage.api_stage]
791
799
  }
@@ -200,22 +200,75 @@ resource "aws_wafv2_web_acl" "api_waf" {
200
200
  }
201
201
  }
202
202
 
203
+ resource "random_id" "waf_log_suffix" {
204
+ count = var.enable_waf ? 1 : 0
205
+ byte_length = 4
206
+ }
207
+
208
+ locals {
209
+ waf_log_group_name = var.enable_waf ? "aws-waf-logs-${var.api_name}-${random_id.waf_log_suffix[0].hex}" : null
210
+ }
211
+
212
+ # KMS key for encrypting WAF request logs at rest
213
+ resource "aws_kms_key" "waf_logs" {
214
+ count = var.enable_waf ? 1 : 0
215
+
216
+ description = "${var.api_name} API WAF log encryption"
217
+ deletion_window_in_days = 7
218
+ enable_key_rotation = true
219
+
220
+ policy = jsonencode({
221
+ Version = "2012-10-17"
222
+ Statement = [
223
+ {
224
+ Sid = "Enable IAM User Permissions"
225
+ Effect = "Allow"
226
+ Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" }
227
+ Action = "kms:*"
228
+ Resource = "*"
229
+ },
230
+ {
231
+ Sid = "Allow CloudWatch Logs"
232
+ Effect = "Allow"
233
+ Principal = { Service = "logs.${data.aws_region.current.region}.amazonaws.com" }
234
+ Action = [
235
+ "kms:Encrypt",
236
+ "kms:Decrypt",
237
+ "kms:ReEncrypt*",
238
+ "kms:GenerateDataKey*",
239
+ "kms:DescribeKey"
240
+ ]
241
+ Resource = "*"
242
+ Condition = {
243
+ ArnEquals = {
244
+ "kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:log-group:${local.waf_log_group_name}"
245
+ }
246
+ }
247
+ }
248
+ ]
249
+ })
250
+
251
+ tags = var.tags
252
+ }
253
+
254
+ resource "aws_kms_alias" "waf_logs" {
255
+ count = var.enable_waf ? 1 : 0
256
+
257
+ name = "alias/${var.api_name}-${random_id.waf_log_suffix[0].hex}-waf-logs"
258
+ target_key_id = aws_kms_key.waf_logs[0].key_id
259
+ }
260
+
203
261
  # CloudWatch Log Group for WAF request logs. Name must start with `aws-waf-logs-`.
204
262
  resource "aws_cloudwatch_log_group" "api_waf_logs" {
205
- #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
206
263
  #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
207
264
  count = var.enable_waf ? 1 : 0
208
265
 
209
- name = "aws-waf-logs-${var.api_name}-${random_id.waf_log_suffix[0].hex}"
266
+ name = local.waf_log_group_name
210
267
  retention_in_days = 30
268
+ kms_key_id = aws_kms_key.waf_logs[0].arn
211
269
  tags = var.tags
212
270
  }
213
271
 
214
- resource "random_id" "waf_log_suffix" {
215
- count = var.enable_waf ? 1 : 0
216
- byte_length = 4
217
- }
218
-
219
272
  resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
220
273
  count = var.enable_waf ? 1 : 0
221
274
 
@@ -15,6 +15,10 @@ terraform {
15
15
  source = "hashicorp/archive"
16
16
  version = "<%- archiveProviderVersion %>"
17
17
  }
18
+ random = {
19
+ source = "hashicorp/random"
20
+ version = "<%- randomProviderVersion %>"
21
+ }
18
22
  }
19
23
  }
20
24
 
@@ -96,3 +96,18 @@ module "add_to_runtime_config" {
96
96
  tableName = module.dynamodb_table.table_name
97
97
  }
98
98
  }
99
+
100
+ output "table_name" {
101
+ description = "Name of the DynamoDB table"
102
+ value = module.dynamodb_table.table_name
103
+ }
104
+
105
+ output "table_arn" {
106
+ description = "ARN of the DynamoDB table, for use in IAM policies granting access to the table. Suffix with /index/* to cover its global secondary indexes."
107
+ value = module.dynamodb_table.table_arn
108
+ }
109
+
110
+ output "kms_key_arn" {
111
+ description = "ARN of the KMS key used to encrypt the DynamoDB table, for use in IAM policies granting access to the table. Null when using the AWS owned key (encryption = DEFAULT)."
112
+ value = module.dynamodb_table.kms_key_arn
113
+ }
@@ -95,6 +95,8 @@ resource "aws_kms_key" "table" {
95
95
  description = "KMS key for DynamoDB table ${var.name}"
96
96
  enable_key_rotation = var.enable_key_rotation
97
97
 
98
+ deletion_window_in_days = 30
99
+
98
100
  policy = jsonencode({
99
101
  Version = "2012-10-17"
100
102
  Statement = [
@@ -171,6 +173,10 @@ resource "aws_dynamodb_table" "table" {
171
173
  enabled = var.encryption != "DEFAULT"
172
174
  kms_key_arn = local.table_kms_key_arn
173
175
  }
176
+
177
+ lifecycle {
178
+ prevent_destroy = true
179
+ }
174
180
  }
175
181
 
176
182
  output "table_name" {
@@ -0,0 +1,12 @@
1
+ terraform {
2
+ required_providers {
3
+ aws = {
4
+ source = "hashicorp/aws"
5
+ version = "<%- awsProviderVersion %>"
6
+ }
7
+ random = {
8
+ source = "hashicorp/random"
9
+ version = "<%- randomProviderVersion %>"
10
+ }
11
+ }
12
+ }
@@ -8,6 +8,10 @@ terraform {
8
8
  source = "hashicorp/archive"
9
9
  version = "<%- archiveProviderVersion %>"
10
10
  }
11
+ random = {
12
+ source = "hashicorp/random"
13
+ version = "<%- randomProviderVersion %>"
14
+ }
11
15
  }
12
16
  }
13
17
 
@@ -29,6 +29,8 @@ import {
29
29
  CfnWebACLAssociation,
30
30
  } from 'aws-cdk-lib/aws-wafv2';
31
31
  import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
32
+ import { Key } from 'aws-cdk-lib/aws-kms';
33
+ import { Effect, PolicyStatement, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
32
34
  import { Construct } from 'constructs';
33
35
  import { RuntimeConfig } from './runtime-config<% if (esm) { %>.js<% } %>';
34
36
  import { Distribution } from 'aws-cdk-lib/aws-cloudfront';
@@ -265,18 +267,41 @@ export class UserIdentity extends Construct {
265
267
  webAclArn: webAcl.attrArn,
266
268
  });
267
269
 
268
- // Send WAF request logs to CloudWatch. The log group name must start with
269
- // `aws-waf-logs-` to satisfy the WAFv2 logging destination requirement.
270
+ // The log group name must start with `aws-waf-logs-` to satisfy the WAFv2
271
+ // logging destination requirement.
272
+ const stack = Stack.of(this);
273
+ const wafLogGroupName = `aws-waf-logs-${id}-${this.node.addr.slice(-8)}`;
274
+
275
+ // KMS key for encrypting WAF logs at rest, usable by CloudWatch Logs
276
+ const logsKey = new Key(this, 'WebAclLogsKey', {
277
+ enableKeyRotation: true,
278
+ });
279
+ logsKey.addToResourcePolicy(
280
+ new PolicyStatement({
281
+ effect: Effect.ALLOW,
282
+ principals: [new ServicePrincipal(`logs.${stack.region}.amazonaws.com`)],
283
+ actions: [
284
+ 'kms:Encrypt',
285
+ 'kms:Decrypt',
286
+ 'kms:ReEncrypt*',
287
+ 'kms:GenerateDataKey*',
288
+ 'kms:DescribeKey',
289
+ ],
290
+ resources: ['*'],
291
+ conditions: {
292
+ ArnEquals: {
293
+ 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${stack.region}:${stack.account}:log-group:${wafLogGroupName}`,
294
+ },
295
+ },
296
+ }),
297
+ );
298
+
270
299
  const wafLogGroup = new LogGroup(this, 'WebAclLogs', {
271
- logGroupName: `aws-waf-logs-${id}-${this.node.addr.slice(-8)}`,
300
+ logGroupName: wafLogGroupName,
272
301
  retention: RetentionDays.ONE_MONTH,
302
+ encryptionKey: logsKey,
273
303
  removalPolicy: RemovalPolicy.DESTROY,
274
304
  });
275
- suppressRules(
276
- wafLogGroup,
277
- ['CKV_AWS_158'],
278
- 'Using default CloudWatch log encryption for WAF logs',
279
- );
280
305
 
281
306
  new CfnLoggingConfiguration(this, 'WebAclLoggingConfig', {
282
307
  resourceArn: webAcl.attrArn,
@@ -4,6 +4,10 @@ terraform {
4
4
  source = "hashicorp/aws"
5
5
  version = "<%- awsProviderVersion %>"
6
6
  }
7
+ null = {
8
+ source = "hashicorp/null"
9
+ version = "<%- nullProviderVersion %>"
10
+ }
7
11
  }
8
12
  }
9
13