@aws/nx-plugin 1.0.0-rc.83 → 1.0.0-rc.85

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 (104) hide show
  1. package/generators.json +7 -0
  2. package/migrations.json +31 -1
  3. package/package.json +1 -1
  4. package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +51 -2
  5. package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +4 -5
  6. package/src/agentcore-harness/files/project/README.md.template +4 -5
  7. package/src/internal/test-matrix/generator.js +16 -0
  8. package/src/internal/test-matrix/generator.js.map +1 -1
  9. package/src/migrations/latest/agentcore-harness-no-tools-by-default/__snapshots__/migration.spec.ts.snap +79 -0
  10. package/src/migrations/latest/agentcore-harness-no-tools-by-default/metadata.json +3 -0
  11. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.d.ts +6 -0
  12. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js +87 -0
  13. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +1 -0
  14. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/__snapshots__/migration.spec.ts.snap +54 -0
  15. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/metadata.json +3 -0
  16. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.d.ts +6 -0
  17. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js +86 -0
  18. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
  19. package/src/migrations/latest/terraform-declare-used-providers/metadata.json +3 -0
  20. package/src/migrations/latest/terraform-declare-used-providers/migration.d.ts +6 -0
  21. package/src/migrations/latest/terraform-declare-used-providers/migration.js +147 -0
  22. package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +1 -0
  23. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/__snapshots__/migration.spec.ts.snap +521 -0
  24. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/metadata.json +3 -0
  25. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.d.ts +6 -0
  26. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js +504 -0
  27. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
  28. package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/metadata.json +3 -0
  29. package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.d.ts +6 -0
  30. package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js +146 -0
  31. package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js.map +1 -0
  32. package/src/migrations/latest/waf-log-group-cmk-encryption/__snapshots__/migration.spec.ts.snap +286 -0
  33. package/src/migrations/latest/waf-log-group-cmk-encryption/metadata.json +3 -0
  34. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.d.ts +6 -0
  35. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js +238 -0
  36. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +1 -0
  37. package/src/open-api/json-metadata/generator.d.ts +24 -0
  38. package/src/open-api/json-metadata/generator.js +37 -0
  39. package/src/open-api/json-metadata/generator.js.map +1 -0
  40. package/src/open-api/json-metadata/schema.d.js +6 -0
  41. package/src/open-api/json-metadata/schema.d.js.map +1 -0
  42. package/src/open-api/json-metadata/schema.d.ts +9 -0
  43. package/src/open-api/json-metadata/schema.json +20 -0
  44. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +12 -0
  45. package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +1875 -623
  46. package/src/py/fast-api/generator.js +2 -1
  47. package/src/py/fast-api/generator.js.map +1 -1
  48. package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  49. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  50. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +31 -2
  51. package/src/sdk/open-api.d.ts +2 -0
  52. package/src/sdk/open-api.js +2 -1
  53. package/src/sdk/open-api.js.map +1 -1
  54. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +1756 -457
  55. package/src/smithy/ts/api/generator.js +2 -1
  56. package/src/smithy/ts/api/generator.js.map +1 -1
  57. package/src/terraform/project/files/application/bootstrap/providers.tf.template +11 -0
  58. package/src/terraform/project/files/application/scripts/bootstrap-destroy.ts.template +111 -0
  59. package/src/terraform/project/files/application/src/providers.tf.template +6 -0
  60. package/src/terraform/project/files/checkov/checkov.yml.template +8 -0
  61. package/src/terraform/project/files/library/src/main.tf.template +9 -0
  62. package/src/terraform/project/generator.js +47 -4
  63. package/src/terraform/project/generator.js.map +1 -1
  64. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +1848 -614
  65. package/src/trpc/backend/files-operations/scripts/generate-operations.ts.template +40 -0
  66. package/src/trpc/backend/generator.js +10 -0
  67. package/src/trpc/backend/generator.js.map +1 -1
  68. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +8 -0
  69. package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +4 -0
  70. package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  71. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  72. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +62 -4
  73. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +4 -0
  74. package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +35 -8
  75. package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +4 -0
  76. package/src/utils/agent-core-constructs/files/cdk/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.ts.template +39 -1
  77. package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +8 -1
  78. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +57 -2
  79. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +258 -24
  80. package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +4 -0
  81. package/src/utils/api-constructs/api-constructs.js +11 -0
  82. package/src/utils/api-constructs/api-constructs.js.map +1 -1
  83. package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +251 -3
  84. package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +437 -2
  85. package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +60 -7
  86. package/src/utils/api-constructs/open-api-metadata.d.ts +20 -3
  87. package/src/utils/api-constructs/open-api-metadata.js +64 -8
  88. package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
  89. package/src/utils/api-constructs/trpc-operations-metadata.d.ts +21 -0
  90. package/src/utils/api-constructs/trpc-operations-metadata.js +55 -0
  91. package/src/utils/api-constructs/trpc-operations-metadata.js.map +1 -0
  92. package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  93. package/src/utils/files/terraform/src/metrics/providers.tf.template +12 -0
  94. package/src/utils/function-constructs/files/terraform/app/lambda-functions/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  95. package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +33 -8
  96. package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +4 -0
  97. package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +51 -2
  98. package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +18 -0
  99. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +15 -4
  100. package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +2 -2
  101. package/src/utils/rdb-constructs/rdb-constructs.d.ts +12 -0
  102. package/src/utils/rdb-constructs/rdb-constructs.js +24 -3
  103. package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
  104. package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +4 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ import { generateFiles, getProjects, joinPathFragments, OverwriteStrategy, updateProjectConfiguration } from "@nx/devkit";
