@aws/nx-plugin 1.0.0-rc.76 → 1.0.0-rc.78
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 +16 -1
- package/package.json +1 -1
- package/src/migrations/latest/dynamodb-configurable-encryption/__snapshots__/migration.spec.ts.snap +429 -0
- package/src/migrations/latest/dynamodb-configurable-encryption/metadata.json +3 -0
- package/src/migrations/latest/dynamodb-configurable-encryption/migration.d.ts +6 -0
- package/src/migrations/latest/dynamodb-configurable-encryption/migration.js +325 -0
- package/src/migrations/latest/dynamodb-configurable-encryption/migration.js.map +1 -0
- package/src/migrations/latest/static-website-configurable-waf-encryption/metadata.json +3 -0
- package/src/migrations/latest/static-website-configurable-waf-encryption/migration.d.ts +6 -0
- package/src/migrations/latest/static-website-configurable-waf-encryption/migration.js +418 -0
- package/src/migrations/latest/static-website-configurable-waf-encryption/migration.js.map +1 -0
- package/src/migrations/latest/user-identity-configurable-mfa/metadata.json +3 -0
- package/src/migrations/latest/user-identity-configurable-mfa/migration.d.ts +6 -0
- package/src/migrations/latest/user-identity-configurable-mfa/migration.js +362 -0
- package/src/migrations/latest/user-identity-configurable-mfa/migration.js.map +1 -0
- package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +90 -9
- package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +90 -9
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +259 -48
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +37 -7
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +26 -0
- package/src/utils/dynamodb-constructs/files/cdk/core/dynamodb.ts.template +23 -3
- package/src/utils/dynamodb-constructs/files/terraform/app/dynamodb/__nameKebabCase__/__nameKebabCase__.tf.template +27 -1
- package/src/utils/dynamodb-constructs/files/terraform/core/dynamodb/dynamodb.tf.template +39 -5
- package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +41 -7
- package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +48 -13
- package/src/utils/identity-constructs/files/terraform/core/user-identity/main.tf.template +26 -0
- package/src/utils/website-constructs/files/cdk/app/static-websites/__websiteNameKebabCase__.ts.template +11 -2
- package/src/utils/website-constructs/files/cdk/core/static-website.ts.template +47 -10
- package/src/utils/website-constructs/files/terraform/app/static-websites/__websiteNameKebabCase__/__websiteNameKebabCase__.tf.template +55 -0
- package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +57 -15
|
@@ -0,0 +1,325 @@
|
|
|
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
|
+
* Bring existing workspaces up to the generators' current DynamoDBTable
|
|
10
|
+
* configurability:
|
|
11
|
+
*
|
|
12
|
+
* - The vended `DynamoDBTable` CDK construct gains `encryption` and
|
|
13
|
+
* `encryptionKey` props. The KMS key is only created when `encryption` is
|
|
14
|
+
* `TableEncryption.CUSTOMER_MANAGED` and no `encryptionKey` is supplied.
|
|
15
|
+
* `enableKeyRotation` (already present) only applies to that auto-created
|
|
16
|
+
* key.
|
|
17
|
+
* - The generated per-table CDK app construct already forwards an optional
|
|
18
|
+
* `props` parameter through to `DynamoDBTable`, so no change is needed
|
|
19
|
+
* there.
|
|
20
|
+
* - The vended Terraform dynamodb core module gains the equivalent
|
|
21
|
+
* `encryption`, `kms_key_arn` and `create_kms_key` variables (`encryption`
|
|
22
|
+
* also accepts `DEFAULT`, the AWS owned key, which CDK already supports for
|
|
23
|
+
* free via `TableEncryption.DEFAULT`), with the KMS key made conditional on
|
|
24
|
+
* `create_kms_key` (not on `kms_key_arn`'s nullness, which `count` can't
|
|
25
|
+
* depend on when the ARN comes from another resource) and
|
|
26
|
+
* `server_side_encryption.enabled` tied to `encryption`. `AWS_MANAGED`
|
|
27
|
+
* resolves to the explicit `alias/aws/dynamodb` ARN rather than `null`,
|
|
28
|
+
* since the provider's `kms_key_arn` is Optional+Computed and a `null`
|
|
29
|
+
* there means "leave whatever is there", not "clear it".
|
|
30
|
+
* - The vended Terraform per-table app module (`app/dynamodb/<name>/<name>.tf`)
|
|
31
|
+
* gains pass-through `encryption`, `kms_key_arn` and `create_kms_key`
|
|
32
|
+
* variables forwarded to the core module call, matching its existing
|
|
33
|
+
* `enable_key_rotation` pass-through.
|
|
34
|
+
*
|
|
35
|
+
* These files are generated with `KeepExisting`, so without this an upgraded
|
|
36
|
+
* workspace has generators that support this configuration but vended files
|
|
37
|
+
* that don't. Diverged files are left untouched and reported via `nextSteps`.
|
|
38
|
+
*/ const CDK_DYNAMODB_FILE = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/core/dynamodb.ts`;
|
|
39
|
+
const TERRAFORM_DYNAMODB_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/dynamodb/dynamodb.tf`;
|
|
40
|
+
const TERRAFORM_DYNAMODB_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/dynamodb`;
|
|
41
|
+
const CDK_DIVERGED_MESSAGE = `${CDK_DYNAMODB_FILE}: has diverged from the generated shape - left untouched. To pick up the encryption and encryptionKey props, manually port them from the vended core/dynamodb.ts template (see the ts#dynamodb generator's DynamoDBTable construct).`;
|
|
42
|
+
const TERRAFORM_DIVERGED_MESSAGE = `${TERRAFORM_DYNAMODB_FILE}: has diverged from the generated shape - left untouched. To pick up the encryption and kms_key_arn variables, manually port them from the vended dynamodb.tf template (see the ts#dynamodb generator's dynamodb module).`;
|
|
43
|
+
const terraformAppDivergedMessage = (filePath)=>`${filePath}: has diverged from the generated shape - left untouched. To make encryption and kms_key_arn configurable from your root Terraform configuration, add pass-through variables here and forward them to the dynamodb_table module call (see the ts#dynamodb generator's dynamodb app template).`;
|
|
44
|
+
/**
|
|
45
|
+
* Surface encryption/encryptionKey on the vended DynamoDBTable CDK
|
|
46
|
+
* construct.
|
|
47
|
+
*/ const migrateCdkConstruct = async (tree, nextSteps)=>{
|
|
48
|
+
if (!tree.exists(CDK_DYNAMODB_FILE)) {
|
|
49
|
+
return; // This workspace has no CDK DynamoDBTable construct.
|
|
50
|
+
}
|
|
51
|
+
if (await matchGritQL(tree, CDK_DYNAMODB_FILE, '`readonly encryption?: TableEncryption`')) {
|
|
52
|
+
return; // Already migrated.
|
|
53
|
+
}
|
|
54
|
+
const ENABLE_KEY_ROTATION_FIELD = '`readonly enableKeyRotation?: boolean`';
|
|
55
|
+
// Matched as a literal so the new fields can be inserted *before* it (see
|
|
56
|
+
// below) — GritQL doesn't include a leading comment in the span it matches
|
|
57
|
+
// for the field itself, so anchoring on the field would strand this
|
|
58
|
+
// comment above the newly-inserted fields instead of above the field it
|
|
59
|
+
// actually documents.
|
|
60
|
+
const ENABLE_KEY_ROTATION_COMMENT = `\`/**
|
|
61
|
+
* Whether to enable automatic key rotation on the KMS key used to encrypt the table.
|
|
62
|
+
*
|
|
63
|
+
* @default true
|
|
64
|
+
*/\``;
|
|
65
|
+
const DESTRUCTURE_OLD = `\`{
|
|
66
|
+
tableName,
|
|
67
|
+
runtimeConfigKey,
|
|
68
|
+
billingMode = BillingMode.PAY_PER_REQUEST,
|
|
69
|
+
pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: true },
|
|
70
|
+
deletionProtection = true,
|
|
71
|
+
removalPolicy = RemovalPolicy.RETAIN,
|
|
72
|
+
enableKeyRotation = true,
|
|
73
|
+
...rest
|
|
74
|
+
}: DynamoDBTableProps\``;
|
|
75
|
+
const KEY_CREATION_OLD = "`const key = new Key(this, 'EncryptionKey', { enableKeyRotation });`";
|
|
76
|
+
const TABLE_ENCRYPTION_OLD = '`encryption: TableEncryption.CUSTOMER_MANAGED`';
|
|
77
|
+
const anchors = [
|
|
78
|
+
ENABLE_KEY_ROTATION_FIELD,
|
|
79
|
+
ENABLE_KEY_ROTATION_COMMENT,
|
|
80
|
+
DESTRUCTURE_OLD,
|
|
81
|
+
KEY_CREATION_OLD,
|
|
82
|
+
TABLE_ENCRYPTION_OLD
|
|
83
|
+
];
|
|
84
|
+
const allPresent = (await Promise.all(anchors.map((pattern)=>matchGritQL(tree, CDK_DYNAMODB_FILE, pattern)))).every(Boolean);
|
|
85
|
+
if (!allPresent) {
|
|
86
|
+
nextSteps.push(CDK_DIVERGED_MESSAGE);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
await addDestructuredImport(tree, CDK_DYNAMODB_FILE, [
|
|
90
|
+
'IKey'
|
|
91
|
+
], 'aws-cdk-lib/aws-kms');
|
|
92
|
+
// Inserted before the enableKeyRotation field's own leading comment (rather
|
|
93
|
+
// than before the bare field) so that comment stays directly above the
|
|
94
|
+
// field it documents, instead of being stranded above the new fields.
|
|
95
|
+
await insertViaGritQL(tree, CDK_DYNAMODB_FILE, `${ENABLE_KEY_ROTATION_COMMENT} as $comment => \`${GRIT_INSERT_PLACEHOLDER}
|
|
96
|
+
$comment\``, `/**
|
|
97
|
+
* Server-side encryption for the table.
|
|
98
|
+
*
|
|
99
|
+
* @default TableEncryption.CUSTOMER_MANAGED
|
|
100
|
+
*/
|
|
101
|
+
readonly encryption?: TableEncryption;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* KMS key used to encrypt the table. Only used when \`encryption\` is
|
|
105
|
+
* \`TableEncryption.CUSTOMER_MANAGED\`. When not provided, a new key is created.
|
|
106
|
+
*/
|
|
107
|
+
readonly encryptionKey?: IKey;
|
|
108
|
+
|
|
109
|
+
`);
|
|
110
|
+
// Update the enableKeyRotation field's own comment to note it now only
|
|
111
|
+
// applies to the auto-created key, matching the vended template exactly.
|
|
112
|
+
// Routed through the insert placeholder (rather than embedding the text
|
|
113
|
+
// directly in the GritQL rewrite) since the comment's own backticks would
|
|
114
|
+
// otherwise be parsed as GritQL code-block delimiters.
|
|
115
|
+
await insertViaGritQL(tree, CDK_DYNAMODB_FILE, `${ENABLE_KEY_ROTATION_COMMENT} => \`${GRIT_INSERT_PLACEHOLDER}\``, `/**
|
|
116
|
+
* Whether to enable automatic key rotation on the KMS key used to encrypt the table.
|
|
117
|
+
* Only applies when \`encryption\` is \`TableEncryption.CUSTOMER_MANAGED\` and no
|
|
118
|
+
* \`encryptionKey\` is supplied.
|
|
119
|
+
*
|
|
120
|
+
* @default true
|
|
121
|
+
*/`);
|
|
122
|
+
await applyGritQL(tree, CDK_DYNAMODB_FILE, `${DESTRUCTURE_OLD} => \`{
|
|
123
|
+
tableName,
|
|
124
|
+
runtimeConfigKey,
|
|
125
|
+
billingMode = BillingMode.PAY_PER_REQUEST,
|
|
126
|
+
pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: true },
|
|
127
|
+
deletionProtection = true,
|
|
128
|
+
removalPolicy = RemovalPolicy.RETAIN,
|
|
129
|
+
encryption = TableEncryption.CUSTOMER_MANAGED,
|
|
130
|
+
encryptionKey,
|
|
131
|
+
enableKeyRotation = true,
|
|
132
|
+
...rest
|
|
133
|
+
}: DynamoDBTableProps\``);
|
|
134
|
+
await applyGritQL(tree, CDK_DYNAMODB_FILE, `${KEY_CREATION_OLD} => \`const key: IKey | undefined =
|
|
135
|
+
encryption === TableEncryption.CUSTOMER_MANAGED
|
|
136
|
+
? (encryptionKey ?? new Key(this, 'EncryptionKey', { enableKeyRotation }))
|
|
137
|
+
: undefined;\``);
|
|
138
|
+
await applyGritQL(tree, CDK_DYNAMODB_FILE, `${TABLE_ENCRYPTION_OLD} => \`encryption\``);
|
|
139
|
+
};
|
|
140
|
+
const hcl = (pattern)=>`language hcl\n${pattern}`;
|
|
141
|
+
const NEW_TERRAFORM_VARIABLES_TEXT = [
|
|
142
|
+
'variable "encryption" {',
|
|
143
|
+
' description = "Server-side encryption for the table. One of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT (the AWS owned key, at no cost and with no key to manage). Changing this on an already-deployed table away from CUSTOMER_MANAGED requires a manual step first - see the ts#dynamodb generator docs."',
|
|
144
|
+
' type = string',
|
|
145
|
+
' default = "CUSTOMER_MANAGED"',
|
|
146
|
+
'',
|
|
147
|
+
' validation {',
|
|
148
|
+
' condition = contains(["CUSTOMER_MANAGED", "AWS_MANAGED", "DEFAULT"], var.encryption)',
|
|
149
|
+
' error_message = "encryption must be one of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT."',
|
|
150
|
+
' }',
|
|
151
|
+
'}',
|
|
152
|
+
'',
|
|
153
|
+
'variable "kms_key_arn" {',
|
|
154
|
+
' description = "ARN of an existing KMS key used to encrypt the table when encryption is CUSTOMER_MANAGED. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the DynamoDB service the necessary permissions in its own key policy."',
|
|
155
|
+
' type = string',
|
|
156
|
+
' default = null',
|
|
157
|
+
'}',
|
|
158
|
+
'',
|
|
159
|
+
'variable "create_kms_key" {',
|
|
160
|
+
' description = "Whether to create a KMS key for the table. Only applies when encryption is CUSTOMER_MANAGED. Set to false when supplying kms_key_arn."',
|
|
161
|
+
' type = bool',
|
|
162
|
+
' default = true',
|
|
163
|
+
'}',
|
|
164
|
+
'',
|
|
165
|
+
'variable "enable_key_rotation" {',
|
|
166
|
+
' description = "Whether to enable automatic key rotation on the KMS key used to encrypt the table. Only applies when encryption is CUSTOMER_MANAGED and create_kms_key is true."',
|
|
167
|
+
' type = bool',
|
|
168
|
+
' default = true',
|
|
169
|
+
'}'
|
|
170
|
+
].join('\n');
|
|
171
|
+
/**
|
|
172
|
+
* Surface encryption/kms_key_arn on the vended Terraform dynamodb core
|
|
173
|
+
* module.
|
|
174
|
+
*/ const migrateTerraformModule = async (tree, nextSteps)=>{
|
|
175
|
+
if (!tree.exists(TERRAFORM_DYNAMODB_FILE)) {
|
|
176
|
+
return; // This workspace has no Terraform dynamodb module.
|
|
177
|
+
}
|
|
178
|
+
if (await matchGritQL(tree, TERRAFORM_DYNAMODB_FILE, hcl('`variable "encryption" { $_ }`'))) {
|
|
179
|
+
return; // Already migrated.
|
|
180
|
+
}
|
|
181
|
+
const OLD_ENABLE_KEY_ROTATION_VAR = hcl([
|
|
182
|
+
'`variable "enable_key_rotation" {',
|
|
183
|
+
' description = "Whether to enable automatic key rotation on the KMS key used to encrypt the table."',
|
|
184
|
+
' type = bool',
|
|
185
|
+
' default = true',
|
|
186
|
+
'}`'
|
|
187
|
+
].join('\n'));
|
|
188
|
+
const LOCALS_BLOCK = hcl('`locals { $_ }`');
|
|
189
|
+
const DATA_PARTITION_LINE = hcl('`data "aws_partition" "current" {}`');
|
|
190
|
+
const KMS_KEY_BLOCK = hcl('`resource "aws_kms_key" "table" { $_ }`');
|
|
191
|
+
const SSE_ENABLED_LINE = hcl('`enabled = true`');
|
|
192
|
+
const SSE_KMS_KEY_LINE = hcl('`kms_key_arn = aws_kms_key.table.arn`');
|
|
193
|
+
const OUTPUT_VALUE_LINE = hcl('`value = aws_kms_key.table.arn`');
|
|
194
|
+
const OUTPUT_DESCRIPTION_LINE = hcl('`description = "ARN of the KMS key used to encrypt the DynamoDB table."`');
|
|
195
|
+
const anchors = [
|
|
196
|
+
OLD_ENABLE_KEY_ROTATION_VAR,
|
|
197
|
+
LOCALS_BLOCK,
|
|
198
|
+
DATA_PARTITION_LINE,
|
|
199
|
+
KMS_KEY_BLOCK,
|
|
200
|
+
SSE_ENABLED_LINE,
|
|
201
|
+
SSE_KMS_KEY_LINE,
|
|
202
|
+
OUTPUT_VALUE_LINE,
|
|
203
|
+
OUTPUT_DESCRIPTION_LINE
|
|
204
|
+
];
|
|
205
|
+
const allPresent = (await Promise.all(anchors.map((pattern)=>matchGritQL(tree, TERRAFORM_DYNAMODB_FILE, pattern)))).every(Boolean);
|
|
206
|
+
if (!allPresent) {
|
|
207
|
+
nextSteps.push(TERRAFORM_DIVERGED_MESSAGE);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// 1. Replace the enable_key_rotation variable with the new variables plus
|
|
211
|
+
// an updated enable_key_rotation description, in the same position.
|
|
212
|
+
await applyGritQL(tree, TERRAFORM_DYNAMODB_FILE, `${OLD_ENABLE_KEY_ROTATION_VAR} => \`${NEW_TERRAFORM_VARIABLES_TEXT.replace(/`/g, '\\`')}\``);
|
|
213
|
+
// 2. New locals, appended into the existing locals block.
|
|
214
|
+
await insertViaGritQL(tree, TERRAFORM_DYNAMODB_FILE, hcl(`\`locals { $body }\` => \`locals {\n $body\n\n ${GRIT_INSERT_PLACEHOLDER}\n}\``), [
|
|
215
|
+
'create_table_key = var.encryption == "CUSTOMER_MANAGED" && var.create_kms_key',
|
|
216
|
+
'table_kms_key_arn = (',
|
|
217
|
+
' var.encryption == "CUSTOMER_MANAGED" ? (local.create_table_key ? aws_kms_key.table[0].arn : var.kms_key_arn) :',
|
|
218
|
+
' var.encryption == "AWS_MANAGED" ? "arn:${data.aws_partition.current.partition}:kms:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:alias/aws/dynamodb" :',
|
|
219
|
+
' null',
|
|
220
|
+
')'
|
|
221
|
+
].join('\n '));
|
|
222
|
+
// 2b. Region is needed to resolve the AWS_MANAGED key's alias ARN above.
|
|
223
|
+
await insertViaGritQL(tree, TERRAFORM_DYNAMODB_FILE, `${DATA_PARTITION_LINE} => \`data "aws_partition" "current" {}\n${GRIT_INSERT_PLACEHOLDER}\``, 'data "aws_region" "current" {}');
|
|
224
|
+
// 3. Make the KMS key conditional on encryption/kms_key_arn.
|
|
225
|
+
await applyGritQL(tree, TERRAFORM_DYNAMODB_FILE, hcl(`\`resource "aws_kms_key" "table" { $body }\` => \`resource "aws_kms_key" "table" {
|
|
226
|
+
count = local.create_table_key ? 1 : 0
|
|
227
|
+
|
|
228
|
+
$body
|
|
229
|
+
}\``));
|
|
230
|
+
// 4. Only enable KMS-backed encryption when not using the AWS owned key.
|
|
231
|
+
await applyGritQL(tree, TERRAFORM_DYNAMODB_FILE, `${SSE_ENABLED_LINE} => \`enabled = var.encryption != "DEFAULT"\``);
|
|
232
|
+
// 4b. Checkov's CKV_AWS_119 only passes when server_side_encryption.enabled
|
|
233
|
+
// is the literal `true` — it can't resolve the conditional above, even
|
|
234
|
+
// though CUSTOMER_MANAGED (the default) always uses a real CMK. Same
|
|
235
|
+
// false-positive shape as CKV_AWS_139 on the vended RDS module.
|
|
236
|
+
await insertViaGritQL(tree, TERRAFORM_DYNAMODB_FILE, hcl(`\`resource "aws_dynamodb_table" "table" { $body }\` => \`resource "aws_dynamodb_table" "table" {\n ${GRIT_INSERT_PLACEHOLDER}\n $body\n}\``), '#checkov:skip=CKV_AWS_119:Encryption is configurable via var.encryption; checkov cannot resolve the conditional server_side_encryption.enabled expression');
|
|
237
|
+
// 5. Resolve the key ARN through the new local everywhere it's consumed.
|
|
238
|
+
await applyGritQL(tree, TERRAFORM_DYNAMODB_FILE, `${SSE_KMS_KEY_LINE} => \`kms_key_arn = local.table_kms_key_arn\``);
|
|
239
|
+
await applyGritQL(tree, TERRAFORM_DYNAMODB_FILE, `${OUTPUT_VALUE_LINE} => \`value = local.table_kms_key_arn\``);
|
|
240
|
+
await applyGritQL(tree, TERRAFORM_DYNAMODB_FILE, `${OUTPUT_DESCRIPTION_LINE} => \`description = "ARN of the KMS key used to encrypt the DynamoDB table, or null when using the AWS owned key (encryption = DEFAULT)."\``);
|
|
241
|
+
};
|
|
242
|
+
const NEW_APP_MODULE_VARIABLES_TEXT = [
|
|
243
|
+
'variable "encryption" {',
|
|
244
|
+
' description = "Server-side encryption for the table. One of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT (the AWS owned key, at no cost and with no key to manage). Changing this on an already-deployed table away from CUSTOMER_MANAGED requires a manual step first - see the ts#dynamodb generator docs."',
|
|
245
|
+
' type = string',
|
|
246
|
+
' default = "CUSTOMER_MANAGED"',
|
|
247
|
+
'',
|
|
248
|
+
' validation {',
|
|
249
|
+
' condition = contains(["CUSTOMER_MANAGED", "AWS_MANAGED", "DEFAULT"], var.encryption)',
|
|
250
|
+
' error_message = "encryption must be one of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT."',
|
|
251
|
+
' }',
|
|
252
|
+
'}',
|
|
253
|
+
'',
|
|
254
|
+
'variable "kms_key_arn" {',
|
|
255
|
+
' description = "ARN of an existing KMS key used to encrypt the table when encryption is CUSTOMER_MANAGED. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the DynamoDB service the necessary permissions in its own key policy."',
|
|
256
|
+
' type = string',
|
|
257
|
+
' default = null',
|
|
258
|
+
'}',
|
|
259
|
+
'',
|
|
260
|
+
'variable "create_kms_key" {',
|
|
261
|
+
' description = "Whether to create a KMS key for the table. Only applies when encryption is CUSTOMER_MANAGED. Set to false when supplying kms_key_arn."',
|
|
262
|
+
' type = bool',
|
|
263
|
+
' default = true',
|
|
264
|
+
'}',
|
|
265
|
+
'',
|
|
266
|
+
'variable "enable_key_rotation" {',
|
|
267
|
+
' description = "Whether to enable automatic key rotation on the KMS key used to encrypt the table. Only applies when encryption is CUSTOMER_MANAGED and create_kms_key is true."',
|
|
268
|
+
' type = bool',
|
|
269
|
+
' default = true',
|
|
270
|
+
'}'
|
|
271
|
+
].join('\n');
|
|
272
|
+
/**
|
|
273
|
+
* Surface the same configuration as pass-through variables on each vended
|
|
274
|
+
* per-table Terraform app module, matching its existing pass-through
|
|
275
|
+
* convention for enable_key_rotation.
|
|
276
|
+
*/ const migrateTerraformAppModules = async (tree, nextSteps)=>{
|
|
277
|
+
if (!tree.exists(TERRAFORM_DYNAMODB_APP_DIR)) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
for (const dirName of tree.children(TERRAFORM_DYNAMODB_APP_DIR)){
|
|
281
|
+
const filePath = joinPathFragments(TERRAFORM_DYNAMODB_APP_DIR, dirName, `${dirName}.tf`);
|
|
282
|
+
if (!tree.exists(filePath)) {
|
|
283
|
+
continue; // Not a dynamodb app module directory.
|
|
284
|
+
}
|
|
285
|
+
if (await matchGritQL(tree, filePath, hcl('`variable "encryption" { $_ }`'))) {
|
|
286
|
+
continue; // Already migrated.
|
|
287
|
+
}
|
|
288
|
+
const OLD_ENABLE_KEY_ROTATION_VAR = hcl([
|
|
289
|
+
'`variable "enable_key_rotation" {',
|
|
290
|
+
' description = "Whether to enable automatic key rotation on the KMS key used to encrypt the table."',
|
|
291
|
+
' type = bool',
|
|
292
|
+
' default = true',
|
|
293
|
+
'}`'
|
|
294
|
+
].join('\n'));
|
|
295
|
+
const MODULE_BLOCK = hcl('`module "dynamodb_table" { $_ }`');
|
|
296
|
+
const DELETION_PROTECTION_LINE = hcl('`deletion_protection_enabled = var.deletion_protection_enabled`');
|
|
297
|
+
const ready = (await Promise.all([
|
|
298
|
+
OLD_ENABLE_KEY_ROTATION_VAR,
|
|
299
|
+
MODULE_BLOCK,
|
|
300
|
+
DELETION_PROTECTION_LINE
|
|
301
|
+
].map((pattern)=>matchGritQL(tree, filePath, pattern)))).every(Boolean);
|
|
302
|
+
if (!ready) {
|
|
303
|
+
nextSteps.push(terraformAppDivergedMessage(filePath));
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
await applyGritQL(tree, filePath, `${OLD_ENABLE_KEY_ROTATION_VAR} => \`${NEW_APP_MODULE_VARIABLES_TEXT.replace(/`/g, '\\`')}\``);
|
|
307
|
+
await insertViaGritQL(tree, filePath, hcl(`\`deletion_protection_enabled = var.deletion_protection_enabled\` => \`deletion_protection_enabled = var.deletion_protection_enabled\n ${GRIT_INSERT_PLACEHOLDER}\``), [
|
|
308
|
+
'encryption = var.encryption',
|
|
309
|
+
'kms_key_arn = var.kms_key_arn',
|
|
310
|
+
'create_kms_key = var.create_kms_key'
|
|
311
|
+
].join('\n '));
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
export default async function migration(tree) {
|
|
315
|
+
const nextSteps = [];
|
|
316
|
+
await migrateCdkConstruct(tree, nextSteps);
|
|
317
|
+
await migrateTerraformModule(tree, nextSteps);
|
|
318
|
+
await migrateTerraformAppModules(tree, nextSteps);
|
|
319
|
+
await formatFilesInSubtree(tree);
|
|
320
|
+
return {
|
|
321
|
+
nextSteps
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/dynamodb-configurable-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';\nimport { formatFilesInSubtree } from '../../../utils/format';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants';\n\n/**\n * Bring existing workspaces up to the generators' current DynamoDBTable\n * configurability:\n *\n * - The vended `DynamoDBTable` CDK construct gains `encryption` and\n * `encryptionKey` props. The KMS key is only created when `encryption` is\n * `TableEncryption.CUSTOMER_MANAGED` and no `encryptionKey` is supplied.\n * `enableKeyRotation` (already present) only applies to that auto-created\n * key.\n * - The generated per-table CDK app construct already forwards an optional\n * `props` parameter through to `DynamoDBTable`, so no change is needed\n * there.\n * - The vended Terraform dynamodb core module gains the equivalent\n * `encryption`, `kms_key_arn` and `create_kms_key` variables (`encryption`\n * also accepts `DEFAULT`, the AWS owned key, which CDK already supports for\n * free via `TableEncryption.DEFAULT`), with the KMS key made conditional on\n * `create_kms_key` (not on `kms_key_arn`'s nullness, which `count` can't\n * depend on when the ARN comes from another resource) and\n * `server_side_encryption.enabled` tied to `encryption`. `AWS_MANAGED`\n * resolves to the explicit `alias/aws/dynamodb` ARN rather than `null`,\n * since the provider's `kms_key_arn` is Optional+Computed and a `null`\n * there means \"leave whatever is there\", not \"clear it\".\n * - The vended Terraform per-table app module (`app/dynamodb/<name>/<name>.tf`)\n * gains pass-through `encryption`, `kms_key_arn` and `create_kms_key`\n * variables forwarded to the core module call, matching its existing\n * `enable_key_rotation` pass-through.\n *\n * These files are generated with `KeepExisting`, so without this an upgraded\n * workspace has generators that support this configuration but vended files\n * that don't. Diverged files are left untouched and reported via `nextSteps`.\n */\n\nconst CDK_DYNAMODB_FILE = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/core/dynamodb.ts`;\nconst TERRAFORM_DYNAMODB_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/dynamodb/dynamodb.tf`;\nconst TERRAFORM_DYNAMODB_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/dynamodb`;\n\nconst CDK_DIVERGED_MESSAGE = `${CDK_DYNAMODB_FILE}: has diverged from the generated shape - left untouched. To pick up the encryption and encryptionKey props, manually port them from the vended core/dynamodb.ts template (see the ts#dynamodb generator's DynamoDBTable construct).`;\n\nconst TERRAFORM_DIVERGED_MESSAGE = `${TERRAFORM_DYNAMODB_FILE}: has diverged from the generated shape - left untouched. To pick up the encryption and kms_key_arn variables, manually port them from the vended dynamodb.tf template (see the ts#dynamodb generator's dynamodb module).`;\n\nconst terraformAppDivergedMessage = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To make encryption and kms_key_arn configurable from your root Terraform configuration, add pass-through variables here and forward them to the dynamodb_table module call (see the ts#dynamodb generator's dynamodb app template).`;\n\n/**\n * Surface encryption/encryptionKey on the vended DynamoDBTable CDK\n * construct.\n */\nconst migrateCdkConstruct = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(CDK_DYNAMODB_FILE)) {\n return; // This workspace has no CDK DynamoDBTable construct.\n }\n\n if (\n await matchGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n '`readonly encryption?: TableEncryption`',\n )\n ) {\n return; // Already migrated.\n }\n\n const ENABLE_KEY_ROTATION_FIELD = '`readonly enableKeyRotation?: boolean`';\n // Matched as a literal so the new fields can be inserted *before* it (see\n // below) — GritQL doesn't include a leading comment in the span it matches\n // for the field itself, so anchoring on the field would strand this\n // comment above the newly-inserted fields instead of above the field it\n // actually documents.\n const ENABLE_KEY_ROTATION_COMMENT = `\\`/**\n * Whether to enable automatic key rotation on the KMS key used to encrypt the table.\n *\n * @default true\n */\\``;\n const DESTRUCTURE_OLD = `\\`{\n tableName,\n runtimeConfigKey,\n billingMode = BillingMode.PAY_PER_REQUEST,\n pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: true },\n deletionProtection = true,\n removalPolicy = RemovalPolicy.RETAIN,\n enableKeyRotation = true,\n ...rest\n }: DynamoDBTableProps\\``;\n const KEY_CREATION_OLD =\n \"`const key = new Key(this, 'EncryptionKey', { enableKeyRotation });`\";\n const TABLE_ENCRYPTION_OLD = '`encryption: TableEncryption.CUSTOMER_MANAGED`';\n\n const anchors = [\n ENABLE_KEY_ROTATION_FIELD,\n ENABLE_KEY_ROTATION_COMMENT,\n DESTRUCTURE_OLD,\n KEY_CREATION_OLD,\n TABLE_ENCRYPTION_OLD,\n ];\n\n const allPresent = (\n await Promise.all(\n anchors.map((pattern) => matchGritQL(tree, CDK_DYNAMODB_FILE, pattern)),\n )\n ).every(Boolean);\n\n if (!allPresent) {\n nextSteps.push(CDK_DIVERGED_MESSAGE);\n return;\n }\n\n await addDestructuredImport(\n tree,\n CDK_DYNAMODB_FILE,\n ['IKey'],\n 'aws-cdk-lib/aws-kms',\n );\n\n // Inserted before the enableKeyRotation field's own leading comment (rather\n // than before the bare field) so that comment stays directly above the\n // field it documents, instead of being stranded above the new fields.\n await insertViaGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${ENABLE_KEY_ROTATION_COMMENT} as $comment => \\`${GRIT_INSERT_PLACEHOLDER}\n $comment\\``,\n `/**\n * Server-side encryption for the table.\n *\n * @default TableEncryption.CUSTOMER_MANAGED\n */\n readonly encryption?: TableEncryption;\n\n /**\n * KMS key used to encrypt the table. Only used when \\`encryption\\` is\n * \\`TableEncryption.CUSTOMER_MANAGED\\`. When not provided, a new key is created.\n */\n readonly encryptionKey?: IKey;\n\n `,\n );\n\n // Update the enableKeyRotation field's own comment to note it now only\n // applies to the auto-created key, matching the vended template exactly.\n // Routed through the insert placeholder (rather than embedding the text\n // directly in the GritQL rewrite) since the comment's own backticks would\n // otherwise be parsed as GritQL code-block delimiters.\n await insertViaGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${ENABLE_KEY_ROTATION_COMMENT} => \\`${GRIT_INSERT_PLACEHOLDER}\\``,\n `/**\n * Whether to enable automatic key rotation on the KMS key used to encrypt the table.\n * Only applies when \\`encryption\\` is \\`TableEncryption.CUSTOMER_MANAGED\\` and no\n * \\`encryptionKey\\` is supplied.\n *\n * @default true\n */`,\n );\n\n await applyGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${DESTRUCTURE_OLD} => \\`{\n tableName,\n runtimeConfigKey,\n billingMode = BillingMode.PAY_PER_REQUEST,\n pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: true },\n deletionProtection = true,\n removalPolicy = RemovalPolicy.RETAIN,\n encryption = TableEncryption.CUSTOMER_MANAGED,\n encryptionKey,\n enableKeyRotation = true,\n ...rest\n }: DynamoDBTableProps\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${KEY_CREATION_OLD} => \\`const key: IKey | undefined =\n encryption === TableEncryption.CUSTOMER_MANAGED\n ? (encryptionKey ?? new Key(this, 'EncryptionKey', { enableKeyRotation }))\n : undefined;\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${TABLE_ENCRYPTION_OLD} => \\`encryption\\``,\n );\n};\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\nconst NEW_TERRAFORM_VARIABLES_TEXT = [\n 'variable \"encryption\" {',\n ' description = \"Server-side encryption for the table. One of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT (the AWS owned key, at no cost and with no key to manage). Changing this on an already-deployed table away from CUSTOMER_MANAGED requires a manual step first - see the ts#dynamodb generator docs.\"',\n ' type = string',\n ' default = \"CUSTOMER_MANAGED\"',\n '',\n ' validation {',\n ' condition = contains([\"CUSTOMER_MANAGED\", \"AWS_MANAGED\", \"DEFAULT\"], var.encryption)',\n ' error_message = \"encryption must be one of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT.\"',\n ' }',\n '}',\n '',\n 'variable \"kms_key_arn\" {',\n ' description = \"ARN of an existing KMS key used to encrypt the table when encryption is CUSTOMER_MANAGED. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the DynamoDB service the necessary permissions in its own key policy.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"create_kms_key\" {',\n ' description = \"Whether to create a KMS key for the table. Only applies when encryption is CUSTOMER_MANAGED. Set to false when supplying kms_key_arn.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"enable_key_rotation\" {',\n ' description = \"Whether to enable automatic key rotation on the KMS key used to encrypt the table. Only applies when encryption is CUSTOMER_MANAGED and create_kms_key is true.\"',\n ' type = bool',\n ' default = true',\n '}',\n].join('\\n');\n\n/**\n * Surface encryption/kms_key_arn on the vended Terraform dynamodb core\n * module.\n */\nconst migrateTerraformModule = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(TERRAFORM_DYNAMODB_FILE)) {\n return; // This workspace has no Terraform dynamodb module.\n }\n\n if (\n await matchGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n hcl('`variable \"encryption\" { $_ }`'),\n )\n ) {\n return; // Already migrated.\n }\n\n const OLD_ENABLE_KEY_ROTATION_VAR = hcl(\n [\n '`variable \"enable_key_rotation\" {',\n ' description = \"Whether to enable automatic key rotation on the KMS key used to encrypt the table.\"',\n ' type = bool',\n ' default = true',\n '}`',\n ].join('\\n'),\n );\n const LOCALS_BLOCK = hcl('`locals { $_ }`');\n const DATA_PARTITION_LINE = hcl('`data \"aws_partition\" \"current\" {}`');\n const KMS_KEY_BLOCK = hcl('`resource \"aws_kms_key\" \"table\" { $_ }`');\n const SSE_ENABLED_LINE = hcl('`enabled = true`');\n const SSE_KMS_KEY_LINE = hcl('`kms_key_arn = aws_kms_key.table.arn`');\n const OUTPUT_VALUE_LINE = hcl('`value = aws_kms_key.table.arn`');\n const OUTPUT_DESCRIPTION_LINE = hcl(\n '`description = \"ARN of the KMS key used to encrypt the DynamoDB table.\"`',\n );\n\n const anchors = [\n OLD_ENABLE_KEY_ROTATION_VAR,\n LOCALS_BLOCK,\n DATA_PARTITION_LINE,\n KMS_KEY_BLOCK,\n SSE_ENABLED_LINE,\n SSE_KMS_KEY_LINE,\n OUTPUT_VALUE_LINE,\n OUTPUT_DESCRIPTION_LINE,\n ];\n\n const allPresent = (\n await Promise.all(\n anchors.map((pattern) =>\n matchGritQL(tree, TERRAFORM_DYNAMODB_FILE, pattern),\n ),\n )\n ).every(Boolean);\n\n if (!allPresent) {\n nextSteps.push(TERRAFORM_DIVERGED_MESSAGE);\n return;\n }\n\n // 1. Replace the enable_key_rotation variable with the new variables plus\n // an updated enable_key_rotation description, in the same position.\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${OLD_ENABLE_KEY_ROTATION_VAR} => \\`${NEW_TERRAFORM_VARIABLES_TEXT.replace(/`/g, '\\\\`')}\\``,\n );\n\n // 2. New locals, appended into the existing locals block.\n await insertViaGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n hcl(\n `\\`locals { $body }\\` => \\`locals {\\n $body\\n\\n ${GRIT_INSERT_PLACEHOLDER}\\n}\\``,\n ),\n [\n 'create_table_key = var.encryption == \"CUSTOMER_MANAGED\" && var.create_kms_key',\n 'table_kms_key_arn = (',\n ' var.encryption == \"CUSTOMER_MANAGED\" ? (local.create_table_key ? aws_kms_key.table[0].arn : var.kms_key_arn) :',\n ' var.encryption == \"AWS_MANAGED\" ? \"arn:${data.aws_partition.current.partition}:kms:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:alias/aws/dynamodb\" :',\n ' null',\n ')',\n ].join('\\n '),\n );\n\n // 2b. Region is needed to resolve the AWS_MANAGED key's alias ARN above.\n await insertViaGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${DATA_PARTITION_LINE} => \\`data \"aws_partition\" \"current\" {}\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n 'data \"aws_region\" \"current\" {}',\n );\n\n // 3. Make the KMS key conditional on encryption/kms_key_arn.\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n hcl(`\\`resource \"aws_kms_key\" \"table\" { $body }\\` => \\`resource \"aws_kms_key\" \"table\" {\n count = local.create_table_key ? 1 : 0\n\n $body\n}\\``),\n );\n\n // 4. Only enable KMS-backed encryption when not using the AWS owned key.\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${SSE_ENABLED_LINE} => \\`enabled = var.encryption != \"DEFAULT\"\\``,\n );\n\n // 4b. Checkov's CKV_AWS_119 only passes when server_side_encryption.enabled\n // is the literal `true` — it can't resolve the conditional above, even\n // though CUSTOMER_MANAGED (the default) always uses a real CMK. Same\n // false-positive shape as CKV_AWS_139 on the vended RDS module.\n await insertViaGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n hcl(\n `\\`resource \"aws_dynamodb_table\" \"table\" { $body }\\` => \\`resource \"aws_dynamodb_table\" \"table\" {\\n ${GRIT_INSERT_PLACEHOLDER}\\n $body\\n}\\``,\n ),\n '#checkov:skip=CKV_AWS_119:Encryption is configurable via var.encryption; checkov cannot resolve the conditional server_side_encryption.enabled expression',\n );\n\n // 5. Resolve the key ARN through the new local everywhere it's consumed.\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${SSE_KMS_KEY_LINE} => \\`kms_key_arn = local.table_kms_key_arn\\``,\n );\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${OUTPUT_VALUE_LINE} => \\`value = local.table_kms_key_arn\\``,\n );\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${OUTPUT_DESCRIPTION_LINE} => \\`description = \"ARN of the KMS key used to encrypt the DynamoDB table, or null when using the AWS owned key (encryption = DEFAULT).\"\\``,\n );\n};\n\nconst NEW_APP_MODULE_VARIABLES_TEXT = [\n 'variable \"encryption\" {',\n ' description = \"Server-side encryption for the table. One of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT (the AWS owned key, at no cost and with no key to manage). Changing this on an already-deployed table away from CUSTOMER_MANAGED requires a manual step first - see the ts#dynamodb generator docs.\"',\n ' type = string',\n ' default = \"CUSTOMER_MANAGED\"',\n '',\n ' validation {',\n ' condition = contains([\"CUSTOMER_MANAGED\", \"AWS_MANAGED\", \"DEFAULT\"], var.encryption)',\n ' error_message = \"encryption must be one of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT.\"',\n ' }',\n '}',\n '',\n 'variable \"kms_key_arn\" {',\n ' description = \"ARN of an existing KMS key used to encrypt the table when encryption is CUSTOMER_MANAGED. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the DynamoDB service the necessary permissions in its own key policy.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"create_kms_key\" {',\n ' description = \"Whether to create a KMS key for the table. Only applies when encryption is CUSTOMER_MANAGED. Set to false when supplying kms_key_arn.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"enable_key_rotation\" {',\n ' description = \"Whether to enable automatic key rotation on the KMS key used to encrypt the table. Only applies when encryption is CUSTOMER_MANAGED and create_kms_key is true.\"',\n ' type = bool',\n ' default = true',\n '}',\n].join('\\n');\n\n/**\n * Surface the same configuration as pass-through variables on each vended\n * per-table Terraform app module, matching its existing pass-through\n * convention for enable_key_rotation.\n */\nconst migrateTerraformAppModules = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(TERRAFORM_DYNAMODB_APP_DIR)) {\n return;\n }\n\n for (const dirName of tree.children(TERRAFORM_DYNAMODB_APP_DIR)) {\n const filePath = joinPathFragments(\n TERRAFORM_DYNAMODB_APP_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a dynamodb app module directory.\n }\n\n if (\n await matchGritQL(tree, filePath, hcl('`variable \"encryption\" { $_ }`'))\n ) {\n continue; // Already migrated.\n }\n\n const OLD_ENABLE_KEY_ROTATION_VAR = hcl(\n [\n '`variable \"enable_key_rotation\" {',\n ' description = \"Whether to enable automatic key rotation on the KMS key used to encrypt the table.\"',\n ' type = bool',\n ' default = true',\n '}`',\n ].join('\\n'),\n );\n const MODULE_BLOCK = hcl('`module \"dynamodb_table\" { $_ }`');\n const DELETION_PROTECTION_LINE = hcl(\n '`deletion_protection_enabled = var.deletion_protection_enabled`',\n );\n\n const ready = (\n await Promise.all(\n [\n OLD_ENABLE_KEY_ROTATION_VAR,\n MODULE_BLOCK,\n DELETION_PROTECTION_LINE,\n ].map((pattern) => matchGritQL(tree, filePath, pattern)),\n )\n ).every(Boolean);\n\n if (!ready) {\n nextSteps.push(terraformAppDivergedMessage(filePath));\n continue;\n }\n\n await applyGritQL(\n tree,\n filePath,\n `${OLD_ENABLE_KEY_ROTATION_VAR} => \\`${NEW_APP_MODULE_VARIABLES_TEXT.replace(/`/g, '\\\\`')}\\``,\n );\n\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`deletion_protection_enabled = var.deletion_protection_enabled\\` => \\`deletion_protection_enabled = var.deletion_protection_enabled\\n ${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n [\n 'encryption = var.encryption',\n 'kms_key_arn = var.kms_key_arn',\n 'create_kms_key = var.create_kms_key',\n ].join('\\n '),\n );\n }\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n await migrateCdkConstruct(tree, nextSteps);\n await migrateTerraformModule(tree, nextSteps);\n await migrateTerraformAppModules(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","CDK_DYNAMODB_FILE","TERRAFORM_DYNAMODB_FILE","TERRAFORM_DYNAMODB_APP_DIR","CDK_DIVERGED_MESSAGE","TERRAFORM_DIVERGED_MESSAGE","terraformAppDivergedMessage","filePath","migrateCdkConstruct","tree","nextSteps","exists","ENABLE_KEY_ROTATION_FIELD","ENABLE_KEY_ROTATION_COMMENT","DESTRUCTURE_OLD","KEY_CREATION_OLD","TABLE_ENCRYPTION_OLD","anchors","allPresent","Promise","all","map","pattern","every","Boolean","push","hcl","NEW_TERRAFORM_VARIABLES_TEXT","join","migrateTerraformModule","OLD_ENABLE_KEY_ROTATION_VAR","LOCALS_BLOCK","DATA_PARTITION_LINE","KMS_KEY_BLOCK","SSE_ENABLED_LINE","SSE_KMS_KEY_LINE","OUTPUT_VALUE_LINE","OUTPUT_DESCRIPTION_LINE","replace","NEW_APP_MODULE_VARIABLES_TEXT","migrateTerraformAppModules","dirName","children","MODULE_BLOCK","DELETION_PROTECTION_LINE","ready","migration"],"mappings":"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"}
|
|
@@ -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>;
|