@intentius/chant-lexicon-terraform 0.101.0 → 0.104.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.
Files changed (374) hide show
  1. package/dist/__fixtures__/terragrunt/affected-cases.d.ts +23 -0
  2. package/dist/__fixtures__/terragrunt/affected-cases.d.ts.map +1 -0
  3. package/dist/__fixtures__/terragrunt/record.d.ts +17 -0
  4. package/dist/__fixtures__/terragrunt/record.d.ts.map +1 -0
  5. package/dist/change-set.d.ts +67 -0
  6. package/dist/change-set.d.ts.map +1 -0
  7. package/dist/changed-roots.d.ts +30 -0
  8. package/dist/changed-roots.d.ts.map +1 -0
  9. package/dist/codegen/package.d.ts +1 -1
  10. package/dist/commands.d.ts +26 -0
  11. package/dist/commands.d.ts.map +1 -0
  12. package/dist/components/capability-plugin.d.ts +54 -0
  13. package/dist/components/capability-plugin.d.ts.map +1 -0
  14. package/dist/composites/terraform-pin-rollout-op.d.ts +44 -0
  15. package/dist/composites/terraform-pin-rollout-op.d.ts.map +1 -0
  16. package/dist/config.d.ts +3 -3
  17. package/dist/hcl/descend.d.ts +1 -1
  18. package/dist/hcl/descend.d.ts.map +1 -1
  19. package/dist/hcl/edges.d.ts +2 -2
  20. package/dist/hcl/edges.d.ts.map +1 -1
  21. package/dist/hcl/jsonencode.d.ts +106 -0
  22. package/dist/hcl/jsonencode.d.ts.map +1 -0
  23. package/dist/hcl/marker.d.ts.map +1 -1
  24. package/dist/hcl/parse.d.ts +29 -1
  25. package/dist/hcl/parse.d.ts.map +1 -1
  26. package/dist/hcl/roots.d.ts +1 -1
  27. package/dist/hcl/roots.d.ts.map +1 -1
  28. package/dist/hcl/suppressions.d.ts.map +1 -1
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/integrity.json +22 -4
  32. package/dist/lint/audit-catalog.d.ts.map +1 -1
  33. package/dist/lint/audit-lineage.d.ts +1 -1
  34. package/dist/lint/audit-lineage.d.ts.map +1 -1
  35. package/dist/lint/post-synth/flat-attributes.d.ts +69 -0
  36. package/dist/lint/post-synth/flat-attributes.d.ts.map +1 -0
  37. package/dist/lint/post-synth/index.d.ts.map +1 -1
  38. package/dist/lint/post-synth/module-source.d.ts +27 -1
  39. package/dist/lint/post-synth/module-source.d.ts.map +1 -1
  40. package/dist/lint/post-synth/terragrunt.d.ts +37 -0
  41. package/dist/lint/post-synth/terragrunt.d.ts.map +1 -0
  42. package/dist/lint/post-synth/tf002.d.ts +3 -1
  43. package/dist/lint/post-synth/tf002.d.ts.map +1 -1
  44. package/dist/lint/post-synth/tf030.d.ts +51 -0
  45. package/dist/lint/post-synth/tf030.d.ts.map +1 -0
  46. package/dist/lint/post-synth/tf031.d.ts +55 -0
  47. package/dist/lint/post-synth/tf031.d.ts.map +1 -0
  48. package/dist/lint/post-synth/tf032.d.ts +71 -0
  49. package/dist/lint/post-synth/tf032.d.ts.map +1 -0
  50. package/dist/lint/post-synth/tf033.d.ts +50 -0
  51. package/dist/lint/post-synth/tf033.d.ts.map +1 -0
  52. package/dist/lint/post-synth/tf034.d.ts +25 -0
  53. package/dist/lint/post-synth/tf034.d.ts.map +1 -0
  54. package/dist/lint/post-synth/tf035.d.ts +33 -0
  55. package/dist/lint/post-synth/tf035.d.ts.map +1 -0
  56. package/dist/lint/post-synth/tf036.d.ts +33 -0
  57. package/dist/lint/post-synth/tf036.d.ts.map +1 -0
  58. package/dist/lint/post-synth/tf037.d.ts +31 -0
  59. package/dist/lint/post-synth/tf037.d.ts.map +1 -0
  60. package/dist/lint/post-synth/tf038.d.ts +22 -0
  61. package/dist/lint/post-synth/tf038.d.ts.map +1 -0
  62. package/dist/lint/post-synth/tf039.d.ts +23 -0
  63. package/dist/lint/post-synth/tf039.d.ts.map +1 -0
  64. package/dist/lint/post-synth/tf040.d.ts +30 -0
  65. package/dist/lint/post-synth/tf040.d.ts.map +1 -0
  66. package/dist/lint/post-synth/tf041.d.ts +20 -0
  67. package/dist/lint/post-synth/tf041.d.ts.map +1 -0
  68. package/dist/lint/post-synth/tf042.d.ts +18 -0
  69. package/dist/lint/post-synth/tf042.d.ts.map +1 -0
  70. package/dist/lint/post-synth/tf043.d.ts +17 -0
  71. package/dist/lint/post-synth/tf043.d.ts.map +1 -0
  72. package/dist/lint/post-synth/tf044.d.ts +17 -0
  73. package/dist/lint/post-synth/tf044.d.ts.map +1 -0
  74. package/dist/lint/post-synth/tf045.d.ts +22 -0
  75. package/dist/lint/post-synth/tf045.d.ts.map +1 -0
  76. package/dist/manifest.json +1 -1
  77. package/dist/okf/index.md +16 -0
  78. package/dist/okf/rules/TF030.md +11 -0
  79. package/dist/okf/rules/TF031.md +11 -0
  80. package/dist/okf/rules/TF032.md +11 -0
  81. package/dist/okf/rules/TF033.md +11 -0
  82. package/dist/okf/rules/TF034.md +11 -0
  83. package/dist/okf/rules/TF035.md +11 -0
  84. package/dist/okf/rules/TF036.md +11 -0
  85. package/dist/okf/rules/TF037.md +11 -0
  86. package/dist/okf/rules/TF038.md +11 -0
  87. package/dist/okf/rules/TF039.md +11 -0
  88. package/dist/okf/rules/TF040.md +11 -0
  89. package/dist/okf/rules/TF041.md +11 -0
  90. package/dist/okf/rules/TF042.md +11 -0
  91. package/dist/okf/rules/TF043.md +11 -0
  92. package/dist/okf/rules/TF044.md +11 -0
  93. package/dist/okf/rules/TF045.md +11 -0
  94. package/dist/op/activities/index.d.ts +4 -0
  95. package/dist/op/activities/index.d.ts.map +1 -1
  96. package/dist/op/activities/pin-rollout.d.ts +46 -0
  97. package/dist/op/activities/pin-rollout.d.ts.map +1 -0
  98. package/dist/op/activities/terraform.d.ts +24 -50
  99. package/dist/op/activities/terraform.d.ts.map +1 -1
  100. package/dist/op/activity-contracts.d.ts +66 -18
  101. package/dist/op/activity-contracts.d.ts.map +1 -1
  102. package/dist/op/builders.d.ts +9 -0
  103. package/dist/op/builders.d.ts.map +1 -1
  104. package/dist/pin/edit-ts.d.ts +21 -0
  105. package/dist/pin/edit-ts.d.ts.map +1 -0
  106. package/dist/pin/edit.d.ts +52 -0
  107. package/dist/pin/edit.d.ts.map +1 -0
  108. package/dist/pin/forge.d.ts +39 -0
  109. package/dist/pin/forge.d.ts.map +1 -0
  110. package/dist/pin/index.d.ts +22 -0
  111. package/dist/pin/index.d.ts.map +1 -0
  112. package/dist/pin/rollout.d.ts +116 -0
  113. package/dist/pin/rollout.d.ts.map +1 -0
  114. package/dist/pin/scan.d.ts +54 -0
  115. package/dist/pin/scan.d.ts.map +1 -0
  116. package/dist/pin/source.d.ts +93 -0
  117. package/dist/pin/source.d.ts.map +1 -0
  118. package/dist/pin/waves.d.ts +66 -0
  119. package/dist/pin/waves.d.ts.map +1 -0
  120. package/dist/plan-digest.d.ts +59 -0
  121. package/dist/plan-digest.d.ts.map +1 -0
  122. package/dist/plugin.d.ts.map +1 -1
  123. package/dist/rules/flat-attributes.ts +146 -0
  124. package/dist/rules/module-source.ts +56 -1
  125. package/dist/rules/terragrunt.ts +76 -0
  126. package/dist/rules/tf002.ts +1 -1
  127. package/dist/rules/tf030.ts +290 -0
  128. package/dist/rules/tf031.ts +370 -0
  129. package/dist/rules/tf032.ts +226 -0
  130. package/dist/rules/tf033.ts +160 -0
  131. package/dist/rules/tf034.ts +63 -0
  132. package/dist/rules/tf035.ts +74 -0
  133. package/dist/rules/tf036.ts +119 -0
  134. package/dist/rules/tf037.ts +101 -0
  135. package/dist/rules/tf038.ts +71 -0
  136. package/dist/rules/tf039.ts +64 -0
  137. package/dist/rules/tf040.ts +76 -0
  138. package/dist/rules/tf041.ts +56 -0
  139. package/dist/rules/tf042.ts +42 -0
  140. package/dist/rules/tf043.ts +49 -0
  141. package/dist/rules/tf044.ts +51 -0
  142. package/dist/rules/tf045.ts +47 -0
  143. package/dist/terragrunt/affected.d.ts +119 -0
  144. package/dist/terragrunt/affected.d.ts.map +1 -0
  145. package/dist/terragrunt/index.d.ts +11 -0
  146. package/dist/terragrunt/index.d.ts.map +1 -0
  147. package/dist/terragrunt/mocks.d.ts +147 -0
  148. package/dist/terragrunt/mocks.d.ts.map +1 -0
  149. package/dist/terragrunt/run.d.ts +130 -0
  150. package/dist/terragrunt/run.d.ts.map +1 -0
  151. package/dist/terragrunt/units.d.ts +103 -0
  152. package/dist/terragrunt/units.d.ts.map +1 -0
  153. package/dist/terragrunt/wave.d.ts +89 -0
  154. package/dist/terragrunt/wave.d.ts.map +1 -0
  155. package/package.json +13 -3
  156. package/src/__fixtures__/change-set/choudoufu-set-plan.change-set.golden.json +95 -0
  157. package/src/__fixtures__/change-set/choudoufu-set-plan.json +76 -0
  158. package/src/__fixtures__/change-set/choudoufu.part.golden.json +173 -0
  159. package/src/__fixtures__/change-set/choudoufu.plan.json +610 -0
  160. package/src/__fixtures__/change-set/terraform.import-forget.plan.json +81 -0
  161. package/src/__fixtures__/change-set/terraform.part.golden.json +173 -0
  162. package/src/__fixtures__/change-set/terraform.plan.json +589 -0
  163. package/src/__fixtures__/change-set/tofu.part.golden.json +173 -0
  164. package/src/__fixtures__/change-set/tofu.plan.json +588 -0
  165. package/src/__fixtures__/change-set/v1.tf.txt +21 -0
  166. package/src/__fixtures__/change-set/v2-module-app.tf.txt +4 -0
  167. package/src/__fixtures__/change-set/v2.tf.txt +21 -0
  168. package/src/__fixtures__/pin/choudoufu-live-waves-n5.json +83 -0
  169. package/src/__fixtures__/pin/largeset-oci-n5/apply-order.txt +5 -0
  170. package/src/__fixtures__/pin/largeset-oci-n5/estates/e01/estate.chdf.hcl +6 -0
  171. package/src/__fixtures__/pin/largeset-oci-n5/estates/e01/main.tf +33 -0
  172. package/src/__fixtures__/pin/largeset-oci-n5/estates/e02/estate.chdf.hcl +6 -0
  173. package/src/__fixtures__/pin/largeset-oci-n5/estates/e02/main.tf +45 -0
  174. package/src/__fixtures__/pin/largeset-oci-n5/estates/e03/estate.chdf.hcl +6 -0
  175. package/src/__fixtures__/pin/largeset-oci-n5/estates/e03/main.tf +47 -0
  176. package/src/__fixtures__/pin/largeset-oci-n5/estates/e04/estate.chdf.hcl +6 -0
  177. package/src/__fixtures__/pin/largeset-oci-n5/estates/e04/main.tf +31 -0
  178. package/src/__fixtures__/pin/largeset-oci-n5/estates/e05/estate.chdf.hcl +6 -0
  179. package/src/__fixtures__/pin/largeset-oci-n5/estates/e05/main.tf +27 -0
  180. package/src/__fixtures__/pin/largeset-oci-n5/fixture.json +77 -0
  181. package/src/__fixtures__/plan-summary/largeset-n5-bump.json +2071 -0
  182. package/src/__fixtures__/plan-summary/largeset-n5.summary.golden.json +95 -0
  183. package/src/__fixtures__/plan-summary/largeset-n5.summary.golden.md +22 -0
  184. package/src/__fixtures__/plan-summary/largeset-n5.summary.golden.txt +12 -0
  185. package/src/__fixtures__/terragrunt/affected/catalog/units/web/terragrunt.hcl +3 -0
  186. package/src/__fixtures__/terragrunt/affected/gitignore +3 -0
  187. package/src/__fixtures__/terragrunt/affected/live/dev/app/terragrunt.hcl +3 -0
  188. package/src/__fixtures__/terragrunt/affected/live/dev/vpc/terragrunt.hcl +3 -0
  189. package/src/__fixtures__/terragrunt/affected/live/prod/app/terragrunt.hcl +3 -0
  190. package/src/__fixtures__/terragrunt/affected/live/stg/terragrunt.stack.hcl +5 -0
  191. package/src/__fixtures__/terragrunt/affected/modules/app/main.tf +6 -0
  192. package/src/__fixtures__/terragrunt/affected/modules/app/policy.json +1 -0
  193. package/src/__fixtures__/terragrunt/affected/modules/net/main.tf +2 -0
  194. package/src/__fixtures__/terragrunt/affected/modules/thing/main.tf +3 -0
  195. package/src/__fixtures__/terragrunt/affected/root.hcl +5 -0
  196. package/src/__fixtures__/terragrunt/affected-cases.ts +126 -0
  197. package/src/__fixtures__/terragrunt/five-units/catalog/units/thing/terragrunt.hcl +3 -0
  198. package/src/__fixtures__/terragrunt/five-units/live/dev/app/terragrunt.hcl +13 -0
  199. package/src/__fixtures__/terragrunt/five-units/live/dev/db/terragrunt.hcl +8 -0
  200. package/src/__fixtures__/terragrunt/five-units/live/dev/vpc/terragrunt.hcl +3 -0
  201. package/src/__fixtures__/terragrunt/five-units/live/prod/app/terragrunt.hcl +8 -0
  202. package/src/__fixtures__/terragrunt/five-units/live/prod/vpc/terragrunt.hcl +3 -0
  203. package/src/__fixtures__/terragrunt/five-units/modules/thing/main.tf +7 -0
  204. package/src/__fixtures__/terragrunt/five-units/root.hcl +5 -0
  205. package/src/__fixtures__/terragrunt/record.ts +56 -0
  206. package/src/__fixtures__/terragrunt/recorded/affected-find.json +50 -0
  207. package/src/__fixtures__/terragrunt/recorded/failed-plan-report.json +36 -0
  208. package/src/__fixtures__/terragrunt/recorded/find.json +67 -0
  209. package/src/__fixtures__/terragrunt/recorded/version.txt +1 -0
  210. package/src/__fixtures__/terragrunt/recorded/wave-1/apply-report.json +22 -0
  211. package/src/__fixtures__/terragrunt/recorded/wave-1/json/live/dev/vpc/tfplan.json +1 -0
  212. package/src/__fixtures__/terragrunt/recorded/wave-1/json/live/prod/vpc/tfplan.json +1 -0
  213. package/src/__fixtures__/terragrunt/recorded/wave-1/plan-report.json +22 -0
  214. package/src/__fixtures__/terragrunt/recorded/wave-2/json/live/dev/db/tfplan.json +1 -0
  215. package/src/__fixtures__/terragrunt/recorded/wave-2/json/live/prod/app/tfplan.json +1 -0
  216. package/src/__fixtures__/terragrunt/recorded/wave-2/plan-report.json +22 -0
  217. package/src/change-set-combined-run.test.ts +156 -0
  218. package/src/change-set.test.ts +151 -0
  219. package/src/change-set.ts +273 -0
  220. package/src/changed-roots.test.ts +58 -0
  221. package/src/changed-roots.ts +102 -0
  222. package/src/commands.ts +112 -0
  223. package/src/components/capability-plugin.test.ts +91 -0
  224. package/src/components/capability-plugin.ts +131 -0
  225. package/src/components/pr-loop.tofu.test.ts +244 -0
  226. package/src/composites/terraform-pin-rollout-op.test.ts +69 -0
  227. package/src/composites/terraform-pin-rollout-op.ts +60 -0
  228. package/src/hcl/descend.ts +1 -1
  229. package/src/hcl/edges.ts +2 -2
  230. package/src/hcl/jsonencode.test.ts +226 -0
  231. package/src/hcl/jsonencode.ts +454 -0
  232. package/src/hcl/parse.test.ts +25 -0
  233. package/src/hcl/parse.ts +91 -6
  234. package/src/hcl/roots.ts +1 -1
  235. package/src/hcl/suppressions.ts +8 -2
  236. package/src/index.ts +18 -0
  237. package/src/lint/audit-catalog.ts +230 -0
  238. package/src/lint/audit-lineage.ts +155 -1
  239. package/src/lint/audit.test.ts +3 -1
  240. package/src/lint/post-synth/estate-2284.test.ts +68 -0
  241. package/src/lint/post-synth/fixtures/TF030/negative-rules.tf +45 -0
  242. package/src/lint/post-synth/fixtures/TF030/negative.tf +55 -0
  243. package/src/lint/post-synth/fixtures/TF030/not-determined-port.tf +28 -0
  244. package/src/lint/post-synth/fixtures/TF030/not-determined-standalone.tf +32 -0
  245. package/src/lint/post-synth/fixtures/TF030/not-determined-vpc-rule.tf +21 -0
  246. package/src/lint/post-synth/fixtures/TF030/not-determined.tf +39 -0
  247. package/src/lint/post-synth/fixtures/TF030/positive-ipv6.tf +44 -0
  248. package/src/lint/post-synth/fixtures/TF030/positive-mixed.tf +36 -0
  249. package/src/lint/post-synth/fixtures/TF030/positive-standalone.tf +25 -0
  250. package/src/lint/post-synth/fixtures/TF030/positive.tf +43 -0
  251. package/src/lint/post-synth/fixtures/TF031/negative.tf +89 -0
  252. package/src/lint/post-synth/fixtures/TF031/not-determined.tf +139 -0
  253. package/src/lint/post-synth/fixtures/TF031/positive-data-source.tf +57 -0
  254. package/src/lint/post-synth/fixtures/TF031/positive-heredoc.tf +149 -0
  255. package/src/lint/post-synth/fixtures/TF031/positive-jsonencode.tf +115 -0
  256. package/src/lint/post-synth/fixtures/TF032/negative-heredoc.tf +20 -0
  257. package/src/lint/post-synth/fixtures/TF032/negative-references.tf +214 -0
  258. package/src/lint/post-synth/fixtures/TF032/negative.tf +99 -0
  259. package/src/lint/post-synth/fixtures/TF032/positive-heredoc.tf +22 -0
  260. package/src/lint/post-synth/fixtures/TF032/positive-sidecar.tf +197 -0
  261. package/src/lint/post-synth/fixtures/TF032/positive.tf +54 -0
  262. package/src/lint/post-synth/fixtures/TF033/negative.tf +39 -0
  263. package/src/lint/post-synth/fixtures/TF033/not-determined-aurora.tf +25 -0
  264. package/src/lint/post-synth/fixtures/TF033/positive-absent.tf +93 -0
  265. package/src/lint/post-synth/fixtures/TF033/positive.tf +50 -0
  266. package/src/lint/post-synth/fixtures/TF034/negative.tf +24 -0
  267. package/src/lint/post-synth/fixtures/TF034/positive-absent.tf +36 -0
  268. package/src/lint/post-synth/fixtures/TF034/positive.tf +14 -0
  269. package/src/lint/post-synth/fixtures/TF035/negative-absent.tf +43 -0
  270. package/src/lint/post-synth/fixtures/TF035/negative.tf +54 -0
  271. package/src/lint/post-synth/fixtures/TF035/positive.tf +37 -0
  272. package/src/lint/post-synth/fixtures/TF036/negative.tf +61 -0
  273. package/src/lint/post-synth/fixtures/TF036/not-determined.tf +28 -0
  274. package/src/lint/post-synth/fixtures/TF036/positive.tf +38 -0
  275. package/src/lint/post-synth/fixtures/TF037/negative-exclusion.tf +63 -0
  276. package/src/lint/post-synth/fixtures/TF037/negative.tf +27 -0
  277. package/src/lint/post-synth/fixtures/TF037/not-determined.tf +27 -0
  278. package/src/lint/post-synth/fixtures/TF037/positive-absent.tf +15 -0
  279. package/src/lint/post-synth/fixtures/TF037/positive.tf +26 -0
  280. package/src/lint/post-synth/fixtures/TF038/positive.tf +3 -0
  281. package/src/lint/post-synth/fixtures/TF039/positive.tf +4 -0
  282. package/src/lint/post-synth/fixtures/TF041/negative/terragrunt.hcl +12 -0
  283. package/src/lint/post-synth/fixtures/TF041/positive/terragrunt.hcl +7 -0
  284. package/src/lint/post-synth/fixtures/TF041/positive-apply/terragrunt.hcl +8 -0
  285. package/src/lint/post-synth/fixtures/TF042/negative/terragrunt.hcl +13 -0
  286. package/src/lint/post-synth/fixtures/TF042/positive/terragrunt.hcl +9 -0
  287. package/src/lint/post-synth/fixtures/TF043/negative/terragrunt.hcl +3 -0
  288. package/src/lint/post-synth/fixtures/TF043/negative-other/terragrunt.hcl +3 -0
  289. package/src/lint/post-synth/fixtures/TF043/positive-git/terragrunt.hcl +3 -0
  290. package/src/lint/post-synth/fixtures/TF043/positive-oci/terragrunt.hcl +3 -0
  291. package/src/lint/post-synth/fixtures/TF043/positive-tfr/terragrunt.hcl +3 -0
  292. package/src/lint/post-synth/fixtures/TF044/negative/terragrunt.hcl +9 -0
  293. package/src/lint/post-synth/fixtures/TF044/positive/terragrunt.hcl +7 -0
  294. package/src/lint/post-synth/fixtures/TF044/positive-generate/terragrunt.hcl +9 -0
  295. package/src/lint/post-synth/fixtures/TF045/negative/root.hcl +1 -0
  296. package/src/lint/post-synth/fixtures/TF045/negative/terragrunt.hcl +3 -0
  297. package/src/lint/post-synth/fixtures/TF045/positive/root.hcl +9 -0
  298. package/src/lint/post-synth/fixtures/TF045/positive/terragrunt.hcl +3 -0
  299. package/src/lint/post-synth/fixtures/TF045/unit-only/terragrunt.hcl +3 -0
  300. package/src/lint/post-synth/fixtures/estate-2284/clean/main.tf +132 -0
  301. package/src/lint/post-synth/fixtures/estate-2284/violating/main.tf +112 -0
  302. package/src/lint/post-synth/flat-attributes.ts +146 -0
  303. package/src/lint/post-synth/index.ts +32 -0
  304. package/src/lint/post-synth/module-source.test.ts +42 -1
  305. package/src/lint/post-synth/module-source.ts +56 -1
  306. package/src/lint/post-synth/terragrunt.ts +76 -0
  307. package/src/lint/post-synth/tf002.ts +1 -1
  308. package/src/lint/post-synth/tf030.test.ts +219 -0
  309. package/src/lint/post-synth/tf030.ts +290 -0
  310. package/src/lint/post-synth/tf031.test.ts +229 -0
  311. package/src/lint/post-synth/tf031.ts +370 -0
  312. package/src/lint/post-synth/tf032.test.ts +127 -0
  313. package/src/lint/post-synth/tf032.ts +226 -0
  314. package/src/lint/post-synth/tf033.test.ts +126 -0
  315. package/src/lint/post-synth/tf033.ts +160 -0
  316. package/src/lint/post-synth/tf034.test.ts +71 -0
  317. package/src/lint/post-synth/tf034.ts +63 -0
  318. package/src/lint/post-synth/tf035.test.ts +68 -0
  319. package/src/lint/post-synth/tf035.ts +74 -0
  320. package/src/lint/post-synth/tf036.test.ts +87 -0
  321. package/src/lint/post-synth/tf036.ts +119 -0
  322. package/src/lint/post-synth/tf037.test.ts +88 -0
  323. package/src/lint/post-synth/tf037.ts +101 -0
  324. package/src/lint/post-synth/tf038.test.ts +51 -0
  325. package/src/lint/post-synth/tf038.ts +71 -0
  326. package/src/lint/post-synth/tf039.test.ts +40 -0
  327. package/src/lint/post-synth/tf039.ts +64 -0
  328. package/src/lint/post-synth/tf040.test.ts +55 -0
  329. package/src/lint/post-synth/tf040.ts +76 -0
  330. package/src/lint/post-synth/tf041.test.ts +50 -0
  331. package/src/lint/post-synth/tf041.ts +56 -0
  332. package/src/lint/post-synth/tf042.test.ts +19 -0
  333. package/src/lint/post-synth/tf042.ts +42 -0
  334. package/src/lint/post-synth/tf043.test.ts +65 -0
  335. package/src/lint/post-synth/tf043.ts +49 -0
  336. package/src/lint/post-synth/tf044.test.ts +27 -0
  337. package/src/lint/post-synth/tf044.ts +51 -0
  338. package/src/lint/post-synth/tf045.test.ts +30 -0
  339. package/src/lint/post-synth/tf045.ts +47 -0
  340. package/src/op/activities/index.ts +4 -0
  341. package/src/op/activities/pin-rollout.ts +77 -0
  342. package/src/op/activities/terraform.test.ts +13 -0
  343. package/src/op/activities/terraform.ts +46 -86
  344. package/src/op/activity-contracts.test.ts +4 -1
  345. package/src/op/activity-contracts.ts +42 -0
  346. package/src/op/builders.ts +13 -0
  347. package/src/pin/bundle.test.ts +53 -0
  348. package/src/pin/edit-ts.test.ts +39 -0
  349. package/src/pin/edit-ts.ts +77 -0
  350. package/src/pin/edit.test.ts +199 -0
  351. package/src/pin/edit.ts +157 -0
  352. package/src/pin/forge.ts +85 -0
  353. package/src/pin/index.ts +22 -0
  354. package/src/pin/rollout.test.ts +232 -0
  355. package/src/pin/rollout.ts +451 -0
  356. package/src/pin/scan.ts +309 -0
  357. package/src/pin/source.ts +206 -0
  358. package/src/pin/waves.test.ts +101 -0
  359. package/src/pin/waves.ts +125 -0
  360. package/src/plan-digest.ts +99 -0
  361. package/src/plan-summary.test.ts +154 -0
  362. package/src/plugin.ts +25 -2
  363. package/src/terragrunt/affected.acceptance.test.ts +83 -0
  364. package/src/terragrunt/affected.test.ts +177 -0
  365. package/src/terragrunt/affected.ts +348 -0
  366. package/src/terragrunt/index.ts +11 -0
  367. package/src/terragrunt/mocks.acceptance.test.ts +105 -0
  368. package/src/terragrunt/mocks.test.ts +309 -0
  369. package/src/terragrunt/mocks.ts +324 -0
  370. package/src/terragrunt/run.ts +369 -0
  371. package/src/terragrunt/terragrunt.acceptance.test.ts +122 -0
  372. package/src/terragrunt/terragrunt.test.ts +332 -0
  373. package/src/terragrunt/units.ts +235 -0
  374. package/src/terragrunt/wave.ts +181 -0
