@aws/nx-plugin 1.0.0-rc.83 → 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 (68) hide show
  1. package/migrations.json +26 -1
  2. package/package.json +1 -1
  3. package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +51 -2
  4. package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +4 -5
  5. package/src/agentcore-harness/files/project/README.md.template +4 -5
  6. package/src/migrations/latest/agentcore-harness-no-tools-by-default/__snapshots__/migration.spec.ts.snap +79 -0
  7. package/src/migrations/latest/agentcore-harness-no-tools-by-default/metadata.json +3 -0
  8. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.d.ts +6 -0
  9. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js +87 -0
  10. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +1 -0
  11. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/__snapshots__/migration.spec.ts.snap +54 -0
  12. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/metadata.json +3 -0
  13. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.d.ts +6 -0
  14. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js +86 -0
  15. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
  16. package/src/migrations/latest/terraform-declare-used-providers/metadata.json +3 -0
  17. package/src/migrations/latest/terraform-declare-used-providers/migration.d.ts +6 -0
  18. package/src/migrations/latest/terraform-declare-used-providers/migration.js +147 -0
  19. package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +1 -0
  20. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/__snapshots__/migration.spec.ts.snap +521 -0
  21. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/metadata.json +3 -0
  22. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.d.ts +6 -0
  23. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js +504 -0
  24. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
  25. package/src/migrations/latest/waf-log-group-cmk-encryption/__snapshots__/migration.spec.ts.snap +286 -0
  26. package/src/migrations/latest/waf-log-group-cmk-encryption/metadata.json +3 -0
  27. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.d.ts +6 -0
  28. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js +238 -0
  29. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +1 -0
  30. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +12 -0
  31. package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +231 -24
  32. package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  33. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  34. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +31 -2
  35. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +216 -25
  36. package/src/terraform/project/files/application/bootstrap/providers.tf.template +9 -0
  37. package/src/terraform/project/files/application/src/providers.tf.template +4 -0
  38. package/src/terraform/project/files/library/src/main.tf.template +9 -0
  39. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +231 -24
  40. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +8 -0
  41. package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +4 -0
  42. package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  43. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  44. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +62 -4
  45. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +4 -0
  46. package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +35 -8
  47. package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +4 -0
  48. package/src/utils/agent-core-constructs/files/cdk/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.ts.template +39 -1
  49. package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +8 -1
  50. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +57 -2
  51. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +258 -24
  52. package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +4 -0
  53. package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +8 -0
  54. package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +9 -1
  55. package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +60 -7
  56. package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  57. package/src/utils/files/terraform/src/metrics/providers.tf.template +12 -0
  58. package/src/utils/function-constructs/files/terraform/app/lambda-functions/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  59. package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +33 -8
  60. package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +4 -0
  61. package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +51 -2
  62. package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +18 -0
  63. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +15 -4
  64. package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +2 -2
  65. package/src/utils/rdb-constructs/rdb-constructs.d.ts +12 -0
  66. package/src/utils/rdb-constructs/rdb-constructs.js +24 -3
  67. package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
  68. package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +4 -0
