@intentius/chant-lexicon-terraform 0.57.0 → 0.59.0
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/README.md +9 -0
- package/dist/composites/terraform-adopt-op.d.ts +131 -0
- package/dist/composites/terraform-adopt-op.d.ts.map +1 -0
- package/dist/composites/terraform-apply-op.d.ts +70 -17
- package/dist/composites/terraform-apply-op.d.ts.map +1 -1
- package/dist/composites/terraform-watch-op.d.ts +104 -37
- package/dist/composites/terraform-watch-op.d.ts.map +1 -1
- package/dist/config.d.ts +49 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/describe-resources.d.ts +256 -16
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/hcl/descend.d.ts +98 -0
- package/dist/hcl/descend.d.ts.map +1 -0
- package/dist/hcl/marker.d.ts +130 -0
- package/dist/hcl/marker.d.ts.map +1 -0
- package/dist/hcl/parse.d.ts +156 -9
- package/dist/hcl/parse.d.ts.map +1 -1
- package/dist/hcl/references.d.ts +76 -0
- package/dist/hcl/references.d.ts.map +1 -0
- package/dist/hcl/roots.d.ts +22 -3
- package/dist/hcl/roots.d.ts.map +1 -1
- package/dist/hcl/suppressions.d.ts +54 -0
- package/dist/hcl/suppressions.d.ts.map +1 -0
- package/dist/hcl/value.d.ts +77 -0
- package/dist/hcl/value.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +34 -5
- package/dist/lint/audit-catalog.d.ts +3 -2
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/audit-lineage.d.ts +95 -0
- package/dist/lint/audit-lineage.d.ts.map +1 -1
- package/dist/lint/post-synth/blocks.d.ts +53 -0
- package/dist/lint/post-synth/blocks.d.ts.map +1 -0
- package/dist/lint/post-synth/fixtures/load.d.ts +37 -0
- package/dist/lint/post-synth/fixtures/load.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/module-source.d.ts +38 -0
- package/dist/lint/post-synth/module-source.d.ts.map +1 -0
- package/dist/lint/post-synth/scope.d.ts +65 -0
- package/dist/lint/post-synth/scope.d.ts.map +1 -0
- package/dist/lint/post-synth/tf001.d.ts +58 -6
- package/dist/lint/post-synth/tf001.d.ts.map +1 -1
- package/dist/lint/post-synth/tf002.d.ts +32 -0
- package/dist/lint/post-synth/tf002.d.ts.map +1 -0
- package/dist/lint/post-synth/tf003.d.ts +22 -0
- package/dist/lint/post-synth/tf003.d.ts.map +1 -0
- package/dist/lint/post-synth/tf004.d.ts +22 -0
- package/dist/lint/post-synth/tf004.d.ts.map +1 -0
- package/dist/lint/post-synth/tf005.d.ts +28 -0
- package/dist/lint/post-synth/tf005.d.ts.map +1 -0
- package/dist/lint/post-synth/tf006.d.ts +21 -0
- package/dist/lint/post-synth/tf006.d.ts.map +1 -0
- package/dist/lint/post-synth/tf007.d.ts +21 -0
- package/dist/lint/post-synth/tf007.d.ts.map +1 -0
- package/dist/lint/post-synth/tf008.d.ts +22 -0
- package/dist/lint/post-synth/tf008.d.ts.map +1 -0
- package/dist/lint/post-synth/tf009.d.ts +24 -0
- package/dist/lint/post-synth/tf009.d.ts.map +1 -0
- package/dist/lint/post-synth/tf010.d.ts +19 -0
- package/dist/lint/post-synth/tf010.d.ts.map +1 -0
- package/dist/lint/post-synth/tf011.d.ts +18 -0
- package/dist/lint/post-synth/tf011.d.ts.map +1 -0
- package/dist/lint/post-synth/tf012.d.ts +17 -0
- package/dist/lint/post-synth/tf012.d.ts.map +1 -0
- package/dist/lint/post-synth/tf013.d.ts +21 -0
- package/dist/lint/post-synth/tf013.d.ts.map +1 -0
- package/dist/lint/post-synth/tf014.d.ts +25 -0
- package/dist/lint/post-synth/tf014.d.ts.map +1 -0
- package/dist/lint/post-synth/tf015.d.ts +30 -0
- package/dist/lint/post-synth/tf015.d.ts.map +1 -0
- package/dist/lint/post-synth/tf016.d.ts +30 -0
- package/dist/lint/post-synth/tf016.d.ts.map +1 -0
- package/dist/lint/post-synth/tf017.d.ts +19 -0
- package/dist/lint/post-synth/tf017.d.ts.map +1 -0
- package/dist/lint/post-synth/tf018.d.ts +20 -0
- package/dist/lint/post-synth/tf018.d.ts.map +1 -0
- package/dist/lint/post-synth/tf019.d.ts +25 -0
- package/dist/lint/post-synth/tf019.d.ts.map +1 -0
- package/dist/lint/post-synth/tf020.d.ts +38 -0
- package/dist/lint/post-synth/tf020.d.ts.map +1 -0
- package/dist/lint/post-synth/tf021.d.ts +41 -0
- package/dist/lint/post-synth/tf021.d.ts.map +1 -0
- package/dist/lint/post-synth/tf022.d.ts +24 -0
- package/dist/lint/post-synth/tf022.d.ts.map +1 -0
- package/dist/lint/post-synth/tf024.d.ts +24 -0
- package/dist/lint/post-synth/tf024.d.ts.map +1 -0
- package/dist/lint/post-synth/tf025.d.ts +29 -0
- package/dist/lint/post-synth/tf025.d.ts.map +1 -0
- package/dist/lint/post-synth/tf026.d.ts +28 -0
- package/dist/lint/post-synth/tf026.d.ts.map +1 -0
- package/dist/lint/post-synth/tf027.d.ts +30 -0
- package/dist/lint/post-synth/tf027.d.ts.map +1 -0
- package/dist/lint/post-synth/tf028.d.ts +33 -0
- package/dist/lint/post-synth/tf028.d.ts.map +1 -0
- package/dist/lint/rules/fixtures/load.d.ts +13 -0
- package/dist/lint/rules/fixtures/load.d.ts.map +1 -0
- package/dist/lint/rules/plan-before-apply.d.ts +11 -0
- package/dist/lint/rules/plan-before-apply.d.ts.map +1 -1
- package/dist/lint/secret-shape.d.ts +68 -0
- package/dist/lint/secret-shape.d.ts.map +1 -0
- package/dist/live-ownership.d.ts +55 -0
- package/dist/live-ownership.d.ts.map +1 -0
- package/dist/lsp/option-keys.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +26 -0
- package/dist/okf/rules/TF002.md +11 -0
- package/dist/okf/rules/TF003.md +11 -0
- package/dist/okf/rules/TF004.md +11 -0
- package/dist/okf/rules/TF005.md +11 -0
- package/dist/okf/rules/TF006.md +11 -0
- package/dist/okf/rules/TF007.md +11 -0
- package/dist/okf/rules/TF008.md +11 -0
- package/dist/okf/rules/TF009.md +11 -0
- package/dist/okf/rules/TF010.md +11 -0
- package/dist/okf/rules/TF011.md +11 -0
- package/dist/okf/rules/TF012.md +11 -0
- package/dist/okf/rules/TF013.md +11 -0
- package/dist/okf/rules/TF014.md +11 -0
- package/dist/okf/rules/TF015.md +11 -0
- package/dist/okf/rules/TF016.md +11 -0
- package/dist/okf/rules/TF017.md +11 -0
- package/dist/okf/rules/TF018.md +11 -0
- package/dist/okf/rules/TF019.md +11 -0
- package/dist/okf/rules/TF020.md +11 -0
- package/dist/okf/rules/TF021.md +11 -0
- package/dist/okf/rules/TF022.md +11 -0
- package/dist/okf/rules/TF024.md +11 -0
- package/dist/okf/rules/TF025.md +11 -0
- package/dist/okf/rules/TF026.md +11 -0
- package/dist/okf/rules/TF027.md +11 -0
- package/dist/okf/rules/TF028.md +11 -0
- package/dist/op/activities/index.d.ts +13 -6
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/live-detect.d.ts +51 -0
- package/dist/op/activities/live-detect.d.ts.map +1 -0
- package/dist/op/activities/terraform.d.ts +466 -17
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activity-contracts.d.ts +148 -0
- package/dist/op/activity-contracts.d.ts.map +1 -0
- package/dist/op/adoption.d.ts +118 -0
- package/dist/op/adoption.d.ts.map +1 -0
- package/dist/op/builders.d.ts +48 -6
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/resolve-root-mode.d.ts +59 -0
- package/dist/op/resolve-root-mode.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/blocks.ts +109 -0
- package/dist/rules/module-source.ts +121 -0
- package/dist/rules/plan-before-apply.ts +11 -0
- package/dist/rules/scope.ts +107 -0
- package/dist/rules/tf001.ts +98 -18
- package/dist/rules/tf002.ts +151 -0
- package/dist/rules/tf003.ts +71 -0
- package/dist/rules/tf004.ts +63 -0
- package/dist/rules/tf005.ts +76 -0
- package/dist/rules/tf006.ts +53 -0
- package/dist/rules/tf007.ts +78 -0
- package/dist/rules/tf008.ts +75 -0
- package/dist/rules/tf009.ts +59 -0
- package/dist/rules/tf010.ts +51 -0
- package/dist/rules/tf011.ts +49 -0
- package/dist/rules/tf012.ts +47 -0
- package/dist/rules/tf013.ts +63 -0
- package/dist/rules/tf014.ts +72 -0
- package/dist/rules/tf015.ts +72 -0
- package/dist/rules/tf016.ts +75 -0
- package/dist/rules/tf017.ts +50 -0
- package/dist/rules/tf018.ts +72 -0
- package/dist/rules/tf019.ts +72 -0
- package/dist/rules/tf020.ts +123 -0
- package/dist/rules/tf021.ts +126 -0
- package/dist/rules/tf022.ts +61 -0
- package/dist/rules/tf024.ts +70 -0
- package/dist/rules/tf025.ts +101 -0
- package/dist/rules/tf026.ts +84 -0
- package/dist/rules/tf027.ts +74 -0
- package/dist/rules/tf028.ts +96 -0
- package/dist/skills/chant-terraform.md +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/__fixtures__/ACCEPTANCE.md +181 -0
- package/src/__fixtures__/live/main.tf +28 -0
- package/src/__fixtures__/live-adopt/estate.chdf.hcl +6 -0
- package/src/__fixtures__/live-adopt/main.tf +21 -0
- package/src/__fixtures__/live-estate/README.md +58 -0
- package/src/__fixtures__/live-estate/compute.tf +10 -0
- package/src/__fixtures__/live-estate/estate.chdf.hcl +1 -0
- package/src/__fixtures__/live-estate/logs.tf +8 -0
- package/src/__fixtures__/live-estate/network.tf +24 -0
- package/src/__fixtures__/live-estate/rule.tf +11 -0
- package/src/__fixtures__/live-estate/unowned.tf +21 -0
- package/src/__fixtures__/live-estate/versions.tf +31 -0
- package/src/__fixtures__/live-ls.json +98 -0
- package/src/__fixtures__/live-plan.json +285 -0
- package/src/__fixtures__/live-sidecar/estate.chdf.hcl +3 -0
- package/src/__fixtures__/live-sidecar/main.tf +24 -0
- package/src/__fixtures__/module-tree/README.md +16 -0
- package/src/__fixtures__/module-tree/cycle/main.tf +3 -0
- package/src/__fixtures__/module-tree/cycle/modules/a/main.tf +9 -0
- package/src/__fixtures__/module-tree/cycle/modules/b/main.tf +9 -0
- package/src/__fixtures__/module-tree/missing/main.tf +9 -0
- package/src/__fixtures__/module-tree/outside/root/main.tf +9 -0
- package/src/__fixtures__/module-tree/root/main.tf +28 -0
- package/src/__fixtures__/module-tree/root/modules/cdn/main.tf +15 -0
- package/src/__fixtures__/module-tree/root/modules/cdn/modules/bucket/main.tf +11 -0
- package/src/__fixtures__/module-tree/shared/main.tf +5 -0
- package/src/__fixtures__/references/main.tf +56 -0
- package/src/__fixtures__/remote-backend/main.tf +34 -0
- package/src/__fixtures__/with-backend/main.tf +7 -0
- package/src/codegen/lint-rules-page.test.ts +45 -0
- package/src/composites/terraform-adopt-op.acceptance.test.ts +193 -0
- package/src/composites/terraform-adopt-op.test.ts +175 -0
- package/src/composites/terraform-adopt-op.ts +202 -0
- package/src/composites/terraform-apply-op.acceptance.test.ts +188 -5
- package/src/composites/terraform-apply-op.test.ts +179 -19
- package/src/composites/terraform-apply-op.ts +123 -30
- package/src/composites/terraform-watch-op.test.ts +169 -29
- package/src/composites/terraform-watch-op.ts +183 -75
- package/src/config.ts +32 -3
- package/src/describe-resources.live.test.ts +598 -0
- package/src/describe-resources.test.ts +53 -0
- package/src/describe-resources.ts +811 -28
- package/src/hcl/descend.test.ts +162 -0
- package/src/hcl/descend.ts +273 -0
- package/src/hcl/marker.test.ts +147 -0
- package/src/hcl/marker.ts +235 -0
- package/src/hcl/parse.test.ts +69 -0
- package/src/hcl/parse.ts +269 -27
- package/src/hcl/references.test.ts +116 -0
- package/src/hcl/references.ts +172 -0
- package/src/hcl/roots.ts +59 -5
- package/src/hcl/suppressions.test.ts +147 -0
- package/src/hcl/suppressions.ts +204 -0
- package/src/hcl/value.test.ts +73 -0
- package/src/hcl/value.ts +106 -0
- package/src/index.ts +31 -1
- package/src/lint/audit-catalog.ts +288 -3
- package/src/lint/audit-lineage.ts +384 -1
- package/src/lint/audit.test.ts +210 -3
- package/src/lint/post-synth/blocks.ts +109 -0
- package/src/lint/post-synth/fixtures/TF001/negative-cloud.tf +28 -0
- package/src/lint/post-synth/fixtures/TF001/negative.tf +30 -0
- package/src/lint/post-synth/fixtures/TF001/positive-local.tf +31 -0
- package/src/lint/post-synth/fixtures/TF001/positive.tf +24 -0
- package/src/lint/post-synth/fixtures/TF002/negative.tf +17 -0
- package/src/lint/post-synth/fixtures/TF002/positive-legacy.tf +10 -0
- package/src/lint/post-synth/fixtures/TF002/positive-no-version.tf +12 -0
- package/src/lint/post-synth/fixtures/TF002/positive.tf +7 -0
- package/src/lint/post-synth/fixtures/TF003/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF003/positive.tf +8 -0
- package/src/lint/post-synth/fixtures/TF004/negative-git.tf +3 -0
- package/src/lint/post-synth/fixtures/TF004/negative-local.tf +3 -0
- package/src/lint/post-synth/fixtures/TF004/negative.tf +4 -0
- package/src/lint/post-synth/fixtures/TF004/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative-local.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative-registry.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative-sha.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/positive-branch.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/positive-non-semver.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF006/negative.tf +5 -0
- package/src/lint/post-synth/fixtures/TF006/positive-empty-default.tf +11 -0
- package/src/lint/post-synth/fixtures/TF006/positive.tf +6 -0
- package/src/lint/post-synth/fixtures/TF007/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF007/positive.tf +9 -0
- package/src/lint/post-synth/fixtures/TF008/negative-variable.tf +10 -0
- package/src/lint/post-synth/fixtures/TF008/negative.tf +8 -0
- package/src/lint/post-synth/fixtures/TF008/positive-other-provider.tf +10 -0
- package/src/lint/post-synth/fixtures/TF008/positive.tf +5 -0
- package/src/lint/post-synth/fixtures/TF009/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF009/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF010/negative.tf +5 -0
- package/src/lint/post-synth/fixtures/TF010/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF011/negative.tf +5 -0
- package/src/lint/post-synth/fixtures/TF011/positive-blank.tf +5 -0
- package/src/lint/post-synth/fixtures/TF011/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF012/negative.tf +4 -0
- package/src/lint/post-synth/fixtures/TF012/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF013/negative.tf +8 -0
- package/src/lint/post-synth/fixtures/TF013/positive-data.tf +8 -0
- package/src/lint/post-synth/fixtures/TF013/positive.tf +8 -0
- package/src/lint/post-synth/fixtures/TF014/negative/main.tf +26 -0
- package/src/lint/post-synth/fixtures/TF014/negative/modules/cdn/main.tf +19 -0
- package/src/lint/post-synth/fixtures/TF014/positive/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF014/positive/modules/cdn/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF015/negative/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF015/negative/modules/cdn/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF015/positive/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF015/positive/modules/cdn/main.tf +14 -0
- package/src/lint/post-synth/fixtures/TF016/negative-template.tf +7 -0
- package/src/lint/post-synth/fixtures/TF016/negative.tf +16 -0
- package/src/lint/post-synth/fixtures/TF016/positive-shared-file.tf +12 -0
- package/src/lint/post-synth/fixtures/TF016/positive.tf +13 -0
- package/src/lint/post-synth/fixtures/TF017/negative.tf +4 -0
- package/src/lint/post-synth/fixtures/TF017/positive.tf +5 -0
- package/src/lint/post-synth/fixtures/TF018/negative.tf +9 -0
- package/src/lint/post-synth/fixtures/TF018/positive-data.tf +9 -0
- package/src/lint/post-synth/fixtures/TF018/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF019/negative.tf +14 -0
- package/src/lint/post-synth/fixtures/TF019/positive-output.tf +5 -0
- package/src/lint/post-synth/fixtures/TF019/positive.tf +16 -0
- package/src/lint/post-synth/fixtures/TF020/negative.tf +43 -0
- package/src/lint/post-synth/fixtures/TF020/positive.tf +32 -0
- package/src/lint/post-synth/fixtures/TF020/scoped/main.tf +21 -0
- package/src/lint/post-synth/fixtures/TF020/scoped/modules/cdn/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF021/negative.tf +31 -0
- package/src/lint/post-synth/fixtures/TF021/positive-length.tf +8 -0
- package/src/lint/post-synth/fixtures/TF021/positive.tf +8 -0
- package/src/lint/post-synth/fixtures/TF022/negative-locator.tf +7 -0
- package/src/lint/post-synth/fixtures/TF022/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF022/positive-nested.tf +10 -0
- package/src/lint/post-synth/fixtures/TF022/positive.tf +6 -0
- package/src/lint/post-synth/fixtures/TF023/negative/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF023/positive/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF023/positive/state.json +24 -0
- package/src/lint/post-synth/fixtures/TF024/live-clean/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF024/live-with-backend/main.tf +14 -0
- package/src/lint/post-synth/fixtures/TF024/live-with-cloud/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF025/live-clean/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF025/live-with-workspace-ref/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF026/live-default/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF026/live-explicit-delete/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF026/live-keep/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF027/live-default/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF027/live-untagged-delete/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF027/live-untagged-delete-scoped/main.tf +17 -0
- package/src/lint/post-synth/fixtures/TF027/live-untagged-report/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF028/live/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF028/stock/main.tf +10 -0
- package/src/lint/post-synth/fixtures/load.ts +108 -0
- package/src/lint/post-synth/index.ts +52 -0
- package/src/lint/post-synth/live-refusals-config-format.test.ts +143 -0
- package/src/lint/post-synth/module-rules.test.ts +153 -0
- package/src/lint/post-synth/module-source.test.ts +75 -0
- package/src/lint/post-synth/module-source.ts +121 -0
- package/src/lint/post-synth/post-synth.test.ts +73 -16
- package/src/lint/post-synth/scope.ts +107 -0
- package/src/lint/post-synth/single-block.test.ts +263 -0
- package/src/lint/post-synth/suppression.test.ts +110 -0
- package/src/lint/post-synth/tf001.ts +98 -18
- package/src/lint/post-synth/tf002.test.ts +93 -0
- package/src/lint/post-synth/tf002.ts +151 -0
- package/src/lint/post-synth/tf003.test.ts +75 -0
- package/src/lint/post-synth/tf003.ts +71 -0
- package/src/lint/post-synth/tf004.test.ts +47 -0
- package/src/lint/post-synth/tf004.ts +63 -0
- package/src/lint/post-synth/tf005.test.ts +57 -0
- package/src/lint/post-synth/tf005.ts +76 -0
- package/src/lint/post-synth/tf006.ts +53 -0
- package/src/lint/post-synth/tf007.ts +78 -0
- package/src/lint/post-synth/tf008.ts +75 -0
- package/src/lint/post-synth/tf009.ts +59 -0
- package/src/lint/post-synth/tf010.ts +51 -0
- package/src/lint/post-synth/tf011.ts +49 -0
- package/src/lint/post-synth/tf012.ts +47 -0
- package/src/lint/post-synth/tf013.ts +63 -0
- package/src/lint/post-synth/tf014.ts +72 -0
- package/src/lint/post-synth/tf015.ts +72 -0
- package/src/lint/post-synth/tf016.ts +75 -0
- package/src/lint/post-synth/tf017.ts +50 -0
- package/src/lint/post-synth/tf018.ts +72 -0
- package/src/lint/post-synth/tf019.ts +72 -0
- package/src/lint/post-synth/tf020.ts +123 -0
- package/src/lint/post-synth/tf021.ts +126 -0
- package/src/lint/post-synth/tf022.ts +61 -0
- package/src/lint/post-synth/tf024.test.ts +42 -0
- package/src/lint/post-synth/tf024.ts +70 -0
- package/src/lint/post-synth/tf025.test.ts +46 -0
- package/src/lint/post-synth/tf025.ts +101 -0
- package/src/lint/post-synth/tf026.test.ts +56 -0
- package/src/lint/post-synth/tf026.ts +84 -0
- package/src/lint/post-synth/tf027.test.ts +62 -0
- package/src/lint/post-synth/tf027.ts +74 -0
- package/src/lint/post-synth/tf028.test.ts +92 -0
- package/src/lint/post-synth/tf028.ts +96 -0
- package/src/lint/presets.test.ts +72 -0
- package/src/lint/rules/fixtures/TF101/negative-op.ts +2 -0
- package/src/lint/rules/fixtures/TF101/positive-op.ts +2 -0
- package/src/lint/rules/fixtures/load.ts +53 -0
- package/src/lint/rules/plan-before-apply.test.ts +112 -7
- package/src/lint/rules/plan-before-apply.ts +11 -0
- package/src/lint/secret-shape.test.ts +150 -0
- package/src/lint/secret-shape.ts +159 -0
- package/src/live-ownership.ts +60 -0
- package/src/lsp/completions.test.ts +3 -2
- package/src/lsp/option-keys.ts +12 -6
- package/src/op/activities/choudoufu.acceptance.test.ts +127 -0
- package/src/op/activities/choudoufu.test.ts +725 -0
- package/src/op/activities/index.ts +40 -4
- package/src/op/activities/live-detect.test.ts +143 -0
- package/src/op/activities/live-detect.ts +162 -0
- package/src/op/activities/registry.test.ts +11 -3
- package/src/op/activities/terraform.test.ts +7 -3
- package/src/op/activities/terraform.ts +861 -69
- package/src/op/activity-contracts.test.ts +147 -0
- package/src/op/activity-contracts.ts +178 -0
- package/src/op/adoption.test.ts +170 -0
- package/src/op/adoption.ts +241 -0
- package/src/op/builders.test.ts +60 -5
- package/src/op/builders.ts +95 -5
- package/src/op/resolve-root-mode.test.ts +85 -0
- package/src/op/resolve-root-mode.ts +86 -0
- package/src/plugin.test.ts +106 -0
- package/src/plugin.ts +186 -29
- package/src/skills/chant-terraform.md +1 -1
- package/src/validate.ts +10 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { tf004 } from "./tf004";
|
|
3
|
+
import { tf005 } from "./tf005";
|
|
4
|
+
import { loadFixture } from "./fixtures/load";
|
|
5
|
+
|
|
6
|
+
describe("TF005: git/hg module source is unpinned, or pinned to a mutable ref", () => {
|
|
7
|
+
test("check metadata", () => {
|
|
8
|
+
expect(tf005.id).toBe("TF005");
|
|
9
|
+
expect(tf005.description).toContain("Git/hg");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("flags an unpinned git source", async () => {
|
|
13
|
+
const diags = tf005.check(await loadFixture("TF005", "positive"));
|
|
14
|
+
expect(diags).toHaveLength(1);
|
|
15
|
+
expect(diags[0].checkId).toBe("TF005");
|
|
16
|
+
expect(diags[0].message).toContain("no `?ref=`");
|
|
17
|
+
expect(diags[0].entity).toBe("TF005/module.vpc");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("flags a ref pinned to a default branch", async () => {
|
|
21
|
+
const diags = tf005.check(await loadFixture("TF005", "positive-branch"));
|
|
22
|
+
expect(diags).toHaveLength(1);
|
|
23
|
+
expect(diags[0].message).toContain("mutable branch");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("flags a ref that is neither a tag nor a full SHA (stricter than checkov's \\d\\.\\d)", async () => {
|
|
27
|
+
const diags = tf005.check(await loadFixture("TF005", "positive-non-semver"));
|
|
28
|
+
expect(diags).toHaveLength(1);
|
|
29
|
+
expect(diags[0].message).toContain("neither a tag nor a full commit SHA");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("passes a ref pinned to a semver tag", async () => {
|
|
33
|
+
const diags = tf005.check(await loadFixture("TF005", "negative"));
|
|
34
|
+
expect(diags).toHaveLength(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("passes a ref pinned to a full 40-hex commit SHA", async () => {
|
|
38
|
+
const diags = tf005.check(await loadFixture("TF005", "negative-sha"));
|
|
39
|
+
expect(diags).toHaveLength(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("never fires on a local source", async () => {
|
|
43
|
+
const diags = tf005.check(await loadFixture("TF005", "negative-local"));
|
|
44
|
+
expect(diags).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("never fires on a registry source", async () => {
|
|
48
|
+
const diags = tf005.check(await loadFixture("TF005", "negative-registry"));
|
|
49
|
+
expect(diags).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("is exclusive of TF004 on a registry-sourced module block", async () => {
|
|
53
|
+
const ctx = await loadFixture("TF005", "negative-registry");
|
|
54
|
+
expect(tf005.check(ctx)).toHaveLength(0);
|
|
55
|
+
expect(tf004.check(ctx)).toHaveLength(1); // no version set, so TF004 fires instead
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF005: a git or hg `module` source with no `?ref=`, or a `?ref=` that
|
|
3
|
+
* isn't tight enough to call pinned.
|
|
4
|
+
*
|
|
5
|
+
* "Git or hg" is `classifyModuleSource`'s `"git"` kind (`./module-source.ts`):
|
|
6
|
+
* an explicit `git::`/`hg::` prefix, the bare `github.com`/`bitbucket.org`
|
|
7
|
+
* shorthands, an scp-style `git@host:path`, or anything ending in `.git`.
|
|
8
|
+
* A ref counts as pinned only if it is a semver-shaped tag or a full 40-hex
|
|
9
|
+
* commit SHA (`isPinnedRef`), stricter than checkov's `CKV_TF_1`/`CKV_TF_2`,
|
|
10
|
+
* which accept any `?ref=` containing `\d\.\d` (so `v1.2-dev` passes there;
|
|
11
|
+
* it does not here, and the rule's page section says so). A ref matching a
|
|
12
|
+
* well-known mutable branch name (`main`, `master`, `develop`, `trunk`) gets
|
|
13
|
+
* its own, more specific message, since that's the exact case tflint's
|
|
14
|
+
* `flexible` style (the style this rule ships; see the page section for what
|
|
15
|
+
* `semver` style would add) already reports on.
|
|
16
|
+
*
|
|
17
|
+
* A local (`./`, `../`) source is never flagged: it can't be pinned to a
|
|
18
|
+
* ref at all, and checkov's own module-pinning checks return `UNKNOWN`
|
|
19
|
+
* (not a failure) for the same reason. A registry source is TF004's
|
|
20
|
+
* territory, not this rule's; the two never fire on the same module block.
|
|
21
|
+
*
|
|
22
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
23
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
24
|
+
* root's is.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type {
|
|
28
|
+
PostSynthCheck,
|
|
29
|
+
PostSynthContext,
|
|
30
|
+
PostSynthDiagnostic,
|
|
31
|
+
} from "@intentius/chant/lint/post-synth";
|
|
32
|
+
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
33
|
+
import { MODULE_TYPE, type BlockBody } from "../../hcl/parse";
|
|
34
|
+
import { classifyModuleSource, isDefaultBranch, isPinnedRef } from "./module-source";
|
|
35
|
+
|
|
36
|
+
export const tf005: PostSynthCheck = {
|
|
37
|
+
id: "TF005",
|
|
38
|
+
description: "Git/hg module source is unpinned, or pinned to a mutable ref",
|
|
39
|
+
|
|
40
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
41
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
42
|
+
|
|
43
|
+
for (const [key, entity] of ctx.entities) {
|
|
44
|
+
if (entity.entityType !== MODULE_TYPE || !isResourceDeclarable(entity)) continue;
|
|
45
|
+
const props = entity.props as { address?: unknown; body?: unknown };
|
|
46
|
+
const body = (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
|
|
47
|
+
const source = typeof body.source === "string" ? body.source : undefined;
|
|
48
|
+
if (source === undefined) continue;
|
|
49
|
+
|
|
50
|
+
const classified = classifyModuleSource(source);
|
|
51
|
+
if (classified.kind !== "git") continue;
|
|
52
|
+
|
|
53
|
+
const address = typeof props.address === "string" ? props.address : key;
|
|
54
|
+
let message: string | undefined;
|
|
55
|
+
|
|
56
|
+
if (!classified.ref) {
|
|
57
|
+
message = `Module "${address}" sources "${source}" from git/hg with no \`?ref=\`. Add one pinned to a tag or a full commit SHA.`;
|
|
58
|
+
} else if (isDefaultBranch(classified.ref)) {
|
|
59
|
+
message = `Module "${address}" sources "${source}" pinned to "${classified.ref}", a mutable branch. Pin to a tag or a full commit SHA instead.`;
|
|
60
|
+
} else if (!isPinnedRef(classified.ref)) {
|
|
61
|
+
message = `Module "${address}" sources "${source}" pinned to "${classified.ref}", which is neither a tag nor a full commit SHA and can move. Pin to a tag or a full commit SHA instead.`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!message) continue;
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
checkId: "TF005",
|
|
67
|
+
severity: "warning",
|
|
68
|
+
message,
|
|
69
|
+
entity: key,
|
|
70
|
+
lexicon: "terraform",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return diagnostics;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF006: a `sensitive = true` variable carries a `default`.
|
|
3
|
+
*
|
|
4
|
+
* Marking a variable sensitive is a statement that its value must not be seen.
|
|
5
|
+
* A default is a value committed to the repository, so the two together are a
|
|
6
|
+
* contradiction: the secret is in git, and the `sensitive` flag only hides it
|
|
7
|
+
* from plan output, where it was never the exposure. It is also the shape that
|
|
8
|
+
* survives longest, because every environment that forgets to pass the variable
|
|
9
|
+
* silently gets the committed one.
|
|
10
|
+
*
|
|
11
|
+
* Any default counts, not only a secret-shaped one. `default = ""` and
|
|
12
|
+
* `default = null` are the two that look harmless, and both make a required
|
|
13
|
+
* secret optional, which is the other half of the failure.
|
|
14
|
+
*
|
|
15
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
16
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
17
|
+
* root's is.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
PostSynthCheck,
|
|
22
|
+
PostSynthContext,
|
|
23
|
+
PostSynthDiagnostic,
|
|
24
|
+
} from "@intentius/chant/lint/post-synth";
|
|
25
|
+
import { VARIABLE_TYPE } from "../../hcl/parse";
|
|
26
|
+
import { blockName, blocksOfType } from "./blocks";
|
|
27
|
+
|
|
28
|
+
export const tf006: PostSynthCheck = {
|
|
29
|
+
id: "TF006",
|
|
30
|
+
description: "Sensitive variable declares a default value",
|
|
31
|
+
|
|
32
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
33
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
34
|
+
|
|
35
|
+
for (const block of blocksOfType(ctx, VARIABLE_TYPE)) {
|
|
36
|
+
if (block.body.sensitive !== true) continue;
|
|
37
|
+
if (!("default" in block.body)) continue;
|
|
38
|
+
|
|
39
|
+
diagnostics.push({
|
|
40
|
+
checkId: "TF006",
|
|
41
|
+
severity: "error",
|
|
42
|
+
message:
|
|
43
|
+
`Variable "${blockName(block.address)}" is marked \`sensitive = true\` and declares a ` +
|
|
44
|
+
"`default`. A default is committed to the repository, so the value the variable is meant " +
|
|
45
|
+
"to protect is already public. Drop the default and require the value to be passed in.",
|
|
46
|
+
entity: block.key,
|
|
47
|
+
lexicon: "terraform",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return diagnostics;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF007: a variable default or a `locals` value holds a secret-shaped literal.
|
|
3
|
+
*
|
|
4
|
+
* This is the check tfsec shipped as `general-secrets-sensitive-in-variable`
|
|
5
|
+
* and `general-secrets-sensitive-in-local`, and that trivy dropped when it
|
|
6
|
+
* absorbed tfsec. #2107's survey found no maintained tool that has it today.
|
|
7
|
+
*
|
|
8
|
+
* A committed default or local is a credential in git history, which no later
|
|
9
|
+
* edit removes, and Terraform copies it into state and into plan output on
|
|
10
|
+
* every run. Both heuristics in `../secret-shape.ts` count: the name (a
|
|
11
|
+
* variable called `db_password`) and the value (something shaped like a live
|
|
12
|
+
* credential whatever it is called). References and interpolations are never
|
|
13
|
+
* flagged, because passing the value in is the fix.
|
|
14
|
+
*
|
|
15
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
16
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
17
|
+
* root's is.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
PostSynthCheck,
|
|
22
|
+
PostSynthContext,
|
|
23
|
+
PostSynthDiagnostic,
|
|
24
|
+
} from "@intentius/chant/lint/post-synth";
|
|
25
|
+
import { LOCALS_TYPE, VARIABLE_TYPE } from "../../hcl/parse";
|
|
26
|
+
import { secretShapedAssignment, type SecretShape } from "../secret-shape";
|
|
27
|
+
import { blockName, blocksOfType } from "./blocks";
|
|
28
|
+
|
|
29
|
+
/** Why the value was flagged, in the words of the heuristic that fired. */
|
|
30
|
+
function because(shape: SecretShape): string {
|
|
31
|
+
return shape.reason === "value"
|
|
32
|
+
? `the value (${shape.length} characters, redacted here) matches a credential shape`
|
|
33
|
+
: "the name reads like a credential and the value is a committed literal";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const tf007: PostSynthCheck = {
|
|
37
|
+
id: "TF007",
|
|
38
|
+
description: "Secret-shaped literal in a variable default or a locals value",
|
|
39
|
+
|
|
40
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
41
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
42
|
+
|
|
43
|
+
for (const block of blocksOfType(ctx, VARIABLE_TYPE)) {
|
|
44
|
+
const name = blockName(block.address);
|
|
45
|
+
const shape = secretShapedAssignment(name, block.body.default);
|
|
46
|
+
if (!shape) continue;
|
|
47
|
+
diagnostics.push({
|
|
48
|
+
checkId: "TF007",
|
|
49
|
+
severity: "error",
|
|
50
|
+
message:
|
|
51
|
+
`Variable "${name}" defaults to what looks like a secret: ${because(shape)}. A default is ` +
|
|
52
|
+
"committed to the repository and copied into state on every run. Remove it, mark the " +
|
|
53
|
+
"variable `sensitive = true`, and pass the value from a secret store at apply time.",
|
|
54
|
+
entity: block.key,
|
|
55
|
+
lexicon: "terraform",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const block of blocksOfType(ctx, LOCALS_TYPE)) {
|
|
60
|
+
for (const [name, value] of Object.entries(block.body)) {
|
|
61
|
+
const shape = secretShapedAssignment(name, value);
|
|
62
|
+
if (!shape) continue;
|
|
63
|
+
diagnostics.push({
|
|
64
|
+
checkId: "TF007",
|
|
65
|
+
severity: "error",
|
|
66
|
+
message:
|
|
67
|
+
`Local value "${name}" looks like a secret: ${because(shape)}. A local is a committed ` +
|
|
68
|
+
"constant with no way to override it per environment. Read the value from a data source " +
|
|
69
|
+
"or a sensitive variable instead.",
|
|
70
|
+
entity: `${block.key}.${name}`,
|
|
71
|
+
lexicon: "terraform",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return diagnostics;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF008: a `provider` block configures a credential inline.
|
|
3
|
+
*
|
|
4
|
+
* The most corroborated rule in #2107's survey: checkov (`CKV_AWS_41` and its
|
|
5
|
+
* `credentials.py` siblings), KICS, Snyk and semgrep all ship a version of it,
|
|
6
|
+
* and it is the only Terraform-language-shaped check any of Snyk or semgrep
|
|
7
|
+
* ships at all. Every one of them is written per provider. This one is written
|
|
8
|
+
* against attribute NAMES, so it covers the providers those tools cover and
|
|
9
|
+
* the several hundred they do not.
|
|
10
|
+
*
|
|
11
|
+
* A credential in a provider block is used by every resource that provider
|
|
12
|
+
* manages, so it is the highest-value one in the root, and it is read from the
|
|
13
|
+
* repository by everyone who can clone it. Providers all accept the same
|
|
14
|
+
* credential through the environment or a shared config file instead.
|
|
15
|
+
*
|
|
16
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
17
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
18
|
+
* root's is.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
PostSynthCheck,
|
|
23
|
+
PostSynthContext,
|
|
24
|
+
PostSynthDiagnostic,
|
|
25
|
+
} from "@intentius/chant/lint/post-synth";
|
|
26
|
+
import { PROVIDER_TYPE } from "../../hcl/parse";
|
|
27
|
+
import { isLiteralString, isPlaceholderValue, isSecretName } from "../secret-shape";
|
|
28
|
+
import { blockName, blocksOfType, walkAttributes } from "./blocks";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Credential attributes the name heuristic in `../secret-shape.ts` does not
|
|
32
|
+
* cover on its own. `client_secret` and the `*_key`/`*_token` names it does
|
|
33
|
+
* cover are not repeated here.
|
|
34
|
+
*/
|
|
35
|
+
const PROVIDER_CREDENTIAL_ATTRS = new Set(["credentials", "client_certificate", "client_key", "shared_credentials_file"]);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A path, not a credential: `credentials = "keys/service-account.json"` and
|
|
39
|
+
* `shared_credentials_file = "~/.aws/credentials"` name where the secret lives,
|
|
40
|
+
* which is what the fix looks like.
|
|
41
|
+
*/
|
|
42
|
+
const FILE_PATH_RE = /^[~./]?[\w./\\-]*\.(json|pem|key|p12|pfx|crt|cer|ini|conf|credentials)$/i;
|
|
43
|
+
|
|
44
|
+
export const tf008: PostSynthCheck = {
|
|
45
|
+
id: "TF008",
|
|
46
|
+
description: "Provider block configures a hardcoded credential",
|
|
47
|
+
|
|
48
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
49
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
50
|
+
|
|
51
|
+
for (const block of blocksOfType(ctx, PROVIDER_TYPE)) {
|
|
52
|
+
for (const attr of walkAttributes(block.body)) {
|
|
53
|
+
if (!PROVIDER_CREDENTIAL_ATTRS.has(attr.name) && !isSecretName(attr.name)) continue;
|
|
54
|
+
if (!isLiteralString(attr.value)) continue;
|
|
55
|
+
const value = attr.value.trim();
|
|
56
|
+
if (value === "" || isPlaceholderValue(value) || FILE_PATH_RE.test(value)) continue;
|
|
57
|
+
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
checkId: "TF008",
|
|
60
|
+
severity: "error",
|
|
61
|
+
message:
|
|
62
|
+
`Provider "${blockName(block.address)}" sets \`${attr.path}\` to a literal value ` +
|
|
63
|
+
`(${value.length} characters, redacted here). A credential in a provider block is ` +
|
|
64
|
+
"committed to the repository and used by every resource the provider manages. Remove it " +
|
|
65
|
+
"and let the provider read the credential from the environment, a shared config file, or " +
|
|
66
|
+
"a sensitive variable.",
|
|
67
|
+
entity: `${block.key}.${attr.path}`,
|
|
68
|
+
lexicon: "terraform",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return diagnostics;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF009: a credential-named variable is not marked `sensitive`.
|
|
3
|
+
*
|
|
4
|
+
* No tool in #2107's survey checks this. The authority is HashiCorp's own style
|
|
5
|
+
* guide, whose variables section says plainly: "For sensitive variables, such
|
|
6
|
+
* as passwords and private keys, set the sensitive parameter to true."
|
|
7
|
+
*
|
|
8
|
+
* Without the flag the value is echoed in `terraform plan` and
|
|
9
|
+
* `terraform apply` output, which means it lands in CI logs, in the pull
|
|
10
|
+
* request comment a plan bot posts, and in every terminal scrollback. The flag
|
|
11
|
+
* does not keep the value out of state (nothing does), which is why the
|
|
12
|
+
* remediation says both things.
|
|
13
|
+
*
|
|
14
|
+
* Only the name is read. A variable with no default and no value in the
|
|
15
|
+
* repository is still flagged, because the finding is about where the value
|
|
16
|
+
* goes at run time, not about what is committed. That is TF007's question.
|
|
17
|
+
*
|
|
18
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
19
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
20
|
+
* root's is.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
PostSynthCheck,
|
|
25
|
+
PostSynthContext,
|
|
26
|
+
PostSynthDiagnostic,
|
|
27
|
+
} from "@intentius/chant/lint/post-synth";
|
|
28
|
+
import { VARIABLE_TYPE } from "../../hcl/parse";
|
|
29
|
+
import { isSecretName } from "../secret-shape";
|
|
30
|
+
import { blockName, blocksOfType } from "./blocks";
|
|
31
|
+
|
|
32
|
+
export const tf009: PostSynthCheck = {
|
|
33
|
+
id: "TF009",
|
|
34
|
+
description: "Credential-named variable is not marked sensitive",
|
|
35
|
+
|
|
36
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
37
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
38
|
+
|
|
39
|
+
for (const block of blocksOfType(ctx, VARIABLE_TYPE)) {
|
|
40
|
+
const name = blockName(block.address);
|
|
41
|
+
if (!isSecretName(name)) continue;
|
|
42
|
+
if (block.body.sensitive === true) continue;
|
|
43
|
+
|
|
44
|
+
diagnostics.push({
|
|
45
|
+
checkId: "TF009",
|
|
46
|
+
severity: "error",
|
|
47
|
+
message:
|
|
48
|
+
`Variable "${name}" is named like a credential but is not marked \`sensitive = true\`, so ` +
|
|
49
|
+
"Terraform prints its value in plan and apply output, and from there into CI logs and plan " +
|
|
50
|
+
"comments. Add `sensitive = true`, and remember the value is still stored in plaintext in " +
|
|
51
|
+
"state either way.",
|
|
52
|
+
entity: block.key,
|
|
53
|
+
lexicon: "terraform",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return diagnostics;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF010: a `variable` declares no `type`.
|
|
3
|
+
*
|
|
4
|
+
* Without a type constraint Terraform accepts whatever it is given and infers
|
|
5
|
+
* the type from the value, so a variable meant to be a list arrives as a
|
|
6
|
+
* string from a `TF_VAR_` environment variable, or a number arrives as `"3"`
|
|
7
|
+
* from a tfvars file, and the failure surfaces deep inside a resource
|
|
8
|
+
* argument rather than at the module boundary where it was introduced.
|
|
9
|
+
*
|
|
10
|
+
* A blank `type` counts as absent, matching KICS, which fails a `type` that
|
|
11
|
+
* trims to empty.
|
|
12
|
+
*
|
|
13
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
14
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
15
|
+
* root's is.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
PostSynthCheck,
|
|
20
|
+
PostSynthContext,
|
|
21
|
+
PostSynthDiagnostic,
|
|
22
|
+
} from "@intentius/chant/lint/post-synth";
|
|
23
|
+
import { VARIABLE_TYPE } from "../../hcl/parse";
|
|
24
|
+
import { blockName, blocksOfType, isBlank } from "./blocks";
|
|
25
|
+
|
|
26
|
+
export const tf010: PostSynthCheck = {
|
|
27
|
+
id: "TF010",
|
|
28
|
+
description: "Variable declares no type constraint",
|
|
29
|
+
|
|
30
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
31
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
32
|
+
|
|
33
|
+
for (const block of blocksOfType(ctx, VARIABLE_TYPE)) {
|
|
34
|
+
if (!isBlank(block.body.type)) continue;
|
|
35
|
+
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
checkId: "TF010",
|
|
38
|
+
severity: "info",
|
|
39
|
+
message:
|
|
40
|
+
`Variable "${blockName(block.address)}" declares no \`type\`, so Terraform accepts any ` +
|
|
41
|
+
"value and infers the type from whatever it is given. Add a type constraint " +
|
|
42
|
+
"(`string`, `number`, `bool`, `list(string)`, an `object({...})`) so a wrong value is " +
|
|
43
|
+
"rejected at the module boundary.",
|
|
44
|
+
entity: block.key,
|
|
45
|
+
lexicon: "terraform",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return diagnostics;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF011: a `variable` declares no `description`.
|
|
3
|
+
*
|
|
4
|
+
* A module's variables are its API. The description is what `terraform-docs`
|
|
5
|
+
* renders into the README, what the HCP Terraform and Terraform Cloud variable
|
|
6
|
+
* UI shows next to the input box, and what a caller reads before deciding what
|
|
7
|
+
* to pass. Without it the only documentation is the variable's name.
|
|
8
|
+
*
|
|
9
|
+
* Report-only: this is documentation hygiene, filed the way KICS files it (as
|
|
10
|
+
* INFO) and the way tflint files it (in the `all` preset, not `recommended`).
|
|
11
|
+
*
|
|
12
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
13
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
14
|
+
* root's is.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
PostSynthCheck,
|
|
19
|
+
PostSynthContext,
|
|
20
|
+
PostSynthDiagnostic,
|
|
21
|
+
} from "@intentius/chant/lint/post-synth";
|
|
22
|
+
import { VARIABLE_TYPE } from "../../hcl/parse";
|
|
23
|
+
import { blockName, blocksOfType, isBlank } from "./blocks";
|
|
24
|
+
|
|
25
|
+
export const tf011: PostSynthCheck = {
|
|
26
|
+
id: "TF011",
|
|
27
|
+
description: "Variable declares no description",
|
|
28
|
+
|
|
29
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
30
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
31
|
+
|
|
32
|
+
for (const block of blocksOfType(ctx, VARIABLE_TYPE)) {
|
|
33
|
+
if (!isBlank(block.body.description)) continue;
|
|
34
|
+
|
|
35
|
+
diagnostics.push({
|
|
36
|
+
checkId: "TF011",
|
|
37
|
+
severity: "info",
|
|
38
|
+
message:
|
|
39
|
+
`Variable "${blockName(block.address)}" declares no \`description\`, so the module's ` +
|
|
40
|
+
"generated documentation and the variable prompt have nothing to show but the name. Add a " +
|
|
41
|
+
"description saying what the value is for and what a valid one looks like.",
|
|
42
|
+
entity: block.key,
|
|
43
|
+
lexicon: "terraform",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return diagnostics;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF012: an `output` declares no `description`.
|
|
3
|
+
*
|
|
4
|
+
* The other half of a module's API. An output is what a caller wires into the
|
|
5
|
+
* next module, and the description is the only place to say what the value
|
|
6
|
+
* actually is (an id or an ARN, a full URL or a bare hostname) and whether it
|
|
7
|
+
* is stable enough to depend on.
|
|
8
|
+
*
|
|
9
|
+
* Report-only, for the same reason TF011 is.
|
|
10
|
+
*
|
|
11
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
12
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
13
|
+
* root's is.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
PostSynthCheck,
|
|
18
|
+
PostSynthContext,
|
|
19
|
+
PostSynthDiagnostic,
|
|
20
|
+
} from "@intentius/chant/lint/post-synth";
|
|
21
|
+
import { OUTPUT_TYPE } from "../../hcl/parse";
|
|
22
|
+
import { blockName, blocksOfType, isBlank } from "./blocks";
|
|
23
|
+
|
|
24
|
+
export const tf012: PostSynthCheck = {
|
|
25
|
+
id: "TF012",
|
|
26
|
+
description: "Output declares no description",
|
|
27
|
+
|
|
28
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
29
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
30
|
+
|
|
31
|
+
for (const block of blocksOfType(ctx, OUTPUT_TYPE)) {
|
|
32
|
+
if (!isBlank(block.body.description)) continue;
|
|
33
|
+
|
|
34
|
+
diagnostics.push({
|
|
35
|
+
checkId: "TF012",
|
|
36
|
+
severity: "info",
|
|
37
|
+
message:
|
|
38
|
+
`Output "${blockName(block.address)}" declares no \`description\`. Outputs are a module's ` +
|
|
39
|
+
"public interface, so say what the value is and what a caller can rely on it for.",
|
|
40
|
+
entity: block.key,
|
|
41
|
+
lexicon: "terraform",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return diagnostics;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF013: `lifecycle { ignore_changes = all }`.
|
|
3
|
+
*
|
|
4
|
+
* `all` tells Terraform to ignore drift on every attribute of the resource
|
|
5
|
+
* after it is created. Nothing about that resource is ever reconciled again:
|
|
6
|
+
* a manual console edit, a deleted security-group rule, a downgraded instance
|
|
7
|
+
* class, all of it plans clean forever. It is also silent, because a plan that
|
|
8
|
+
* finds nothing looks exactly like a plan that was told to look at nothing.
|
|
9
|
+
*
|
|
10
|
+
* That is the observe half of chant's lifecycle switched off, so this is
|
|
11
|
+
* merge-worthy rather than hygiene. A list of specific attributes is fine and
|
|
12
|
+
* is not reported: ignoring `tags` an external tool writes is a real need, and
|
|
13
|
+
* naming them is what makes the intent auditable.
|
|
14
|
+
*
|
|
15
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
16
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
17
|
+
* root's is.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
PostSynthCheck,
|
|
22
|
+
PostSynthContext,
|
|
23
|
+
PostSynthDiagnostic,
|
|
24
|
+
} from "@intentius/chant/lint/post-synth";
|
|
25
|
+
import { DATA_TYPE, RESOURCE_TYPE } from "../../hcl/parse";
|
|
26
|
+
import { blocksOfTypes, nestedBodies } from "./blocks";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `ignore_changes = all` parses to the string `"${all}"`: hcl2json renders the
|
|
30
|
+
* bare keyword as an expression template. A list of attributes parses to an
|
|
31
|
+
* array, so an array is only a match if `all` is the whole of it.
|
|
32
|
+
*/
|
|
33
|
+
function ignoresEverything(value: unknown): boolean {
|
|
34
|
+
if (value === "${all}") return true;
|
|
35
|
+
return Array.isArray(value) && value.length === 1 && value[0] === "${all}";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const tf013: PostSynthCheck = {
|
|
39
|
+
id: "TF013",
|
|
40
|
+
description: "lifecycle ignore_changes is set to all",
|
|
41
|
+
|
|
42
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
43
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
44
|
+
|
|
45
|
+
for (const block of blocksOfTypes(ctx, [RESOURCE_TYPE, DATA_TYPE])) {
|
|
46
|
+
for (const lifecycle of nestedBodies(block.body, "lifecycle")) {
|
|
47
|
+
if (!ignoresEverything(lifecycle.ignore_changes)) continue;
|
|
48
|
+
diagnostics.push({
|
|
49
|
+
checkId: "TF013",
|
|
50
|
+
severity: "warning",
|
|
51
|
+
message:
|
|
52
|
+
`"${block.address}" sets \`ignore_changes = all\`, so Terraform stops reconciling every ` +
|
|
53
|
+
"attribute of the resource after it is created. Drift becomes invisible: a manual change " +
|
|
54
|
+
"in the console plans clean forever. List the specific attributes to ignore instead.",
|
|
55
|
+
entity: block.key,
|
|
56
|
+
lexicon: "terraform",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return diagnostics;
|
|
62
|
+
},
|
|
63
|
+
};
|