@@ -0,0 +1,219 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import type { PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
3
+ import { blocksToEntities } from "../../hcl/parse";
4
+ import { loadFixture } from "./fixtures/load";
5
+ import { tf030 } from "./tf030";
6
+
7
+ async function run(name: string): Promise<PostSynthDiagnostic[]> {
8
+ return tf030.check(await loadFixture("TF030", name));
9
+ }
10
+
11
+ /** For the shapes no vendored fixture carries: parse one inline source. */
12
+ async function runInline(source: string): Promise<PostSynthDiagnostic[]> {
13
+ const entities = await blocksToEntities([{ name: "main.tf", source }], "inline");
14
+ return tf030.check({ outputs: new Map(), entities } as unknown as PostSynthContext);
15
+ }
16
+
17
+ const errors = (d: PostSynthDiagnostic[]) => d.filter((x) => x.severity === "error");
18
+ const infos = (d: PostSynthDiagnostic[]) => d.filter((x) => x.severity === "info");
19
+
20
+ describe("TF030: unrestricted ingress on a sensitive port", () => {
21
+ describe("fires (vendored fixtures)", () => {
22
+ test("a nested ingress block opening SSH to 0.0.0.0/0, and not the 8080 rule beside it", async () => {
23
+ const d = await run("positive-mixed");
24
+ expect(d).toHaveLength(1);
25
+ expect(d[0].checkId).toBe("TF030");
26
+ expect(d[0].severity).toBe("error");
27
+ expect(d[0].entity).toBe("TF030/aws_security_group.jenkins_port");
28
+ expect(d[0].message).toContain('"aws_security_group.jenkins_port" ingress rule 2');
29
+ expect(d[0].message).toContain("0.0.0.0/0 on port 22 (SSH)");
30
+ });
31
+
32
+ test("a port range spanning sensitive ports, and not the 443 rule", async () => {
33
+ const d = await run("positive");
34
+ expect(d).toHaveLength(1);
35
+ expect(d[0].severity).toBe("error");
36
+ expect(d[0].message).toContain("ingress rule 2");
37
+ expect(d[0].message).toContain("ports 1025-65535, which include MySQL (3306), RDP (3389) and PostgreSQL (5432)");
38
+ });
39
+
40
+ test("standalone aws_security_group_rule and aws_vpc_security_group_ingress_rule", async () => {
41
+ const d = await run("positive-standalone");
42
+ expect(errors(d).map((x) => x.entity).sort()).toEqual([
43
+ "TF030/aws_security_group_rule.fail",
44
+ "TF030/aws_vpc_security_group_ingress_rule.fail",
45
+ ]);
46
+ expect(d).toHaveLength(2);
47
+ });
48
+
49
+ test("::/0 on IPv6, and protocol -1 in attribute syntax reaching every port", async () => {
50
+ const d = await run("positive-ipv6");
51
+ expect(d).toHaveLength(2);
52
+ const byEntity = new Map(d.map((x) => [x.entity, x]));
53
+ expect(byEntity.get("TF030/aws_security_group.fail-ipv6")?.message).toContain("from ::/0 on port 22");
54
+ expect(byEntity.get("TF030/aws_security_group.fail4")?.message).toContain("every port (protocol -1)");
55
+ });
56
+ });
57
+
58
+ describe("silent (vendored fixtures)", () => {
59
+ test("SSH open only to private ranges", async () => {
60
+ expect(await run("negative")).toEqual([]);
61
+ });
62
+
63
+ test("no ingress at all, ICMP from anywhere, SSH from a security group, open egress", async () => {
64
+ expect(await run("negative-rules")).toEqual([]);
65
+ });
66
+ });
67
+
68
+ describe("not determined (vendored fixtures)", () => {
69
+ test("cidr_blocks = var.allowed_ssh_cidrs on port 22 is an info, not an error", async () => {
70
+ const d = await run("not-determined");
71
+ expect(errors(d)).toEqual([]);
72
+ expect(infos(d)).toHaveLength(1);
73
+ expect(d[0].message).toMatch(/^Not determined: /);
74
+ expect(d[0].message).toContain("`cidr_blocks` is `var.allowed_ssh_cidrs`");
75
+ });
76
+
77
+ test("an open CIDR on from_port/to_port = var.ssh_port is an info naming both", async () => {
78
+ const d = await run("not-determined-port");
79
+ expect(d).toHaveLength(1);
80
+ expect(d[0].severity).toBe("info");
81
+ expect(d[0].message).toContain("`from_port` is `var.ssh_port`; `to_port` is `var.ssh_port`");
82
+ });
83
+
84
+ test("a standalone rule with a variable CIDR is an info; the open egress rule is silent", async () => {
85
+ const d = await run("not-determined-standalone");
86
+ expect(d).toHaveLength(1);
87
+ expect(d[0].severity).toBe("info");
88
+ expect(d[0].entity).toBe("TF030/aws_security_group_rule.allow_ssh_internal");
89
+ expect(d[0].message).toContain("an element of `cidr_blocks` is `var.cidr_vpc`");
90
+ });
91
+
92
+ test("a for_each over each.value is an info, even though its locals open SSH to the world", async () => {
93
+ const d = await run("not-determined-vpc-rule");
94
+ expect(d).toHaveLength(1);
95
+ expect(d[0].severity).toBe("info");
96
+ expect(d[0].message).toContain("`cidr_ipv4` is `each.value.cidr_ipv4`");
97
+ expect(d[0].message).toContain("`ip_protocol` is `each.value.ip_protocol`");
98
+ });
99
+ });
100
+
101
+ describe("edge cases (inline)", () => {
102
+ test("a known-safe fact settles the rule even beside an unreadable one", async () => {
103
+ const d = await runInline(`
104
+ resource "aws_security_group_rule" "https" {
105
+ type = "ingress"
106
+ from_port = 443
107
+ to_port = 443
108
+ protocol = "tcp"
109
+ cidr_blocks = var.allowed
110
+ }
111
+ resource "aws_security_group_rule" "private" {
112
+ type = "ingress"
113
+ from_port = var.port
114
+ to_port = var.port
115
+ protocol = "tcp"
116
+ cidr_blocks = ["10.0.0.0/8"]
117
+ }`);
118
+ expect(d).toEqual([]);
119
+ });
120
+
121
+ test("egress and an unknown type", async () => {
122
+ const d = await runInline(`
123
+ resource "aws_security_group_rule" "out" {
124
+ type = "egress"
125
+ from_port = 22
126
+ to_port = 22
127
+ protocol = "tcp"
128
+ cidr_blocks = ["0.0.0.0/0"]
129
+ }
130
+ resource "aws_security_group_rule" "either" {
131
+ type = var.direction
132
+ from_port = 22
133
+ to_port = 22
134
+ protocol = "tcp"
135
+ cidr_blocks = ["0.0.0.0/0"]
136
+ }`);
137
+ expect(d).toHaveLength(1);
138
+ expect(d[0].severity).toBe("info");
139
+ expect(d[0].message).toContain("`type` is `var.direction`");
140
+ });
141
+
142
+ test("protocol written as the number -1, a quoted port, and upper-case TCP", async () => {
143
+ const d = await runInline(`
144
+ resource "aws_security_group" "a" {
145
+ ingress {
146
+ from_port = 0
147
+ to_port = 0
148
+ protocol = -1
149
+ cidr_blocks = ["0.0.0.0/0"]
150
+ }
151
+ ingress {
152
+ from_port = "5432"
153
+ to_port = "5432"
154
+ protocol = "TCP"
155
+ ipv6_cidr_blocks = ["::/0"]
156
+ }
157
+ }`);
158
+ expect(errors(d)).toHaveLength(2);
159
+ expect(d[1].message).toContain("port 5432 (PostgreSQL)");
160
+ });
161
+
162
+ test("tcp 0-0 is port 0, not every port", async () => {
163
+ const d = await runInline(`
164
+ resource "aws_security_group" "a" {
165
+ ingress {
166
+ from_port = 0
167
+ to_port = 0
168
+ protocol = "tcp"
169
+ cidr_blocks = ["0.0.0.0/0"]
170
+ }
171
+ }`);
172
+ expect(d).toEqual([]);
173
+ });
174
+
175
+ test("ingress = var.rules and a dynamic ingress block are not determined", async () => {
176
+ const d = await runInline(`
177
+ resource "aws_security_group" "whole" {
178
+ ingress = var.rules
179
+ }
180
+ resource "aws_security_group" "dyn" {
181
+ dynamic "ingress" {
182
+ for_each = var.ports
183
+ content {
184
+ from_port = ingress.value
185
+ to_port = ingress.value
186
+ protocol = "tcp"
187
+ cidr_blocks = ["0.0.0.0/0"]
188
+ }
189
+ }
190
+ }
191
+ resource "aws_security_group" "dyn_private" {
192
+ dynamic "ingress" {
193
+ for_each = var.ports
194
+ content {
195
+ from_port = ingress.value
196
+ to_port = ingress.value
197
+ protocol = "tcp"
198
+ cidr_blocks = ["10.0.0.0/8"]
199
+ }
200
+ }
201
+ }`);
202
+ const byEntity = new Map(d.map((x) => [x.entity, x]));
203
+ expect([...byEntity.keys()].sort()).toEqual(["inline/aws_security_group.dyn", "inline/aws_security_group.whole"]);
204
+ expect(d.every((x) => x.severity === "info")).toBe(true);
205
+ expect(byEntity.get("inline/aws_security_group.whole")?.message).toContain("`ingress` is `var.rules`");
206
+ expect(byEntity.get("inline/aws_security_group.dyn")?.message).toContain("`for_each = var.ports`");
207
+ });
208
+
209
+ test("a vpc ingress rule with ip_protocol -1 and no ports", async () => {
210
+ const d = await runInline(`
211
+ resource "aws_vpc_security_group_ingress_rule" "all" {
212
+ security_group_id = aws_security_group.x.id
213
+ ip_protocol = "-1"
214
+ cidr_ipv6 = "::/0"
215
+ }`);
216
+ expect(errors(d)).toHaveLength(1);
217
+ });
218
+ });
219
+ });
@@ -0,0 +1,290 @@
1
+ /**
2
+ * TF030: a security group rule opens a sensitive port to the whole internet.
3
+ *
4
+ * The native Terraform counterpart of the aws lexicon's WAW019 (#2285, under
5
+ * #2284), with the same port list and the same two open CIDRs. It cannot be a
6
+ * bridge to WAW019: the probe (docs/design/waw-hcl-fidelity-probe.md) ran the
7
+ * shipped WAW019 over a root opening 22, 3389, 3306 and 5432 to the world and
8
+ * got nothing back, because none of the three Terraform shapes looks like
9
+ * CloudFormation's `SecurityGroupIngress[].CidrIp`:
10
+ *
11
+ * - `ingress` on `aws_security_group`. The AWS provider types it as an
12
+ * attribute, `set(object({...}))`, not a nested block, though HCL lets it
13
+ * be written either way. hcl2json renders both spellings as one array of
14
+ * rule objects, so one reader covers both. `cidr_blocks` and
15
+ * `ipv6_cidr_blocks` are lists where CloudFormation has a scalar.
16
+ * - `aws_security_group_rule`, which is ingress only when `type = "ingress"`.
17
+ * - `aws_vpc_security_group_ingress_rule`, which spells the CIDR as the
18
+ * scalars `cidr_ipv4` / `cidr_ipv6` and the protocol as `ip_protocol`.
19
+ *
20
+ * A rule is reported when three things are all proved by literals: it is
21
+ * ingress, a CIDR is `0.0.0.0/0` or `::/0`, and its ports reach a sensitive
22
+ * port. Protocol `-1` (or `"all"`) reaches every port whatever `from_port` and
23
+ * `to_port` say. `tcp`, `udp`, `6` and `17` reach the ports between
24
+ * `from_port` and `to_port` inclusive, so `0`-`65535` reaches all four.
25
+ * Anything else (`icmp`, `icmpv6`, a protocol number like `50`) has no ports
26
+ * and is never reported.
27
+ *
28
+ * Nothing here evaluates an expression. When the answer hangs on a value the
29
+ * parse hands back as a reference or a template (`cidr_blocks =
30
+ * var.allowed`, `from_port = var.ssh_port`, `ingress = local.rules`, a
31
+ * `dynamic "ingress"` block), the rule emits one `info` finding that starts
32
+ * `Not determined:` and names the attributes, per the convention fixed on
33
+ * #2284. It never guesses a warning or an error out of something it could
34
+ * not read. A fact that settles the rule on its own still settles it, so
35
+ * `cidr_blocks = var.x` on port 443 is silent: no CIDR makes 443 sensitive.
36
+ *
37
+ * Absence is safe for this rule, which is the easy case of #2284's
38
+ * three-valued question. An `aws_security_group` with no `ingress` admits
39
+ * nothing (the provider default is no rules), and its rules may live in
40
+ * standalone resources, which this check reads in their own right.
41
+ *
42
+ * Scope: root and child modules alike (#2112). The condition is a property
43
+ * of the block itself.
44
+ */
45
+
46
+ import type {
47
+ PostSynthCheck,
48
+ PostSynthContext,
49
+ PostSynthDiagnostic,
50
+ } from "@intentius/chant/lint/post-synth";
51
+ import { RESOURCE_TYPE, type BlockBody } from "../../hcl/parse";
52
+ import { attr } from "../../hcl/value";
53
+ import { blocksOfType, nestedBodies, type TerraformBlock } from "./blocks";
54
+
55
+ /** The ports WAW019 treats as sensitive, with the name a message gives each. */
56
+ export const SENSITIVE_PORTS: ReadonlyMap<number, string> = new Map([
57
+ [22, "SSH"],
58
+ [3389, "RDP"],
59
+ [3306, "MySQL"],
60
+ [5432, "PostgreSQL"],
61
+ ]);
62
+
63
+ /** The two CIDRs that mean "every address". */
64
+ export const OPEN_CIDRS: ReadonlySet<string> = new Set(["0.0.0.0/0", "::/0"]);
65
+
66
+ /** Protocols whose rules carry a port range. */
67
+ const PORTED_PROTOCOLS = new Set(["tcp", "udp", "6", "17"]);
68
+ /** Protocols that mean every protocol and therefore every port. */
69
+ const ALL_PROTOCOLS = new Set(["-1", "all"]);
70
+
71
+ /**
72
+ * A three-valued answer. `unknown` carries the sentence fragments that say
73
+ * which attribute could not be read and what it holds.
74
+ */
75
+ type Answer<T> = { state: "yes"; value: T } | { state: "no" } | { state: "unknown"; why: string[] };
76
+
77
+ const NO = { state: "no" } as const;
78
+
79
+ /**
80
+ * An expression as its author wrote it. hcl2json wraps a bare reference in
81
+ * `${...}` (`var.x` arrives as `"${var.x}"`), so a message quoting the raw
82
+ * value would show a spelling that is not in the file.
83
+ */
84
+ function shown(raw: unknown): string {
85
+ const s = typeof raw === "string" ? raw : JSON.stringify(raw);
86
+ const whole = /^\$\{([\s\S]*)\}$/.exec(s);
87
+ return whole && !whole[1].includes("${") ? whole[1].trim() : s;
88
+ }
89
+
90
+ function unknown(name: string, raw: unknown): { state: "unknown"; why: string[] } {
91
+ return { state: "unknown", why: [`\`${name}\` is \`${shown(raw)}\``] };
92
+ }
93
+
94
+ /** Does a string carry an interpolation, i.e. is it something the parse did not evaluate? */
95
+ function isExpression(value: string): boolean {
96
+ return value.includes("${");
97
+ }
98
+
99
+ /**
100
+ * Which open CIDR, if any, a rule admits. List attributes (`cidr_blocks`,
101
+ * `ipv6_cidr_blocks`) and scalar ones (`cidr_ipv4`, `cidr_ipv6`) both pass
102
+ * through here: a scalar literal is read as a one-element list.
103
+ */
104
+ function openCidr(body: BlockBody, names: readonly string[]): Answer<string> {
105
+ const why: string[] = [];
106
+ for (const name of names) {
107
+ const a = attr(body, name);
108
+ if (a.kind === "absent") continue;
109
+ if (a.kind !== "literal") {
110
+ why.push(...unknown(name, a.raw).why);
111
+ continue;
112
+ }
113
+ const items = Array.isArray(a.value) ? a.value : [a.value];
114
+ for (const item of items) {
115
+ if (typeof item !== "string") continue;
116
+ if (OPEN_CIDRS.has(item.trim())) return { state: "yes", value: item.trim() };
117
+ if (isExpression(item)) why.push(`an element of \`${name}\` is \`${shown(item)}\``);
118
+ }
119
+ }
120
+ return why.length > 0 ? { state: "unknown", why } : NO;
121
+ }
122
+
123
+ /** A literal port: a number, or a numeric string, which hcl2json passes through when the source quotes it. */
124
+ function literalPort(body: BlockBody, name: string): Answer<number> {
125
+ const a = attr(body, name);
126
+ if (a.kind === "absent") return { state: "unknown", why: [`\`${name}\` is not set`] };
127
+ if (a.kind !== "literal") return unknown(name, a.raw);
128
+ const n = typeof a.value === "number" ? a.value : typeof a.value === "string" ? Number(a.value.trim()) : NaN;
129
+ return Number.isInteger(n) ? { state: "yes", value: n } : unknown(name, a.raw);
130
+ }
131
+
132
+ /** What a rule's ports reach: every port, a closed range, or nothing port-shaped. */
133
+ interface Reach {
134
+ /** `null` for protocol `-1`/`all`, which reaches every port whatever the range says. */
135
+ range: [number, number] | null;
136
+ /** The sensitive ports inside the reach, in list order. */
137
+ ports: number[];
138
+ }
139
+
140
+ function sensitiveReach(body: BlockBody, protocolName: string): Answer<Reach> {
141
+ const p = attr(body, protocolName);
142
+ let protocol = "tcp"; // the provider requires a protocol; a body without one is read as port-scoped
143
+ if (p.kind === "reference" || p.kind === "template") return unknown(protocolName, p.raw);
144
+ if (p.kind === "literal") protocol = String(p.value).trim().toLowerCase();
145
+
146
+ if (ALL_PROTOCOLS.has(protocol)) return { state: "yes", value: { range: null, ports: [...SENSITIVE_PORTS.keys()] } };
147
+ if (!PORTED_PROTOCOLS.has(protocol)) return NO;
148
+
149
+ const from = literalPort(body, "from_port");
150
+ const to = literalPort(body, "to_port");
151
+ if (from.state !== "yes" || to.state !== "yes") {
152
+ const why = [...(from.state === "unknown" ? from.why : []), ...(to.state === "unknown" ? to.why : [])];
153
+ return { state: "unknown", why };
154
+ }
155
+ const ports = [...SENSITIVE_PORTS.keys()].sort((a, b) => a - b).filter((port) => from.value <= port && port <= to.value);
156
+ return ports.length > 0 ? { state: "yes", value: { range: [from.value, to.value], ports } } : NO;
157
+ }
158
+
159
+ /** Is a standalone `aws_security_group_rule` an ingress rule? */
160
+ function isIngress(body: BlockBody): Answer<true> {
161
+ const t = attr(body, "type");
162
+ if (t.kind === "absent") return NO;
163
+ if (t.kind !== "literal") return unknown("type", t.raw);
164
+ return String(t.value).trim().toLowerCase() === "ingress" ? { state: "yes", value: true } : NO;
165
+ }
166
+
167
+ /** The outcome for one ingress rule, from its three facts. */
168
+ type Verdict =
169
+ | { state: "open"; cidr: string; reach: Reach }
170
+ | { state: "closed" }
171
+ | { state: "unknown"; why: string[] };
172
+
173
+ function judge(ingress: Answer<true>, cidr: Answer<string>, reach: Answer<Reach>): Verdict {
174
+ // One proved "no" settles it: whatever the unread values hold, this rule
175
+ // cannot open a sensitive port to the world.
176
+ if (ingress.state === "no" || cidr.state === "no" || reach.state === "no") return { state: "closed" };
177
+ const why = [ingress, cidr, reach].flatMap((a) => (a.state === "unknown" ? a.why : []));
178
+ if (why.length > 0) return { state: "unknown", why };
179
+ if (cidr.state !== "yes" || reach.state !== "yes") return { state: "closed" }; // unreachable; narrows the types
180
+ return { state: "open", cidr: cidr.value, reach: reach.value };
181
+ }
182
+
183
+ const INGRESS: Answer<true> = { state: "yes", value: true };
184
+
185
+ /** An inline rule of an `aws_security_group` (block or attribute syntax, same shape after the parse). */
186
+ function judgeInline(rule: BlockBody): Verdict {
187
+ return judge(INGRESS, openCidr(rule, ["cidr_blocks", "ipv6_cidr_blocks"]), sensitiveReach(rule, "protocol"));
188
+ }
189
+
190
+ /** "a, b and c" */
191
+ function listed(items: string[]): string {
192
+ return items.length <= 1 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
193
+ }
194
+
195
+ function describeReach(reach: Reach): string {
196
+ if (reach.range === null) return "every port (protocol -1), SSH, RDP, MySQL and PostgreSQL among them";
197
+ const [from, to] = reach.range;
198
+ if (from === to) return `port ${from} (${SENSITIVE_PORTS.get(from)})`;
199
+ return `ports ${from}-${to}, which include ${listed(reach.ports.map((p) => `${SENSITIVE_PORTS.get(p)} (${p})`))}`;
200
+ }
201
+
202
+ export const tf030: PostSynthCheck = {
203
+ id: "TF030",
204
+ description: "Security group rule allows unrestricted ingress on a sensitive port",
205
+
206
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
207
+ const diagnostics: PostSynthDiagnostic[] = [];
208
+
209
+ const report = (block: TerraformBlock, where: string, verdict: Verdict): void => {
210
+ const subject = where === "" ? `"${block.address}"` : `"${block.address}" ${where}`;
211
+ if (verdict.state === "open") {
212
+ diagnostics.push({
213
+ checkId: "TF030",
214
+ severity: "error",
215
+ message:
216
+ `${subject} allows ingress from ${verdict.cidr} on ${describeReach(verdict.reach)}. ` +
217
+ "Anyone on the internet can reach it. Restrict the CIDR to the sources that need it, or " +
218
+ "reach the host through a bastion, a VPN or SSM Session Manager instead.",
219
+ entity: block.key,
220
+ lexicon: "terraform",
221
+ });
222
+ } else if (verdict.state === "unknown") {
223
+ diagnostics.push({
224
+ checkId: "TF030",
225
+ severity: "info",
226
+ message:
227
+ `Not determined: ${subject}: ${verdict.why.join("; ")}. TF030 does not evaluate expressions, ` +
228
+ "so it cannot tell whether this rule opens SSH, RDP, MySQL or PostgreSQL to 0.0.0.0/0 or ::/0. " +
229
+ "Check the value the expression resolves to.",
230
+ entity: block.key,
231
+ lexicon: "terraform",
232
+ });
233
+ }
234
+ };
235
+
236
+ for (const block of blocksOfType(ctx, RESOURCE_TYPE)) {
237
+ const type = block.address.slice(0, block.address.indexOf("."));
238
+
239
+ if (type === "aws_security_group") {
240
+ const ingress = attr(block.body, "ingress");
241
+ if (ingress.kind === "reference" || ingress.kind === "template") {
242
+ report(block, "", { state: "unknown", why: unknown("ingress", ingress.raw).why });
243
+ } else if (ingress.kind === "literal") {
244
+ const rules = Array.isArray(ingress.value) ? ingress.value : [ingress.value];
245
+ rules.forEach((rule, i) => {
246
+ const where = `ingress rule ${i + 1}`;
247
+ if (typeof rule === "object" && rule !== null && !Array.isArray(rule)) {
248
+ report(block, where, judgeInline(rule as BlockBody));
249
+ } else {
250
+ report(block, where, unknown(`ingress[${i}]`, rule));
251
+ }
252
+ });
253
+ }
254
+ // A `dynamic "ingress"` block's rules come from its for_each, which is
255
+ // not evaluated. Its content still settles the case where no element
256
+ // could be open (a private CIDR literal, a non-sensitive port).
257
+ for (const dyn of nestedBodies(block.body, "dynamic")) {
258
+ for (const generator of nestedBodies(dyn, "ingress")) {
259
+ for (const content of nestedBodies(generator, "content")) {
260
+ const verdict = judgeInline(content);
261
+ if (verdict.state === "closed") continue;
262
+ const why = verdict.state === "unknown" ? verdict.why : [];
263
+ report(block, 'dynamic "ingress"', {
264
+ state: "unknown",
265
+ why: [`its rules come from \`for_each = ${shown(generator.for_each)}\``, ...why],
266
+ });
267
+ }
268
+ }
269
+ }
270
+ // No `ingress` at all: the group admits nothing. Absence is safe here.
271
+ continue;
272
+ }
273
+
274
+ if (type === "aws_security_group_rule") {
275
+ report(
276
+ block,
277
+ "",
278
+ judge(isIngress(block.body), openCidr(block.body, ["cidr_blocks", "ipv6_cidr_blocks"]), sensitiveReach(block.body, "protocol")),
279
+ );
280
+ continue;
281
+ }
282
+
283
+ if (type === "aws_vpc_security_group_ingress_rule") {
284
+ report(block, "", judge(INGRESS, openCidr(block.body, ["cidr_ipv4", "cidr_ipv6"]), sensitiveReach(block.body, "ip_protocol")));
285
+ }
286
+ }
287
+
288
+ return diagnostics;
289
+ },
290
+ };