@@ -0,0 +1,286 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`waf-log-group-cmk-encryption migration > should encrypt the cdk user identity WAF log group 1`] = `
4
+ "import { Effect, PolicyStatement, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
5
+ import { Key } from 'aws-cdk-lib/aws-kms';
6
+ import { RemovalPolicy, Stack } from 'aws-cdk-lib';
7
+ import { CfnLoggingConfiguration, CfnWebACL } from 'aws-cdk-lib/aws-wafv2';
8
+ import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
9
+ import { Construct } from 'constructs';
10
+ import { suppressRules } from './checkov.js';
11
+
12
+ export class UserIdentity extends Construct {
13
+ private createWebAcl = (id: string) => {
14
+ const webAcl = new CfnWebACL(this, 'WebAcl', {});
15
+
16
+ // Send WAF request logs to CloudWatch. The log group name must start with
17
+ // \`aws-waf-logs-\` to satisfy the WAFv2 logging destination requirement.
18
+ const stack = Stack.of(this);
19
+ const wafLogGroupName = \`aws-waf-logs-\${id}-\${this.node.addr.slice(-8)}\`;
20
+
21
+ // KMS key for encrypting WAF logs at rest, usable by CloudWatch Logs
22
+ const logsKey = new Key(this, 'WebAclLogsKey', {
23
+ enableKeyRotation: true,
24
+ });
25
+ logsKey.addToResourcePolicy(
26
+ new PolicyStatement({
27
+ effect: Effect.ALLOW,
28
+ principals: [
29
+ new ServicePrincipal(\`logs.\${stack.region}.amazonaws.com\`),
30
+ ],
31
+ actions: [
32
+ 'kms:Encrypt',
33
+ 'kms:Decrypt',
34
+ 'kms:ReEncrypt*',
35
+ 'kms:GenerateDataKey*',
36
+ 'kms:DescribeKey',
37
+ ],
38
+ resources: ['*'],
39
+ conditions: {
40
+ ArnEquals: {
41
+ 'kms:EncryptionContext:aws:logs:arn': \`arn:aws:logs:\${stack.region}:\${stack.account}:log-group:\${wafLogGroupName}\`,
42
+ },
43
+ },
44
+ }),
45
+ );
46
+
47
+ const wafLogGroup = new LogGroup(this, 'WebAclLogs', {
48
+ logGroupName: wafLogGroupName,
49
+ encryptionKey: logsKey,
50
+ retention: RetentionDays.ONE_MONTH,
51
+ removalPolicy: RemovalPolicy.DESTROY,
52
+ });
53
+
54
+ new CfnLoggingConfiguration(this, 'WebAclLoggingConfig', {
55
+ resourceArn: webAcl.attrArn,
56
+ logDestinationConfigs: [wafLogGroup.logGroupArn],
57
+ });
58
+
59
+ return webAcl;
60
+ };
61
+ }
62
+ "
63
+ `;
64
+
65
+ exports[`waf-log-group-cmk-encryption migration > should encrypt the terraform gateway WAF log group via the module locals 1`] = `
66
+ "data "aws_caller_identity" "current" {}
67
+ data "aws_region" "current" {}
68
+
69
+ locals {
70
+ aws_account_id = data.aws_caller_identity.current.account_id
71
+ aws_region = data.aws_region.current.id
72
+ gateway_waf_log_group_name = var.enable_waf ? "aws-waf-logs-my-gateway-\${random_id.unique_suffix.hex}" : null
73
+ }
74
+
75
+ resource "random_id" "unique_suffix" {
76
+ byte_length = 4
77
+ }
78
+
79
+ # WAF request logs. WAFv2 requires the \`aws-waf-logs-\` name prefix.
80
+ resource "aws_cloudwatch_log_group" "gateway_waf_logs" {
81
+ #checkov:skip=CKV_AWS_338:Log retention set to one year which is sufficient for WAF logs
82
+ count = var.enable_waf ? 1 : 0
83
+
84
+ name = local.gateway_waf_log_group_name
85
+ kms_key_id = aws_kms_key.gateway_waf_logs[0].arn
86
+ retention_in_days = 365
87
+ }
88
+
89
+ # KMS key for encrypting WAF request logs at rest
90
+ resource "aws_kms_key" "gateway_waf_logs" {
91
+ count = var.enable_waf ? 1 : 0
92
+
93
+ description = "my-gateway gateway WAF log encryption"
94
+ deletion_window_in_days = 7
95
+ enable_key_rotation = true
96
+
97
+ policy = jsonencode({
98
+ Version = "2012-10-17"
99
+ Statement = [
100
+ {
101
+ Sid = "Enable IAM User Permissions"
102
+ Effect = "Allow"
103
+ Principal = { AWS = "arn:aws:iam::\${local.aws_account_id}:root" }
104
+ Action = "kms:*"
105
+ Resource = "*"
106
+ },
107
+ {
108
+ Sid = "Allow CloudWatch Logs"
109
+ Effect = "Allow"
110
+ Principal = { Service = "logs.\${local.aws_region}.amazonaws.com" }
111
+ Action = [
112
+ "kms:Encrypt",
113
+ "kms:Decrypt",
114
+ "kms:ReEncrypt*",
115
+ "kms:GenerateDataKey*",
116
+ "kms:DescribeKey"
117
+ ]
118
+ Resource = "*"
119
+ Condition = {
120
+ ArnEquals = {
121
+ "kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${local.aws_region}:\${local.aws_account_id}:log-group:\${local.gateway_waf_log_group_name}"
122
+ }
123
+ }
124
+ }
125
+ ]
126
+ })
127
+ }
128
+
129
+ resource "aws_kms_alias" "gateway_waf_logs" {
130
+ count = var.enable_waf ? 1 : 0
131
+
132
+ name = "alias/my-gateway-\${random_id.unique_suffix.hex}-waf-logs"
133
+ target_key_id = aws_kms_key.gateway_waf_logs[0].key_id
134
+ }
135
+ "
136
+ `;
137
+
138
+ exports[`waf-log-group-cmk-encryption migration > should encrypt the terraform rest api WAF log group 1`] = `
139
+ "data "aws_region" "current" {}
140
+ data "aws_caller_identity" "current" {}
141
+
142
+ locals {
143
+ existing = "untouched"
144
+ waf_log_group_name = var.enable_waf ? "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}" : null
145
+ }
146
+
147
+ # CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
148
+ resource "aws_cloudwatch_log_group" "api_waf_logs" {
149
+ #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
150
+ count = var.enable_waf ? 1 : 0
151
+
152
+ name = local.waf_log_group_name
153
+ kms_key_id = aws_kms_key.waf_logs[0].arn
154
+ retention_in_days = 30
155
+ tags = var.tags
156
+ }
157
+
158
+ # KMS key for encrypting WAF request logs at rest
159
+ resource "aws_kms_key" "waf_logs" {
160
+ count = var.enable_waf ? 1 : 0
161
+
162
+ description = "\${var.api_name} API WAF log encryption"
163
+ deletion_window_in_days = 7
164
+ enable_key_rotation = true
165
+
166
+ policy = jsonencode({
167
+ Version = "2012-10-17"
168
+ Statement = [
169
+ {
170
+ Sid = "Enable IAM User Permissions"
171
+ Effect = "Allow"
172
+ Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
173
+ Action = "kms:*"
174
+ Resource = "*"
175
+ },
176
+ {
177
+ Sid = "Allow CloudWatch Logs"
178
+ Effect = "Allow"
179
+ Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
180
+ Action = [
181
+ "kms:Encrypt",
182
+ "kms:Decrypt",
183
+ "kms:ReEncrypt*",
184
+ "kms:GenerateDataKey*",
185
+ "kms:DescribeKey"
186
+ ]
187
+ Resource = "*"
188
+ Condition = {
189
+ ArnEquals = {
190
+ "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}"
191
+ }
192
+ }
193
+ }
194
+ ]
195
+ })
196
+
197
+ tags = var.tags
198
+ }
199
+
200
+ resource "aws_kms_alias" "waf_logs" {
201
+ count = var.enable_waf ? 1 : 0
202
+
203
+ name = "alias/\${var.api_name}-\${random_id.waf_log_suffix[0].hex}-waf-logs"
204
+ target_key_id = aws_kms_key.waf_logs[0].key_id
205
+ }
206
+
207
+ resource "random_id" "waf_log_suffix" {
208
+ count = var.enable_waf ? 1 : 0
209
+ byte_length = 4
210
+ }
211
+ "
212
+ `;
213
+
214
+ exports[`waf-log-group-cmk-encryption migration > should encrypt the terraform user identity WAF log group 1`] = `
215
+ "data "aws_caller_identity" "current" {}
216
+ data "aws_region" "current" {}
217
+
218
+ locals {
219
+ local_callback_urls = [
220
+ "http://localhost:4200"
221
+ ]
222
+ user_pool_waf_log_group_name = var.enable_waf ? "aws-waf-logs-UserPool-\${random_id.unique_suffix.hex}" : null
223
+ }
224
+
225
+ resource "random_id" "unique_suffix" {
226
+ byte_length = 4
227
+ }
228
+
229
+ # CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
230
+ resource "aws_cloudwatch_log_group" "user_pool_waf_logs" {
231
+ #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
232
+ count = var.enable_waf ? 1 : 0
233
+
234
+ name = local.user_pool_waf_log_group_name
235
+ kms_key_id = aws_kms_key.user_pool_waf_logs[0].arn
236
+ retention_in_days = 30
237
+ }
238
+
239
+ # KMS key for encrypting WAF request logs at rest
240
+ resource "aws_kms_key" "user_pool_waf_logs" {
241
+ count = var.enable_waf ? 1 : 0
242
+
243
+ description = "User pool WAF log encryption"
244
+ deletion_window_in_days = 7
245
+ enable_key_rotation = true
246
+
247
+ policy = jsonencode({
248
+ Version = "2012-10-17"
249
+ Statement = [
250
+ {
251
+ Sid = "Enable IAM User Permissions"
252
+ Effect = "Allow"
253
+ Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
254
+ Action = "kms:*"
255
+ Resource = "*"
256
+ },
257
+ {
258
+ Sid = "Allow CloudWatch Logs"
259
+ Effect = "Allow"
260
+ Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
261
+ Action = [
262
+ "kms:Encrypt",
263
+ "kms:Decrypt",
264
+ "kms:ReEncrypt*",
265
+ "kms:GenerateDataKey*",
266
+ "kms:DescribeKey"
267
+ ]
268
+ Resource = "*"
269
+ Condition = {
270
+ ArnEquals = {
271
+ "kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:\${local.user_pool_waf_log_group_name}"
272
+ }
273
+ }
274
+ }
275
+ ]
276
+ })
277
+ }
278
+
279
+ resource "aws_kms_alias" "user_pool_waf_logs" {
280
+ count = var.enable_waf ? 1 : 0
281
+
282
+ name = "alias/user-pool-\${random_id.unique_suffix.hex}-waf-logs"
283
+ target_key_id = aws_kms_key.user_pool_waf_logs[0].key_id
284
+ }
285
+ "
286
+ `;
@@ -0,0 +1,3 @@
1
+ {
2
+ "description": "Encrypt vended WAF log groups with a customer-managed KMS key"
3
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { type MigrationReturnObject, type Tree } from '@nx/devkit';
6
+ export default function migration(tree: Tree): Promise<MigrationReturnObject>;
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ import { joinPathFragments } from "@nx/devkit";
5
+ import { addDestructuredImport, applyGritQL, GRIT_INSERT_PLACEHOLDER, insertViaGritQL, matchGritQL } from "../../../utils/ast.js";
6
+ import { formatFilesInSubtree } from "../../../utils/format.js";
7
+ import { PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, SHARED_TERRAFORM_DIR } from "../../../utils/shared-constructs-constants.js";
8
+ /**
9
+ * WAF request logs carry full request metadata - URIs, headers and client IPs -
10
+ * so the vended WAF log groups are encrypted with a customer-managed KMS key,
11
+ * matching how the vended access log groups beside them already are. Three were
12
+ * plaintext: the Terraform REST API and user-pool WAF log groups, the Terraform
13
+ * AgentCore gateway WAF log group, and the CDK user-pool WAF log group. Each now
14
+ * gets a key whose policy grants CloudWatch Logs use of it, scoped by
15
+ * `kms:EncryptionContext:aws:logs:arn` to that log group alone.
16
+ *
17
+ * CloudWatch Logs cannot encrypt a log group that already exists in place, so
18
+ * this only rewrites the vended templates. On the next deploy Terraform sets
19
+ * `kms_key_id` on the existing group (an in-place update) and CloudFormation
20
+ * replaces the CDK one, which the vended `RemovalPolicy.DESTROY` already allows.
21
+ * Events written before then stay unencrypted; the `nextSteps` note says so.
22
+ */ const CONSTRUCTS_SRC = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src`;
23
+ const TERRAFORM_SRC = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src`;
24
+ const CDK_USER_IDENTITY_FILE = `${CONSTRUCTS_SRC}/core/user-identity.ts`;
25
+ const TERRAFORM_REST_API_FILE = `${TERRAFORM_SRC}/core/api/rest-api/rest-api.tf`;
26
+ const TERRAFORM_USER_IDENTITY_FILE = `${TERRAFORM_SRC}/core/user-identity/identity/identity.tf`;
27
+ const TERRAFORM_GATEWAYS_DIR = `${TERRAFORM_SRC}/app/gateways`;
28
+ const divergedMessage = (file)=>`${file}: the WAF log group has diverged from the generated shape - left untouched. Its request logs stay unencrypted. To encrypt them, add a customer-managed KMS key granting the \`logs.<region>.amazonaws.com\` service principal kms:Encrypt/Decrypt/ReEncrypt*/GenerateDataKey*/DescribeKey, conditioned on \`kms:EncryptionContext:aws:logs:arn\` matching the log group, and point the log group at it (see the vended WAF log group template).`;
29
+ const ALREADY_ENCRYPTED_TERRAFORM = 'aws_kms_key.';
30
+ // Terraform (HCL) patterns are built from plain strings rather than JS template
31
+ // literals, since the HCL text itself is full of `${...}` interpolations that
32
+ // would otherwise be parsed as JS interpolation.
33
+ const hcl = (pattern)=>'language hcl\n' + pattern;
34
+ const REST_API_LOG_GROUP = {
35
+ logGroupLabel: 'api_waf_logs',
36
+ keyLabel: 'waf_logs',
37
+ nameLocal: 'waf_log_group_name',
38
+ nameExpression: 'var.enable_waf ? "aws-waf-logs-${var.api_name}-${random_id.waf_log_suffix[0].hex}" : null',
39
+ keyDescription: '"${var.api_name} API WAF log encryption"',
40
+ aliasName: '"alias/${var.api_name}-${random_id.waf_log_suffix[0].hex}-waf-logs"',
41
+ accountId: 'data.aws_caller_identity.current.account_id',
42
+ region: 'data.aws_region.current.region',
43
+ tagged: true
44
+ };
45
+ const USER_IDENTITY_LOG_GROUP = {
46
+ logGroupLabel: 'user_pool_waf_logs',
47
+ keyLabel: 'user_pool_waf_logs',
48
+ nameLocal: 'user_pool_waf_log_group_name',
49
+ nameExpression: 'var.enable_waf ? "aws-waf-logs-UserPool-${random_id.unique_suffix.hex}" : null',
50
+ keyDescription: '"User pool WAF log encryption"',
51
+ aliasName: '"alias/user-pool-${random_id.unique_suffix.hex}-waf-logs"',
52
+ accountId: 'data.aws_caller_identity.current.account_id',
53
+ region: 'data.aws_region.current.region',
54
+ tagged: false
55
+ };
56
+ const gatewayLogGroup = (nameKebabCase)=>({
57
+ logGroupLabel: 'gateway_waf_logs',
58
+ keyLabel: 'gateway_waf_logs',
59
+ nameLocal: 'gateway_waf_log_group_name',
60
+ nameExpression: `var.enable_waf ? "aws-waf-logs-${nameKebabCase}-\${random_id.unique_suffix.hex}" : null`,
61
+ keyDescription: `"${nameKebabCase} gateway WAF log encryption"`,
62
+ aliasName: `"alias/${nameKebabCase}-\${random_id.unique_suffix.hex}-waf-logs"`,
63
+ accountId: 'local.aws_account_id',
64
+ region: 'local.aws_region',
65
+ tagged: false
66
+ });
67
+ /** The KMS key and alias resources, as the templates now vend them. */ const keyResourcesText = (group)=>[
68
+ '# KMS key for encrypting WAF request logs at rest',
69
+ `resource "aws_kms_key" "${group.keyLabel}" {`,
70
+ ' count = var.enable_waf ? 1 : 0',
71
+ '',
72
+ ` description = ${group.keyDescription}`,
73
+ ' deletion_window_in_days = 7',
74
+ ' enable_key_rotation = true',
75
+ '',
76
+ ' policy = jsonencode({',
77
+ ' Version = "2012-10-17"',
78
+ ' Statement = [',
79
+ ' {',
80
+ ' Sid = "Enable IAM User Permissions"',
81
+ ' Effect = "Allow"',
82
+ ` Principal = { AWS = "arn:aws:iam::\${${group.accountId}}:root" }`,
83
+ ' Action = "kms:*"',
84
+ ' Resource = "*"',
85
+ ' },',
86
+ ' {',
87
+ ' Sid = "Allow CloudWatch Logs"',
88
+ ' Effect = "Allow"',
89
+ ` Principal = { Service = "logs.\${${group.region}}.amazonaws.com" }`,
90
+ ' Action = [',
91
+ ' "kms:Encrypt",',
92
+ ' "kms:Decrypt",',
93
+ ' "kms:ReEncrypt*",',
94
+ ' "kms:GenerateDataKey*",',
95
+ ' "kms:DescribeKey"',
96
+ ' ]',
97
+ ' Resource = "*"',
98
+ ' Condition = {',
99
+ ' ArnEquals = {',
100
+ ` "kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${${group.region}}:\${${group.accountId}}:log-group:\${local.${group.nameLocal}}"`,
101
+ ' }',
102
+ ' }',
103
+ ' }',
104
+ ' ]',
105
+ ' })',
106
+ ...group.tagged ? [
107
+ '',
108
+ ' tags = var.tags'
109
+ ] : [],
110
+ '}',
111
+ '',
112
+ `resource "aws_kms_alias" "${group.keyLabel}" {`,
113
+ ' count = var.enable_waf ? 1 : 0',
114
+ '',
115
+ ` name = ${group.aliasName}`,
116
+ ` target_key_id = aws_kms_key.${group.keyLabel}[0].key_id`,
117
+ '}'
118
+ ].join('\n');
119
+ const migrateTerraformLogGroup = async (tree, file, group, nextSteps)=>{
120
+ const contents = tree.read(file, 'utf-8') ?? '';
121
+ // The WAF log group is only vended when WAF is enabled, so it may be absent.
122
+ if (!contents.includes(`"aws_cloudwatch_log_group" "${group.logGroupLabel}"`)) {
123
+ return;
124
+ }
125
+ const LOG_GROUP_BLOCK = hcl(`\`resource "aws_cloudwatch_log_group" "${group.logGroupLabel}" { $body }\``);
126
+ const NAME_LINE = hcl(`\`name = ${group.nameExpression.replace(/^var\.enable_waf \? /, '').replace(/ : null$/, '')}\` as $line where {\n` + ` $line <: within \`resource "aws_cloudwatch_log_group" "${group.logGroupLabel}" { $_ }\`\n` + '}');
127
+ const SUPPRESSION_LINE = '#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption';
128
+ const LOCALS_BLOCK = hcl('`locals { $body }`');
129
+ if (contents.includes(ALREADY_ENCRYPTED_TERRAFORM + group.keyLabel)) {
130
+ return; // Already migrated.
131
+ }
132
+ const ready = await matchGritQL(tree, file, LOG_GROUP_BLOCK) && await matchGritQL(tree, file, NAME_LINE) && await matchGritQL(tree, file, LOCALS_BLOCK) && contents.includes(SUPPRESSION_LINE);
133
+ if (!ready) {
134
+ nextSteps.push(divergedMessage(file));
135
+ return;
136
+ }
137
+ // 1. Route the log group name through a local, so the key policy's encryption
138
+ // context condition can name the same log group.
139
+ await insertViaGritQL(tree, file, LOCALS_BLOCK + ` as $l => \`locals {\n $body\n ${GRIT_INSERT_PLACEHOLDER}\n}\``, `${group.nameLocal} = ${group.nameExpression}`);
140
+ await applyGritQL(tree, file, NAME_LINE + ` => \`name = local.${group.nameLocal}\``);
141
+ // 2. Point the log group at a new key, added after it so the log group keeps
142
+ // the comment above it.
143
+ await insertViaGritQL(tree, file, hcl(`\`name = local.${group.nameLocal}\` as $line where {\n` + ` $line <: within \`resource "aws_cloudwatch_log_group" "${group.logGroupLabel}" { $_ }\`\n` + '}') + ` => \`$line\n ${GRIT_INSERT_PLACEHOLDER}\``, `kms_key_id = aws_kms_key.${group.keyLabel}[0].arn`);
144
+ await insertViaGritQL(tree, file, LOG_GROUP_BLOCK + ` as $lg => \`$lg\n\n${GRIT_INSERT_PLACEHOLDER}\``, keyResourcesText(group));
145
+ // 3. Drop the suppression the encryption makes redundant.
146
+ const updated = (tree.read(file, 'utf-8') ?? '').split('\n').filter((line)=>line.trim() !== SUPPRESSION_LINE).join('\n');
147
+ tree.write(file, updated);
148
+ };
149
+ /**
150
+ * The vended CDK user-pool WAF log group gains an `encryptionKey`, matching the
151
+ * eight sibling CDK WAF log groups which already pass one.
152
+ */ const migrateCdkUserIdentity = async (tree, nextSteps)=>{
153
+ if (!tree.exists(CDK_USER_IDENTITY_FILE)) {
154
+ return; // This workspace has no CDK user identity construct.
155
+ }
156
+ const contents = tree.read(CDK_USER_IDENTITY_FILE, 'utf-8') ?? '';
157
+ // The WAF web ACL (and its log group) is optional, so it may be absent.
158
+ if (!contents.includes(`new LogGroup(this, 'WebAclLogs'`)) {
159
+ return;
160
+ }
161
+ if (contents.includes('WebAclLogsKey')) {
162
+ return; // Already migrated.
163
+ }
164
+ // Already encrypted, whether by this migration or by the user's own key.
165
+ if (await matchGritQL(tree, CDK_USER_IDENTITY_FILE, "`new LogGroup(this, 'WebAclLogs', { $props })` where { $props <: contains `encryptionKey` }")) {
166
+ return;
167
+ }
168
+ const LOG_GROUP = "`new LogGroup(this, 'WebAclLogs', { $props })` as $lg where { $props <: contains `logGroupName: $name` }";
169
+ const SUPPRESS_CALL = "`suppressRules(wafLogGroup, ['CKV_AWS_158'], $reason)` as $call";
170
+ const ready = await matchGritQL(tree, CDK_USER_IDENTITY_FILE, LOG_GROUP) && await matchGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL);
171
+ if (!ready) {
172
+ nextSteps.push(divergedMessage(CDK_USER_IDENTITY_FILE));
173
+ return;
174
+ }
175
+ await addDestructuredImport(tree, CDK_USER_IDENTITY_FILE, [
176
+ 'Key'
177
+ ], 'aws-cdk-lib/aws-kms');
178
+ await addDestructuredImport(tree, CDK_USER_IDENTITY_FILE, [
179
+ 'Effect',
180
+ 'PolicyStatement',
181
+ 'ServicePrincipal'
182
+ ], 'aws-cdk-lib/aws-iam');
183
+ // Hoist the log group name into a const so the key policy's encryption context
184
+ // condition can name the same log group, then create the key above the group.
185
+ await insertViaGritQL(tree, CDK_USER_IDENTITY_FILE, "`const wafLogGroup = new LogGroup(this, 'WebAclLogs', { $props })` as $decl => " + `\`${GRIT_INSERT_PLACEHOLDER}\n\n$decl\``, `const stack = Stack.of(this);
186
+ const wafLogGroupName = \`aws-waf-logs-\${id}-\${this.node.addr.slice(-8)}\`;
187
+
188
+ // KMS key for encrypting WAF logs at rest, usable by CloudWatch Logs
189
+ const logsKey = new Key(this, 'WebAclLogsKey', {
190
+ enableKeyRotation: true,
191
+ });
192
+ logsKey.addToResourcePolicy(
193
+ new PolicyStatement({
194
+ effect: Effect.ALLOW,
195
+ principals: [new ServicePrincipal(\`logs.\${stack.region}.amazonaws.com\`)],
196
+ actions: [
197
+ 'kms:Encrypt',
198
+ 'kms:Decrypt',
199
+ 'kms:ReEncrypt*',
200
+ 'kms:GenerateDataKey*',
201
+ 'kms:DescribeKey',
202
+ ],
203
+ resources: ['*'],
204
+ conditions: {
205
+ ArnEquals: {
206
+ 'kms:EncryptionContext:aws:logs:arn': \`arn:aws:logs:\${stack.region}:\${stack.account}:log-group:\${wafLogGroupName}\`,
207
+ },
208
+ },
209
+ }),
210
+ );`);
211
+ await applyGritQL(tree, CDK_USER_IDENTITY_FILE, "`logGroupName: $name` as $prop where { $prop <: within `new LogGroup(this, 'WebAclLogs', $_)` } => `logGroupName: wafLogGroupName,\n encryptionKey: logsKey`");
212
+ // Drop the suppression the encryption makes redundant.
213
+ await applyGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL + ' => ``');
214
+ };
215
+ export default async function migration(tree) {
216
+ const nextSteps = [];
217
+ if (tree.exists(TERRAFORM_REST_API_FILE)) {
218
+ await migrateTerraformLogGroup(tree, TERRAFORM_REST_API_FILE, REST_API_LOG_GROUP, nextSteps);
219
+ }
220
+ if (tree.exists(TERRAFORM_USER_IDENTITY_FILE)) {
221
+ await migrateTerraformLogGroup(tree, TERRAFORM_USER_IDENTITY_FILE, USER_IDENTITY_LOG_GROUP, nextSteps);
222
+ }
223
+ if (tree.exists(TERRAFORM_GATEWAYS_DIR)) {
224
+ for (const dirName of tree.children(TERRAFORM_GATEWAYS_DIR)){
225
+ const file = joinPathFragments(TERRAFORM_GATEWAYS_DIR, dirName, `${dirName}.tf`);
226
+ if (tree.exists(file)) {
227
+ await migrateTerraformLogGroup(tree, file, gatewayLogGroup(dirName), nextSteps);
228
+ }
229
+ }
230
+ }
231
+ await migrateCdkUserIdentity(tree, nextSteps);
232
+ await formatFilesInSubtree(tree);
233
+ return {
234
+ nextSteps
235
+ };
236
+ }
237
+
238
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/waf-log-group-cmk-encryption/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport {\n addDestructuredImport,\n applyGritQL,\n GRIT_INSERT_PLACEHOLDER,\n insertViaGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * WAF request logs carry full request metadata - URIs, headers and client IPs -\n * so the vended WAF log groups are encrypted with a customer-managed KMS key,\n * matching how the vended access log groups beside them already are. Three were\n * plaintext: the Terraform REST API and user-pool WAF log groups, the Terraform\n * AgentCore gateway WAF log group, and the CDK user-pool WAF log group. Each now\n * gets a key whose policy grants CloudWatch Logs use of it, scoped by\n * `kms:EncryptionContext:aws:logs:arn` to that log group alone.\n *\n * CloudWatch Logs cannot encrypt a log group that already exists in place, so\n * this only rewrites the vended templates. On the next deploy Terraform sets\n * `kms_key_id` on the existing group (an in-place update) and CloudFormation\n * replaces the CDK one, which the vended `RemovalPolicy.DESTROY` already allows.\n * Events written before then stay unencrypted; the `nextSteps` note says so.\n */\n\nconst CONSTRUCTS_SRC = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src`;\nconst TERRAFORM_SRC = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src`;\n\nconst CDK_USER_IDENTITY_FILE = `${CONSTRUCTS_SRC}/core/user-identity.ts`;\nconst TERRAFORM_REST_API_FILE = `${TERRAFORM_SRC}/core/api/rest-api/rest-api.tf`;\nconst TERRAFORM_USER_IDENTITY_FILE = `${TERRAFORM_SRC}/core/user-identity/identity/identity.tf`;\nconst TERRAFORM_GATEWAYS_DIR = `${TERRAFORM_SRC}/app/gateways`;\n\nconst divergedMessage = (file: string) =>\n `${file}: the WAF log group has diverged from the generated shape - left untouched. Its request logs stay unencrypted. To encrypt them, add a customer-managed KMS key granting the \\`logs.<region>.amazonaws.com\\` service principal kms:Encrypt/Decrypt/ReEncrypt*/GenerateDataKey*/DescribeKey, conditioned on \\`kms:EncryptionContext:aws:logs:arn\\` matching the log group, and point the log group at it (see the vended WAF log group template).`;\n\nconst ALREADY_ENCRYPTED_TERRAFORM = 'aws_kms_key.';\n\n// Terraform (HCL) patterns are built from plain strings rather than JS template\n// literals, since the HCL text itself is full of `${...}` interpolations that\n// would otherwise be parsed as JS interpolation.\nconst hcl = (pattern: string) => 'language hcl\\n' + pattern;\n\n/** A vended Terraform WAF log group and the key this migration gives it. */\ninterface TerraformWafLogGroup {\n /** The `aws_cloudwatch_log_group` resource label. */\n readonly logGroupLabel: string;\n /** The `aws_kms_key` / `aws_kms_alias` label to create. */\n readonly keyLabel: string;\n /** The `locals` entry the log group name moves into. */\n readonly nameLocal: string;\n /** The log group's name expression, which becomes the local's value. */\n readonly nameExpression: string;\n /** The KMS key description. */\n readonly keyDescription: string;\n /** The KMS alias name expression. */\n readonly aliasName: string;\n /** How the account id is referenced in this module. */\n readonly accountId: string;\n /** How the region is referenced in this module. */\n readonly region: string;\n /** Whether the module tags its resources. */\n readonly tagged: boolean;\n}\n\nconst REST_API_LOG_GROUP: TerraformWafLogGroup = {\n logGroupLabel: 'api_waf_logs',\n keyLabel: 'waf_logs',\n nameLocal: 'waf_log_group_name',\n nameExpression:\n 'var.enable_waf ? \"aws-waf-logs-${var.api_name}-${random_id.waf_log_suffix[0].hex}\" : null',\n keyDescription: '\"${var.api_name} API WAF log encryption\"',\n aliasName:\n '\"alias/${var.api_name}-${random_id.waf_log_suffix[0].hex}-waf-logs\"',\n accountId: 'data.aws_caller_identity.current.account_id',\n region: 'data.aws_region.current.region',\n tagged: true,\n};\n\nconst USER_IDENTITY_LOG_GROUP: TerraformWafLogGroup = {\n logGroupLabel: 'user_pool_waf_logs',\n keyLabel: 'user_pool_waf_logs',\n nameLocal: 'user_pool_waf_log_group_name',\n nameExpression:\n 'var.enable_waf ? \"aws-waf-logs-UserPool-${random_id.unique_suffix.hex}\" : null',\n keyDescription: '\"User pool WAF log encryption\"',\n aliasName: '\"alias/user-pool-${random_id.unique_suffix.hex}-waf-logs\"',\n accountId: 'data.aws_caller_identity.current.account_id',\n region: 'data.aws_region.current.region',\n tagged: false,\n};\n\nconst gatewayLogGroup = (nameKebabCase: string): TerraformWafLogGroup => ({\n logGroupLabel: 'gateway_waf_logs',\n keyLabel: 'gateway_waf_logs',\n nameLocal: 'gateway_waf_log_group_name',\n nameExpression: `var.enable_waf ? \"aws-waf-logs-${nameKebabCase}-\\${random_id.unique_suffix.hex}\" : null`,\n keyDescription: `\"${nameKebabCase} gateway WAF log encryption\"`,\n aliasName: `\"alias/${nameKebabCase}-\\${random_id.unique_suffix.hex}-waf-logs\"`,\n accountId: 'local.aws_account_id',\n region: 'local.aws_region',\n tagged: false,\n});\n\n/** The KMS key and alias resources, as the templates now vend them. */\nconst keyResourcesText = (group: TerraformWafLogGroup): string =>\n [\n '# KMS key for encrypting WAF request logs at rest',\n `resource \"aws_kms_key\" \"${group.keyLabel}\" {`,\n ' count = var.enable_waf ? 1 : 0',\n '',\n ` description = ${group.keyDescription}`,\n ' deletion_window_in_days = 7',\n ' enable_key_rotation = true',\n '',\n ' policy = jsonencode({',\n ' Version = \"2012-10-17\"',\n ' Statement = [',\n ' {',\n ' Sid = \"Enable IAM User Permissions\"',\n ' Effect = \"Allow\"',\n ` Principal = { AWS = \"arn:aws:iam::\\${${group.accountId}}:root\" }`,\n ' Action = \"kms:*\"',\n ' Resource = \"*\"',\n ' },',\n ' {',\n ' Sid = \"Allow CloudWatch Logs\"',\n ' Effect = \"Allow\"',\n ` Principal = { Service = \"logs.\\${${group.region}}.amazonaws.com\" }`,\n ' Action = [',\n ' \"kms:Encrypt\",',\n ' \"kms:Decrypt\",',\n ' \"kms:ReEncrypt*\",',\n ' \"kms:GenerateDataKey*\",',\n ' \"kms:DescribeKey\"',\n ' ]',\n ' Resource = \"*\"',\n ' Condition = {',\n ' ArnEquals = {',\n ` \"kms:EncryptionContext:aws:logs:arn\" = \"arn:aws:logs:\\${${group.region}}:\\${${group.accountId}}:log-group:\\${local.${group.nameLocal}}\"`,\n ' }',\n ' }',\n ' }',\n ' ]',\n ' })',\n ...(group.tagged ? ['', ' tags = var.tags'] : []),\n '}',\n '',\n `resource \"aws_kms_alias\" \"${group.keyLabel}\" {`,\n ' count = var.enable_waf ? 1 : 0',\n '',\n ` name = ${group.aliasName}`,\n ` target_key_id = aws_kms_key.${group.keyLabel}[0].key_id`,\n '}',\n ].join('\\n');\n\nconst migrateTerraformLogGroup = async (\n tree: Tree,\n file: string,\n group: TerraformWafLogGroup,\n nextSteps: string[],\n): Promise<void> => {\n const contents = tree.read(file, 'utf-8') ?? '';\n\n // The WAF log group is only vended when WAF is enabled, so it may be absent.\n if (\n !contents.includes(`\"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\"`)\n ) {\n return;\n }\n\n const LOG_GROUP_BLOCK = hcl(\n `\\`resource \"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\" { $body }\\``,\n );\n const NAME_LINE = hcl(\n `\\`name = ${group.nameExpression.replace(/^var\\.enable_waf \\? /, '').replace(/ : null$/, '')}\\` as $line where {\\n` +\n ` $line <: within \\`resource \"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\" { $_ }\\`\\n` +\n '}',\n );\n const SUPPRESSION_LINE =\n '#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption';\n const LOCALS_BLOCK = hcl('`locals { $body }`');\n\n if (contents.includes(ALREADY_ENCRYPTED_TERRAFORM + group.keyLabel)) {\n return; // Already migrated.\n }\n\n const ready =\n (await matchGritQL(tree, file, LOG_GROUP_BLOCK)) &&\n (await matchGritQL(tree, file, NAME_LINE)) &&\n (await matchGritQL(tree, file, LOCALS_BLOCK)) &&\n contents.includes(SUPPRESSION_LINE);\n\n if (!ready) {\n nextSteps.push(divergedMessage(file));\n return;\n }\n\n // 1. Route the log group name through a local, so the key policy's encryption\n // context condition can name the same log group.\n await insertViaGritQL(\n tree,\n file,\n LOCALS_BLOCK +\n ` as $l => \\`locals {\\n $body\\n ${GRIT_INSERT_PLACEHOLDER}\\n}\\``,\n `${group.nameLocal} = ${group.nameExpression}`,\n );\n await applyGritQL(\n tree,\n file,\n NAME_LINE + ` => \\`name = local.${group.nameLocal}\\``,\n );\n\n // 2. Point the log group at a new key, added after it so the log group keeps\n // the comment above it.\n await insertViaGritQL(\n tree,\n file,\n hcl(\n `\\`name = local.${group.nameLocal}\\` as $line where {\\n` +\n ` $line <: within \\`resource \"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\" { $_ }\\`\\n` +\n '}',\n ) + ` => \\`$line\\n ${GRIT_INSERT_PLACEHOLDER}\\``,\n `kms_key_id = aws_kms_key.${group.keyLabel}[0].arn`,\n );\n await insertViaGritQL(\n tree,\n file,\n LOG_GROUP_BLOCK + ` as $lg => \\`$lg\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n keyResourcesText(group),\n );\n\n // 3. Drop the suppression the encryption makes redundant.\n const updated = (tree.read(file, 'utf-8') ?? '')\n .split('\\n')\n .filter((line) => line.trim() !== SUPPRESSION_LINE)\n .join('\\n');\n tree.write(file, updated);\n};\n\n/**\n * The vended CDK user-pool WAF log group gains an `encryptionKey`, matching the\n * eight sibling CDK WAF log groups which already pass one.\n */\nconst migrateCdkUserIdentity = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(CDK_USER_IDENTITY_FILE)) {\n return; // This workspace has no CDK user identity construct.\n }\n\n const contents = tree.read(CDK_USER_IDENTITY_FILE, 'utf-8') ?? '';\n\n // The WAF web ACL (and its log group) is optional, so it may be absent.\n if (!contents.includes(`new LogGroup(this, 'WebAclLogs'`)) {\n return;\n }\n\n if (contents.includes('WebAclLogsKey')) {\n return; // Already migrated.\n }\n\n // Already encrypted, whether by this migration or by the user's own key.\n if (\n await matchGritQL(\n tree,\n CDK_USER_IDENTITY_FILE,\n \"`new LogGroup(this, 'WebAclLogs', { $props })` where { $props <: contains `encryptionKey` }\",\n )\n ) {\n return;\n }\n\n const LOG_GROUP =\n \"`new LogGroup(this, 'WebAclLogs', { $props })` as $lg where { $props <: contains `logGroupName: $name` }\";\n const SUPPRESS_CALL =\n \"`suppressRules(wafLogGroup, ['CKV_AWS_158'], $reason)` as $call\";\n\n const ready =\n (await matchGritQL(tree, CDK_USER_IDENTITY_FILE, LOG_GROUP)) &&\n (await matchGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL));\n\n if (!ready) {\n nextSteps.push(divergedMessage(CDK_USER_IDENTITY_FILE));\n return;\n }\n\n await addDestructuredImport(\n tree,\n CDK_USER_IDENTITY_FILE,\n ['Key'],\n 'aws-cdk-lib/aws-kms',\n );\n await addDestructuredImport(\n tree,\n CDK_USER_IDENTITY_FILE,\n ['Effect', 'PolicyStatement', 'ServicePrincipal'],\n 'aws-cdk-lib/aws-iam',\n );\n\n // Hoist the log group name into a const so the key policy's encryption context\n // condition can name the same log group, then create the key above the group.\n await insertViaGritQL(\n tree,\n CDK_USER_IDENTITY_FILE,\n \"`const wafLogGroup = new LogGroup(this, 'WebAclLogs', { $props })` as $decl => \" +\n `\\`${GRIT_INSERT_PLACEHOLDER}\\n\\n$decl\\``,\n `const stack = Stack.of(this);\n const wafLogGroupName = \\`aws-waf-logs-\\${id}-\\${this.node.addr.slice(-8)}\\`;\n\n // KMS key for encrypting WAF logs at rest, usable by CloudWatch Logs\n const logsKey = new Key(this, 'WebAclLogsKey', {\n enableKeyRotation: true,\n });\n logsKey.addToResourcePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n principals: [new ServicePrincipal(\\`logs.\\${stack.region}.amazonaws.com\\`)],\n actions: [\n 'kms:Encrypt',\n 'kms:Decrypt',\n 'kms:ReEncrypt*',\n 'kms:GenerateDataKey*',\n 'kms:DescribeKey',\n ],\n resources: ['*'],\n conditions: {\n ArnEquals: {\n 'kms:EncryptionContext:aws:logs:arn': \\`arn:aws:logs:\\${stack.region}:\\${stack.account}:log-group:\\${wafLogGroupName}\\`,\n },\n },\n }),\n );`,\n );\n\n await applyGritQL(\n tree,\n CDK_USER_IDENTITY_FILE,\n \"`logGroupName: $name` as $prop where { $prop <: within `new LogGroup(this, 'WebAclLogs', $_)` } => `logGroupName: wafLogGroupName,\\n encryptionKey: logsKey`\",\n );\n\n // Drop the suppression the encryption makes redundant.\n await applyGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL + ' => ``');\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (tree.exists(TERRAFORM_REST_API_FILE)) {\n await migrateTerraformLogGroup(\n tree,\n TERRAFORM_REST_API_FILE,\n REST_API_LOG_GROUP,\n nextSteps,\n );\n }\n if (tree.exists(TERRAFORM_USER_IDENTITY_FILE)) {\n await migrateTerraformLogGroup(\n tree,\n TERRAFORM_USER_IDENTITY_FILE,\n USER_IDENTITY_LOG_GROUP,\n nextSteps,\n );\n }\n if (tree.exists(TERRAFORM_GATEWAYS_DIR)) {\n for (const dirName of tree.children(TERRAFORM_GATEWAYS_DIR)) {\n const file = joinPathFragments(\n TERRAFORM_GATEWAYS_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (tree.exists(file)) {\n await migrateTerraformLogGroup(\n tree,\n file,\n gatewayLogGroup(dirName),\n nextSteps,\n );\n }\n }\n }\n\n await migrateCdkUserIdentity(tree, nextSteps);\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","addDestructuredImport","applyGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","CONSTRUCTS_SRC","TERRAFORM_SRC","CDK_USER_IDENTITY_FILE","TERRAFORM_REST_API_FILE","TERRAFORM_USER_IDENTITY_FILE","TERRAFORM_GATEWAYS_DIR","divergedMessage","file","ALREADY_ENCRYPTED_TERRAFORM","hcl","pattern","REST_API_LOG_GROUP","logGroupLabel","keyLabel","nameLocal","nameExpression","keyDescription","aliasName","accountId","region","tagged","USER_IDENTITY_LOG_GROUP","gatewayLogGroup","nameKebabCase","keyResourcesText","group","join","migrateTerraformLogGroup","tree","nextSteps","contents","read","includes","LOG_GROUP_BLOCK","NAME_LINE","replace","SUPPRESSION_LINE","LOCALS_BLOCK","ready","push","updated","split","filter","line","trim","write","migrateCdkUserIdentity","exists","LOG_GROUP","SUPPRESS_CALL","migration","dirName","children"],"mappings":"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"}
@@ -3,6 +3,10 @@
3
3
  exports[`py#agent generator > should create DynamoDB checkpoint table and S3 offload bucket infrastructure when session is dynamodb-s3 (Terraform) > terraform-agent.tf (dynamodb-s3) 1`] = `
4
4
  "terraform {
5
5
  required_providers {
6
+ aws = {
7
+ source = "hashicorp/aws"
8
+ version = "6.61.0"
9
+ }
6
10
  random = {
7
11
  source = "hashicorp/random"
8
12
  version = "3.9.0"
@@ -363,6 +367,10 @@ output "security_group_id" {
363
367
  exports[`py#agent generator > should match snapshot for Terraform generated files > terraform-agent.tf 1`] = `
364
368
  "terraform {
365
369
  required_providers {
370
+ aws = {
371
+ source = "hashicorp/aws"
372
+ version = "6.61.0"
373
+ }
366
374
  random = {
367
375
  source = "hashicorp/random"
368
376
  version = "3.9.0"
@@ -674,6 +682,10 @@ exports[`py#agent generator > should match snapshot for Terraform generated file
674
682
  source = "hashicorp/aws"
675
683
  version = "6.61.0"
676
684
  }
685
+ external = {
686
+ source = "hashicorp/external"
687
+ version = "2.4.1"
688
+ }
677
689
  null = {
678
690
  source = "hashicorp/null"
679
691
  version = "3.3.1"