@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.
- package/migrations.json +26 -1
- package/package.json +1 -1
- package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +51 -2
- package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +4 -5
- package/src/agentcore-harness/files/project/README.md.template +4 -5
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/__snapshots__/migration.spec.ts.snap +79 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/metadata.json +3 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.d.ts +6 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js +87 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +1 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/__snapshots__/migration.spec.ts.snap +54 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/metadata.json +3 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.d.ts +6 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js +86 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
- package/src/migrations/latest/terraform-declare-used-providers/metadata.json +3 -0
- package/src/migrations/latest/terraform-declare-used-providers/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-declare-used-providers/migration.js +147 -0
- package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +1 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/__snapshots__/migration.spec.ts.snap +521 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/metadata.json +3 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js +504 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/__snapshots__/migration.spec.ts.snap +286 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/metadata.json +3 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.d.ts +6 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js +238 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +1 -0
- package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +12 -0
- package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +231 -24
- package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
- package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +31 -2
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +216 -25
- package/src/terraform/project/files/application/bootstrap/providers.tf.template +9 -0
- package/src/terraform/project/files/application/src/providers.tf.template +4 -0
- package/src/terraform/project/files/library/src/main.tf.template +9 -0
- package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +231 -24
- package/src/ts/agent/__snapshots__/generator.spec.ts.snap +8 -0
- package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
- package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +62 -4
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +35 -8
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +4 -0
- package/src/utils/agent-core-constructs/files/cdk/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.ts.template +39 -1
- package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +8 -1
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +57 -2
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +258 -24
- package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +4 -0
- package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +8 -0
- package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +9 -1
- package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +60 -7
- package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
- package/src/utils/files/terraform/src/metrics/providers.tf.template +12 -0
- package/src/utils/function-constructs/files/terraform/app/lambda-functions/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
- package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +33 -8
- package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +4 -0
- package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +51 -2
- package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +18 -0
- package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +15 -4
- package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +2 -2
- package/src/utils/rdb-constructs/rdb-constructs.d.ts +12 -0
- package/src/utils/rdb-constructs/rdb-constructs.js +24 -3
- package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
- package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +4 -0
package/src/migrations/latest/waf-log-group-cmk-encryption/__snapshots__/migration.spec.ts.snap
ADDED
|
@@ -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,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"
|