5
+ import { TERRAFORM_PROJECT_GENERATOR_INFO } from "../../../terraform/project/generator.js";
6
+ import { applyGritQL, matchGritQL } from "../../../utils/ast.js";
7
+ import { formatFilesInSubtree } from "../../../utils/format.js";
8
+ import { kebabCase } from "../../../utils/names.js";
9
+ import { normalizeTargetKeyOrder } from "../../../utils/nx.js";
10
+ import { sortObjectKeys } from "../../../utils/object.js";
11
+ /**
12
+ * Terraform projects gained three fixes:
13
+ *
14
+ * - A `checkov` target carries the security scan, matching the name CDK
15
+ * infrastructure projects already use, so `nx run-many --target checkov` no
16
+ * longer skips Terraform projects. An existing `test` target is the user's,
17
+ * so it is left exactly as it is.
18
+ * - A `checkov.yml` gives Terraform users the same central place to configure
19
+ * skips that the CDK app has, wired in via `--config-file`.
20
+ * - `bootstrap-destroy` runs a script that resolves the region from the AWS SDK
21
+ * credential chain and passes it as `-var=aws_region`. The vended
22
+ * `aws_region` variable has no default, so the previous bare
23
+ * `terraform destroy` blocked forever on an input prompt in any non-TTY
24
+ * context.
25
+ * - Both vended `providers.tf` declare `required_version = ">= 1.0"`, matching
26
+ * every other `.tf` file the plugin vends, and the bootstrap one pins the AWS
27
+ * provider.
28
+ *
29
+ * How to write a migration:
30
+ * - https://nx.dev/docs/kb/migration-generators
31
+ * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject
32
+ */ const hcl = (pattern)=>`language hcl\n${pattern}`;
33
+ /** The scan command, as the pre-fix generator vended it on `test`. */ const CHECKOV_COMMAND_PATTERN = /uvx --from checkov==/;
34
+ const divergedTargetsStep = (projectName)=>`${projectName}: no 'test' target carrying the checkov scan was found, so no 'checkov' target was added - add one running checkov over the project so 'nx run-many --target checkov' includes this project.`;
35
+ const divergedProvidersStep = (filePath)=>`${filePath}: has diverged from the generated shape - left untouched. Add \`required_version = ">= 1.0"\` to its \`terraform\` block to pin the minimum Terraform version.`;
36
+ const divergedBootstrapDestroyStep = (projectName)=>`${projectName}: its 'bootstrap-destroy' target no longer matches the shape the generator produced - left untouched. Pass \`-var=aws_region=<region>\` to \`terraform destroy\`, otherwise it blocks on an input prompt when run without a TTY.`;
37
+ /**
38
+ * Add `required_version` to a `providers.tf` that omits it.
39
+ *
40
+ * The bootstrap copy has no `terraform` block at all, so it is created;
41
+ * the src copy has one holding `required_providers`, so it is added to.
42
+ */ const migrateRequiredVersion = async (tree, filePath, nextSteps)=>{
43
+ if (!tree.exists(filePath)) return;
44
+ // Already declared - nothing to do, whatever constraint the user chose.
45
+ if (await matchGritQL(tree, filePath, hcl('`required_version = $_`'))) {
46
+ return;
47
+ }
48
+ const hasRequiredProviders = await matchGritQL(tree, filePath, hcl('`terraform { $body }` where { $body <: contains `required_providers` }'));
49
+ if (hasRequiredProviders) {
50
+ await applyGritQL(tree, filePath, hcl('`terraform { $body }` => `terraform {\n required_version = ">= 1.0"\n\n $body\n}`'));
51
+ return;
52
+ }
53
+ // No `terraform` block: prepend one ahead of the aws provider block.
54
+ const providerBlock = hcl('`provider "aws" { $body }`');
55
+ if (!await matchGritQL(tree, filePath, providerBlock)) {
56
+ nextSteps.push(divergedProvidersStep(filePath));
57
+ return;
58
+ }
59
+ await applyGritQL(tree, filePath, hcl('`provider "aws" { $body }` => `terraform {\n required_version = ">= 1.0"\n}\n\nprovider "aws" {\n $body\n}`'));
60
+ };
61
+ export default async function migration(tree) {
62
+ const nextSteps = [];
63
+ for (const [projectName, project] of getProjects(tree)){
64
+ const generator = project.metadata?.generator;
65
+ if (generator !== TERRAFORM_PROJECT_GENERATOR_INFO.id) continue;
66
+ const targets = project.targets ?? {};
67
+ let changed = false;
68
+ // Add `checkov`, copying the scan from the `test` target the pre-fix
69
+ // generator put it on. `test` itself belongs to the user and is left as it
70
+ // is. Guarded on `checkov` being absent so a re-run is a no-op.
71
+ if (!targets.checkov) {
72
+ const command = targets.test?.options?.command;
73
+ if (typeof command !== 'string' || !CHECKOV_COMMAND_PATTERN.test(command)) {
74
+ nextSteps.push(divergedTargetsStep(projectName));
75
+ } else {
76
+ // The config file is vended below, so wire it in at the same time.
77
+ const withConfigFile = command.includes('--config-file') ? command : command.replace('checkov --directory', 'checkov --config-file ../checkov.yml --directory');
78
+ targets.checkov = normalizeTargetKeyOrder({
79
+ ...targets.test,
80
+ options: {
81
+ ...targets.test.options,
82
+ command: withConfigFile
83
+ }
84
+ });
85
+ changed = true;
86
+ }
87
+ }
88
+ // Point `bootstrap-destroy` at the vended script, which resolves the
89
+ // region rather than leaving terraform to prompt for it.
90
+ const bootstrapDestroy = targets['bootstrap-destroy'];
91
+ if (bootstrapDestroy) {
92
+ const scriptCommand = `tsx {projectRoot}/scripts/bootstrap-destroy.ts {projectRoot}`;
93
+ const alreadyMigrated = bootstrapDestroy.options?.commands?.[0] === scriptCommand;
94
+ if (!alreadyMigrated) {
95
+ const command = bootstrapDestroy.options?.command;
96
+ // Only rewrite the exact shape the generator produced.
97
+ const isVendedShape = typeof command === 'string' && /^terraform destroy -state=\S+bootstrap\.tfstate$/.test(command.trim());
98
+ if (!isVendedShape) {
99
+ nextSteps.push(divergedBootstrapDestroyStep(projectName));
100
+ } else {
101
+ targets['bootstrap-destroy'] = normalizeTargetKeyOrder({
102
+ executor: bootstrapDestroy.executor,
103
+ options: {
104
+ forwardAllArgs: true,
105
+ commands: [
106
+ scriptCommand
107
+ ],
108
+ cwd: '{workspaceRoot}'
109
+ }
110
+ });
111
+ changed = true;
112
+ }
113
+ }
114
+ }
115
+ if (changed) {
116
+ updateProjectConfiguration(tree, projectName, {
117
+ ...project,
118
+ targets: sortObjectKeys(targets)
119
+ });
120
+ }
121
+ // Vend the checkov config and the bootstrap-destroy script. KeepExisting
122
+ // leaves a user's own copy alone and makes a re-run a no-op.
123
+ generateFiles(tree, joinPathFragments(import.meta.dirname, '../../../terraform/project/files/checkov'), project.root, {}, {
124
+ overwriteStrategy: OverwriteStrategy.KeepExisting
125
+ });
126
+ // Only application projects have a bootstrap dir to destroy. The scripts
127
+ // dir is shared with the generator's templates, so `stateKeyPrefix` is
128
+ // resolved the same way it computes it; KeepExisting leaves the scripts
129
+ // that already exist untouched regardless.
130
+ if (tree.exists(joinPathFragments(project.root, 'bootstrap'))) {
131
+ generateFiles(tree, joinPathFragments(import.meta.dirname, '../../../terraform/project/files/application/scripts'), joinPathFragments(project.root, 'scripts'), {
132
+ stateKeyPrefix: kebabCase(projectName)
133
+ }, {
134
+ overwriteStrategy: OverwriteStrategy.KeepExisting
135
+ });
136
+ }
137
+ await migrateRequiredVersion(tree, joinPathFragments(project.root, 'src/providers.tf'), nextSteps);
138
+ await migrateRequiredVersion(tree, joinPathFragments(project.root, 'bootstrap/providers.tf'), nextSteps);
139
+ }
140
+ await formatFilesInSubtree(tree);
141
+ return {
142
+ nextSteps
143
+ };
144
+ }
145
+
146
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n generateFiles,\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n OverwriteStrategy,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { TERRAFORM_PROJECT_GENERATOR_INFO } from '../../../terraform/project/generator.js';\nimport { applyGritQL, matchGritQL } from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport { kebabCase } from '../../../utils/names.js';\nimport { normalizeTargetKeyOrder } from '../../../utils/nx.js';\nimport { sortObjectKeys } from '../../../utils/object.js';\n\n/**\n * Terraform projects gained three fixes:\n *\n * - A `checkov` target carries the security scan, matching the name CDK\n * infrastructure projects already use, so `nx run-many --target checkov` no\n * longer skips Terraform projects. An existing `test` target is the user's,\n * so it is left exactly as it is.\n * - A `checkov.yml` gives Terraform users the same central place to configure\n * skips that the CDK app has, wired in via `--config-file`.\n * - `bootstrap-destroy` runs a script that resolves the region from the AWS SDK\n * credential chain and passes it as `-var=aws_region`. The vended\n * `aws_region` variable has no default, so the previous bare\n * `terraform destroy` blocked forever on an input prompt in any non-TTY\n * context.\n * - Both vended `providers.tf` declare `required_version = \">= 1.0\"`, matching\n * every other `.tf` file the plugin vends, and the bootstrap one pins the AWS\n * provider.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n */\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\n/** The scan command, as the pre-fix generator vended it on `test`. */\nconst CHECKOV_COMMAND_PATTERN = /uvx --from checkov==/;\n\nconst divergedTargetsStep = (projectName: string) =>\n `${projectName}: no 'test' target carrying the checkov scan was found, so no 'checkov' target was added - add one running checkov over the project so 'nx run-many --target checkov' includes this project.`;\n\nconst divergedProvidersStep = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. Add \\`required_version = \">= 1.0\"\\` to its \\`terraform\\` block to pin the minimum Terraform version.`;\n\nconst divergedBootstrapDestroyStep = (projectName: string) =>\n `${projectName}: its 'bootstrap-destroy' target no longer matches the shape the generator produced - left untouched. Pass \\`-var=aws_region=<region>\\` to \\`terraform destroy\\`, otherwise it blocks on an input prompt when run without a TTY.`;\n\n/**\n * Add `required_version` to a `providers.tf` that omits it.\n *\n * The bootstrap copy has no `terraform` block at all, so it is created;\n * the src copy has one holding `required_providers`, so it is added to.\n */\nconst migrateRequiredVersion = async (\n tree: Tree,\n filePath: string,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(filePath)) return;\n\n // Already declared - nothing to do, whatever constraint the user chose.\n if (await matchGritQL(tree, filePath, hcl('`required_version = $_`'))) {\n return;\n }\n\n const hasRequiredProviders = await matchGritQL(\n tree,\n filePath,\n hcl(\n '`terraform { $body }` where { $body <: contains `required_providers` }',\n ),\n );\n\n if (hasRequiredProviders) {\n await applyGritQL(\n tree,\n filePath,\n hcl(\n '`terraform { $body }` => `terraform {\\n required_version = \">= 1.0\"\\n\\n $body\\n}`',\n ),\n );\n return;\n }\n\n // No `terraform` block: prepend one ahead of the aws provider block.\n const providerBlock = hcl('`provider \"aws\" { $body }`');\n if (!(await matchGritQL(tree, filePath, providerBlock))) {\n nextSteps.push(divergedProvidersStep(filePath));\n return;\n }\n\n await applyGritQL(\n tree,\n filePath,\n hcl(\n '`provider \"aws\" { $body }` => `terraform {\\n required_version = \">= 1.0\"\\n}\\n\\nprovider \"aws\" {\\n $body\\n}`',\n ),\n );\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const [projectName, project] of getProjects(tree)) {\n const generator = (project.metadata as { generator?: string } | undefined)\n ?.generator;\n if (generator !== TERRAFORM_PROJECT_GENERATOR_INFO.id) continue;\n\n const targets = project.targets ?? {};\n let changed = false;\n\n // Add `checkov`, copying the scan from the `test` target the pre-fix\n // generator put it on. `test` itself belongs to the user and is left as it\n // is. Guarded on `checkov` being absent so a re-run is a no-op.\n if (!targets.checkov) {\n const command = targets.test?.options?.command;\n\n if (\n typeof command !== 'string' ||\n !CHECKOV_COMMAND_PATTERN.test(command)\n ) {\n nextSteps.push(divergedTargetsStep(projectName));\n } else {\n // The config file is vended below, so wire it in at the same time.\n const withConfigFile = command.includes('--config-file')\n ? command\n : command.replace(\n 'checkov --directory',\n 'checkov --config-file ../checkov.yml --directory',\n );\n\n targets.checkov = normalizeTargetKeyOrder({\n ...targets.test,\n options: { ...targets.test.options, command: withConfigFile },\n });\n changed = true;\n }\n }\n\n // Point `bootstrap-destroy` at the vended script, which resolves the\n // region rather than leaving terraform to prompt for it.\n const bootstrapDestroy = targets['bootstrap-destroy'];\n if (bootstrapDestroy) {\n const scriptCommand = `tsx {projectRoot}/scripts/bootstrap-destroy.ts {projectRoot}`;\n const alreadyMigrated =\n bootstrapDestroy.options?.commands?.[0] === scriptCommand;\n\n if (!alreadyMigrated) {\n const command = bootstrapDestroy.options?.command;\n // Only rewrite the exact shape the generator produced.\n const isVendedShape =\n typeof command === 'string' &&\n /^terraform destroy -state=\\S+bootstrap\\.tfstate$/.test(\n command.trim(),\n );\n\n if (!isVendedShape) {\n nextSteps.push(divergedBootstrapDestroyStep(projectName));\n } else {\n targets['bootstrap-destroy'] = normalizeTargetKeyOrder({\n executor: bootstrapDestroy.executor,\n options: {\n forwardAllArgs: true,\n commands: [scriptCommand],\n cwd: '{workspaceRoot}',\n },\n });\n changed = true;\n }\n }\n }\n\n if (changed) {\n updateProjectConfiguration(tree, projectName, {\n ...project,\n targets: sortObjectKeys(targets),\n });\n }\n\n // Vend the checkov config and the bootstrap-destroy script. KeepExisting\n // leaves a user's own copy alone and makes a re-run a no-op.\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n '../../../terraform/project/files/checkov',\n ),\n project.root,\n {},\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n\n // Only application projects have a bootstrap dir to destroy. The scripts\n // dir is shared with the generator's templates, so `stateKeyPrefix` is\n // resolved the same way it computes it; KeepExisting leaves the scripts\n // that already exist untouched regardless.\n if (tree.exists(joinPathFragments(project.root, 'bootstrap'))) {\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n '../../../terraform/project/files/application/scripts',\n ),\n joinPathFragments(project.root, 'scripts'),\n { stateKeyPrefix: kebabCase(projectName) },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n }\n\n await migrateRequiredVersion(\n tree,\n joinPathFragments(project.root, 'src/providers.tf'),\n nextSteps,\n );\n await migrateRequiredVersion(\n tree,\n joinPathFragments(project.root, 'bootstrap/providers.tf'),\n nextSteps,\n );\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["generateFiles","getProjects","joinPathFragments","OverwriteStrategy","updateProjectConfiguration","TERRAFORM_PROJECT_GENERATOR_INFO","applyGritQL","matchGritQL","formatFilesInSubtree","kebabCase","normalizeTargetKeyOrder","sortObjectKeys","hcl","pattern","CHECKOV_COMMAND_PATTERN","divergedTargetsStep","projectName","divergedProvidersStep","filePath","divergedBootstrapDestroyStep","migrateRequiredVersion","tree","nextSteps","exists","hasRequiredProviders","providerBlock","push","migration","project","generator","metadata","id","targets","changed","checkov","command","test","options","withConfigFile","includes","replace","bootstrapDestroy","scriptCommand","alreadyMigrated","commands","isVendedShape","trim","executor","forwardAllArgs","cwd","dirname","root","overwriteStrategy","KeepExisting","stateKeyPrefix"],"mappings":"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"}
@@ -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>;