@intentius/chant-lexicon-terraform 0.124.31 → 0.124.32
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/dist/__fixtures__/terragrunt/affected-cases.js +111 -0
- package/dist/__fixtures__/terragrunt/record.js +57 -0
- package/dist/behaviour/index.js +6 -0
- package/dist/behaviour/kinds.js +194 -0
- package/dist/behaviour/predict.js +297 -0
- package/dist/behaviour/reference-catalog.js +178 -0
- package/dist/behaviour/request.js +482 -0
- package/dist/change-set.js +234 -0
- package/dist/changed-roots.js +97 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +44 -0
- package/dist/codegen/generate-cli.js +9 -0
- package/dist/codegen/generate.js +59 -0
- package/dist/codegen/package.js +43 -0
- package/dist/commands.js +106 -0
- package/dist/components/capability-plugin.d.ts +38 -2
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability-plugin.js +260 -0
- package/dist/composites/terraform-adopt-op.js +152 -0
- package/dist/composites/terraform-apply-op.js +183 -0
- package/dist/composites/terraform-pin-rollout-op.js +45 -0
- package/dist/composites/terraform-watch-op.js +219 -0
- package/dist/config.js +118 -0
- package/dist/describe-resources.js +984 -0
- package/dist/generated/index.js +1 -0
- package/dist/hcl/descend.js +208 -0
- package/dist/hcl/edges.js +287 -0
- package/dist/hcl/jsonencode.js +442 -0
- package/dist/hcl/marker.js +212 -0
- package/dist/hcl/parse.js +372 -0
- package/dist/hcl/references.js +156 -0
- package/dist/hcl/roots.js +98 -0
- package/dist/hcl/suppressions.js +189 -0
- package/dist/hcl/value.js +72 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +501 -0
- package/dist/lint/audit-lineage.js +442 -0
- package/dist/lint/post-synth/blocks.js +87 -0
- package/dist/lint/post-synth/fixtures/load.js +94 -0
- package/dist/lint/post-synth/flat-attributes.js +135 -0
- package/dist/lint/post-synth/index.js +90 -0
- package/dist/lint/post-synth/module-source.js +153 -0
- package/dist/lint/post-synth/scope.js +104 -0
- package/dist/lint/post-synth/terragrunt.js +63 -0
- package/dist/lint/post-synth/tf001.js +138 -0
- package/dist/lint/post-synth/tf002.js +143 -0
- package/dist/lint/post-synth/tf003.js +60 -0
- package/dist/lint/post-synth/tf004.js +54 -0
- package/dist/lint/post-synth/tf005.js +68 -0
- package/dist/lint/post-synth/tf006.js +43 -0
- package/dist/lint/post-synth/tf007.js +66 -0
- package/dist/lint/post-synth/tf008.js +64 -0
- package/dist/lint/post-synth/tf009.js +49 -0
- package/dist/lint/post-synth/tf010.js +40 -0
- package/dist/lint/post-synth/tf011.js +38 -0
- package/dist/lint/post-synth/tf012.js +36 -0
- package/dist/lint/post-synth/tf013.js +53 -0
- package/dist/lint/post-synth/tf014.js +61 -0
- package/dist/lint/post-synth/tf015.js +62 -0
- package/dist/lint/post-synth/tf016.js +66 -0
- package/dist/lint/post-synth/tf017.js +39 -0
- package/dist/lint/post-synth/tf018.js +63 -0
- package/dist/lint/post-synth/tf019.js +60 -0
- package/dist/lint/post-synth/tf020.js +103 -0
- package/dist/lint/post-synth/tf021.js +117 -0
- package/dist/lint/post-synth/tf022.js +51 -0
- package/dist/lint/post-synth/tf024.js +62 -0
- package/dist/lint/post-synth/tf025.js +83 -0
- package/dist/lint/post-synth/tf026.js +74 -0
- package/dist/lint/post-synth/tf027.js +66 -0
- package/dist/lint/post-synth/tf028.js +83 -0
- package/dist/lint/post-synth/tf029.js +73 -0
- package/dist/lint/post-synth/tf030.js +253 -0
- package/dist/lint/post-synth/tf031.js +332 -0
- package/dist/lint/post-synth/tf032.js +201 -0
- package/dist/lint/post-synth/tf033.js +126 -0
- package/dist/lint/post-synth/tf034.js +45 -0
- package/dist/lint/post-synth/tf035.js +63 -0
- package/dist/lint/post-synth/tf036.js +97 -0
- package/dist/lint/post-synth/tf037.js +80 -0
- package/dist/lint/post-synth/tf038.js +63 -0
- package/dist/lint/post-synth/tf039.js +55 -0
- package/dist/lint/post-synth/tf040.js +67 -0
- package/dist/lint/post-synth/tf041.js +53 -0
- package/dist/lint/post-synth/tf042.js +38 -0
- package/dist/lint/post-synth/tf043.js +45 -0
- package/dist/lint/post-synth/tf044.js +45 -0
- package/dist/lint/post-synth/tf045.js +44 -0
- package/dist/lint/rules/fixtures/load.js +50 -0
- package/dist/lint/rules/index.js +4 -0
- package/dist/lint/rules/plan-before-apply.js +136 -0
- package/dist/lint/secret-shape.js +149 -0
- package/dist/live-ownership.js +57 -0
- package/dist/lsp/completions.js +95 -0
- package/dist/lsp/context.js +358 -0
- package/dist/lsp/hover.js +45 -0
- package/dist/lsp/option-keys.js +93 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/index.js +30 -0
- package/dist/op/activities/live-detect.js +143 -0
- package/dist/op/activities/pin-rollout.js +45 -0
- package/dist/op/activities/terraform.js +1270 -0
- package/dist/op/activity-contracts.js +162 -0
- package/dist/op/adoption.js +272 -0
- package/dist/op/builders.js +125 -0
- package/dist/op/resolve-root-mode.js +77 -0
- package/dist/package-cli.js +16 -0
- package/dist/pin/edit-ts.js +76 -0
- package/dist/pin/edit.js +118 -0
- package/dist/pin/forge.js +65 -0
- package/dist/pin/index.js +16 -0
- package/dist/pin/rollout.d.ts.map +1 -1
- package/dist/pin/rollout.js +382 -0
- package/dist/pin/scan.js +287 -0
- package/dist/pin/source.js +171 -0
- package/dist/pin/waves.js +109 -0
- package/dist/plan-digest.js +67 -0
- package/dist/plugin.js +323 -0
- package/dist/serializer.js +23 -0
- package/dist/state-ownership.js +29 -0
- package/dist/terragrunt/affected.d.ts.map +1 -1
- package/dist/terragrunt/affected.js +290 -0
- package/dist/terragrunt/index.js +10 -0
- package/dist/terragrunt/mocks.js +270 -0
- package/dist/terragrunt/run.js +264 -0
- package/dist/terragrunt/units.js +195 -0
- package/dist/terragrunt/wave.js +125 -0
- package/dist/validate-cli.js +4 -0
- package/dist/validate.js +31 -0
- package/package.json +10 -10
- package/src/codegen/package.ts +2 -2
- package/src/components/capability-plugin.test.ts +97 -2
- package/src/components/capability-plugin.ts +117 -12
- package/src/components/pr-loop.tofu.test.ts +51 -11
- package/src/index.ts +1 -0
- package/src/pin/rollout.ts +2 -1
- package/src/terragrunt/affected.test.ts +11 -0
- package/src/terragrunt/affected.ts +2 -1
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { applyLineage } from "@intentius/chant/audit/catalog";
|
|
2
|
+
import { terraformAuditLineage } from "./audit-lineage.js";
|
|
3
|
+
const HASHICORP_STYLE_VARIABLES = {
|
|
4
|
+
name: "HashiCorp Terraform style guide — Variables",
|
|
5
|
+
url: "https://developer.hashicorp.com/terraform/language/style#variables",
|
|
6
|
+
};
|
|
7
|
+
const AWS_IAM_LEAST_PRIVILEGE = {
|
|
8
|
+
name: "AWS IAM security best practices (Apply least-privilege permissions)",
|
|
9
|
+
url: "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege",
|
|
10
|
+
};
|
|
11
|
+
// TF032's authority: the ECS developer guide's own instruction for credentials,
|
|
12
|
+
// which is the remediation the rule's message gives.
|
|
13
|
+
const ECS_SENSITIVE_DATA = {
|
|
14
|
+
name: "Amazon ECS Developer Guide: Pass sensitive data to an Amazon ECS container",
|
|
15
|
+
url: "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html",
|
|
16
|
+
};
|
|
17
|
+
// TF033-TF037 (#2288) cite the AWS service guide page that states the
|
|
18
|
+
// encryption or immutability control each rule checks.
|
|
19
|
+
const AWS_RDS_ENCRYPTION = {
|
|
20
|
+
name: "Amazon RDS User Guide: Encrypting Amazon RDS resources",
|
|
21
|
+
url: "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Encryption.html",
|
|
22
|
+
};
|
|
23
|
+
const AWS_SNS_SSE = {
|
|
24
|
+
name: "Amazon SNS Developer Guide: Securing Amazon SNS data with server-side encryption",
|
|
25
|
+
url: "https://docs.aws.amazon.com/sns/latest/dg/sns-server-side-encryption.html",
|
|
26
|
+
};
|
|
27
|
+
const AWS_SQS_SSE = {
|
|
28
|
+
name: "Amazon SQS Developer Guide: Encryption at rest in Amazon SQS",
|
|
29
|
+
url: "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-server-side-encryption.html",
|
|
30
|
+
};
|
|
31
|
+
const AWS_EBS_ENCRYPTION = {
|
|
32
|
+
name: "Amazon EBS User Guide: Amazon EBS encryption",
|
|
33
|
+
url: "https://docs.aws.amazon.com/ebs/latest/userguide/ebs-encryption.html",
|
|
34
|
+
};
|
|
35
|
+
const AWS_ECR_TAG_IMMUTABILITY = {
|
|
36
|
+
name: "Amazon ECR User Guide: Preventing image tags from being overwritten",
|
|
37
|
+
url: "https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-tag-mutability.html",
|
|
38
|
+
};
|
|
39
|
+
// The vendor guides behind TF014, TF015 and TF021 are credited as lineage in
|
|
40
|
+
// ./audit-lineage.ts, not as `authority` here. `authority` is reserved for
|
|
41
|
+
// security rules that fail a merge (packages/core/src/audit/catalog.e2e.test.ts
|
|
42
|
+
// holds both halves of that invariant), and these three are correctness and
|
|
43
|
+
// best-practice rules (#2112).
|
|
44
|
+
/**
|
|
45
|
+
* The document WAW019 cites for the same finding in the aws lexicon, named
|
|
46
|
+
* without the em-dash its entry there carries (the docs prose lint counts them).
|
|
47
|
+
*/
|
|
48
|
+
const AWS_SECURITY_PILLAR = {
|
|
49
|
+
name: "AWS Well-Architected Framework, Security Pillar",
|
|
50
|
+
url: "https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html",
|
|
51
|
+
};
|
|
52
|
+
export const terraformAuditCatalog = {
|
|
53
|
+
TF001: {
|
|
54
|
+
id: "TF001",
|
|
55
|
+
tier: "merge-worthy",
|
|
56
|
+
fixKind: "guidance",
|
|
57
|
+
category: "correctness",
|
|
58
|
+
title: "Root module declares no remote backend",
|
|
59
|
+
remediation: 'Add a `backend "<type>"` block (s3, gcs, azurerm, http) or a `cloud {}` block to the root ' +
|
|
60
|
+
"module's terraform block, then `terraform init -migrate-state`.",
|
|
61
|
+
yamlBased: false,
|
|
62
|
+
},
|
|
63
|
+
TF024: {
|
|
64
|
+
id: "TF024",
|
|
65
|
+
tier: "merge-worthy",
|
|
66
|
+
fixKind: "guidance",
|
|
67
|
+
category: "correctness",
|
|
68
|
+
title: "Live root declares a backend or cloud block, which choudoufu refuses",
|
|
69
|
+
remediation: "Remove the `backend`/`cloud` block from the live root's terraform block; a live root's prior " +
|
|
70
|
+
"state is a projection rebuilt from the live system every run, so there is no state to store.",
|
|
71
|
+
yamlBased: false,
|
|
72
|
+
},
|
|
73
|
+
TF025: {
|
|
74
|
+
id: "TF025",
|
|
75
|
+
tier: "merge-worthy",
|
|
76
|
+
fixKind: "guidance",
|
|
77
|
+
category: "correctness",
|
|
78
|
+
title: "Live root references a non-default terraform.workspace, which choudoufu refuses",
|
|
79
|
+
remediation: "Remove `workspace` from the root's terraform.roots config, and remove any `terraform.workspace` " +
|
|
80
|
+
'reference from its HCL; choudoufu refuses any workspace but "default" on a live root.',
|
|
81
|
+
yamlBased: false,
|
|
82
|
+
},
|
|
83
|
+
TF026: {
|
|
84
|
+
id: "TF026",
|
|
85
|
+
tier: "merge-worthy",
|
|
86
|
+
fixKind: "guidance",
|
|
87
|
+
category: "correctness",
|
|
88
|
+
title: 'Live root declares delete: "never" but its policy leaves undeclared_tagged at "delete"',
|
|
89
|
+
remediation: 'Add undeclared_tagged = "keep" (or "untag" or "report") to the live root\'s policy block, or ' +
|
|
90
|
+
'change terraform.roots.<name>.delete to "owned-only" or "gated" if an owned orphan should be ' +
|
|
91
|
+
"deleted after all.",
|
|
92
|
+
yamlBased: false,
|
|
93
|
+
},
|
|
94
|
+
TF027: {
|
|
95
|
+
id: "TF027",
|
|
96
|
+
tier: "merge-worthy",
|
|
97
|
+
fixKind: "guidance",
|
|
98
|
+
category: "correctness",
|
|
99
|
+
title: 'Live root\'s policy block sets undeclared_untagged = "delete"',
|
|
100
|
+
remediation: 'Remove undeclared_untagged = "delete" from the live root\'s policy block. chant never proposes ' +
|
|
101
|
+
"deleting a resource it does not own; narrow the estate's own ownership answer " +
|
|
102
|
+
"(undeclared_tagged) instead of the account's.",
|
|
103
|
+
yamlBased: false,
|
|
104
|
+
},
|
|
105
|
+
TF028: {
|
|
106
|
+
id: "TF028",
|
|
107
|
+
tier: "merge-worthy",
|
|
108
|
+
fixKind: "guidance",
|
|
109
|
+
category: "correctness",
|
|
110
|
+
title: "Live root is watched by a TerraformWatchOp built without live: true",
|
|
111
|
+
remediation: "Set live: true on the TerraformWatchOp that names this root, so the Plan phase runs " +
|
|
112
|
+
"choudoufuLivePlan and reports the unowned and adoptable counts as well as drift. Drop the root's " +
|
|
113
|
+
"estate instead if it is not a live root.",
|
|
114
|
+
yamlBased: false,
|
|
115
|
+
},
|
|
116
|
+
TF029: {
|
|
117
|
+
id: "TF029",
|
|
118
|
+
tier: "merge-worthy",
|
|
119
|
+
fixKind: "guidance",
|
|
120
|
+
category: "correctness",
|
|
121
|
+
title: "Live root names its estate both in chant.config and in its own HCL",
|
|
122
|
+
remediation: "Pick one. A root whose directory is shared across environments declares no estate in HCL and " +
|
|
123
|
+
"names it per root with terraform.roots.<name>.estate; a root with its own live block already " +
|
|
124
|
+
"has an estate and needs nothing in chant.config. The declaration wins today, so the config " +
|
|
125
|
+
"value is written down and never used.",
|
|
126
|
+
yamlBased: false,
|
|
127
|
+
},
|
|
128
|
+
TF002: {
|
|
129
|
+
id: "TF002",
|
|
130
|
+
tier: "merge-worthy",
|
|
131
|
+
fixKind: "guidance",
|
|
132
|
+
category: "correctness",
|
|
133
|
+
title: "Provider implied by the root has no required_providers entry",
|
|
134
|
+
remediation: "Add the provider to the terraform block's `required_providers`, with both `source` and " +
|
|
135
|
+
"`version` set, so a future provider release can't silently change behavior.",
|
|
136
|
+
yamlBased: false,
|
|
137
|
+
},
|
|
138
|
+
TF003: {
|
|
139
|
+
id: "TF003",
|
|
140
|
+
tier: "merge-worthy",
|
|
141
|
+
fixKind: "guidance",
|
|
142
|
+
category: "correctness",
|
|
143
|
+
title: "Root module's terraform block has no required_version",
|
|
144
|
+
remediation: 'Set `required_version = ">= <lowest supported version>"` in the terraform block.',
|
|
145
|
+
yamlBased: false,
|
|
146
|
+
},
|
|
147
|
+
TF004: {
|
|
148
|
+
id: "TF004",
|
|
149
|
+
tier: "merge-worthy",
|
|
150
|
+
fixKind: "guidance",
|
|
151
|
+
category: "correctness",
|
|
152
|
+
title: "Registry-sourced module block has no version",
|
|
153
|
+
remediation: "Add a `version` constraint to the module block (e.g. `~> 5.0`).",
|
|
154
|
+
yamlBased: false,
|
|
155
|
+
},
|
|
156
|
+
TF005: {
|
|
157
|
+
id: "TF005",
|
|
158
|
+
tier: "merge-worthy",
|
|
159
|
+
fixKind: "guidance",
|
|
160
|
+
category: "correctness",
|
|
161
|
+
title: "Git/hg module source is unpinned, or pinned to a mutable ref",
|
|
162
|
+
remediation: "Pin the module's `?ref=` to a tag or a full commit SHA.",
|
|
163
|
+
yamlBased: false,
|
|
164
|
+
},
|
|
165
|
+
TF006: {
|
|
166
|
+
id: "TF006",
|
|
167
|
+
tier: "merge-worthy",
|
|
168
|
+
fixKind: "guidance",
|
|
169
|
+
category: "security",
|
|
170
|
+
title: "Default value on a sensitive variable",
|
|
171
|
+
remediation: "Delete the `default` and require the caller to supply the value at apply time, through a tfvars " +
|
|
172
|
+
"file kept out of version control, a `TF_VAR_` entry in the environment, or a workspace input.",
|
|
173
|
+
yamlBased: false,
|
|
174
|
+
},
|
|
175
|
+
TF007: {
|
|
176
|
+
id: "TF007",
|
|
177
|
+
tier: "merge-worthy",
|
|
178
|
+
fixKind: "guidance",
|
|
179
|
+
category: "security",
|
|
180
|
+
title: "Plaintext secret in a variable default or a locals value",
|
|
181
|
+
remediation: "Remove the literal, mark the variable `sensitive = true`, and read the value from a secret " +
|
|
182
|
+
"manager data source or an input at apply time, then rotate the credential, which is already " +
|
|
183
|
+
"in version control history.",
|
|
184
|
+
yamlBased: false,
|
|
185
|
+
},
|
|
186
|
+
TF008: {
|
|
187
|
+
id: "TF008",
|
|
188
|
+
tier: "merge-worthy",
|
|
189
|
+
fixKind: "guidance",
|
|
190
|
+
category: "security",
|
|
191
|
+
title: "Provider block configures a hardcoded credential",
|
|
192
|
+
remediation: "Delete the credential from the provider block and let the provider read it from the " +
|
|
193
|
+
"environment, a shared credentials file, or an OIDC role assumption. Rotate the credential.",
|
|
194
|
+
yamlBased: false,
|
|
195
|
+
},
|
|
196
|
+
TF009: {
|
|
197
|
+
id: "TF009",
|
|
198
|
+
tier: "merge-worthy",
|
|
199
|
+
fixKind: "guidance",
|
|
200
|
+
category: "security",
|
|
201
|
+
title: "Credential-named variable is not marked sensitive",
|
|
202
|
+
remediation: "Add `sensitive = true` to the variable so its value is not printed in plan and apply output. " +
|
|
203
|
+
"The value is still stored in plaintext in state, so keep the state remote and encrypted.",
|
|
204
|
+
authority: [HASHICORP_STYLE_VARIABLES],
|
|
205
|
+
yamlBased: false,
|
|
206
|
+
},
|
|
207
|
+
TF010: {
|
|
208
|
+
id: "TF010",
|
|
209
|
+
tier: "report-only",
|
|
210
|
+
fixKind: "guidance",
|
|
211
|
+
category: "best-practice",
|
|
212
|
+
title: "Variable declares no type constraint",
|
|
213
|
+
remediation: "Add a `type` to the variable (`string`, `number`, `bool`, `list(string)`, `object({...})`).",
|
|
214
|
+
yamlBased: false,
|
|
215
|
+
},
|
|
216
|
+
TF011: {
|
|
217
|
+
id: "TF011",
|
|
218
|
+
tier: "report-only",
|
|
219
|
+
fixKind: "guidance",
|
|
220
|
+
category: "best-practice",
|
|
221
|
+
title: "Undocumented variable",
|
|
222
|
+
remediation: "Add a `description` saying what the value is for and what a valid one looks like.",
|
|
223
|
+
yamlBased: false,
|
|
224
|
+
},
|
|
225
|
+
TF012: {
|
|
226
|
+
id: "TF012",
|
|
227
|
+
tier: "report-only",
|
|
228
|
+
fixKind: "guidance",
|
|
229
|
+
category: "best-practice",
|
|
230
|
+
title: "Undocumented output",
|
|
231
|
+
remediation: "Write a `description` for the output, covering what the value is and what a caller can rely on it for.",
|
|
232
|
+
yamlBased: false,
|
|
233
|
+
},
|
|
234
|
+
TF013: {
|
|
235
|
+
id: "TF013",
|
|
236
|
+
tier: "merge-worthy",
|
|
237
|
+
fixKind: "guidance",
|
|
238
|
+
category: "correctness",
|
|
239
|
+
title: "lifecycle ignore_changes is set to all",
|
|
240
|
+
remediation: "Replace `ignore_changes = all` with the list of attributes that genuinely change outside " +
|
|
241
|
+
"Terraform, so every other attribute is still reconciled.",
|
|
242
|
+
yamlBased: false,
|
|
243
|
+
},
|
|
244
|
+
TF014: {
|
|
245
|
+
id: "TF014",
|
|
246
|
+
tier: "merge-worthy",
|
|
247
|
+
fixKind: "guidance",
|
|
248
|
+
category: "correctness",
|
|
249
|
+
title: "Child module configures a provider block",
|
|
250
|
+
remediation: "Move the provider configuration to the root module and pass it into the module with " +
|
|
251
|
+
"`providers = { ... }`, leaving at most an `alias`-only block in the module.",
|
|
252
|
+
yamlBased: false,
|
|
253
|
+
},
|
|
254
|
+
TF015: {
|
|
255
|
+
id: "TF015",
|
|
256
|
+
tier: "merge-worthy",
|
|
257
|
+
fixKind: "guidance",
|
|
258
|
+
category: "correctness",
|
|
259
|
+
title: "Child module declares a backend or cloud block",
|
|
260
|
+
remediation: "Delete the `backend`/`cloud` block from the child module. State belongs to the root module " +
|
|
261
|
+
"that calls it, one state for the whole tree.",
|
|
262
|
+
yamlBased: false,
|
|
263
|
+
},
|
|
264
|
+
TF020: {
|
|
265
|
+
id: "TF020",
|
|
266
|
+
tier: "report-only",
|
|
267
|
+
fixKind: "guidance",
|
|
268
|
+
category: "best-practice",
|
|
269
|
+
title: "Declaration is never referenced in its module scope",
|
|
270
|
+
remediation: "Delete the variable, local, data source or aliased provider, or reference it where it was " +
|
|
271
|
+
"meant to be used.",
|
|
272
|
+
yamlBased: false,
|
|
273
|
+
},
|
|
274
|
+
TF021: {
|
|
275
|
+
id: "TF021",
|
|
276
|
+
tier: "report-only",
|
|
277
|
+
fixKind: "guidance",
|
|
278
|
+
category: "best-practice",
|
|
279
|
+
title: "count builds instance identities from count.index where for_each is safer",
|
|
280
|
+
remediation: "Switch to `for_each` over a map or set so each instance is addressed by a stable key, then " +
|
|
281
|
+
"`terraform state mv` the existing indexed instances onto their new keys.",
|
|
282
|
+
yamlBased: false,
|
|
283
|
+
},
|
|
284
|
+
TF016: {
|
|
285
|
+
id: "TF016",
|
|
286
|
+
tier: "report-only",
|
|
287
|
+
fixKind: "deterministic",
|
|
288
|
+
category: "best-practice",
|
|
289
|
+
title: "Attribute value is a quoted interpolation of a single expression",
|
|
290
|
+
remediation: 'Drop the quotes and the `${}`: write `x = var.y`, not `x = "${var.y}"`.',
|
|
291
|
+
yamlBased: false,
|
|
292
|
+
},
|
|
293
|
+
TF017: {
|
|
294
|
+
id: "TF017",
|
|
295
|
+
tier: "report-only",
|
|
296
|
+
fixKind: "guidance",
|
|
297
|
+
category: "best-practice",
|
|
298
|
+
title: "Module block uses depends_on",
|
|
299
|
+
remediation: "Remove `depends_on` from the module call and pass an attribute of the dependency into a module " +
|
|
300
|
+
"input, so Terraform derives the narrower edge itself.",
|
|
301
|
+
yamlBased: false,
|
|
302
|
+
},
|
|
303
|
+
TF018: {
|
|
304
|
+
id: "TF018",
|
|
305
|
+
tier: "report-only",
|
|
306
|
+
fixKind: "guidance",
|
|
307
|
+
category: "best-practice",
|
|
308
|
+
title: "Output value is a whole resource or data source",
|
|
309
|
+
remediation: "Return the attribute the caller needs (`.id`, `.arn`, `.endpoint`) instead of the whole block.",
|
|
310
|
+
yamlBased: false,
|
|
311
|
+
},
|
|
312
|
+
TF019: {
|
|
313
|
+
id: "TF019",
|
|
314
|
+
tier: "report-only",
|
|
315
|
+
fixKind: "deterministic",
|
|
316
|
+
category: "best-practice",
|
|
317
|
+
title: "Meta-argument explicitly set to its default of false",
|
|
318
|
+
remediation: "Deleting the line is the whole fix. `sensitive`, `ephemeral`, `prevent_destroy` and `create_before_destroy` are false unless set.",
|
|
319
|
+
yamlBased: false,
|
|
320
|
+
},
|
|
321
|
+
TF022: {
|
|
322
|
+
id: "TF022",
|
|
323
|
+
tier: "merge-worthy",
|
|
324
|
+
fixKind: "guidance",
|
|
325
|
+
category: "security",
|
|
326
|
+
title: "Credential-named resource attribute holds a plaintext literal",
|
|
327
|
+
remediation: "Replace the constant with a lookup, either an input the caller supplies or a data source " +
|
|
328
|
+
"pointed at whatever vault owns the credential, and rotate what was committed.",
|
|
329
|
+
yamlBased: false,
|
|
330
|
+
},
|
|
331
|
+
TF030: {
|
|
332
|
+
id: "TF030",
|
|
333
|
+
tier: "merge-worthy",
|
|
334
|
+
fixKind: "guidance",
|
|
335
|
+
category: "security",
|
|
336
|
+
title: "Security group rule allows unrestricted ingress on a sensitive port",
|
|
337
|
+
remediation: "Restrict the CIDR on SSH, RDP, MySQL and PostgreSQL ports to the sources that need them, or reach " +
|
|
338
|
+
"the host through a bastion, a VPN or SSM Session Manager.",
|
|
339
|
+
authority: [AWS_SECURITY_PILLAR],
|
|
340
|
+
yamlBased: false,
|
|
341
|
+
},
|
|
342
|
+
TF031: {
|
|
343
|
+
id: "TF031",
|
|
344
|
+
tier: "merge-worthy",
|
|
345
|
+
fixKind: "guidance",
|
|
346
|
+
category: "security",
|
|
347
|
+
title: "IAM policy allows a wildcard Action or Resource",
|
|
348
|
+
remediation: "Name the actions the workload calls and the ARNs it touches. For the few actions that accept only " +
|
|
349
|
+
'Resource "*" (`ecr:GetAuthorizationToken`, `sts:GetCallerIdentity`), keep that statement on its own and ' +
|
|
350
|
+
"suppress it with `# chant-ignore-block: TF031`. A policy TF031 reports as not determined is built from " +
|
|
351
|
+
"an expression; it is not a finding.",
|
|
352
|
+
authority: [AWS_IAM_LEAST_PRIVILEGE],
|
|
353
|
+
yamlBased: false,
|
|
354
|
+
},
|
|
355
|
+
TF032: {
|
|
356
|
+
id: "TF032",
|
|
357
|
+
tier: "merge-worthy",
|
|
358
|
+
fixKind: "guidance",
|
|
359
|
+
category: "security",
|
|
360
|
+
title: "ECS container definition passes a credential as a plaintext environment value",
|
|
361
|
+
remediation: "Move the value from the container's `environment` to its `secrets`, with a `valueFrom` naming a " +
|
|
362
|
+
"Secrets Manager secret or SSM parameter, and rotate the committed value.",
|
|
363
|
+
authority: [ECS_SENSITIVE_DATA],
|
|
364
|
+
yamlBased: false,
|
|
365
|
+
},
|
|
366
|
+
TF033: {
|
|
367
|
+
id: "TF033",
|
|
368
|
+
tier: "merge-worthy",
|
|
369
|
+
fixKind: "guidance",
|
|
370
|
+
category: "security",
|
|
371
|
+
title: "RDS DB instance or cluster storage is not encrypted",
|
|
372
|
+
remediation: "Set `storage_encrypted = true` (and `kms_key_id` for a customer managed key). An existing database cannot be " +
|
|
373
|
+
"encrypted in place: restore an encrypted copy of a snapshot. An Aurora cluster, or a database created from a " +
|
|
374
|
+
"snapshot or replica, is reported as not determined, not as a finding.",
|
|
375
|
+
authority: [AWS_RDS_ENCRYPTION],
|
|
376
|
+
yamlBased: false,
|
|
377
|
+
},
|
|
378
|
+
TF034: {
|
|
379
|
+
id: "TF034",
|
|
380
|
+
tier: "merge-worthy",
|
|
381
|
+
fixKind: "guidance",
|
|
382
|
+
category: "security",
|
|
383
|
+
title: "SNS topic has no server-side encryption",
|
|
384
|
+
remediation: 'Set `kms_master_key_id` to a customer managed KMS key, or to `"alias/aws/sns"` for the AWS managed one. No AWS ' +
|
|
385
|
+
"default encrypts a topic that names no key.",
|
|
386
|
+
authority: [AWS_SNS_SSE],
|
|
387
|
+
yamlBased: false,
|
|
388
|
+
},
|
|
389
|
+
TF035: {
|
|
390
|
+
id: "TF035",
|
|
391
|
+
tier: "merge-worthy",
|
|
392
|
+
fixKind: "guidance",
|
|
393
|
+
category: "security",
|
|
394
|
+
title: "SQS queue turns server-side encryption off",
|
|
395
|
+
remediation: "Remove `sqs_managed_sse_enabled = false` (new queues are encrypted with SSE-SQS by default), set it to `true`, " +
|
|
396
|
+
"or set `kms_master_key_id`. A queue that sets neither attribute is encrypted and is not reported.",
|
|
397
|
+
authority: [AWS_SQS_SSE],
|
|
398
|
+
yamlBased: false,
|
|
399
|
+
},
|
|
400
|
+
TF036: {
|
|
401
|
+
id: "TF036",
|
|
402
|
+
tier: "merge-worthy",
|
|
403
|
+
fixKind: "guidance",
|
|
404
|
+
category: "security",
|
|
405
|
+
title: "EBS volume does not ask for encryption",
|
|
406
|
+
remediation: "Set `encrypted = true` (and `kms_key_id` for a customer managed key). A volume that leaves `encrypted` unset " +
|
|
407
|
+
"depends on the Region's EBS encryption by default and is reported as not determined, not as a finding.",
|
|
408
|
+
authority: [AWS_EBS_ENCRYPTION],
|
|
409
|
+
yamlBased: false,
|
|
410
|
+
},
|
|
411
|
+
TF037: {
|
|
412
|
+
id: "TF037",
|
|
413
|
+
tier: "merge-worthy",
|
|
414
|
+
fixKind: "guidance",
|
|
415
|
+
category: "security",
|
|
416
|
+
title: "ECR repository allows mutable image tags",
|
|
417
|
+
remediation: 'Set `image_tag_mutability = "IMMUTABLE"`, or `"IMMUTABLE_WITH_EXCLUSION"` with an ' +
|
|
418
|
+
"`image_tag_mutability_exclusion_filter` naming the few moving tags (`latest`) that must stay mutable. The " +
|
|
419
|
+
"provider default is `MUTABLE`.",
|
|
420
|
+
authority: [AWS_ECR_TAG_IMMUTABILITY],
|
|
421
|
+
yamlBased: false,
|
|
422
|
+
},
|
|
423
|
+
TF038: {
|
|
424
|
+
id: "TF038",
|
|
425
|
+
tier: "merge-worthy",
|
|
426
|
+
fixKind: "guidance",
|
|
427
|
+
category: "correctness",
|
|
428
|
+
title: "OCI module source has no tag or digest, or a mutable tag",
|
|
429
|
+
remediation: "Pin the `oci://` source with `?digest=sha256:...` (a digest cannot move) or an exact version tag " +
|
|
430
|
+
"(`?tag=1.4.0`). A source with no tag pulls `latest`.",
|
|
431
|
+
yamlBased: false,
|
|
432
|
+
},
|
|
433
|
+
TF039: {
|
|
434
|
+
id: "TF039",
|
|
435
|
+
tier: "report-only",
|
|
436
|
+
fixKind: "guidance",
|
|
437
|
+
category: "best-practice",
|
|
438
|
+
title: "Registry module version is a range, not an exact version",
|
|
439
|
+
remediation: 'Set an exact `version` (`version = "1.4.0"`) for projects that pin modules. Off by default.',
|
|
440
|
+
yamlBased: false,
|
|
441
|
+
},
|
|
442
|
+
TF040: {
|
|
443
|
+
id: "TF040",
|
|
444
|
+
tier: "report-only",
|
|
445
|
+
fixKind: "guidance",
|
|
446
|
+
category: "best-practice",
|
|
447
|
+
title: "Root has no committed .terraform.lock.hcl",
|
|
448
|
+
remediation: "Run `terraform init` in the root and commit the `.terraform.lock.hcl` it writes. Off by default; " +
|
|
449
|
+
"checked only where the root's directory is known.",
|
|
450
|
+
yamlBased: false,
|
|
451
|
+
},
|
|
452
|
+
TF041: {
|
|
453
|
+
id: "TF041",
|
|
454
|
+
tier: "merge-worthy",
|
|
455
|
+
fixKind: "guidance",
|
|
456
|
+
category: "correctness",
|
|
457
|
+
title: "Terragrunt dependency lets mock_outputs stand in for apply",
|
|
458
|
+
remediation: 'Set `mock_outputs_allowed_terraform_commands = ["validate", "plan"]` on every `dependency` that sets `mock_outputs`. ' +
|
|
459
|
+
"Terragrunt reads a missing or empty list as every command, apply included.",
|
|
460
|
+
yamlBased: false,
|
|
461
|
+
},
|
|
462
|
+
TF042: {
|
|
463
|
+
id: "TF042",
|
|
464
|
+
tier: "merge-worthy",
|
|
465
|
+
fixKind: "guidance",
|
|
466
|
+
category: "correctness",
|
|
467
|
+
title: "Terragrunt dependency sets skip_outputs together with mock_outputs",
|
|
468
|
+
remediation: "Drop `skip_outputs`, or pass the values as `inputs`. With `skip_outputs = true` the upstream outputs are never read, so every run uses the mocks.",
|
|
469
|
+
yamlBased: false,
|
|
470
|
+
},
|
|
471
|
+
TF043: {
|
|
472
|
+
id: "TF043",
|
|
473
|
+
tier: "merge-worthy",
|
|
474
|
+
fixKind: "guidance",
|
|
475
|
+
category: "correctness",
|
|
476
|
+
title: "Terragrunt terraform.source names no version (git ref, tfr version or oci tag)",
|
|
477
|
+
remediation: "Pin the source: `?ref=` for git, `?version=` for `tfr://`, `?digest=` or `?tag=` for `oci://`.",
|
|
478
|
+
yamlBased: false,
|
|
479
|
+
},
|
|
480
|
+
TF044: {
|
|
481
|
+
id: "TF044",
|
|
482
|
+
tier: "merge-worthy",
|
|
483
|
+
fixKind: "guidance",
|
|
484
|
+
category: "correctness",
|
|
485
|
+
title: "Terragrunt config keeps state in a local backend",
|
|
486
|
+
remediation: 'Use a shared backend (S3, GCS) in `remote_state`. State written by `backend = "local"` stays on the runner and is lost with it.',
|
|
487
|
+
yamlBased: false,
|
|
488
|
+
},
|
|
489
|
+
TF045: {
|
|
490
|
+
id: "TF045",
|
|
491
|
+
tier: "report-only",
|
|
492
|
+
fixKind: "guidance",
|
|
493
|
+
category: "best-practice",
|
|
494
|
+
title: "No Terragrunt config sets terragrunt_version_constraint",
|
|
495
|
+
remediation: 'Set `terragrunt_version_constraint = ">= 1.1.0"` in the root config. Off by default; judged only across the configs scanned.',
|
|
496
|
+
yamlBased: false,
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
// Prior art credits, if any, live beside the rules in ./audit-lineage.ts (see
|
|
500
|
+
// core audit/prior-art.ts).
|
|
501
|
+
applyLineage(terraformAuditCatalog, terraformAuditLineage);
|