diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 9a2df7094cc1..000000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "iac"]
- path = iac
- url = https://github.com/advanced-security/codeql-extractor-iac
diff --git a/codeql-workspace.yml b/codeql-workspace.yml
index 6d8c99cb009f..e3ae23adfd4f 100644
--- a/codeql-workspace.yml
+++ b/codeql-workspace.yml
@@ -19,7 +19,6 @@ provide:
- "misc/suite-helpers/qlpack.yml"
- ".github/codeql/extensions/**/codeql-pack.yml"
- "actions/ql/extensions/**/qlpack.yml"
- - "iac/lib/src/qlpack.yml"
- "iac/ql/src/qlpack.yml"
versionPolicies:
default:
diff --git a/iac b/iac
deleted file mode 160000
index e389dc1ae37b..000000000000
--- a/iac
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit e389dc1ae37bf1ec88e317f69d2ddcdf057efdcd
diff --git a/iac/.codeqlmanifest.json b/iac/.codeqlmanifest.json
new file mode 100644
index 000000000000..8941685b2f8c
--- /dev/null
+++ b/iac/.codeqlmanifest.json
@@ -0,0 +1,12 @@
+{
+ "provide": [
+ "ql/*/qlpack.yml"
+ ],
+ "versionPolicies": {
+ "default": {
+ "requireChangeNotes": true,
+ "committedPrereleaseSuffix": "dev",
+ "committedVersion": "nextPatchRelease"
+ }
+ }
+ }
\ No newline at end of file
diff --git a/iac/Cargo.lock b/iac/Cargo.lock
new file mode 100644
index 000000000000..c5bfaec8486b
--- /dev/null
+++ b/iac/Cargo.lock
@@ -0,0 +1,1047 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
+dependencies = [
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "bstr"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "cc"
+version = "1.2.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+
+[[package]]
+name = "chrono"
+version = "0.4.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-link 0.2.0",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+
+[[package]]
+name = "codeql-extractor"
+version = "0.2.0"
+source = "git+https://github.com/github/codeql?rev=c524a98eb91c769cb2994b8373181c2ebd27c20f#c524a98eb91c769cb2994b8373181c2ebd27c20f"
+dependencies = [
+ "chrono",
+ "encoding",
+ "flate2",
+ "globset",
+ "lazy_static",
+ "num_cpus",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_json",
+ "tracing",
+ "tracing-subscriber",
+ "tree-sitter",
+]
+
+[[package]]
+name = "codeql-extractor-iac"
+version = "0.5.1"
+dependencies = [
+ "clap",
+ "codeql-extractor",
+ "flate2",
+ "rayon",
+ "regex",
+ "tracing",
+ "tracing-subscriber",
+ "tree-sitter",
+ "tree-sitter-dockerfile",
+ "tree-sitter-hcl",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encoding"
+version = "0.2.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
+dependencies = [
+ "encoding-index-japanese",
+ "encoding-index-korean",
+ "encoding-index-simpchinese",
+ "encoding-index-singlebyte",
+ "encoding-index-tradchinese",
+]
+
+[[package]]
+name = "encoding-index-japanese"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-korean"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-simpchinese"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-singlebyte"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-tradchinese"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding_index_tests"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
+
+[[package]]
+name = "flate2"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "globset"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.175"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+
+[[package]]
+name = "log"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
+
+[[package]]
+name = "matchers"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.50.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.143"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex-automata",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "tree-sitter"
+version = "0.24.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75"
+dependencies = [
+ "cc",
+ "regex",
+ "regex-syntax",
+ "streaming-iterator",
+ "tree-sitter-language",
+]
+
+[[package]]
+name = "tree-sitter-dockerfile"
+version = "0.2.0"
+source = "git+https://github.com/GeekMasher/tree-sitter-dockerfile?rev=54af8c9009d1c55f47e09fc896ba9857edf09f23#54af8c9009d1c55f47e09fc896ba9857edf09f23"
+dependencies = [
+ "cc",
+ "tree-sitter-language",
+]
+
+[[package]]
+name = "tree-sitter-hcl"
+version = "0.0.1"
+source = "git+https://github.com/GeekMasher/tree-sitter-hcl?rev=3e0539c5fe690b353af38dce1423880d0f0c81f9#3e0539c5fe690b353af38dce1423880d0f0c81f9"
+dependencies = [
+ "cc",
+ "tree-sitter-language",
+]
+
+[[package]]
+name = "tree-sitter-language"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.1.3",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-link"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.3",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
+dependencies = [
+ "windows-link 0.1.3",
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
diff --git a/iac/Cargo.toml b/iac/Cargo.toml
new file mode 100644
index 000000000000..9adb710ad0f1
--- /dev/null
+++ b/iac/Cargo.toml
@@ -0,0 +1,3 @@
+[workspace]
+resolver = "2"
+members = ["extractor"]
diff --git a/iac/LICENSE b/iac/LICENSE
new file mode 100644
index 000000000000..0d381fb9df8a
--- /dev/null
+++ b/iac/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 GitHub Advanced Security
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/iac/README.md b/iac/README.md
new file mode 100644
index 000000000000..ff39f13672a5
--- /dev/null
+++ b/iac/README.md
@@ -0,0 +1,91 @@
+
+
+
+
CodeQL IaC Extractor, Libraries, and Queries
+
+[](https://github.com/advanced-security/codeql-extractor-iac)
+[](https://github.com/advanced-security/codeql-extractor-iac/actions/workflows/publish.yml?query=branch%3Amain)
+[](https://github.com/advanced-security/codeql-extractor-iac/issues)
+[](https://github.com/advanced-security/codeql-extractor-iac)
+[](./LICENSE)
+
+
+
+
+> [!NOTE]
+> This is **not** an official GitHub project and is not supported by the CodeQL team.
+> This is a community project.
+>
+> β οΈ **Important:** As of September 2025, this project **no longer supports Bicep or GitHub Actions scanning**. Please refer to the documentation for supported languages and frameworks.
+
+## π Overview
+
+[CodeQL][codeql] Extractor, Library, and Queries for Infrastructure as Code.
+
+## π οΈ Usage
+
+```yaml
+- name: Initialize and Analyze IaC
+ uses: advanced-security/codeql-extractor-iac@v0.5.1
+```
+
+**Note:** See full documentation on [Workflows][docs-workflows].
+
+## π Documentation
+
+The [documentation for CodeQL IaC extractor can be found here][docs].
+
+**Other Useful Links:**
+
+- [Q&A][docs-qanda] β
+- [Languages and Frameworks][docs-languages-and-frameworks] π
+- [Coverage][docs-coverage] π
+- [Example Workflows][docs-workflows] π
+
+## π License
+
+This project is licensed under the terms of the MIT open source license.
+Please refer to [MIT](./LICENSE.md) for the full terms.
+
+To use this project for Infrastructure as Code, you will need to [use CodeQL][codeql] and follow all terms and conditions of the [CodeQL License][codeql-license].
+
+For use in private repositories and code, you will need to [purchase a GitHub Advanced Security license][advanced-security].
+
+## π₯ Contributors
+
+
+
+
+
+
+
+
+
+
+
+## π¬ Support
+
+Support is via [GitHub Issues][issues] or [GitHub Discussions][discussions].
+
+
+
+[issues]: https://github.com/advanced-security/codeql-extractor-iac/issues
+[discussions]: https://github.com/advanced-security/codeql-extractor-iac/discussions
+[codeql]: https://codeql.github.com/
+[codeql-license]: https://github.com/github/codeql-cli-binaries/blob/main/LICENSE.md
+[advanced-security]: https://github.com/features/security
+[docs]: ./docs
+[docs-qanda]: ./docs/Q%26A.md
+[docs-languages-and-frameworks]: ./docs/languages-and-frameworks.md
+[docs-coverage]: ./docs/coverage.md
+[docs-workflows]: ./docs/workflows.md
diff --git a/iac/codeql-extractor.yml b/iac/codeql-extractor.yml
new file mode 100644
index 000000000000..2e79ae192fb2
--- /dev/null
+++ b/iac/codeql-extractor.yml
@@ -0,0 +1,26 @@
+name: "iac"
+display_name: "IAC"
+version: 0.5.1
+column_kind: "utf8"
+legacy_qltest_extraction: true
+github_api_languages:
+ - HCL
+ - Docker
+scc_languages:
+ - HCL
+ - Docker
+
+# File types
+file_types:
+ - name: hcl
+ display_name: HCL
+ extensions:
+ - .tf
+ - .tfvars
+ - .hcl
+
+ - name: dockerfile
+ display_name: Dockerfile
+ extensions:
+ - .Dockerfile
+ - .Containerfile
diff --git a/iac/docs/Q&A.md b/iac/docs/Q&A.md
new file mode 100644
index 000000000000..48c0eae45271
--- /dev/null
+++ b/iac/docs/Q&A.md
@@ -0,0 +1,55 @@
+# Questions and Answers
+
+## Q: What is `codeql-extractor-iac`?
+
+The `codeql-extractor-iac` is a community extractor for the [CodeQL] static analysis engine.
+It is a community extractor that is designed to extract and analyze Infrastructure as Code (IaC) files.
+
+## Q: What is the `codeql-extractor-iac` used for?
+
+The `codeql-extractor-iac` is used to extract and analyze IaC files.
+It is designed to find security vulnerabilities, misconfigurations, and best practices for different IaC frameworks and technologies.
+
+## Q: What support does GitHub / CodeQL provide to `codeql-extractor-iac`?
+
+The CodeQL IaC extractor is a community extractor and is not supported by GitHub or CodeQL teams.
+All issues should be reported to the `codeql-extractor-iac` [Issues] or [Discussions] and is not officially supported as part of the [GitHub Advanced Security] offering..
+
+## Q: What languages does the `codeql-extractor-iac` support?
+
+The list of the supported languages can be found in the [Languages and Frameworks](./languages-and-frameworks.md#languages) documentation.
+
+## Q: What frameworks and technologies does the `codeql-extractor-iac` support?
+
+The list of the supported frameworks and technologies can be found in the [Languages and Frameworks](./languages-and-frameworks.md#frameworks-and-technologies) documentation.
+
+## Q: How can I get support for a new framework or technology?
+
+If a framework or technology is not listed above and you would like to see it supported, [please open an issue](https://github.com/advanced-security/codeql-extractor-iac/issues).
+
+## Q: Can I use the `iac-all` CodeQL Pack to write custom queries?
+
+Yes, if you want to create your own queries and customisations for `codeql-extractor-iac` you can use the `iac-all` CodeQL Pack with your own pack.
+
+Be aware that the `iac-all` CodeQL Pack can be unstable and may change without notice.
+Pinning to a specific version of the `iac-all` CodeQL Pack is recommended along with test cases to make sure your queries are working as expectΒ§d.s
+
+## Q: What Operating Systems does the `codeql-extractor-iac` support?
+
+The `codeql-extractor-iac` is a community extractor and supports the following operating systems:
+
+- Linux (Ubuntu / Debian tested)
+
+In the future other operating systems may be supported.
+
+## Q: What can I do to support this project?
+
+We are always looking for help and support from the community.
+Everything from staring on GitHub to updating documentation to contributing code is greatly appreciated!
+
+
+
+[Issues]: https://github.com/advanced-security/codeql-extractor-iac/issues
+[Discussions]: https://github.com/advanced-security/codeql-extractor-iac/discussions
+[CodeQL]: https://codeql.github.com
+[GitHub Advanced Security]: https://github.com/features/security
diff --git a/iac/docs/coverage.md b/iac/docs/coverage.md
new file mode 100644
index 000000000000..75f1bf33ec98
--- /dev/null
+++ b/iac/docs/coverage.md
@@ -0,0 +1,52 @@
+# Coverage
+
+The `codeql-extractor-iac` covers a number of technologies today but is being continuously improved.
+
+## Coverage Report
+
+[CSV Coverage reports](https://github.com/advanced-security/codeql-extractor-iac/actions/workflows/coverage.yml) are created every release and push into the main branch.
+
+
+| Suite | Query ID | Severity |
+| ------------- | ---------------------------------------------------- | -------- |
+| code-scanning | tf/alicloud/storage-publicly-accessible | 10.0 |
+| code-scanning | tf/azure/database-unencrypted | 7.0 |
+| code-scanning | tf/azure/database-geo-backup-unset-or-disabled | 2.0 |
+| code-scanning | tf/azure/database-weak-encryption | 4.0 |
+| code-scanning | tf/azure/database-tls-disable | 10.0 |
+| code-scanning | tf/azure/security-center-disabled-notifications | 3.0 |
+| code-scanning | tf/azure/vault-weak-key | 8.0 |
+| code-scanning | tf/azure/storage-publicly-accessible | 10.0 |
+| code-scanning | tf/azure/storage-unencrypted | 6.0 |
+| code-scanning | tf/all/hardcoded-passwords | 8.0 |
+| code-scanning | tf/gcp/abac-enabled | 8.0 |
+| code-scanning | tf/gcp/cluster-pod-security-policy | 5.0 |
+| code-scanning | tf/gcp/cluster-control-plane-publicly-accessible | 5.0 |
+| code-scanning | tf/gcp/storage-publicly-accessible | 10.0 |
+| code-scanning | tf/aws/storage-publicly-accessible | 10.0 |
+| code-scanning | tf/aws/storage-versioning-disabled | 6.0 |
+| code-scanning | tf/aws/storage-unencrypted | 6.0 |
+| code-scanning | tf/aws/s3-public-access-disabled | 5.0 |
+| code-scanning | tf/aws/storage-logging-disabled | 8.0 |
+| code-scanning | tf/aws/elastic-search-disabled-logging | 6.0 |
+| code-scanning | tf/aws/rds-database-unencrytped | 8.0 |
+| code-scanning | tf/aws/eks-unencrypted-secrets | 8.0 |
+| code-scanning | tf/aws/eks-public-cluster | 9.0 |
+| code-scanning | hc/kubernetes/pod-run-as-root | 8.0 |
+| code-scanning | hc/kubernetes/privileged-pod | 9.0 |
+| code-scanning | openapi/web/http-allowed | 2.0 |
+| code-scanning | containers/docker/latest-images | 2.0 |
+| code-scanning | cf/aws/storage-publicly-accessible | 10.0 |
+| code-scanning | iac/ecs/assignpublicip | NA |
+| code-scanning | iac/ecs/non-priv | NA |
+| code-scanning | iac/ecs/container-insights | NA |
+| code-scanning | iac/ecs/log-configuration | NA |
+| code-scanning | iac/ecs/secrets | NA |
+| code-scanning | iac/ecs/assign-publicip-taskset | NA |
+| code-scanning | iac/ecs/read-only-root-filesystem | NA |
+| code-scanning | iac/ecs/pidmode | NA |
+
+
+
+Download the coverage report zip, extract the CSV file, and view the content.
+This will be continuously updated as new queries are added to the extractor.
diff --git a/iac/docs/languages-and-frameworks.md b/iac/docs/languages-and-frameworks.md
new file mode 100644
index 000000000000..28b894a689ad
--- /dev/null
+++ b/iac/docs/languages-and-frameworks.md
@@ -0,0 +1,54 @@
+# Languages and Frameworks
+
+## Languages
+
+The `codeql-extractor-iac` extractor supports the following languages:
+
+| Name | Extension(s) |
+| --------------- | ------------------------------- |
+| Terraform / HCL | `.tf`, `.tfvars`, `.hcl` |
+| JSON | `.json`, `.jsonl`, `.jsonc` |
+| YAML | `.yaml`, `.yml` |
+| Container files | `*Dockerfile`, `*Containerfile` |
+| Bicep | `.bicep` |
+
+All of these files will be extracted and stored inside the IaC CodeQL Database.
+
+## Frameworks and Technologies
+
+The `codeql-extractor-iac` is a community extractor and supports a number of frameworks and technologies.
+The following table lists the supported frameworks and technologies:
+
+| Name | Level Grade | Support Level |
+| -------------------------- | :---------: | ------------------------------- |
+| Terraform (HCL) | 3 | extractor, library, and queries |
+| GitHub Actions | 3 | extractor, library, and queries |
+| HelmChart (Kubernetes) | 3 | extractor, library, and queries |
+| Azure Bicep | 3 | extractor, library, and queries |
+| AWS CloudFormation | 3 | extractor, library, and queries |
+| ARM Template (YAML / JSON) | 2 | extractor and library |
+| Azure Pipelines | 2 | extractor and library |
+| Compose (Docker/Podman) | 2 | extractor and library |
+| Docker / Container file(s) | 2 | extractor and library |
+| OpenAPI / Swagger | 2 | extractor and library |
+
+_levels grades are based on completeness, higher the grade the better its supported._
+
+### Support Levels
+
+There are 5 levels of support for each framework or technology:
+
+- `0` - Unsupported
+ - Not supported at this time but being worked on or planned
+- `1` - Extractor Support
+ - Code / Data is in the CodeQL Database but no library or queries are provided
+ - This results in no alerts and little support for CodeQL writers
+- `2` - Extractor and Library Support
+ - This includes the extractor and a library to support CodeQL writers
+ - This will make writing CodeQL queries easier but still requires CodeQL writers to write their own queries
+- `3` - Extractor, Library and Queries Support
+ - This includes the extractor, library and CodeQL queries
+ - End users can run and generate alerts without having to write their own CodeQL queries
+ - This only means at least one query is provided, more queries may be added over time
+- `4` - Comprehensive Support
+ - This includes the extractor, library, CodeQL queries, tests, and documentation
diff --git a/iac/docs/workflows.md b/iac/docs/workflows.md
new file mode 100644
index 000000000000..2bfa2ce76168
--- /dev/null
+++ b/iac/docs/workflows.md
@@ -0,0 +1,112 @@
+# Workflows
+
+## Basic Usage
+
+To use the CodeQL Extractor, Library, and Queries for Infrastructure as Code, you will need to add the following step to your workflow:
+
+```yaml
+- name: Initialize and Analyze IaC
+ uses: advanced-security/codeql-extractor-iac@v0.5.1
+```
+
+### Uploading SARIF files to GitHub
+
+The CodeQL Extractor will produce a SARIF file but will not upload it for you.
+This has to be done manually or using the `github/codeql-action/upload-sarif` action like so:
+
+```yaml
+- name: Upload SARIF file
+ uses: github/codeql-action/upload-sarif@v3
+ with:
+ sarif_file: codeql-iac.sarif
+```
+
+### Full Action Example
+
+_`.github/workflows/codeql-iac.yml`_ :
+
+```yaml
+name: "CodeQL IaC"
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+ workflow_dispatch:
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: "ubuntu-latest"
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Initialize and Analyze IaC
+ id: codeql_iac
+ uses: advanced-security/codeql-extractor-iac@v0.5.1
+
+ - name: Upload SARIF file
+ uses: github/codeql-action/upload-sarif@v3
+ with:
+ sarif_file: ${{ steps.codeql_iac.outputs.sarif }}
+```
+
+## CodeQL CLI
+
+The CodeQL CLI can be used to analyze IaC files using the CodeQL Extractor, Library, and Queries for Infrastructure as Code. You will need to follow these steps to use the CodeQL CLI:
+
+1. Download the latest CodeQL CLI
+ - Recommended to use the [GitHub CLI Extension github/gh-codeql](https://github.com/github/gh-codeql) for CodeQL
+2. Download and install the extractor version you want to use
+ - The extractor should be placed in the codeql dist folder
+ - Running `codeql version --format=json` will show the location of the codeql dist folder
+3. Check the extractor is installed correctly by running:
+ - `codeql resolve languages` and checking if `iac` is listed
+4. Install the IaC queries pack by running:
+ - `codeql pack install advanced-security/iac-queries`
+5. Run the CodeQL database commands to create and analyze the IaC files
+ - `codeql database create --language=iac --source-root=`
+ - `codeql database analyze --format=sarif-latest --output= advanced-security/iac-queries`
+
+### CLI Example
+
+#### Install extractor
+
+```bash
+# CodeQL Dist directory
+CODEQL_DIST=$(codeql version --format=json | jq -r '.unpackedLocation')
+
+# Download
+gh release download \
+ -R "advanced-security/codeql-extractor-iac" \
+ -D "$CODEQL_DIST" \
+ --clobber \
+ --pattern 'extractor-*.tar.gz'
+
+tar -zxf "$CODEQL_DIST/extractor-iac.tar.gz" --directory "$CODEQL_DIST"
+```
+
+#### Create and analyze database
+
+```bash
+CODEQL_DATABASE="codeql-iac"
+# Create database
+codeql database create \
+ --language=iac \
+ --overwrite \
+ "$CODEQL_DATABASE"
+
+# Analyze database and output SARIF file
+codeql database analyze \
+ --format="sarif-latest" \
+ --output="./codeql-iac.sarif" \
+ "$CODEQL_DATABASE" \
+ "advanced-security/iac-queries"
+```
diff --git a/iac/downgrades/README b/iac/downgrades/README
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/extractor/Cargo.toml b/iac/extractor/Cargo.toml
new file mode 100644
index 000000000000..59c42b6d40ba
--- /dev/null
+++ b/iac/extractor/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "codeql-extractor-iac"
+version = "0.5.1"
+authors = ["GitHub"]
+
+edition = "2024"
+rust-version = "1.85"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+flate2 = "1.1"
+clap = { version = "4.5", features = ["derive"] }
+tracing = "0.1"
+tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+rayon = "1.10"
+regex = "1.11"
+
+# TreeSitter
+tree-sitter = "^0.24"
+# Grammars
+# tree-sitter-hcl = { path = "./tree-sitter-hcl" }
+tree-sitter-hcl = { git = "https://github.com/GeekMasher/tree-sitter-hcl", rev = "3e0539c5fe690b353af38dce1423880d0f0c81f9" }
+# tree-sitter-dockerfile = { path = "./tree-sitter-dockerfile" }
+tree-sitter-dockerfile = { git = "https://github.com/GeekMasher/tree-sitter-dockerfile", rev = "54af8c9009d1c55f47e09fc896ba9857edf09f23" }
+
+# CodeQL - v2.20.4
+codeql-extractor = { git = "https://github.com/github/codeql", rev = "c524a98eb91c769cb2994b8373181c2ebd27c20f" }
diff --git a/iac/extractor/README.md b/iac/extractor/README.md
new file mode 100644
index 000000000000..a3c7fb7242e1
--- /dev/null
+++ b/iac/extractor/README.md
@@ -0,0 +1,12 @@
+# Extractor
+
+## TreeSitter
+
+| Tool/Library | Version | Description |
+| ------------------------------------------------------------------ | ------- | ------------------------------------------------------------------ |
+| Tree-Sitter | 0.24 | Core library for parsing code using TreeSitter |
+| [Tree-Sitter CLI](https://crates.io/crates/tree-sitter-cli/0.24.5) | 0.24.5 | Command-line interface for TreeSitter, used for generating parsers |
+| Tree-Sitter Language (*1) | 14 | Version of the TreeSitter language grammar |
+| CodeQL Extractor | 2.20.4 | Base CodeQL extractor version |
+
+1: Tree-Sitter language versions can cause issues if its newer than the supported CodeQL version.
diff --git a/iac/extractor/src/autobuilder.rs b/iac/extractor/src/autobuilder.rs
new file mode 100644
index 000000000000..9b69ea54464f
--- /dev/null
+++ b/iac/extractor/src/autobuilder.rs
@@ -0,0 +1,30 @@
+use std::env;
+use std::path::PathBuf;
+
+use clap::Args;
+
+use codeql_extractor::autobuilder;
+
+#[derive(Args)]
+// The autobuilder takes no command-line options, but this may change in the future.
+pub struct Options {}
+
+pub fn run(_: Options) -> std::io::Result<()> {
+ let database = env::var("CODEQL_EXTRACTOR_IAC_WIP_DATABASE")
+ .expect("CODEQL_EXTRACTOR_IAC_WIP_DATABASE not set");
+
+ autobuilder::Autobuilder::new("iac", PathBuf::from(database))
+ .include_extensions(&[
+ ".hcl",
+ ".tf",
+ ".ftvars", // Terraform / HCL files
+ ".Dockerfile", // Docker files
+ ])
+ .include_globs(&[
+ "**/Dockerfile",
+ "**/Containerfile", // Docker / Container files
+ ])
+ .exclude_globs(&["**/.git"])
+ .size_limit("10m")
+ .run()
+}
diff --git a/iac/extractor/src/extractor.rs b/iac/extractor/src/extractor.rs
new file mode 100644
index 000000000000..814d4b9150d6
--- /dev/null
+++ b/iac/extractor/src/extractor.rs
@@ -0,0 +1,55 @@
+use clap::Args;
+use std::path::PathBuf;
+
+use codeql_extractor::{extractor::simple, file_paths, trap};
+
+#[derive(Args)]
+pub struct Options {
+ /// Sets a custom source achive folder
+ #[arg(long)]
+ source_archive_dir: PathBuf,
+
+ /// Sets a custom trap folder
+ #[arg(long)]
+ output_dir: PathBuf,
+
+ /// A text file containing the paths of the files to extract
+ #[arg(long)]
+ file_list: String,
+}
+
+pub fn run(options: Options) -> std::io::Result<()> {
+ tracing_subscriber::fmt()
+ .with_target(false)
+ .without_time()
+ .with_level(true)
+ .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
+ .init();
+
+ let file_list = file_paths::path_from_string(&options.file_list);
+ let file_lists: Vec = vec![file_list];
+
+ let extractor = simple::Extractor {
+ prefix: "iac".to_string(),
+ languages: vec![
+ simple::LanguageSpec {
+ prefix: "hcl",
+ ts_language: tree_sitter_hcl::LANGUAGE.into(),
+ node_types: tree_sitter_hcl::NODE_TYPES,
+ file_globs: vec!["*.hcl".into(), "*.tf".into(), "*.tfvar".into()],
+ },
+ simple::LanguageSpec {
+ prefix: "dockerfile",
+ ts_language: tree_sitter_dockerfile::LANGUAGE.into(),
+ node_types: tree_sitter_dockerfile::NODE_TYPES,
+ file_globs: vec!["*Dockerfile".into(), "*Containerfile".into()],
+ },
+ ],
+ trap_dir: options.output_dir,
+ trap_compression: trap::Compression::from_env("CODEQL_IAC_TRAP_COMPRESSION"),
+ source_archive_dir: options.source_archive_dir,
+ file_lists,
+ };
+
+ extractor.run()
+}
diff --git a/iac/extractor/src/generator.rs b/iac/extractor/src/generator.rs
new file mode 100644
index 000000000000..538055c81a17
--- /dev/null
+++ b/iac/extractor/src/generator.rs
@@ -0,0 +1,37 @@
+use clap::Args;
+use std::path::PathBuf;
+
+use codeql_extractor::generator::{generate, language::Language};
+
+#[derive(Args)]
+pub struct Options {
+ /// Path of the generated dbscheme file
+ #[arg(long)]
+ dbscheme: PathBuf,
+
+ /// Path of the generated QLL file
+ #[arg(long)]
+ library: PathBuf,
+}
+
+pub fn run(options: Options) -> std::io::Result<()> {
+ tracing_subscriber::fmt()
+ .with_target(false)
+ .without_time()
+ .with_level(true)
+ .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
+ .init();
+
+ let languages = vec![
+ Language {
+ name: "HCL".to_owned(),
+ node_types: tree_sitter_hcl::NODE_TYPES,
+ },
+ Language {
+ name: "DOCKERFILE".to_owned(),
+ node_types: tree_sitter_dockerfile::NODE_TYPES,
+ },
+ ];
+
+ generate(languages, options.dbscheme, options.library)
+}
diff --git a/iac/extractor/src/main.rs b/iac/extractor/src/main.rs
new file mode 100644
index 000000000000..e6721d4e2243
--- /dev/null
+++ b/iac/extractor/src/main.rs
@@ -0,0 +1,23 @@
+use clap::Parser;
+
+mod autobuilder;
+mod extractor;
+mod generator;
+
+#[derive(Parser)]
+#[command(author, version, about)]
+enum Cli {
+ Extract(extractor::Options),
+ Generate(generator::Options),
+ Autobuild(autobuilder::Options),
+}
+
+fn main() -> std::io::Result<()> {
+ let cli = Cli::parse();
+
+ match cli {
+ Cli::Extract(options) => extractor::run(options),
+ Cli::Generate(options) => generator::run(options),
+ Cli::Autobuild(options) => autobuilder::run(options),
+ }
+}
diff --git a/iac/ql/lib/codeql-pack.lock.yml b/iac/ql/lib/codeql-pack.lock.yml
new file mode 100644
index 000000000000..69ce09bcb67b
--- /dev/null
+++ b/iac/ql/lib/codeql-pack.lock.yml
@@ -0,0 +1,8 @@
+---
+lockVersion: 1.0.0
+dependencies:
+ codeql/util:
+ version: 1.0.12
+ codeql/yaml:
+ version: 1.0.25
+compiled: false
diff --git a/iac/ql/lib/codeql/Locations.qll b/iac/ql/lib/codeql/Locations.qll
new file mode 100644
index 000000000000..ae8058c916df
--- /dev/null
+++ b/iac/ql/lib/codeql/Locations.qll
@@ -0,0 +1,54 @@
+/** Provides classes for working with locations. */
+
+import files.FileSystem
+
+/**
+ * A location as given by a file, a start line, a start column,
+ * an end line, and an end column.
+ *
+ * For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+class Location extends @location_default {
+ /** Gets the file for this location. */
+ File getFile() { locations_default(this, result, _, _, _, _) }
+
+ /** Gets the 1-based line number (inclusive) where this location starts. */
+ int getStartLine() { locations_default(this, _, result, _, _, _) }
+
+ /** Gets the 1-based column number (inclusive) where this location starts. */
+ int getStartColumn() { locations_default(this, _, _, result, _, _) }
+
+ /** Gets the 1-based line number (inclusive) where this location ends. */
+ int getEndLine() { locations_default(this, _, _, _, result, _) }
+
+ /** Gets the 1-based column number (inclusive) where this location ends. */
+ int getEndColumn() { locations_default(this, _, _, _, _, result) }
+
+ /** Gets the number of lines covered by this location. */
+ int getNumLines() { result = this.getEndLine() - this.getStartLine() + 1 }
+
+ /** Gets a textual representation of this element. */
+ cached
+ string toString() {
+ exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
+ this.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
+ result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
+ )
+ }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ exists(File f |
+ locations_default(this, f, startline, startcolumn, endline, endcolumn) and
+ filepath = f.getAbsolutePath()
+ )
+ }
+}
diff --git a/iac/ql/lib/codeql/files/FileSystem.qll b/iac/ql/lib/codeql/files/FileSystem.qll
new file mode 100644
index 000000000000..552b85a4673f
--- /dev/null
+++ b/iac/ql/lib/codeql/files/FileSystem.qll
@@ -0,0 +1,177 @@
+/** Provides classes for working with files and folders. */
+
+private import codeql.Locations
+
+/** A file or folder. */
+abstract class Container extends @container {
+ /** Gets a file or sub-folder in this container. */
+ Container getAChildContainer() { this = result.getParentContainer() }
+
+ /** Gets a file in this container. */
+ File getAFile() { result = this.getAChildContainer() }
+
+ /** Gets a sub-folder in this container. */
+ Folder getAFolder() { result = this.getAChildContainer() }
+
+ /**
+ * Gets the absolute, canonical path of this container, using forward slashes
+ * as path separator.
+ *
+ * The path starts with a _root prefix_ followed by zero or more _path
+ * segments_ separated by forward slashes.
+ *
+ * The root prefix is of one of the following forms:
+ *
+ * 1. A single forward slash `/` (Unix-style)
+ * 2. An upper-case drive letter followed by a colon and a forward slash,
+ * such as `C:/` (Windows-style)
+ * 3. Two forward slashes, a computer name, and then another forward slash,
+ * such as `//FileServer/` (UNC-style)
+ *
+ * Path segments are never empty (that is, absolute paths never contain two
+ * contiguous slashes, except as part of a UNC-style root prefix). Also, path
+ * segments never contain forward slashes, and no path segment is of the
+ * form `.` (one dot) or `..` (two dots).
+ *
+ * Note that an absolute path never ends with a forward slash, except if it is
+ * a bare root prefix, that is, the path has no path segments. A container
+ * whose absolute path has no segments is always a `Folder`, not a `File`.
+ */
+ abstract string getAbsolutePath();
+
+ /**
+ * Gets the base name of this container including extension, that is, the last
+ * segment of its absolute path, or the empty string if it has no segments.
+ *
+ * Here are some examples of absolute paths and the corresponding base names
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ * | Absolute path | Base name |
+ * | "/tmp/tst.go" | "tst.go" |
+ * | "C:/Program Files (x86)" | "Program Files (x86)" |
+ * | "/" | "" |
+ * | "C:/" | "" |
+ * | "D:/" | "" |
+ * | "//FileServer/" | "" |
+ *
+ */
+ string getBaseName() {
+ result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
+ }
+
+ /**
+ * Gets the extension of this container, that is, the suffix of its base name
+ * after the last dot character, if any.
+ *
+ * In particular,
+ *
+ * - if the name does not include a dot, there is no extension, so this
+ * predicate has no result;
+ * - if the name ends in a dot, the extension is the empty string;
+ * - if the name contains multiple dots, the extension follows the last dot.
+ *
+ * Here are some examples of absolute paths and the corresponding extensions
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ * | Absolute path | Extension |
+ * | "/tmp/tst.go" | "go" |
+ * | "/tmp/.classpath" | "classpath" |
+ * | "/bin/bash" | not defined |
+ * | "/tmp/tst2." | "" |
+ * | "/tmp/x.tar.gz" | "gz" |
+ *
+ */
+ string getExtension() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
+ }
+
+ /** Gets the file in this container that has the given `baseName`, if any. */
+ File getFile(string baseName) {
+ result = this.getAFile() and
+ result.getBaseName() = baseName
+ }
+
+ /** Gets the sub-folder in this container that has the given `baseName`, if any. */
+ Folder getFolder(string baseName) {
+ result = this.getAFolder() and
+ result.getBaseName() = baseName
+ }
+
+ /** Gets the parent container of this file or folder, if any. */
+ Container getParentContainer() { containerparent(result, this) }
+
+ /**
+ * Gets the relative path of this file or folder from the root folder of the
+ * analyzed source location. The relative path of the root folder itself is
+ * the empty string.
+ *
+ * This has no result if the container is outside the source root, that is,
+ * if the root folder is not a reflexive, transitive parent of this container.
+ */
+ string getRelativePath() {
+ exists(string absPath, string pref |
+ absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
+ |
+ absPath = pref and result = ""
+ or
+ absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
+ not result.matches("/%")
+ )
+ }
+
+ /**
+ * Gets the stem of this container, that is, the prefix of its base name up to
+ * (but not including) the last dot character if there is one, or the entire
+ * base name if there is not.
+ *
+ * Here are some examples of absolute paths and the corresponding stems
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ * | Absolute path | Stem |
+ * | "/tmp/tst.go" | "tst" |
+ * | "/tmp/.classpath" | "" |
+ * | "/bin/bash" | "bash" |
+ * | "/tmp/tst2." | "tst2" |
+ * | "/tmp/x.tar.gz" | "x.tar" |
+ *
+ */
+ string getStem() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
+ }
+
+ /**
+ * Gets a URL representing the location of this container.
+ *
+ * For more information see https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls.
+ */
+ abstract string getURL();
+
+ /**
+ * Gets a textual representation of the path of this container.
+ *
+ * This is the absolute path of the container.
+ */
+ string toString() { result = this.getAbsolutePath() }
+}
+
+/** A folder. */
+class Folder extends Container, @folder {
+ override string getAbsolutePath() { folders(this, result) }
+
+ /** Gets the URL of this folder. */
+ override string getURL() { result = "folder://" + this.getAbsolutePath() }
+}
+
+/** A file. */
+class File extends Container, @file {
+ override string getAbsolutePath() { files(this, result) }
+
+ /** Gets the URL of this file. */
+ override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
+
+ /** Holds if this file was extracted from ordinary source code. */
+ predicate fromSource() { any() }
+}
diff --git a/iac/ql/lib/codeql/hcl/AST.qll b/iac/ql/lib/codeql/hcl/AST.qll
new file mode 100644
index 000000000000..5f7fb386236c
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/AST.qll
@@ -0,0 +1,12 @@
+/**
+ * Classes and predicates for representing and analyzing HCL (HashiCorp Configuration Language)
+ * Abstract Syntax Trees. This module includes all AST node types such as expressions, statements,
+ * literals, blocks, function calls, variables, and attributes.
+ */
+
+import codeql.hcl.ast.AstNodes
+import codeql.hcl.ast.Block
+import codeql.hcl.ast.Calls
+import codeql.hcl.ast.Literals
+import codeql.hcl.ast.Variables
+import codeql.hcl.ast.Attributes
diff --git a/iac/ql/lib/codeql/hcl/Constants.qll b/iac/ql/lib/codeql/hcl/Constants.qll
new file mode 100644
index 000000000000..e54fe182730a
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/Constants.qll
@@ -0,0 +1,18 @@
+import codeql.hcl.AST
+import Locals
+
+private string evalStringConstant(Expr e) {
+ result = e.(StringLiteral).getValue()
+ or
+ e.(GetAttrExpr).getExpr().(Variable).getName() = "local" and
+ result = evalStringConstant(getLocal(e.(GetAttrExpr).getKey().getName()))
+ // TODO: handle string interpolation
+}
+
+class ConstantExpr extends Expr {
+ string value;
+
+ ConstantExpr() { value = evalStringConstant(this) }
+
+ string getStringValue() { result = value }
+}
diff --git a/iac/ql/lib/codeql/hcl/Locals.qll b/iac/ql/lib/codeql/hcl/Locals.qll
new file mode 100644
index 000000000000..ed687eebea2f
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/Locals.qll
@@ -0,0 +1,5 @@
+import hcl
+
+Expr getLocal(string name) {
+ exists(Block locals | locals.getType() = "locals" and result = locals.getAttribute(name))
+}
diff --git a/iac/ql/lib/codeql/hcl/Providers.qll b/iac/ql/lib/codeql/hcl/Providers.qll
new file mode 100644
index 000000000000..cc2d7ac01eec
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/Providers.qll
@@ -0,0 +1,24 @@
+/**
+ * Classes and predicates for analyzing cloud provider resources and configurations in HCL.
+ * This module imports provider-specific implementations for major cloud platforms and services
+ * commonly used in Terraform and other infrastructure-as-code tools.
+ *
+ * Supported providers include:
+ * - AWS (Amazon Web Services) - EC2, S3, RDS, EKS, and other AWS services
+ * - Azure (Microsoft Azure) - Virtual machines, storage accounts, databases, and Azure services
+ * - GCP (Google Cloud Platform) - Compute Engine, Cloud Storage, GKE, and other GCP services
+ * - GitHub - Repository management, actions, and GitHub-specific resources
+ * - Kubernetes - Kubernetes cluster resources and configurations
+ * - Helm - Helm chart deployments and package management
+ * - OCI (Oracle Cloud Infrastructure) - Oracle cloud services and resources
+ * - Alicloud (Alibaba Cloud) - Alibaba cloud platform services
+ */
+
+import providers.Alicloud
+import providers.AWS
+import providers.Azure
+import providers.GCP
+import providers.GitHub
+import providers.Helm
+import providers.Kubernetes
+import providers.OCI
diff --git a/iac/ql/lib/codeql/hcl/Resources.qll b/iac/ql/lib/codeql/hcl/Resources.qll
new file mode 100644
index 000000000000..b418b0ad7f13
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/Resources.qll
@@ -0,0 +1,39 @@
+private import codeql.Locations
+private import codeql.hcl.AST
+
+// Resources are the most important element in the Terraform language.
+// Each resource block describes one or more infrastructure objects, such as
+// virtual networks, compute instances, or higher-level components such as DNS
+// records.
+//
+// https://developer.hashicorp.com/terraform/language/resources/syntax
+class Resource extends Block {
+ Resource() { this.hasType("resource") }
+
+ string getName() { result = this.getLabel(1) }
+
+ /**
+ * Returns the resource id.
+ */
+ string getId() { result = this.getName() }
+
+ /**
+ * Returns the resource type.
+ */
+ string getResourceType() { result = this.getLabel(0) }
+
+ /**
+ * Checks to see if the resource type matches the given type.
+ */
+ predicate hasResourceType(string type) { this.getResourceType() = type }
+
+ override string toString() {
+ result = "resource " + this.getResourceType() + " " + this.getName()
+ }
+}
+
+class Provider extends Block {
+ Provider() { this.hasType("provider") }
+
+ string getName() { result = this.getLabel(0) }
+}
diff --git a/iac/ql/lib/codeql/hcl/Security.qll b/iac/ql/lib/codeql/hcl/Security.qll
new file mode 100644
index 000000000000..f859723cd414
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/Security.qll
@@ -0,0 +1 @@
+import security.HardcodedPasswords
diff --git a/iac/ql/lib/codeql/hcl/Terraform.qll b/iac/ql/lib/codeql/hcl/Terraform.qll
new file mode 100644
index 000000000000..a0c6cb725d77
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/Terraform.qll
@@ -0,0 +1,97 @@
+private import codeql.files.FileSystem
+private import codeql.hcl.AST
+private import Resources
+
+module Terraform {
+ /**
+ * A Terraform file.
+ */
+ class TerraformFile extends File {
+ TerraformFile() { this.getBaseName().regexpMatch(".*.(tf|hcl)$") }
+ }
+
+ /**
+ * A Terraform settings block.
+ */
+ class Terraform extends Block {
+ Terraform() { this.hasType("terraform") }
+
+ /**
+ * Get the required provider.
+ */
+ RequiredProvider getRequiredProvider() {
+ result = this.getAttribute("required_providers").getAChild()
+ }
+
+ /**
+ * Get required version of Terraform.
+ */
+ string getRequiredVersion() {
+ result = this.getAttribute("required_version").(StringLiteral).getValue()
+ }
+ }
+
+ abstract class RequiredProvider extends Expr {
+ /**
+ * Gets the name of the provider.
+ */
+ abstract string getName();
+
+ /**
+ * Gets the version of the provider.
+ */
+ abstract string getVersion();
+
+ /**
+ * Gets the source of the provider.
+ */
+ abstract string getSource();
+ }
+
+ /**
+ * Basic Terraform required provider String.
+ */
+ class BasicRequiredProvider extends RequiredProvider, StringLiteral {
+ private Terraform terraform;
+
+ BasicRequiredProvider() { this = terraform.getAttribute("required_providers").getAChild() }
+
+ override string toString() { result = "RequiredProvider " + this.getName() }
+
+ override string getName() { result = this.getParent().(Block).getAttributeName(this).getName() }
+
+ override string getVersion() { result = this.getValue() }
+
+ /**
+ * Basic providers are assumed to be from the Hashicorp namespace.
+ */
+ override string getSource() { result = "hashicorp/" + this.getName() }
+ }
+
+ /**
+ * A Terraform required provider object.
+ */
+ class ComplexRequiredProvider extends RequiredProvider, Object {
+ private Terraform terraform;
+
+ ComplexRequiredProvider() { this = terraform.getAttribute("required_providers").getAChild() }
+
+ override string toString() { result = "RequiredProvider " + this.getName() }
+
+ override string getName() { result = this.getParent().(Block).getAttributeName(this).getName() }
+
+ /**
+ * Gets the source of the provider.
+ */
+ override string getSource() {
+ result = this.getElementByName("source").(StringLiteral).getValue()
+ }
+
+ /**
+ * Gets the version of the provider.
+ */
+ override string getVersion() {
+ result = this.getElementByName("version").(StringLiteral).getValue()
+ }
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/ast/AstNodes.qll b/iac/ql/lib/codeql/hcl/ast/AstNodes.qll
new file mode 100644
index 000000000000..8366c12c8e18
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/ast/AstNodes.qll
@@ -0,0 +1,329 @@
+/**
+ * Base classes and core AST node implementations for HCL (HashiCorp Configuration Language).
+ * This module defines the fundamental AST node types including expressions, identifiers, objects,
+ * binary operations, and other core language constructs.
+ */
+
+private import codeql.Locations
+private import codeql.files.FileSystem
+private import codeql.iac.ast.internal.Hcl
+private import codeql.hcl.ast.Literals
+private import codeql.hcl.ast.Variables
+
+/**
+ * An AST node of an HCL program.
+ *
+ * HCL AST nodes represent the structure of HashiCorp Configuration Language code,
+ * including all expressions, statements, blocks, and other language constructs.
+ *
+ * Example HCL code:
+ * ```hcl
+ * resource "aws_instance" "example" {
+ * ami = "ami-12345678"
+ * instance_type = "t2.micro"
+ * }
+ * ```
+ */
+class HclAstNode extends THclAstNode {
+ string toString() { result = this.getAPrimaryQlClass() }
+
+ /** Gets the location of the AST node. */
+ cached
+ Location getLocation() { result = this.getFullLocation() } // overridden in some subclasses
+
+ /** Gets the file containing this AST node. */
+ cached
+ File getFile() { result = this.getFullLocation().getFile() }
+
+ /** Gets the location that spans the entire AST node. */
+ cached
+ final Location getFullLocation() { result = toHclTreeSitter(this).getLocation() }
+
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ if exists(this.getLocation())
+ then this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ else (
+ filepath = "" and
+ startline = 0 and
+ startcolumn = 0 and
+ endline = 0 and
+ endcolumn = 0
+ )
+ }
+
+ /**
+ * Gets the parent in the AST for this node.
+ *
+ * Returns the parent AST node that contains this node as a child.
+ */
+ cached
+ HclAstNode getParent() { result.getAChild(_) = this }
+
+ /**
+ * Gets a child of this node, which can also be retrieved using a predicate
+ * named `pred`.
+ *
+ * For example, for a binary operation, this might return the left and right operands
+ * with predicates "getLeftOperand" and "getRightOperand".
+ */
+ cached
+ HclAstNode getAChild(string pred) { none() }
+
+ /**
+ * Gets any child of this node.
+ *
+ * This is a convenience predicate that returns all children regardless of their predicate name.
+ */
+ HclAstNode getAChild() { result = this.getAChild(_) }
+
+ /**
+ * Gets the primary QL class for the AST node.
+ *
+ * This is used for identification and debugging purposes.
+ */
+ string getAPrimaryQlClass() { result = "???" }
+}
+
+/**
+ * A comment in HCL code.
+ *
+ * Comments are used for documentation and annotations in HCL files.
+ * They can be single-line or multi-line comments.
+ *
+ * Example HCL code:
+ * ```
+ * // This is a single-line comment
+ * resource "aws_instance" "example" {
+ * ami = "ami-12345678"
+ * }
+ * ```
+ */
+class Comment extends HclAstNode, TComment {
+ private HCL::Comment comment;
+
+ override string getAPrimaryQlClass() { result = "Comment" }
+
+ Comment() { this = TComment(comment) }
+
+ /** Gets the textual contents of the comment. */
+ string getContents() { result = comment.getValue() }
+}
+
+/**
+ * An expression in HCL code.
+ *
+ * Expressions represent values and computations in HCL, including literals,
+ * variables, function calls, binary operations, and complex data structures.
+ *
+ * Example HCL expressions:
+ * ```
+ * resource "aws_instance" "example" {
+ * ami = "ami-12345678" // String literal expression
+ * instance_type = var.instance_type // Variable expression
+ * count = length(var.subnets) // Function call expression
+ * enabled = true && var.enabled // Binary operation expression
+ * }
+ * ```
+ */
+class Expr extends HclAstNode, TExpr {
+ override string getAPrimaryQlClass() { result = "Expr" }
+}
+
+/**
+ * A binary operation expression in HCL.
+ *
+ * Binary operations combine two expressions with an operator such as +, -, *, /,
+ * ==, !=, &&, ||, etc.
+ *
+ * Example HCL binary operations:
+ * ```
+ * locals {
+ * sum = var.a + var.b // Addition
+ * enabled = var.debug && true // Logical AND
+ * equal = var.env == "prod" // Equality comparison
+ * }
+ * ```
+ */
+class BinaryOperation extends Expr {
+ HCL::BinaryOperation binaryOp;
+
+ override string getAPrimaryQlClass() { result = "BinaryOperation" }
+
+ BinaryOperation() { this = TBinaryOperation(binaryOp) }
+
+ /** Gets the left operand of the binary operation. */
+ Expr getLeftOperand() { toHclTreeSitter(result) = binaryOp.getLeft() }
+
+ /** Gets the right operand of the binary operation. */
+ Expr getRightOperand() { toHclTreeSitter(result) = binaryOp.getRight() }
+
+ /** Gets the operator string (e.g., "+", "&&", "=="). */
+ string getOperator() { result = binaryOp.getOperator() }
+
+ override string toString() {
+ result = this.getLeftOperand() + " " + this.getOperator() + " " + this.getRightOperand()
+ }
+
+ override HclAstNode getAChild(string pred) {
+ pred = "getLeftOperand" and result = this.getLeftOperand()
+ or
+ pred = "getRightOperand" and result = this.getRightOperand()
+ }
+}
+
+/**
+ * An identifier in HCL code.
+ *
+ * Identifiers represent names of variables, attributes, functions, and other named entities.
+ * They must follow HCL naming conventions (alphanumeric characters and underscores).
+ *
+ * Example HCL identifiers:
+ * ```
+ * resource "aws_instance" "web_server" {
+ * ami = var.instance_ami // 'ami', 'var', 'instance_ami' are identifiers
+ * instance_type = "t2.micro"
+ * tags = {
+ * Name = "WebServer" // 'Name' is an identifier
+ * Environment = var.environment // 'Environment', 'environment' are identifiers
+ * }
+ * }
+ * ```
+ */
+class Identifier extends Expr, TIdentifier {
+ private HCL::Identifier identifier;
+
+ override string getAPrimaryQlClass() { result = "Identifier" }
+
+ Identifier() { this = TIdentifier(identifier) }
+
+ /** Gets the name of the identifier. */
+ string getName() { result = identifier.getValue() }
+}
+
+/**
+ * An object expression in HCL.
+ *
+ * Objects are collections of key-value pairs enclosed in curly braces.
+ * They represent structured data similar to maps or dictionaries in other languages.
+ *
+ * Example HCL objects:
+ * ```
+ * resource "aws_instance" "example" {
+ * tags = { // This is an object
+ * Name = "WebServer"
+ * Environment = "production"
+ * Owner = var.team_name
+ * }
+ * }
+ * ```
+ */
+class Object extends Expr, TObject {
+ private HCL::Object object;
+
+ override string getAPrimaryQlClass() { result = "Object" }
+
+ Object() { this = TObject(object) }
+
+ /** Gets the object element at the specified index. */
+ ObjectElement getElement(int index) { toHclTreeSitter(result) = object.getElement(index) }
+
+ /** Gets any object element in this object. */
+ ObjectElement getElements() { toHclTreeSitter(result) = object.getElement(_) }
+
+ /**
+ * Gets the value expression for an element with the specified key name.
+ *
+ * This predicate handles both identifier keys and string literal keys.
+ */
+ Expr getElementByName(string name) {
+ exists(ObjectElement elem | this.getElements() = elem |
+ (
+ // Variable / Identifier
+ elem.getKey().(Variable).getName() = name
+ or
+ // StringLiteral
+ elem.getKey().(StringLiteral).getValue() = name
+ ) and
+ result = elem.getExpr()
+ )
+ }
+}
+
+/**
+ * An object element (key-value pair) in HCL.
+ *
+ * Object elements represent individual key-value pairs within an object.
+ * The key can be an identifier, string literal, or expression, and the value
+ * can be any valid HCL expression.
+ *
+ * Example HCL object elements:
+ * ```
+ * tags = {
+ * Name = "WebServer" // ObjectElement with identifier key
+ * "Team-Name" = var.team // ObjectElement with string literal key
+ * }
+ * ```
+ */
+class ObjectElement extends Expr, TObjectElem {
+ private HCL::ObjectElem objectElem;
+
+ override string getAPrimaryQlClass() { result = "ObjectElement" }
+
+ ObjectElement() { this = TObjectElem(objectElem) }
+
+ /** Gets the key expression of this object element. */
+ Expr getKey() { toHclTreeSitter(result) = objectElem.getKey() }
+
+ /** Gets the value expression of this object element. */
+ Expr getExpr() { toHclTreeSitter(result) = objectElem.getVal() }
+}
+
+/**
+ * A tuple expression in HCL.
+ *
+ * Tuples are ordered collections of expressions enclosed in square brackets.
+ * They represent lists or arrays of values.
+ *
+ * Example HCL tuples:
+ * ```
+ * resource "aws_security_group" "example" {
+ * ingress {
+ * cidr_blocks = ["10.0.0.0/8", "172.16.0.0/12"] // This is a tuple
+ * }
+ * }
+ * ```
+ */
+class Tuple extends Expr, TTuple {
+ private HCL::Tuple tuple;
+
+ override string getAPrimaryQlClass() { result = "Tuple" }
+
+ Tuple() { this = TTuple(tuple) }
+
+ /** Gets the element at the specified index in the tuple. */
+ Expr getElement(int i) { toHclTreeSitter(result) = tuple.getElement(i) }
+}
+
+/**
+ * A heredoc template expression in HCL.
+ *
+ * Heredocs allow multi-line string literals with embedded expressions.
+ * They are useful for defining large text blocks or templates.
+ *
+ * Example HCL heredoc:
+ * ```
+ * resource "aws_instance" "example" {
+ * user_data = <<-EOF
+ * #!/bin/bash
+ * echo "Hello, ${var.name}!"
+ * EOF
+ * }
+ * ```
+ */
+class HereDoc extends Expr, THeredocTemplate {
+ private HCL::HeredocTemplate hereDoc;
+
+ HereDoc() { this = THeredocTemplate(hereDoc) }
+}
diff --git a/iac/ql/lib/codeql/hcl/ast/Attributes.qll b/iac/ql/lib/codeql/hcl/ast/Attributes.qll
new file mode 100644
index 000000000000..8e27c836672d
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/ast/Attributes.qll
@@ -0,0 +1,110 @@
+/**
+ * Classes and predicates for analyzing HCL attribute expressions.
+ * Attributes represent key-value assignments within blocks and handle
+ * reference resolution for variables, resources, and data sources.
+ */
+
+import codeql.iac.ast.internal.TreeSitter
+import codeql.iac.ast.internal.Hcl
+import codeql.hcl.AST
+import codeql.hcl.ast.AstNodes
+import codeql.hcl.Resources
+
+/**
+ * An attribute in HCL code.
+ *
+ * Attributes represent key-value assignments within blocks. They define configuration
+ * parameters, settings, and properties for resources, data sources, variables, and
+ * other block types. Attributes can contain literal values, expressions, or references
+ * to other resources and variables.
+ *
+ * This class also provides reference resolution capabilities, automatically resolving
+ * variable references, resource attribute access, and data source lookups to their
+ * corresponding definitions.
+ *
+ * Example HCL attributes:
+ * ```
+ * resource "aws_instance" "web" {
+ * ami = "ami-12345678" // Simple attribute with string literal
+ * instance_type = var.instance_type // Attribute with variable reference
+ * subnet_id = aws_subnet.main.id // Attribute with resource reference
+ * vpc_id = data.aws_vpc.default.id // Attribute with data source reference
+ *
+ * tags = { // Attribute with object value
+ * Name = "WebServer"
+ * Environment = var.environment
+ * }
+ * }
+ *
+ * variable "instance_type" {
+ * description = "EC2 instance type" // Attribute with description
+ * type = string // Attribute with type constraint
+ * default = "t2.micro" // Attribute with default value
+ * }
+ * ```
+ */
+class Attribute extends Expr, TAttribute {
+ private HCL::Attribute attribute;
+
+ Attribute() { this = TAttribute(attribute) }
+
+ override string getAPrimaryQlClass() { result = "Attribute" }
+
+ /** Gets the key (name) of this attribute as an identifier. */
+ Identifier getKey() { toHclTreeSitter(result) = attribute.getKey() }
+
+ /**
+ * Gets the value expression of this attribute.
+ *
+ * This may return either the direct value or a resolved reference
+ * if the attribute contains variable, resource, or data source references.
+ */
+ Expr getExpr() {
+ result = this.getReference() or
+ toHclTreeSitter(result) = attribute.getVal()
+ }
+
+ /**
+ * Gets the resolved reference for this attribute, if it contains one.
+ *
+ * This predicate handles automatic resolution of:
+ * - Variable references (var.name) to their default values
+ * - Resource attribute references (resource.name.attribute) to the corresponding resource attributes
+ * - Data source references (data.type.name.attribute) to the corresponding data source attributes
+ *
+ * Returns the resolved expression if a reference is found, or fails if the attribute
+ * contains a literal value or unresolvable reference.
+ */
+ Expr getReference() {
+ exists(GetAttrExpr e | e = this.getExpr() |
+ (
+ // variable / var lookup
+ // Example: var.name
+ e.getExpr().(Variable).getName() = "var" and
+ exists(Block var |
+ var.getType() = "variable" and var.getLabel(0) = e.getKey().(Identifier).getName()
+ |
+ result = var.getAttribute("default")
+ )
+ or
+ // TODO: data lookup
+ // resource lookup
+ // Example: resource.name.attribute
+ exists(Resource resources |
+ // resource
+ resources.getResourceType() = e.getExpr().(GetAttrExpr).getExpr().(Variable).getName() and
+ // resource name
+ resources.getLabel(1) = e.getExpr().(GetAttrExpr).getKey().(Identifier).getName()
+ |
+ // resource attribute key
+ // ID attribute return the resource itself
+ e.getKey().(Identifier).getName() = "id" and
+ result = resources
+ or
+ // get attribute in resource
+ result = resources.getAttribute(e.getKey().(Identifier).getName())
+ )
+ )
+ )
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/ast/Block.qll b/iac/ql/lib/codeql/hcl/ast/Block.qll
new file mode 100644
index 000000000000..ac85c3f5158c
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/ast/Block.qll
@@ -0,0 +1,114 @@
+/**
+ * Classes and predicates for analyzing HCL block structures.
+ * Blocks are the primary organizational unit in HCL, representing resources,
+ * data sources, variables, outputs, and other configuration elements.
+ */
+
+private import codeql.Locations
+private import codeql.files.FileSystem
+private import codeql.iac.ast.internal.Hcl
+private import codeql.hcl.ast.AstNodes
+private import codeql.hcl.ast.Attributes
+
+/**
+ * A block in HCL code.
+ *
+ * Blocks are the fundamental structural elements in HCL configuration files.
+ * They define resources, data sources, variables, outputs, and other configuration
+ * components. Blocks have a type, optional labels, and a body containing attributes
+ * and nested blocks.
+ *
+ * Example HCL blocks:
+ * ```
+ * resource "aws_instance" "web" { // Block type: "resource", Labels: ["aws_instance", "web"]
+ * ami = "ami-12345678"
+ * instance_type = "t2.micro"
+ *
+ * tags = {
+ * Name = "WebServer"
+ * }
+ * }
+ *
+ * variable "environment" { // Block type: "variable", Labels: ["environment"]
+ * description = "The deployment environment"
+ * type = string
+ * default = "dev"
+ * }
+ * ```
+ */
+class Block extends Expr, TBlock {
+ HCL::Block block;
+
+ override string getAPrimaryQlClass() { result = "Block" }
+
+ Block() { this = TBlock(block) }
+
+ /** Gets the type of this block (e.g., "resource", "variable", "data"). */
+ string getType() { result = block.getType().getValue() }
+
+ /** Holds if this block has the specified type. */
+ predicate hasType(string type) { type = this.getType() }
+
+ override string toString() { result = this.getType() }
+
+ /** Gets any nested block within this block's body. */
+ Block getABlock() { toHclTreeSitter(result) = block.getBody().getChild(_) }
+
+ /**
+ * Gets the label at the specified index.
+ *
+ * Labels provide additional identification for blocks. For example,
+ * in `resource "aws_instance" "web"`, the labels are "aws_instance" and "web".
+ */
+ string getLabel(int i) {
+ result = block.getLabel(i).(HCL::Identifier).getValue() or
+ result = block.getLabel(i).(HCL::StringLit).getChild().getValue()
+ }
+
+ /** Holds if this block has the specified label at the given index. */
+ predicate hasLabel(int i, string label) { label = this.getLabel(i) }
+
+ override HclAstNode getAChild(string pred) {
+ pred = "getABlock" and result = this.getABlock()
+ or
+ pred = "getAttribute" and result = this.getAttribute(_)
+ }
+
+ /**
+ * Gets the value of an attribute or nested block with the specified name.
+ *
+ * This predicate can return either attribute values or nested blocks
+ * that match the given name.
+ */
+ Expr getAttribute(string name) {
+ exists(Attribute attr | block.getBody().getChild(_) = toHclTreeSitter(attr) |
+ attr.getKey().(Identifier).getName() = name and
+ result = attr.getExpr()
+ )
+ or
+ result = this.getABlock() and result.(Block).hasType(name)
+ }
+
+ /** Gets any attribute defined in this block's body. */
+ Attribute getAttributes() { toHclTreeSitter(result) = block.getBody().getChild(_) }
+
+ /** Holds if this block has an attribute with the specified name. */
+ predicate hasAttribute(string name) {
+ exists(Attribute attr | block.getBody().getChild(_) = toHclTreeSitter(attr) |
+ attr.getKey().(Identifier).getName() = name
+ )
+ }
+
+ /**
+ * Gets the identifier that serves as the key for the given attribute expression.
+ *
+ * This is useful for reverse lookups to find which attribute name
+ * corresponds to a particular expression value.
+ */
+ Identifier getAttributeName(Expr expr) {
+ exists(Attribute attr | attr = this.getAttributes() |
+ attr.getExpr() = expr and
+ result = attr.getKey()
+ )
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/ast/Calls.qll b/iac/ql/lib/codeql/hcl/ast/Calls.qll
new file mode 100644
index 000000000000..540c225a345d
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/ast/Calls.qll
@@ -0,0 +1,57 @@
+/**
+ * Classes and predicates for analyzing HCL function call expressions.
+ * Function calls invoke built-in functions or user-defined functions with
+ * arguments to compute values dynamically.
+ */
+
+private import codeql.iac.ast.internal.Hcl
+private import codeql.hcl.ast.AstNodes
+
+/**
+ * A function call expression in HCL code.
+ *
+ * Function calls invoke functions with zero or more arguments to compute values.
+ * HCL provides many built-in functions for string manipulation, mathematical
+ * operations, data transformation, and more.
+ *
+ * Example HCL function calls:
+ * ```
+ * resource "aws_instance" "example" {
+ * count = length(var.availability_zones) // Function call: length()
+ * ami = data.aws_ami.ubuntu.id
+ * instance_type = "t2.micro"
+ * availability_zone = element(var.availability_zones, count.index) // Function call: element()
+ *
+ * tags = {
+ * Name = format("web-server-%02d", count.index + 1) // Function call: format()
+ * }
+ * }
+ *
+ * locals {
+ * subnet_ids = concat( // Function call: concat()
+ * aws_subnet.public[*].id,
+ * aws_subnet.private[*].id
+ * )
+ * timestamp = formatdate("YYYY-MM-DD", timestamp()) // Function calls: formatdate(), timestamp()
+ * }
+ * ```
+ */
+class FunctionCall extends Expr, TFunctionCall {
+ HCL::FunctionCall functionCall;
+
+ override string getAPrimaryQlClass() { result = "FunctionCall" }
+
+ FunctionCall() { this = TFunctionCall(functionCall) }
+
+ /** Gets the function being called (typically an identifier). */
+ Expr getCallee() { toHclTreeSitter(result) = functionCall.getFunction() }
+
+ /** Gets the argument at the specified index. */
+ Expr getArgument(int i) { toHclTreeSitter(result) = functionCall.getArgument(i) }
+
+ override HclAstNode getAChild(string pred) {
+ pred = "getCallee" and result = this.getCallee()
+ or
+ pred = "getArgument" and result = this.getArgument(_)
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/ast/Literals.qll b/iac/ql/lib/codeql/hcl/ast/Literals.qll
new file mode 100644
index 000000000000..8c7345c9645f
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/ast/Literals.qll
@@ -0,0 +1,151 @@
+/**
+ * Classes and predicates for analyzing HCL literal values.
+ * This includes string literals, numeric literals, boolean literals,
+ * and null literals used in HCL configuration files.
+ */
+
+private import codeql.iac.ast.internal.Hcl
+private import codeql.hcl.ast.AstNodes
+
+/**
+ * A literal value in HCL code.
+ *
+ * Literals represent constant values directly written in the code, such as
+ * strings, numbers, booleans, and null values. They are the basic building
+ * blocks for expressing concrete values in HCL configurations.
+ *
+ * Example HCL literals:
+ * ```
+ * resource "aws_instance" "example" {
+ * ami = "ami-12345678" // String literal
+ * instance_type = "t2.micro" // String literal
+ * monitoring = true // Boolean literal
+ * count = 2 // Numeric literal
+ * description = null // Null literal
+ * }
+ * ```
+ */
+class Literal extends Expr, TLiteral {
+ @hcl_underscore_literal_value literal;
+
+ override string getAPrimaryQlClass() { result = "Literal" }
+
+ Literal() { literal = toHclTreeSitter(this) }
+
+ /** Gets the string representation of this literal's value. */
+ string getValue() { none() }
+
+ override string toString() { result = this.getValue() }
+}
+
+/**
+ * A string literal in HCL code.
+ *
+ * String literals represent textual values enclosed in double quotes.
+ * They can contain escape sequences and interpolation expressions.
+ *
+ * Example HCL string literals:
+ * ```
+ * resource "aws_instance" "example" {
+ * ami = "ami-12345678" // Simple string literal
+ * name = "web-server-${var.env}" // String with interpolation
+ * description = "A \"quoted\" description" // String with escape sequences
+ * }
+ * ```
+ */
+class StringLiteral extends Literal, TStringLit {
+ override HCL::StringLit literal;
+
+ override string getAPrimaryQlClass() { result = "StringLiteral" }
+
+ StringLiteral() { this = TStringLit(literal) }
+
+ /** Gets the string value of this literal, without the surrounding quotes. */
+ override string getValue() { result = literal.getChild().getValue() }
+}
+
+/**
+ * A numeric literal in HCL code.
+ *
+ * Numeric literals represent integer or floating-point numbers.
+ * They can be written in decimal, hexadecimal, octal, or binary notation.
+ *
+ * Example HCL numeric literals:
+ * ```
+ * resource "aws_instance" "example" {
+ * count = 3 // Integer literal
+ * cpu_credits = 0.5 // Floating-point literal
+ * port = 0x50 // Hexadecimal literal
+ * allocated_storage = 20 // Integer literal
+ * }
+ * ```
+ */
+class NumericLiteral extends Literal, TNumericLit {
+ override HCL::NumericLit literal;
+
+ override string getAPrimaryQlClass() { result = "NumericLiteral" }
+
+ NumericLiteral() { this = TNumericLit(literal) }
+
+ /** Gets the string representation of this numeric literal. */
+ override string getValue() { result = literal.getValue() }
+
+ /** Gets the integer value of this numeric literal, if it represents an integer. */
+ int getInt() { result = literal.getValue().toInt() }
+}
+
+/**
+ * A boolean literal in HCL code.
+ *
+ * Boolean literals represent true or false values.
+ * They are used for flags, conditions, and binary configuration options.
+ *
+ * Example HCL boolean literals:
+ * ```
+ * resource "aws_instance" "example" {
+ * monitoring = true // Boolean literal (true)
+ * ebs_optimized = false // Boolean literal (false)
+ * associate_public_ip_address = true // Boolean literal (true)
+ * }
+ * ```
+ */
+class BooleanLiteral extends Literal, TBooleanLiteral {
+ override HCL::BoolLit literal;
+
+ override string getAPrimaryQlClass() { result = "BooleanLiteral" }
+
+ BooleanLiteral() { this = TBooleanLiteral(literal) }
+
+ /** Gets the string representation of this boolean literal ("true" or "false"). */
+ override string getValue() { result = literal.getValue() }
+
+ /** Gets the boolean value of this literal. */
+ boolean getBool() { result.toString() = literal.getValue() }
+}
+
+/**
+ * A null literal in HCL code.
+ *
+ * Null literals represent the absence of a value. They are used to explicitly
+ * indicate that an attribute should have no value or to reset a previously
+ * defined value.
+ *
+ * Example HCL null literals:
+ * ```
+ * resource "aws_instance" "example" {
+ * key_name = null // Null literal - no key pair
+ * user_data = null // Null literal - no user data
+ * iam_instance_profile = null // Null literal - no IAM profile
+ * }
+ * ```
+ */
+class NullLiteral extends Literal, TNullLiteral {
+ override HCL::NullLit literal;
+
+ override string getAPrimaryQlClass() { result = "NullLiteral" }
+
+ NullLiteral() { this = TNullLiteral(literal) }
+
+ /** Gets the string representation of this null literal ("null"). */
+ override string getValue() { result = literal.getValue() }
+}
diff --git a/iac/ql/lib/codeql/hcl/ast/Variables.qll b/iac/ql/lib/codeql/hcl/ast/Variables.qll
new file mode 100644
index 000000000000..45a2f858f4aa
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/ast/Variables.qll
@@ -0,0 +1,82 @@
+/**
+ * Classes and predicates for analyzing HCL variable expressions and attribute access expressions.
+ * This includes variable references, attribute access (dot notation), and property lookups.
+ */
+
+private import codeql.Locations
+private import codeql.files.FileSystem
+private import codeql.iac.ast.internal.Hcl
+private import codeql.hcl.ast.AstNodes
+
+/**
+ * A variable expression in HCL code.
+ *
+ * Variable expressions reference values defined elsewhere, such as input variables,
+ * local values, data sources, resources, or built-in variables like `var`, `local`,
+ * `data`, etc.
+ *
+ * Example HCL variable expressions:
+ * ```
+ * variable "instance_type" {
+ * description = "EC2 instance type"
+ * default = "t2.micro"
+ * }
+ *
+ * resource "aws_instance" "example" {
+ * ami = var.instance_ami // Variable expression: var
+ * instance_type = var.instance_type // Variable expression: var
+ * subnet_id = local.subnet_id // Variable expression: local
+ * }
+ * ```
+ */
+class Variable extends Expr, TVariable {
+ HCL::VariableExpr var;
+
+ override string getAPrimaryQlClass() { result = "Variable" }
+
+ Variable() { this = TVariable(var) }
+
+ override string toString() { result = this.getName() }
+
+ /** Gets the name of the variable being referenced. */
+ string getName() { result = var.getName().getValue() }
+}
+
+/**
+ * An attribute access expression in HCL code.
+ *
+ * Attribute access expressions use dot notation to access properties or attributes
+ * of objects, resources, variables, or other complex values. They consist of an
+ * expression followed by a dot and an identifier.
+ *
+ * Example HCL attribute access expressions:
+ * ```
+ * resource "aws_instance" "web" {
+ * ami = data.aws_ami.ubuntu.id // Access 'id' attribute of data source
+ * subnet_id = aws_subnet.main.id // Access 'id' attribute of resource
+ * instance_type = var.instance_config.type // Access 'type' attribute of variable
+ * vpc_id = local.network_config.vpc_id // Access 'vpc_id' attribute of local
+ * }
+ * ```
+ */
+class GetAttrExpr extends Expr, TGetAttrExpr {
+ private HCL::GetAttrExpr getAttr;
+
+ override string getAPrimaryQlClass() { result = "GetExpr" }
+
+ GetAttrExpr() { this = TGetAttrExpr(getAttr) }
+
+ /** Gets the base expression being accessed (the part before the dot). */
+ Expr getExpr() { toHclTreeSitter(result) = getAttr.getExpr() }
+
+ /** Gets the attribute name being accessed (the identifier after the dot). */
+ Identifier getKey() { toHclTreeSitter(result) = getAttr.getKey() }
+
+ override string toString() { result = this.getExpr() + "." + this.getKey().getName() }
+
+ override HclAstNode getAChild(string pred) {
+ pred = "getExpr" and result = this.getExpr()
+ or
+ pred = "getKey" and result = this.getKey()
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/providers/AWS.qll b/iac/ql/lib/codeql/hcl/providers/AWS.qll
new file mode 100644
index 000000000000..23c7fc65ec99
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/providers/AWS.qll
@@ -0,0 +1,357 @@
+/**
+ * Classes and predicates for analyzing AWS (Amazon Web Services) provider resources and configurations in HCL.
+ * This module provides specific support for AWS resources, data sources, and provider configurations
+ * commonly used in Terraform and other HCL-based infrastructure-as-code tools.
+ */
+
+private import codeql.hcl.AST
+private import codeql.hcl.Resources
+private import codeql.hcl.Constants
+
+/**
+ * AWS provider module containing classes for analyzing AWS-specific HCL configurations.
+ *
+ * This module provides specialized classes for AWS resources like EC2 instances, S3 buckets,
+ * RDS databases, and other AWS services commonly defined in Terraform configurations.
+ */
+module AWS {
+ /**
+ * An AWS resource in HCL configuration.
+ *
+ * AWS resources represent infrastructure components that are managed by AWS,
+ * such as EC2 instances, S3 buckets, RDS databases, VPCs, etc.
+ *
+ * Example AWS resources:
+ * ```
+ * resource "aws_instance" "web" {
+ * ami = "ami-12345678"
+ * instance_type = "t2.micro"
+ * }
+ *
+ * resource "aws_s3_bucket" "data" {
+ * bucket = "my-data-bucket"
+ * }
+ * ```
+ */
+ class AwsResource extends Resource, Block {
+ AwsResource() { this.getResourceType().regexpMatch("^aws.*") }
+ }
+
+ /**
+ * An AWS provider configuration block.
+ *
+ * The AWS provider block configures authentication and regional settings
+ * for AWS resource management in Terraform.
+ *
+ * Example AWS provider:
+ * ```
+ * provider "aws" {
+ * region = "us-west-2"
+ * access_key = var.aws_access_key
+ * secret_key = var.aws_secret_key
+ * }
+ * ```
+ */
+ class AwsProvider extends Provider {
+ AwsProvider() { this.getName() = "aws" }
+
+ /** Gets the AWS region configuration expression. */
+ Expr getRegion() { result = this.getAttribute("region") }
+
+ /** Gets the AWS access key configuration expression. */
+ Expr getAccessKey() { result = this.getAttribute("access_key") }
+
+ /** Gets the AWS secret key configuration expression. */
+ Expr getSecretKey() { result = this.getAttribute("secret_key") }
+ }
+
+ /**
+ * An AWS S3 bucket resource.
+ *
+ * S3 buckets provide object storage with high availability and scalability.
+ * They are commonly used for storing files, backups, static websites, and data lakes.
+ *
+ * Example S3 bucket:
+ * ```
+ * resource "aws_s3_bucket" "website" {
+ * bucket = "my-website-bucket"
+ *
+ * tags = {
+ * Name = "Website Bucket"
+ * Environment = "production"
+ * }
+ * }
+ * ```
+ */
+ class S3Bucket extends AwsResource {
+ S3Bucket() { this.getResourceType() = "aws_s3_bucket" }
+
+ override string getId() { result = this.getAttribute("bucket").(StringLiteral).getValue() }
+
+ /**
+ * Gets the ACL (Access Control List) configuration for this bucket.
+ * This can be either a legacy attribute or a separate ACL resource.
+ */
+ Expr getAcl() {
+ // Legacy attribute
+ result = this.getAttribute("acl")
+ or
+ // New Block
+ exists(S3BucketAcl acl | acl.getBucket() = this | result = acl)
+ }
+
+ /**
+ * Gets the ACL value as a string (e.g., "private", "public-read").
+ */
+ string getAclValue() {
+ result = this.getAcl().(StringLiteral).getValue()
+ or
+ result = this.getAcl().(S3BucketAcl).getAcl().(StringLiteral).getValue()
+ }
+
+ /**
+ * Gets the logging configuration for this bucket.
+ * This can be either a legacy attribute or a separate logging resource.
+ */
+ Expr getLogging() {
+ // Legacy attribute
+ result = this.getAttribute("logging")
+ or
+ // New Block
+ exists(S3BucketLogging logs | logs.getBucket() = this | result = logs)
+ }
+
+ /**
+ * Gets the versioning configuration for this bucket.
+ * This can be either a legacy attribute or a separate versioning resource.
+ */
+ Expr getVersioning() {
+ result = this.getAttribute("versioning")
+ or
+ exists(S3BucketVersioning versioning | versioning.getBucket() = this | result = versioning)
+ }
+
+ /**
+ * Gets the versioning status as a boolean value.
+ */
+ boolean getVersioningValue() {
+ result = this.getVersioning().(Block).getAttribute("enabled").(BooleanLiteral).getBool()
+ or
+ result =
+ this.getVersioning().(S3BucketVersioning).getVersioningStatus().(BooleanLiteral).getBool()
+ }
+
+ /**
+ * Gets the server-side encryption configuration for this bucket.
+ * This can be either a legacy attribute or a separate encryption configuration resource.
+ */
+ Expr getEncryptionConfiguration() {
+ result = this.getAttribute("server_side_encryption_configuration")
+ or
+ exists(S3BucketEncryptionConfiguration config | config.getBucket() = this | result = config)
+ }
+ }
+
+ /**
+ * An AWS S3 bucket logging configuration resource.
+ *
+ * This resource manages access logging for S3 buckets, allowing you to track
+ * requests made to your bucket for security and audit purposes.
+ *
+ * Example S3 bucket logging:
+ * ```
+ * resource "aws_s3_bucket_logging" "example" {
+ * bucket = aws_s3_bucket.example.id
+ *
+ * target_bucket = aws_s3_bucket.log_bucket.id
+ * target_prefix = "log/"
+ * }
+ * ```
+ */
+ class S3BucketLogging extends AwsResource {
+ S3BucketLogging() { this.getResourceType() = "aws_s3_bucket_logging" }
+
+ /** Gets the S3 bucket that this logging configuration applies to. */
+ S3Bucket getBucket() { result = this.getAttribute("bucket") }
+ }
+
+ /**
+ * An AWS S3 bucket ACL (Access Control List) configuration resource.
+ *
+ * This resource manages the access control list for S3 buckets, controlling
+ * who can access your bucket and what permissions they have.
+ *
+ * Example S3 bucket ACL:
+ * ```
+ * resource "aws_s3_bucket_acl" "example" {
+ * bucket = aws_s3_bucket.example.id
+ * acl = "private"
+ * }
+ * ```
+ */
+ class S3BucketAcl extends AwsResource {
+ S3BucketAcl() { this.getResourceType() = "aws_s3_bucket_acl" }
+
+ /** Gets the S3 bucket that this ACL configuration applies to. */
+ S3Bucket getBucket() { result = this.getAttribute("bucket") }
+
+ /** Gets the ACL setting expression. */
+ Expr getAcl() { result = this.getAttribute("acl") }
+ }
+
+ /**
+ * An AWS S3 bucket versioning configuration resource.
+ *
+ * This resource manages versioning settings for S3 buckets, allowing you to
+ * keep multiple versions of objects in the same bucket.
+ *
+ * Example S3 bucket versioning:
+ * ```
+ * resource "aws_s3_bucket_versioning" "example" {
+ * bucket = aws_s3_bucket.example.id
+ * versioning_configuration {
+ * status = "Enabled"
+ * }
+ * }
+ * ```
+ */
+ class S3BucketVersioning extends AwsResource {
+ S3BucketVersioning() { this.getResourceType() = "aws_s3_bucket_versioning" }
+
+ /** Gets the S3 bucket that this versioning configuration applies to. */
+ S3Bucket getBucket() { result = this.getAttribute("bucket") }
+
+ /** Gets the versioning configuration block. */
+ Block getVersioning() { result = this.getAttribute("versioning") }
+
+ /** Gets the versioning status expression. */
+ Expr getVersioningStatus() { result = this.getVersioning().getAttribute("enabled") }
+ }
+
+ /**
+ * An AWS S3 bucket server-side encryption configuration resource.
+ *
+ * This resource manages encryption settings for S3 buckets, ensuring that
+ * objects stored in the bucket are encrypted at rest.
+ *
+ * Example S3 bucket encryption:
+ * ```
+ * resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
+ * bucket = aws_s3_bucket.example.id
+ *
+ * rule {
+ * apply_server_side_encryption_by_default {
+ * sse_algorithm = "AES256"
+ * }
+ * }
+ * }
+ * ```
+ */
+ class S3BucketEncryptionConfiguration extends AwsResource {
+ S3BucketEncryptionConfiguration() {
+ this.getResourceType() = "aws_s3_bucket_server_side_encryption_configuration"
+ }
+
+ /** Gets the S3 bucket that this encryption configuration applies to. */
+ S3Bucket getBucket() { result = this.getAttribute("bucket") }
+ }
+
+ /**
+ * An AWS RDS database instance resource.
+ *
+ * RDS provides managed relational database services for various database engines
+ * like MySQL, PostgreSQL, Oracle, SQL Server, and others.
+ *
+ * Example RDS database:
+ * ```
+ * resource "aws_db_instance" "main" {
+ * identifier = "main-database"
+ * engine = "mysql"
+ * engine_version = "8.0"
+ * instance_class = "db.t3.micro"
+ * allocated_storage = 20
+ *
+ * db_name = "myapp"
+ * username = "admin"
+ * password = var.db_password
+ * }
+ * ```
+ */
+ class Database extends AwsResource {
+ Database() { this.getResourceType() = "aws_db_instance" }
+
+ /** Gets the database password expression. */
+ Expr getPassword() { result = this.getAttribute("password") }
+ }
+
+ /**
+ * An AWS Elastic Kubernetes Service (EKS) cluster resource.
+ *
+ * EKS provides managed Kubernetes clusters, making it easier to run Kubernetes
+ * applications on AWS without needing to install and operate your own cluster.
+ *
+ * Example EKS cluster:
+ * ```
+ * resource "aws_eks_cluster" "main" {
+ * name = "main-cluster"
+ * role_arn = aws_iam_role.cluster.arn
+ * version = "1.21"
+ *
+ * vpc_config {
+ * subnet_ids = var.subnet_ids
+ * }
+ * }
+ * ```
+ */
+ class EKSCluster extends AwsResource {
+ EKSCluster() { this.getResourceType() = "aws_eks_cluster" }
+
+ /** Gets the VPC configuration block for this EKS cluster. */
+ Block getVpcConfig() { result = this.getAttribute("vpc_config") }
+
+ /** Gets whether the cluster endpoint has public access enabled. */
+ boolean getEndpointPublicAccess() {
+ result = this.getVpcConfig().getAttribute("endpoint_public_access").(BooleanLiteral).getBool()
+ }
+
+ /** Gets the CIDR blocks that can access the public cluster endpoint. */
+ Expr getPublicAccessCidrs() { result = this.getVpcConfig().getAttribute("public_access_cidrs") }
+
+ /** Gets the public access sources configuration. */
+ Expr getPublicAccessSources() {
+ result = this.getVpcConfig().getAttribute("public_access_sources")
+ }
+
+ /** Gets the encryption configuration for this cluster. */
+ Expr getEncryptionConfig() { result = this.getAttribute("encryption_config") }
+ }
+
+ /**
+ * An AWS EKS cluster encryption configuration.
+ *
+ * This configuration enables envelope encryption of Kubernetes secrets using
+ * AWS Key Management Service (KMS) keys.
+ *
+ * Example EKS encryption configuration:
+ * ```
+ * resource "aws_eks_cluster" "main" {
+ * name = "main-cluster"
+ *
+ * encryption_config {
+ * provider {
+ * key_arn = aws_kms_key.cluster.arn
+ * }
+ * resources = ["secrets"]
+ * }
+ * }
+ * ```
+ */
+ class EKSClusterEncryptionConfig extends AwsResource {
+ private EKSCluster cluster;
+
+ EKSClusterEncryptionConfig() { this = cluster.getAttribute("encryption_config") }
+
+ /** Gets the list of resources to be encrypted (e.g., "secrets"). */
+ Expr getResources() { result = this.getAttribute("resources") }
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/providers/Alicloud.qll b/iac/ql/lib/codeql/hcl/providers/Alicloud.qll
new file mode 100644
index 000000000000..bb5b46073995
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/providers/Alicloud.qll
@@ -0,0 +1,19 @@
+private import codeql.hcl.AST
+private import codeql.hcl.Resources
+private import codeql.hcl.Constants
+
+module Alicloud {
+ /**
+ * Alibaba Cloud resources.
+ */
+ class AlicloudResource extends Resource, Block {
+ AlicloudResource() { this.getResourceType().regexpMatch("^alicloud.*") }
+ }
+
+ /**
+ * Alibaba Cloud provider.
+ */
+ class AlicloudProvider extends Provider {
+ AlicloudProvider() { this.getName() = "alicloud" }
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/providers/Azure.qll b/iac/ql/lib/codeql/hcl/providers/Azure.qll
new file mode 100644
index 000000000000..4ac0c29cdc9a
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/providers/Azure.qll
@@ -0,0 +1,269 @@
+/**
+ * Classes and predicates for analyzing Azure (Microsoft Azure) provider resources and configurations in HCL.
+ * This module provides specific support for Azure resources, data sources, and provider configurations
+ * commonly used in Terraform and other HCL-based infrastructure-as-code tools.
+ */
+
+private import codeql.hcl.AST
+private import codeql.hcl.Resources
+private import codeql.hcl.Constants
+
+/**
+ * Azure provider module containing classes for analyzing Azure-specific HCL configurations.
+ *
+ * This module provides specialized classes for Azure resources like virtual machines, storage accounts,
+ * Azure Kubernetes Service clusters, and other Azure services commonly defined in Terraform configurations.
+ */
+module Azure {
+ /**
+ * An Azure resource in HCL configuration.
+ *
+ * Azure resources represent infrastructure components that are managed by Microsoft Azure,
+ * such as virtual machines, storage accounts, databases, networking components, etc.
+ *
+ * Example Azure resources:
+ * ```
+ * resource "azurerm_virtual_machine" "main" {
+ * name = "my-vm"
+ * location = azurerm_resource_group.main.location
+ * resource_group_name = azurerm_resource_group.main.name
+ * }
+ *
+ * resource "azurerm_storage_account" "example" {
+ * name = "mystorageaccount"
+ * resource_group_name = azurerm_resource_group.main.name
+ * location = azurerm_resource_group.main.location
+ * account_tier = "Standard"
+ * account_replication_type = "LRS"
+ * }
+ * ```
+ */
+ class AzureResource extends Resource, Block {
+ AzureResource() { this.getResourceType().regexpMatch("^azurerm.*") }
+ }
+
+ /**
+ * An Azure provider configuration block.
+ *
+ * The Azure provider block configures authentication and regional settings
+ * for Azure resource management in Terraform.
+ *
+ * Example Azure provider:
+ * ```
+ * provider "azurerm" {
+ * features {}
+ *
+ * subscription_id = var.azure_subscription_id
+ * client_id = var.azure_client_id
+ * client_secret = var.azure_client_secret
+ * tenant_id = var.azure_tenant_id
+ * }
+ * ```
+ */
+ class AzureProvider extends Provider {
+ AzureProvider() { this.getName() = "azurerm" }
+
+ /** Gets the Azure host configuration expression. */
+ Expr getHost() { result = this.getAttribute("host") }
+
+ /** Gets the configuration path expression. */
+ Expr getConfigPath() { result = this.getAttribute("config_path") }
+
+ /** Gets the features configuration block. */
+ Expr getFeatures() { result = this.getAttribute("features") }
+ }
+
+ /**
+ * An Azure Resource Group resource.
+ *
+ * Resource groups are containers that hold related resources for an Azure solution.
+ * They provide a way to organize and manage resources as a group.
+ *
+ * Example Azure resource group:
+ * ```
+ * resource "azurerm_resource_group" "main" {
+ * name = "my-resource-group"
+ * location = "West Europe"
+ *
+ * tags = {
+ * Environment = "production"
+ * }
+ * }
+ * ```
+ */
+ class ResourceGroup extends AzureResource {
+ ResourceGroup() { this.getResourceType() = "azurerm_resource_group" }
+
+ override string toString() { result = "ResourceGroup " + this.getName() }
+
+ override string getName() { result = this.getAttribute("name").(StringLiteral).getValue() }
+
+ /** Gets the Azure region location for this resource group. */
+ Expr getResourceLocation() { result = this.getAttribute("location") }
+ }
+
+ /**
+ * An Azure Managed Disk resource.
+ *
+ * Managed disks provide persistent storage for Azure virtual machines with
+ * built-in high availability and scalability features.
+ *
+ * Example Azure managed disk:
+ * ```
+ * resource "azurerm_managed_disk" "example" {
+ * name = "example-disk"
+ * location = azurerm_resource_group.main.location
+ * resource_group_name = azurerm_resource_group.main.name
+ * storage_account_type = "Premium_LRS"
+ * create_option = "Empty"
+ * disk_size_gb = 100
+ * }
+ * ```
+ */
+ class ManagedDisk extends AzureResource {
+ ManagedDisk() { this.getResourceType() = "azurerm_managed_disk" }
+
+ override string toString() { result = "ManagedDisk " + this.getName() }
+
+ override string getName() { result = this.getAttribute("name").(StringLiteral).getValue() }
+
+ /** Gets the storage account type (e.g., "Premium_LRS", "Standard_LRS"). */
+ string getStorageAccountType() {
+ result = this.getAttribute("storage_account_type").(StringLiteral).getValue()
+ }
+
+ /**
+ * Gets the encryption settings configuration for this managed disk.
+ */
+ ManagedDiskEncryptionSettings getEncryptionSettings() {
+ result = this.getAttribute("encryption_settings")
+ }
+ }
+
+ /**
+ * Azure Managed Disk encryption settings configuration.
+ *
+ * This configuration block defines encryption settings for managed disks,
+ * including customer-managed keys and disk encryption sets.
+ *
+ * Example encryption settings:
+ * ```
+ * resource "azurerm_managed_disk" "example" {
+ * name = "example-disk"
+ *
+ * encryption_settings {
+ * enabled = true
+ *
+ * disk_encryption_key {
+ * secret_url = azurerm_key_vault_secret.example.id
+ * source_vault_id = azurerm_key_vault.example.id
+ * }
+ * }
+ * }
+ * ```
+ */
+ class ManagedDiskEncryptionSettings extends Block {
+ private ManagedDisk disk;
+
+ ManagedDiskEncryptionSettings() { disk.getAttribute("encryption_settings").(Block) = this }
+
+ override string toString() { result = "ManagedDiskEncryptionSettings" }
+
+ boolean getEnabled() { result = this.getAttribute("enabled").(BooleanLiteral).getBool() }
+ }
+
+ class StorageContainer extends AzureResource {
+ StorageContainer() { this.getResourceType() = "azurerm_storage_container" }
+
+ string getContainerAccessType() {
+ result = this.getAttribute("container_access_type").(StringLiteral).getValue()
+ }
+
+ /**
+ * Get the properties of the managed disk.
+ */
+ Object getProperties() { result = this.getAttribute("properties") }
+
+ /**
+ * Get a property of the managed disk.
+ */
+ Expr getProperty(string name) { result = this.getProperties().getElementByName(name) }
+ }
+
+ /**
+ * Azure Databases
+ */
+ class Database extends AzureResource {
+ Database() {
+ this.getResourceType()
+ .regexpMatch("^azurerm_(sql|mariadb|mssql|postgresql)_(server|database)")
+ }
+
+ override string toString() { result = "Database " + this.getName() }
+
+ override string getName() { result = this.getAttribute("name").(StringLiteral).getValue() }
+
+ string getVersion() { result = this.getAttribute("version").(StringLiteral).getValue() }
+
+ boolean getSslEnforcementEnabled() {
+ result = this.getAttribute("ssl_enforcement_enabled").(BooleanLiteral).getBool()
+ }
+
+ boolean getInfrastructureEncryptionEnabled() {
+ result = this.getAttribute("infrastructure_encryption_enabled").(BooleanLiteral).getBool()
+ }
+
+ boolean getGeoRedundantBackupEnabled() {
+ result = this.getAttribute("geo_redundant_backup_enabled").(BooleanLiteral).getBool()
+ }
+
+ Expr getAdministratorPassword() { result = this.getAttribute("administrator_login_password") }
+ }
+
+ /**
+ * Azure Key Vault.
+ */
+ class KeyVault extends AzureResource {
+ KeyVault() { this.getResourceType() = "azurerm_key_vault" }
+
+ override string toString() { result = "KeyVault " + this.getName() }
+ }
+
+ /**
+ * Azure Key Vault Key.
+ */
+ class KeyVaultKey extends AzureResource {
+ KeyVaultKey() { this.getResourceType() = "azurerm_key_vault_key" }
+
+ override string toString() { result = "KeyVaultKey " + this.getName() }
+
+ string getKeyType() { result = this.getAttribute("key_type").(StringLiteral).getValue() }
+
+ int getKeySize() { result = this.getAttribute("key_size").(NumericLiteral).getInt() }
+ // string getKeyOpts() { result = this.getAttribute("key_opts") }
+ }
+
+ /**
+ * Azure Key Vault Secret.
+ */
+ class KeyVaultSecret extends AzureResource {
+ KeyVaultSecret() { this.getResourceType() = "azurerm_key_vault_secret" }
+ }
+
+ /**
+ * Azure Security Center Contact.
+ */
+ class SecurityCenterContact extends AzureResource {
+ SecurityCenterContact() { this.getResourceType() = "azurerm_security_center_contact" }
+
+ string getEmail() { result = this.getAttribute("email").(StringLiteral).getValue() }
+
+ boolean getAlertNotifications() {
+ result = this.getAttribute("alert_notifications").(BooleanLiteral).getBool()
+ }
+
+ boolean getAlertsToAdmins() {
+ result = this.getAttribute("alerts_to_admins").(BooleanLiteral).getBool()
+ }
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/providers/GCP.qll b/iac/ql/lib/codeql/hcl/providers/GCP.qll
new file mode 100644
index 000000000000..00c2c87e27ef
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/providers/GCP.qll
@@ -0,0 +1,130 @@
+/**
+ * Classes and predicates for analyzing GCP (Google Cloud Platform) provider resources and configurations in HCL.
+ * This module provides specific support for Google Cloud resources, data sources, and provider configurations
+ * commonly used in Terraform and other HCL-based infrastructure-as-code tools.
+ */
+
+private import codeql.hcl.AST
+private import codeql.hcl.Resources
+private import codeql.hcl.Constants
+
+/**
+ * Google Cloud Platform (GCP) provider module containing classes for analyzing GCP-specific HCL configurations.
+ *
+ * This module provides specialized classes for GCP resources like Compute Engine instances, Cloud Storage buckets,
+ * GKE clusters, and other Google Cloud services commonly defined in Terraform configurations.
+ */
+module GCP {
+ /**
+ * A Google Cloud Platform (GCP) resource in HCL configuration.
+ *
+ * GCP resources represent infrastructure components that are managed by Google Cloud,
+ * such as Compute Engine instances, Cloud Storage buckets, databases, networking components, etc.
+ *
+ * Example GCP resources:
+ * ```
+ * resource "google_compute_instance" "default" {
+ * name = "my-instance"
+ * machine_type = "e2-medium"
+ * zone = "us-central1-a"
+ * }
+ *
+ * resource "google_storage_bucket" "example" {
+ * name = "my-storage-bucket"
+ * location = "US"
+ * }
+ * ```
+ */
+ class GcpResource extends Resource, Block {
+ GcpResource() { this.getResourceType().regexpMatch("^google.*") }
+ }
+
+ class ComputeZones extends GcpResource {
+ ComputeZones() { this.getResourceType() = "google_compute_zones" }
+
+ Expr getProject() { result = this.getAttribute("project") }
+ }
+
+ /**
+ * Google Cloud Platform (GCP) resource.
+ *
+ * https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster
+ */
+ class ContainerCluster extends GcpResource {
+ ContainerCluster() { this.getResourceType() = "google_container_cluster" }
+
+ /**
+ * Get the Legacy Role-Based Access Control (RBAC) setting.
+ */
+ boolean getRbac() {
+ result = this.getAttribute("enable_legacy_abac").(BooleanLiteral).getBool()
+ }
+
+ /**
+ */
+ boolean getRemoveDefaultNodePool() {
+ result = this.getAttribute("remove_default_node_pool").(BooleanLiteral).getBool()
+ }
+
+ Expr getMonitoringService() { result = this.getAttribute("monitoring_service") }
+
+ Expr getLoggingService() { result = this.getAttribute("logging_service") }
+
+ Expr getAuthorizedNetworks() { result = this.getAttribute("master_authorized_networks_config") }
+
+ PodSecurityPolicyConfig getPodSecurityPolicyConfig() {
+ result = this.getAttribute("pod_security_policy_config")
+ }
+ }
+
+ class PodSecurityPolicyConfig extends Block {
+ private ContainerCluster cluster;
+
+ PodSecurityPolicyConfig() { cluster.getAttribute("pod_security_policy_config") = this }
+
+ string getEnabled() { result = this.getAttribute("enabled").(StringLiteral).getValue() }
+ }
+
+ class ContainerNodePool extends GcpResource {
+ private ContainerCluster cluster;
+
+ ContainerNodePool() { this.getResourceType() = "google_container_node_pool" }
+
+ /**
+ * Get the cluster that this node pool belongs to.
+ */
+ ContainerCluster getCluster() { none() }
+
+ Expr getNodeConfig() { result = this.getAttribute("node_config") }
+ }
+
+ /**
+ * Google Cloud Platform (GCP) Storage Bucket resource.
+ */
+ class StorageBucket extends GcpResource {
+ StorageBucket() { this.getResourceType() = "google_storage_bucket" }
+
+ StorageBucketAccessControl getAccessControl() {
+ exists(StorageBucketAccessControl sbac | sbac.getBucket() = this | result = sbac)
+ }
+ }
+
+ /**
+ * Google Cloud Platform (GCP) Storage Bucket Access Control resource.
+ */
+ class StorageBucketAccessControl extends GcpResource {
+ StorageBucketAccessControl() { this.getResourceType() = "google_storage_bucket_access_control" }
+
+ StorageBucket getBucket() { result = this.getAttribute("bucket").getParent() }
+
+ /**
+ * Get the role of the access control.
+ */
+ string getRole() { result = this.getAttribute("role").(StringLiteral).getValue() }
+
+ /**
+ * Get the entity of the access control.
+ */
+ string getEntity() { result = this.getAttribute("entity").(StringLiteral).getValue() }
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/providers/GitHub.qll b/iac/ql/lib/codeql/hcl/providers/GitHub.qll
new file mode 100644
index 000000000000..bd535d4ee3f6
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/providers/GitHub.qll
@@ -0,0 +1,64 @@
+/**
+ * Classes and predicates for analyzing GitHub provider resources and configurations in HCL.
+ * This module provides specific support for GitHub resources, data sources, and provider configurations
+ * commonly used in Terraform for managing GitHub repositories, teams, and organization settings.
+ */
+
+private import codeql.hcl.AST
+private import codeql.hcl.Resources
+private import codeql.hcl.Constants
+
+/**
+ * GitHub provider module containing classes for analyzing GitHub-specific HCL configurations.
+ *
+ * This module provides specialized classes for GitHub resources like repositories, teams,
+ * webhooks, and other GitHub management features commonly defined in Terraform configurations.
+ */
+module GitHub {
+ /**
+ * A GitHub resource in HCL configuration.
+ *
+ * GitHub resources represent components that are managed through the GitHub API,
+ * such as repositories, teams, organization settings, webhooks, etc.
+ *
+ * Example GitHub resources:
+ * ```
+ * resource "github_repository" "example" {
+ * name = "example-repo"
+ * description = "My example repository"
+ * visibility = "public"
+ * }
+ *
+ * resource "github_team" "developers" {
+ * name = "developers"
+ * description = "Development team"
+ * privacy = "closed"
+ * }
+ * ```
+ */
+ class GitHubResource extends Resource, Block {
+ GitHubResource() { this.getResourceType().regexpMatch("^github.*") }
+ }
+
+ /**
+ * GitHub provider.
+ */
+ class GitHubProvider extends Provider {
+ GitHubProvider() { this.getName() = "github" }
+ }
+
+ /**
+ * GitHub Repository.
+ */
+ class Repository extends GitHubResource {
+ Repository() { this.getResourceType() = "github_repository" }
+
+ override string toString() { result = "Repository " + this.getName() }
+
+ override string getName() { result = this.getAttribute("name").(StringLiteral).getValue() }
+
+ string getDescription() { result = this.getAttribute("description").(StringLiteral).getValue() }
+
+ string getVisibility() { result = this.getAttribute("visibility").(StringLiteral).getValue() }
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/providers/Helm.qll b/iac/ql/lib/codeql/hcl/providers/Helm.qll
new file mode 100644
index 000000000000..da71c6c42a7a
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/providers/Helm.qll
@@ -0,0 +1,79 @@
+private import codeql.hcl.AST
+private import codeql.hcl.Resources
+private import codeql.hcl.Constants
+
+module TerraformHelmChart {
+ /**
+ * HelmChart resources.
+ */
+ class HelmResource extends Resource, Block {
+ HelmResource() { this.getResourceType().regexpMatch("^helm_.*") }
+ }
+
+ /**
+ * Helm provider.
+ */
+ class HelmProvider extends Provider {
+ HelmProvider() { this.getName() = "helm" }
+
+ HelmProviderRegistry getRegistry() { result = this.getAttribute("registry") }
+ }
+
+ /**
+ * Helm provider registry.
+ */
+ class HelmProviderRegistry extends Block {
+ private HelmProvider provider;
+
+ HelmProviderRegistry() { provider.getAttribute("registry") = this }
+
+ /**
+ * Helm registry URL.
+ */
+ Expr getUrl() { result = this.getAttribute("url") }
+
+ /**
+ * Helm registry username.
+ */
+ Expr getUsername() { result = this.getAttribute("username") }
+
+ /**
+ * Helm registry password.
+ */
+ Expr getPassword() { result = this.getAttribute("password") }
+ }
+
+ /**
+ * Helm Chart release.
+ */
+ class Release extends HelmResource {
+ Release() { this.getResourceType() = "helm_release" }
+
+ override string getName() { result = this.getAttribute("name").(StringLiteral).getValue() }
+
+ string getChart() { result = this.getAttribute("chart").(StringLiteral).getValue() }
+
+ string getRepository() { result = this.getAttribute("repository").(StringLiteral).getValue() }
+
+ ReleaseSet getSets() { result = this.getAttribute("set") }
+
+ ReleaseSet getSet(string name) {
+ exists(ReleaseSet set | set = this.getSets() | set.getName() = name and result = set)
+ }
+ }
+
+ /**
+ * Value block with custom values to be merged with the values yaml.
+ *
+ * https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release#set
+ */
+ class ReleaseSet extends Block {
+ private Release release;
+
+ ReleaseSet() { release.getAttribute("set").(Block) = this }
+
+ string getName() { result = this.getAttribute("name").(StringLiteral).getValue() }
+
+ Expr getValue() { result = this.getAttribute("value") }
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/providers/Kubernetes.qll b/iac/ql/lib/codeql/hcl/providers/Kubernetes.qll
new file mode 100644
index 000000000000..9c20ab46a55b
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/providers/Kubernetes.qll
@@ -0,0 +1,57 @@
+/**
+ * Classes and predicates for analyzing Kubernetes provider resources and configurations in HCL.
+ * This module provides specific support for Kubernetes resources, data sources, and provider configurations
+ * commonly used in Terraform for managing Kubernetes cluster resources.
+ */
+
+private import codeql.hcl.AST
+private import codeql.hcl.Resources
+private import codeql.hcl.Constants
+
+/**
+ * Kubernetes provider module containing classes for analyzing Kubernetes-specific HCL configurations.
+ *
+ * This module provides specialized classes for Kubernetes resources like deployments, services,
+ * config maps, and other Kubernetes objects commonly defined in Terraform configurations.
+ */
+module Kubernetes {
+ /**
+ * A Kubernetes resource in HCL configuration.
+ *
+ * Kubernetes resources represent objects that are managed within a Kubernetes cluster,
+ * such as deployments, services, pods, config maps, secrets, etc.
+ *
+ * Example Kubernetes resources:
+ * ```
+ * resource "kubernetes_deployment" "nginx" {
+ * metadata {
+ * name = "nginx-deployment"
+ * }
+ *
+ * spec {
+ * replicas = 3
+ *
+ * selector {
+ * match_labels = {
+ * App = "nginx"
+ * }
+ * }
+ * }
+ * }
+ * ```
+ */
+ class KubernetesResource extends Resource, Block {
+ KubernetesResource() { this.getResourceType().regexpMatch("^kubernetes.*") }
+ }
+
+ /**
+ * Kubernetes provider.
+ */
+ class KubernetesProvider extends Provider {
+ KubernetesProvider() { this.getName() = "kubernetes" }
+
+ Expr getHost() { result = this.getAttribute("host") }
+
+ Expr getConfigPath() { result = this.getAttribute("config_path") }
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/providers/OCI.qll b/iac/ql/lib/codeql/hcl/providers/OCI.qll
new file mode 100644
index 000000000000..f2dce38493c3
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/providers/OCI.qll
@@ -0,0 +1,19 @@
+private import codeql.hcl.AST
+private import codeql.hcl.Resources
+private import codeql.hcl.Constants
+
+module OCI {
+ /**
+ * Oracle resources.
+ */
+ class OracleResource extends Resource, Block {
+ OracleResource() { this.getResourceType().regexpMatch("^oci.*") }
+ }
+
+ /**
+ * Oracle provider.
+ */
+ class OracleProvider extends Provider {
+ OracleProvider() { this.getName() = "oci" }
+ }
+}
diff --git a/iac/ql/lib/codeql/hcl/security/HardcodedPasswords.qll b/iac/ql/lib/codeql/hcl/security/HardcodedPasswords.qll
new file mode 100644
index 000000000000..980c842fd3e8
--- /dev/null
+++ b/iac/ql/lib/codeql/hcl/security/HardcodedPasswords.qll
@@ -0,0 +1,15 @@
+import iac
+
+abstract class PasswordSinks extends Expr { }
+
+class AzurePasswords extends PasswordSinks {
+ AzurePasswords() { exists(Azure::Database db | this = db.getAdministratorPassword()) }
+}
+
+class AwsPasswords extends PasswordSinks {
+ AwsPasswords() {
+ exists(AWS::AwsProvider provider, AWS::Database db |
+ this = db.getPassword() or this = provider.getAccessKey() or this = provider.getSecretKey()
+ )
+ }
+}
diff --git a/iac/ql/lib/codeql/iac/Dependencies.qll b/iac/ql/lib/codeql/iac/Dependencies.qll
new file mode 100644
index 000000000000..5a5f26ac485d
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/Dependencies.qll
@@ -0,0 +1,131 @@
+/**
+ * Dependencies module for Infrastructure as Code languages.
+ */
+private import codeql.Locations
+private import codeql.hcl.Terraform
+
+/**
+ * Dependency for all Infrastructure as Code languages.
+ */
+abstract class Dependency extends Location {
+ /**
+ * Gets the name of the dependency.
+ */
+ abstract string getName();
+ /**
+ * Gets the version of the dependency (in a format that is human-readable).
+ */
+ abstract string getVersion();
+ /**
+ * Gets the raw version of the dependency (in a format that is machine-readable).
+ */
+ abstract string getRawVersion();
+ /**
+ * Gets the semantic version of the dependency.
+ */
+ abstract SemanticVersion getSemanticVersion();
+}
+
+
+/**
+ * Dependency for Terraform.
+ */
+class TerraformDependency extends Dependency {
+ string name;
+ string version;
+
+ TerraformDependency() {
+ // Terraform Provider
+ exists(Terraform::Terraform tf, Terraform::RequiredProvider rp | rp = tf.getRequiredProvider() |
+ this = rp.getLocation() and
+ name = rp.getSource() and
+ version = rp.getVersion()
+ )
+ }
+
+ override string toString() { result = this.getName() + "@" + this.getVersion() }
+
+ override string getName() { result = name }
+
+
+ override string getVersion() { result = this.getSemanticVersion().getPretty() }
+
+
+ override string getRawVersion() { result = version }
+
+ override SemanticVersion getSemanticVersion() { result = version }
+}
+
+class SemanticVersion extends string {
+ Dependency dep;
+ string normalized;
+ string pretty;
+
+ SemanticVersion() {
+ this = dep.getRawVersion() and
+ normalized = normalizeSemver(this) and
+ pretty = prettySemver(this)
+ }
+
+ /**
+ * Holds if this version may be before `last`.
+ */
+ bindingset[last]
+ predicate maybeBefore(string last) { normalized < normalizeSemver(last) }
+
+ /**
+ * Holds if this version may be after `first`.
+ */
+ bindingset[first]
+ predicate maybeAfter(string first) { normalizeSemver(first) < normalized }
+
+ /**
+ * Holds if this version may be between `first` (inclusive) and `last` (exclusive).
+ */
+ bindingset[first, last]
+ predicate maybeBetween(string first, string last) {
+ normalizeSemver(first) <= normalized and
+ normalized < normalizeSemver(last)
+ }
+
+ /**
+ * Holds if this version is equivalent to `other`.
+ */
+ bindingset[other]
+ predicate is(string other) { normalized = normalizeSemver(other) }
+
+ string getPretty() { result = pretty }
+}
+
+bindingset[str]
+private string leftPad(string str) { result = ("000" + str).suffix(str.length()) }
+
+/**
+ * Normalizes a SemVer string such that the lexicographical ordering
+ * of two normalized strings is consistent with the SemVer ordering.
+ *
+ * Pre-release information and build metadata is not yet supported.
+ */
+bindingset[orig]
+private string normalizeSemver(string orig) {
+ exists(string pattern, string major, string minor, string patch |
+ pattern = "(=|~>|^)(\\d+)\\.(\\d+)\\.(\\d+)" and
+ major = orig.regexpCapture(pattern, 2) and
+ minor = orig.regexpCapture(pattern, 3) and
+ patch = orig.regexpCapture(pattern, 4)
+ |
+ result = leftPad(major) + "." + leftPad(minor) + "." + leftPad(patch)
+ )
+}
+
+bindingset[orig]
+private string prettySemver(string orig) {
+ exists(string pattern, string major, string minor, string patch |
+ pattern = "(=|~>|^)(\\d+)\\.(\\d+)\\.(\\d+)" and
+ major = orig.regexpCapture(pattern, 2) and
+ minor = orig.regexpCapture(pattern, 3) and
+ patch = orig.regexpCapture(pattern, 4)
+ |
+ result = major + "." + minor + "." + patch
+ )
+}
\ No newline at end of file
diff --git a/iac/ql/lib/codeql/iac/Diagnostics.qll b/iac/ql/lib/codeql/iac/Diagnostics.qll
new file mode 100644
index 000000000000..faf7b8420a0e
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/Diagnostics.qll
@@ -0,0 +1,52 @@
+private import codeql.Locations
+
+/** A diagnostic emitted during extraction, such as a parse error */
+class Diagnostic extends @diagnostic {
+ int severity;
+ string tag;
+ string message;
+ string fullMessage;
+ Location location;
+
+ Diagnostic() { diagnostics(this, severity, tag, message, fullMessage, location) }
+
+ /**
+ * Gets the numerical severity level associated with this diagnostic.
+ */
+ int getSeverity() { result = severity }
+
+ /** Gets a string representation of the severity of this diagnostic. */
+ string getSeverityText() {
+ severity = 10 and result = "Debug"
+ or
+ severity = 20 and result = "Info"
+ or
+ severity = 30 and result = "Warning"
+ or
+ severity = 40 and result = "Error"
+ }
+
+ /** Gets the error code associated with this diagnostic, e.g. parse_error. */
+ string getTag() { result = tag }
+
+ /**
+ * Gets the error message text associated with this diagnostic.
+ */
+ string getMessage() { result = message }
+
+ /**
+ * Gets the full error message text associated with this diagnostic.
+ */
+ string getFullMessage() { result = fullMessage }
+
+ /** Gets the source location of this diagnostic. */
+ Location getLocation() { result = location }
+
+ /** Gets a textual representation of this diagnostic. */
+ string toString() { result = this.getMessage() }
+}
+
+/** A diagnostic relating to a particular error in extracting a file. */
+class ExtractionError extends Diagnostic {
+ ExtractionError() { this.getTag() = "parse_error" }
+}
diff --git a/iac/ql/lib/codeql/iac/YAML.qll b/iac/ql/lib/codeql/iac/YAML.qll
new file mode 100644
index 000000000000..ad1c29a14165
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/YAML.qll
@@ -0,0 +1,62 @@
+/**
+ * Provides classes for working with YAML data.
+ *
+ * YAML documents are represented as abstract syntax trees whose nodes
+ * are either YAML values or alias nodes referring to another YAML value.
+ */
+
+import iac
+private import codeql.yaml.Yaml as LibYaml
+
+private module YamlSig implements LibYaml::InputSig {
+ class LocatableBase extends @yaml_locatable {
+ Location getLocation() { yaml_locations(this, result) }
+
+ string toString() { yaml(this, _, _, _, _, result) }
+ }
+
+ import iac
+
+ class NodeBase extends LocatableBase, @yaml_node {
+ NodeBase getChildNode(int i) { yaml(result, _, this, i, _, _) }
+
+ string getTag() { yaml(this, _, _, _, result, _) }
+
+ string getAnchor() { yaml_anchors(this, result) }
+
+ override string toString() { yaml(this, _, _, _, _, result) }
+ }
+
+ class ScalarNodeBase extends NodeBase, @yaml_scalar_node {
+ int getStyle() { yaml_scalars(this, result, _) }
+
+ string getValue() { yaml_scalars(this, _, result) }
+ }
+
+ class CollectionNodeBase extends NodeBase, @yaml_collection_node { }
+
+ class MappingNodeBase extends CollectionNodeBase, @yaml_mapping_node { }
+
+ class SequenceNodeBase extends CollectionNodeBase, @yaml_sequence_node { }
+
+ class AliasNodeBase extends NodeBase, @yaml_alias_node {
+ string getTarget() { yaml_aliases(this, result) }
+ }
+
+ class ParseErrorBase extends LocatableBase, @yaml_error {
+ string getMessage() { yaml_errors(this, result) }
+ }
+
+ class CommentBase extends LocatableBase, @yaml_comment {
+ string getText() { yaml_comments(this, result, _) }
+
+ override string toString() { yaml_comments(this, _, result) }
+ }
+}
+
+import LibYaml::Make
+
+/**
+ * A YAML Node to string and removes quotes.
+ */
+string yamlToString(YamlString str) { result = str.toString().regexpReplaceAll("(\"|')", "") }
diff --git a/iac/ql/lib/codeql/iac/ansible/Ansible.qll b/iac/ql/lib/codeql/iac/ansible/Ansible.qll
new file mode 100644
index 000000000000..d2cee2420633
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ansible/Ansible.qll
@@ -0,0 +1,3 @@
+private import codeql.files.FileSystem
+
+module Ansible { }
diff --git a/iac/ql/lib/codeql/iac/ast/Container.qll b/iac/ql/lib/codeql/iac/ast/Container.qll
new file mode 100644
index 000000000000..c0f04732570f
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ast/Container.qll
@@ -0,0 +1,55 @@
+private import codeql.Locations
+private import codeql.files.FileSystem
+private import codeql.iac.ast.internal.Container
+
+/** An AST node of a IAC program */
+class ContainerAstNode extends TContainerAstNode {
+ string toString() { result = this.getAPrimaryQlClass() }
+
+ /** Gets the location of the AST node. */
+ cached
+ Location getLocation() { result = this.getFullLocation() } // overridden in some subclasses
+
+ /** Gets the file containing this AST node. */
+ cached
+ File getFile() { result = this.getFullLocation().getFile() }
+
+ /** Gets the location that spans the entire AST node. */
+ cached
+ final Location getFullLocation() { result = toContainerTreeSitter(this).getLocation() }
+
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ if exists(this.getLocation())
+ then this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ else (
+ filepath = "" and
+ startline = 0 and
+ startcolumn = 0 and
+ endline = 0 and
+ endcolumn = 0
+ )
+ }
+
+ /**
+ * Gets the parent in the AST for this node.
+ */
+ cached
+ ContainerAstNode getParent() { result.getAChild(_) = this }
+
+ /**
+ * Gets a child of this node, which can also be retrieved using a predicate
+ * named `pred`.
+ */
+ cached
+ ContainerAstNode getAChild(string pred) { none() }
+
+ /** Gets any child of this node. */
+ ContainerAstNode getAChild() { result = this.getAChild(_) }
+
+ /**
+ * Gets the primary QL class for the ast node.
+ */
+ string getAPrimaryQlClass() { result = "???" }
+}
diff --git a/iac/ql/lib/codeql/iac/ast/Hcl.qll b/iac/ql/lib/codeql/iac/ast/Hcl.qll
new file mode 100644
index 000000000000..117cd8f6496d
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ast/Hcl.qll
@@ -0,0 +1 @@
+import codeql.hcl.AST
diff --git a/iac/ql/lib/codeql/iac/ast/internal/AstNodes.qll b/iac/ql/lib/codeql/iac/ast/internal/AstNodes.qll
new file mode 100644
index 000000000000..c6c2e1b45928
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ast/internal/AstNodes.qll
@@ -0,0 +1,11 @@
+private import TreeSitter
+import Hcl
+import Container
+
+/**
+ * AST Common Type
+ */
+cached
+newtype TAstNode =
+ THclAstNode(HCL::AstNode node) or
+ TContainerAstNode(DOCKERFILE::AstNode node)
diff --git a/iac/ql/lib/codeql/iac/ast/internal/Container.qll b/iac/ql/lib/codeql/iac/ast/internal/Container.qll
new file mode 100644
index 000000000000..9b2f6a85532c
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ast/internal/Container.qll
@@ -0,0 +1,30 @@
+import TreeSitter
+
+/**
+ * Container AST nodes
+ */
+cached
+newtype TContainerAstNode =
+ TSourceFile(DOCKERFILE::SourceFile sources) or
+ TReservedWord(DOCKERFILE::ReservedWord reservedWord) or
+ TImageAlias(DOCKERFILE::ImageAlias imageAlias) or
+ TImageDigest(DOCKERFILE::ImageDigest imageDigest) or
+ TImageName(DOCKERFILE::ImageName imageName) or
+ TImageSpec(DOCKERFILE::ImageSpec imageSpecifier) or
+ TImageTag(DOCKERFILE::ImageTag imageTag)
+
+class TImage = TImageName or TImageTag or TImageDigest or TImageAlias;
+
+/**
+ * Gets the underlying TreeSitter entity for a given AST node.
+ */
+cached
+DOCKERFILE::AstNode toContainerTreeSitter(TContainerAstNode n) {
+ n = TSourceFile(result) or
+ n = TReservedWord(result) or
+ n = TImageAlias(result) or
+ n = TImageDigest(result) or
+ n = TImageName(result) or
+ n = TImageSpec(result) or
+ n = TImageTag(result)
+}
diff --git a/iac/ql/lib/codeql/iac/ast/internal/Hcl.qll b/iac/ql/lib/codeql/iac/ast/internal/Hcl.qll
new file mode 100644
index 000000000000..2835e0f7f79f
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ast/internal/Hcl.qll
@@ -0,0 +1,107 @@
+/**
+ * Internal type system for HCL AST nodes.
+ * This module contains the newtype definitions and type hierarchies
+ * that form the foundation of the HCL AST representation.
+ *
+ * This is an internal module that should not be imported directly.
+ * Use `codeql.hcl.AST` or specific AST modules instead.
+ */
+
+import TreeSitter
+
+/**
+ * The union type of all HCL AST node types.
+ *
+ * This newtype defines all possible AST nodes that can appear in HCL code,
+ * including expressions, statements, literals, blocks, and other language constructs.
+ */
+cached
+newtype THclAstNode =
+ TAttribute(HCL::Attribute attribute) or
+ TAttrSplat(HCL::AttrSplat attrSplat) or
+ TBinaryOperation(HCL::BinaryOperation operation) or
+ TBlock(HCL::Block block) or
+ TBody(HCL::Body body) or
+ TBooleanLiteral(HCL::BoolLit literal) or
+ TComment(HCL::Comment comment) or
+ TConditional(HCL::Conditional conditional) or
+ TConfigFile(HCL::ConfigFile file) or
+ TEllipsis(HCL::Ellipsis ellipsis) or
+ TForExpr(HCL::ForExpr forExpr) or
+ TForObjectExpr(HCL::ForObjectExpr forObjectExpr) or
+ TForTupleExpr(HCL::ForTupleExpr forTupleExpr) or
+ TFullSplat(HCL::FullSplat fullSplat) or
+ TFunctionCall(HCL::FunctionCall call) or
+ TGetAttr(HCL::GetAttr getAttr) or
+ TGetAttrExpr(HCL::GetAttrExpr getAttr) or
+ TIdentifier(HCL::Identifier identifier) or
+ TIndex(HCL::Index index) or
+ TNullLiteral(HCL::NullLit literal) or
+ TNumericLit(HCL::NumericLit literal) or
+ TObject(HCL::Object object) or
+ TObjectElem(HCL::ObjectElem objectElem) or
+ TQuotedTemplate(HCL::QuotedTemplate quotedTemplate) or
+ TSplatExpr(HCL::SplatExpr splat) or
+ TStringLit(HCL::StringLit literal) or
+ TTemplateFor(HCL::TemplateFor templateFor) or
+ TTemplateIf(HCL::TemplateIf templateIf) or
+ TTemplateInterpolation(HCL::TemplateInterpolation templateInterpolation) or
+ TTemplateLiteral(HCL::TemplateLiteral templateLiteral) or
+ TTuple(HCL::Tuple tuple) or
+ TUnaryOperation(HCL::UnaryOperation operation) or
+ TVariable(HCL::VariableExpr variable) or
+ THeredocTemplate(HCL::HeredocTemplate heredocTemplate)
+
+/** Template directive types (for conditional and loop constructs). */
+class TTemplateDirective = TTemplateFor or TTemplateIf;
+
+/** Literal value types (for constant values). */
+class TLiteral = TBooleanLiteral or TNumericLit or TStringLit or TNullLiteral;
+
+/** Expression types (for any value-producing construct). */
+class TExpr =
+ TLiteral or TIdentifier or TAttribute or TVariable or TFunctionCall or TUnaryOperation or
+ TBinaryOperation or TConditional or TGetAttrExpr or TIndex or TTemplateLiteral or
+ TTemplateInterpolation or TTemplateFor or TTemplateIf or TForExpr or TForObjectExpr or
+ TForTupleExpr or TTuple or TObject or TObjectElem or TAttrSplat or TFullSplat or TSplatExpr or
+ TQuotedTemplate or TBlock or THeredocTemplate;
+
+/**
+ * Gets the underlying TreeSitter entity for a given AST node.
+ */
+cached
+HCL::AstNode toHclTreeSitter(THclAstNode n) {
+ n = TAttribute(result) or
+ n = TAttrSplat(result) or
+ n = TBinaryOperation(result) or
+ n = TBlock(result) or
+ n = TBody(result) or
+ n = TBooleanLiteral(result) or
+ n = TComment(result) or
+ n = TConditional(result) or
+ n = TConfigFile(result) or
+ n = TEllipsis(result) or
+ n = TForExpr(result) or
+ n = TForObjectExpr(result) or
+ n = TForTupleExpr(result) or
+ n = TFullSplat(result) or
+ n = TFunctionCall(result) or
+ n = TGetAttrExpr(result) or
+ n = TIdentifier(result) or
+ n = TIndex(result) or
+ n = TNullLiteral(result) or
+ n = TNumericLit(result) or
+ n = TObject(result) or
+ n = TObjectElem(result) or
+ n = TQuotedTemplate(result) or
+ n = TSplatExpr(result) or
+ n = TStringLit(result) or
+ n = TTemplateFor(result) or
+ n = TTemplateIf(result) or
+ n = TTemplateInterpolation(result) or
+ n = TTemplateLiteral(result) or
+ n = TTuple(result) or
+ n = TUnaryOperation(result) or
+ n = TVariable(result) or
+ n = THeredocTemplate(result)
+}
diff --git a/iac/ql/lib/codeql/iac/ast/internal/TreeSitter.qll b/iac/ql/lib/codeql/iac/ast/internal/TreeSitter.qll
new file mode 100644
index 000000000000..3530285f581b
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ast/internal/TreeSitter.qll
@@ -0,0 +1,1230 @@
+/**
+ * CodeQL library for HCL
+ * Automatically generated from the tree-sitter grammar; do not edit
+ */
+
+import codeql.Locations as L
+
+module HCL {
+ /** The base class for all AST nodes */
+ class AstNode extends @hcl_ast_node {
+ /** Gets a string representation of this element. */
+ string toString() { result = this.getAPrimaryQlClass() }
+
+ /** Gets the location of this element. */
+ final L::Location getLocation() { hcl_ast_node_location(this, result) }
+
+ /** Gets the parent of this element. */
+ final AstNode getParent() { hcl_ast_node_parent(this, result, _) }
+
+ /** Gets the index of this node among the children of its parent. */
+ final int getParentIndex() { hcl_ast_node_parent(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ AstNode getAFieldOrChild() { none() }
+
+ /** Gets the name of the primary QL class for this element. */
+ string getAPrimaryQlClass() { result = "???" }
+
+ /** Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs. */
+ string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
+ }
+
+ /** A token. */
+ class Token extends @hcl_token, AstNode {
+ /** Gets the value of this token. */
+ final string getValue() { hcl_tokeninfo(this, _, result) }
+
+ /** Gets a string representation of this element. */
+ final override string toString() { result = this.getValue() }
+
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Token" }
+ }
+
+ /** A reserved word. */
+ class ReservedWord extends @hcl_reserved_word, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ReservedWord" }
+ }
+
+ class UnderscoreCollectionValue extends @hcl_underscore_collection_value, AstNode { }
+
+ class UnderscoreExprTerm extends @hcl_underscore_expr_term, AstNode { }
+
+ class UnderscoreExpression extends @hcl_underscore_expression, AstNode { }
+
+ class UnderscoreLiteralValue extends @hcl_underscore_literal_value, AstNode { }
+
+ class UnderscoreOperation extends @hcl_underscore_operation, AstNode { }
+
+ class UnderscoreSplat extends @hcl_underscore_splat, AstNode { }
+
+ class UnderscoreTemplateDirective extends @hcl_underscore_template_directive, AstNode { }
+
+ class UnderscoreTemplateExpr extends @hcl_underscore_template_expr, AstNode { }
+
+ /** A class representing `attr_splat` nodes. */
+ class AttrSplat extends @hcl_attr_splat, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "AttrSplat" }
+
+ /** Gets the node corresponding to the field `element`. */
+ final GetAttr getElement(int i) { hcl_attr_splat_element(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_attr_splat_element(this, _, result) }
+ }
+
+ /** A class representing `attribute` nodes. */
+ class Attribute extends @hcl_attribute, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Attribute" }
+
+ /** Gets the node corresponding to the field `key`. */
+ final Identifier getKey() { hcl_attribute_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `val`. */
+ final UnderscoreExpression getVal() { hcl_attribute_def(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_attribute_def(this, result, _) or hcl_attribute_def(this, _, result)
+ }
+ }
+
+ /** A class representing `binary_operation` nodes. */
+ class BinaryOperation extends @hcl_binary_operation, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "BinaryOperation" }
+
+ /** Gets the node corresponding to the field `left`. */
+ final UnderscoreExprTerm getLeft() { hcl_binary_operation_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `operator`. */
+ final string getOperator() {
+ exists(int value | hcl_binary_operation_def(this, _, value, _) |
+ result = "!=" and value = 0
+ or
+ result = "%" and value = 1
+ or
+ result = "&&" and value = 2
+ or
+ result = "*" and value = 3
+ or
+ result = "+" and value = 4
+ or
+ result = "-" and value = 5
+ or
+ result = "/" and value = 6
+ or
+ result = "<" and value = 7
+ or
+ result = "<=" and value = 8
+ or
+ result = "==" and value = 9
+ or
+ result = ">" and value = 10
+ or
+ result = ">=" and value = 11
+ or
+ result = "||" and value = 12
+ )
+ }
+
+ /** Gets the node corresponding to the field `right`. */
+ final UnderscoreExprTerm getRight() { hcl_binary_operation_def(this, _, _, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_binary_operation_def(this, result, _, _) or hcl_binary_operation_def(this, _, _, result)
+ }
+ }
+
+ /** A class representing `block` nodes. */
+ class Block extends @hcl_block, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Block" }
+
+ /** Gets the node corresponding to the field `body`. */
+ final Body getBody() { hcl_block_body(this, result) }
+
+ /** Gets the node corresponding to the field `label`. */
+ final AstNode getLabel(int i) { hcl_block_label(this, i, result) }
+
+ /** Gets the node corresponding to the field `type`. */
+ final Identifier getType() { hcl_block_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_block_body(this, result) or
+ hcl_block_label(this, _, result) or
+ hcl_block_def(this, result)
+ }
+ }
+
+ /** A class representing `body` nodes. */
+ class Body extends @hcl_body, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Body" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { hcl_body_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_body_child(this, _, result) }
+ }
+
+ /** A class representing `bool_lit` tokens. */
+ class BoolLit extends @hcl_token_bool_lit, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "BoolLit" }
+ }
+
+ /** A class representing `comment` tokens. */
+ class Comment extends @hcl_token_comment, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Comment" }
+ }
+
+ /** A class representing `conditional` nodes. */
+ class Conditional extends @hcl_conditional, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Conditional" }
+
+ /** Gets the node corresponding to the field `alternative`. */
+ final UnderscoreExpression getAlternative() { hcl_conditional_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `body`. */
+ final UnderscoreExpression getBody() { hcl_conditional_def(this, _, result, _) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ final UnderscoreExpression getCondition() { hcl_conditional_def(this, _, _, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_conditional_def(this, result, _, _) or
+ hcl_conditional_def(this, _, result, _) or
+ hcl_conditional_def(this, _, _, result)
+ }
+ }
+
+ /** A class representing `config_file` nodes. */
+ class ConfigFile extends @hcl_config_file, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ConfigFile" }
+
+ /** Gets the child of this node. */
+ final AstNode getChild() { hcl_config_file_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_config_file_child(this, result) }
+ }
+
+ /** A class representing `ellipsis` tokens. */
+ class Ellipsis extends @hcl_token_ellipsis, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Ellipsis" }
+ }
+
+ /** A class representing `for_expr` nodes. */
+ class ForExpr extends @hcl_for_expr, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ForExpr" }
+
+ /** Gets the child of this node. */
+ final AstNode getChild() { hcl_for_expr_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_for_expr_def(this, result) }
+ }
+
+ /** A class representing `for_object_expr` nodes. */
+ class ForObjectExpr extends @hcl_for_object_expr, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ForObjectExpr" }
+
+ /** Gets the node corresponding to the field `condition`. */
+ final UnderscoreExpression getCondition() { hcl_for_object_expr_condition(this, result) }
+
+ /** Gets the node corresponding to the field `iter`. */
+ final UnderscoreExpression getIter() { hcl_for_object_expr_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `key`. */
+ final UnderscoreExpression getKey() { hcl_for_object_expr_def(this, _, result, _) }
+
+ /** Gets the node corresponding to the field `target`. */
+ final Identifier getTarget(int i) { hcl_for_object_expr_target(this, i, result) }
+
+ /** Gets the node corresponding to the field `val`. */
+ final UnderscoreExpression getVal() { hcl_for_object_expr_def(this, _, _, result) }
+
+ /** Gets the child of this node. */
+ final Ellipsis getChild() { hcl_for_object_expr_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_for_object_expr_condition(this, result) or
+ hcl_for_object_expr_def(this, result, _, _) or
+ hcl_for_object_expr_def(this, _, result, _) or
+ hcl_for_object_expr_target(this, _, result) or
+ hcl_for_object_expr_def(this, _, _, result) or
+ hcl_for_object_expr_child(this, result)
+ }
+ }
+
+ /** A class representing `for_tuple_expr` nodes. */
+ class ForTupleExpr extends @hcl_for_tuple_expr, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ForTupleExpr" }
+
+ /** Gets the node corresponding to the field `condition`. */
+ final UnderscoreExpression getCondition() { hcl_for_tuple_expr_condition(this, result) }
+
+ /** Gets the node corresponding to the field `expr`. */
+ final UnderscoreExpression getExpr() { hcl_for_tuple_expr_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `iter`. */
+ final UnderscoreExpression getIter() { hcl_for_tuple_expr_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `target`. */
+ final Identifier getTarget(int i) { hcl_for_tuple_expr_target(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_for_tuple_expr_condition(this, result) or
+ hcl_for_tuple_expr_def(this, result, _) or
+ hcl_for_tuple_expr_def(this, _, result) or
+ hcl_for_tuple_expr_target(this, _, result)
+ }
+ }
+
+ /** A class representing `full_splat` nodes. */
+ class FullSplat extends @hcl_full_splat, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "FullSplat" }
+
+ /** Gets the node corresponding to the field `element`. */
+ final AstNode getElement(int i) { hcl_full_splat_element(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_full_splat_element(this, _, result) }
+ }
+
+ /** A class representing `function_call` nodes. */
+ class FunctionCall extends @hcl_function_call, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "FunctionCall" }
+
+ /** Gets the node corresponding to the field `argument`. */
+ final UnderscoreExpression getArgument(int i) { hcl_function_call_argument(this, i, result) }
+
+ /** Gets the node corresponding to the field `function`. */
+ final Identifier getFunction() { hcl_function_call_def(this, result) }
+
+ /** Gets the child of this node. */
+ final Ellipsis getChild() { hcl_function_call_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_function_call_argument(this, _, result) or
+ hcl_function_call_def(this, result) or
+ hcl_function_call_child(this, result)
+ }
+ }
+
+ /** A class representing `get_attr` nodes. */
+ class GetAttr extends @hcl_get_attr, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "GetAttr" }
+
+ /** Gets the node corresponding to the field `key`. */
+ final Identifier getKey() { hcl_get_attr_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_get_attr_def(this, result) }
+ }
+
+ /** A class representing `get_attr_expr` nodes. */
+ class GetAttrExpr extends @hcl_get_attr_expr, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "GetAttrExpr" }
+
+ /** Gets the node corresponding to the field `expr`. */
+ final UnderscoreExprTerm getExpr() { hcl_get_attr_expr_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `key`. */
+ final Identifier getKey() { hcl_get_attr_expr_def(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_get_attr_expr_def(this, result, _) or hcl_get_attr_expr_def(this, _, result)
+ }
+ }
+
+ /** A class representing `heredoc_template` nodes. */
+ class HeredocTemplate extends @hcl_heredoc_template, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "HeredocTemplate" }
+
+ /** Gets the node corresponding to the field `start`. */
+ final string getStart() {
+ exists(int value | hcl_heredoc_template_def(this, value) |
+ result = "<<" and value = 0
+ or
+ result = "<<-" and value = 1
+ )
+ }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { hcl_heredoc_template_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_heredoc_template_child(this, _, result) }
+ }
+
+ /** A class representing `identifier` tokens. */
+ class Identifier extends @hcl_token_identifier, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Identifier" }
+ }
+
+ /** A class representing `index` nodes. */
+ class Index extends @hcl_index, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Index" }
+
+ /** Gets the node corresponding to the field `index`. */
+ final AstNode getIndex() { hcl_index_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_index_def(this, result) }
+ }
+
+ /** A class representing `index_expr` nodes. */
+ class IndexExpr extends @hcl_index_expr, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "IndexExpr" }
+
+ /** Gets the node corresponding to the field `expr`. */
+ final UnderscoreExprTerm getExpr() { hcl_index_expr_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `index`. */
+ final AstNode getIndex() { hcl_index_expr_def(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_index_expr_def(this, result, _) or hcl_index_expr_def(this, _, result)
+ }
+ }
+
+ /** A class representing `null_lit` tokens. */
+ class NullLit extends @hcl_token_null_lit, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "NullLit" }
+ }
+
+ /** A class representing `numeric_lit` tokens. */
+ class NumericLit extends @hcl_token_numeric_lit, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "NumericLit" }
+ }
+
+ /** A class representing `object` nodes. */
+ class Object extends @hcl_object, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Object" }
+
+ /** Gets the node corresponding to the field `element`. */
+ final ObjectElem getElement(int i) { hcl_object_element(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_object_element(this, _, result) }
+ }
+
+ /** A class representing `object_elem` nodes. */
+ class ObjectElem extends @hcl_object_elem, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ObjectElem" }
+
+ /** Gets the node corresponding to the field `key`. */
+ final UnderscoreExpression getKey() { hcl_object_elem_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `val`. */
+ final UnderscoreExpression getVal() { hcl_object_elem_def(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_object_elem_def(this, result, _) or hcl_object_elem_def(this, _, result)
+ }
+ }
+
+ /** A class representing `parenthesized_expr` nodes. */
+ class ParenthesizedExpr extends @hcl_parenthesized_expr, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ParenthesizedExpr" }
+
+ /** Gets the child of this node. */
+ final UnderscoreExpression getChild() { hcl_parenthesized_expr_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_parenthesized_expr_def(this, result) }
+ }
+
+ /** A class representing `quoted_template` nodes. */
+ class QuotedTemplate extends @hcl_quoted_template, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "QuotedTemplate" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { hcl_quoted_template_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_quoted_template_child(this, _, result) }
+ }
+
+ /** A class representing `splat_expr` nodes. */
+ class SplatExpr extends @hcl_splat_expr, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "SplatExpr" }
+
+ /** Gets the node corresponding to the field `expr`. */
+ final UnderscoreExprTerm getExpr() { hcl_splat_expr_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `splat`. */
+ final UnderscoreSplat getSplat() { hcl_splat_expr_def(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_splat_expr_def(this, result, _) or hcl_splat_expr_def(this, _, result)
+ }
+ }
+
+ /** A class representing `string_lit` nodes. */
+ class StringLit extends @hcl_string_lit, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "StringLit" }
+
+ /** Gets the child of this node. */
+ final TemplateLiteral getChild() { hcl_string_lit_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_string_lit_child(this, result) }
+ }
+
+ /** A class representing `template_for` nodes. */
+ class TemplateFor extends @hcl_template_for, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "TemplateFor" }
+
+ /** Gets the node corresponding to the field `body`. */
+ final AstNode getBody(int i) { hcl_template_for_body(this, i, result) }
+
+ /** Gets the node corresponding to the field `iter`. */
+ final UnderscoreExpression getIter() { hcl_template_for_def(this, result) }
+
+ /** Gets the node corresponding to the field `target`. */
+ final Identifier getTarget(int i) { hcl_template_for_target(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_template_for_body(this, _, result) or
+ hcl_template_for_def(this, result) or
+ hcl_template_for_target(this, _, result)
+ }
+ }
+
+ /** A class representing `template_if` nodes. */
+ class TemplateIf extends @hcl_template_if, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "TemplateIf" }
+
+ /** Gets the node corresponding to the field `alternative`. */
+ final AstNode getAlternative(int i) { hcl_template_if_alternative(this, i, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ final AstNode getBody(int i) { hcl_template_if_body(this, i, result) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ final UnderscoreExpression getCondition() { hcl_template_if_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ hcl_template_if_alternative(this, _, result) or
+ hcl_template_if_body(this, _, result) or
+ hcl_template_if_def(this, result)
+ }
+ }
+
+ /** A class representing `template_interpolation` nodes. */
+ class TemplateInterpolation extends @hcl_template_interpolation, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "TemplateInterpolation" }
+
+ /** Gets the node corresponding to the field `expr`. */
+ final UnderscoreExpression getExpr() { hcl_template_interpolation_expr(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_template_interpolation_expr(this, result) }
+ }
+
+ /** A class representing `template_literal` tokens. */
+ class TemplateLiteral extends @hcl_token_template_literal, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "TemplateLiteral" }
+ }
+
+ /** A class representing `tuple` nodes. */
+ class Tuple extends @hcl_tuple, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Tuple" }
+
+ /** Gets the node corresponding to the field `element`. */
+ final UnderscoreExpression getElement(int i) { hcl_tuple_element(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_tuple_element(this, _, result) }
+ }
+
+ /** A class representing `unary_operation` nodes. */
+ class UnaryOperation extends @hcl_unary_operation, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "UnaryOperation" }
+
+ /** Gets the node corresponding to the field `operand`. */
+ final UnderscoreExprTerm getOperand() { hcl_unary_operation_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `operator`. */
+ final string getOperator() {
+ exists(int value | hcl_unary_operation_def(this, _, value) |
+ result = "!" and value = 0
+ or
+ result = "-" and value = 1
+ )
+ }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_unary_operation_def(this, result, _) }
+ }
+
+ /** A class representing `variable_expr` nodes. */
+ class VariableExpr extends @hcl_variable_expr, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "VariableExpr" }
+
+ /** Gets the node corresponding to the field `name`. */
+ final Identifier getName() { hcl_variable_expr_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { hcl_variable_expr_def(this, result) }
+ }
+}
+
+module DOCKERFILE {
+ /** The base class for all AST nodes */
+ class AstNode extends @dockerfile_ast_node {
+ /** Gets a string representation of this element. */
+ string toString() { result = this.getAPrimaryQlClass() }
+
+ /** Gets the location of this element. */
+ final L::Location getLocation() { dockerfile_ast_node_location(this, result) }
+
+ /** Gets the parent of this element. */
+ final AstNode getParent() { dockerfile_ast_node_parent(this, result, _) }
+
+ /** Gets the index of this node among the children of its parent. */
+ final int getParentIndex() { dockerfile_ast_node_parent(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ AstNode getAFieldOrChild() { none() }
+
+ /** Gets the name of the primary QL class for this element. */
+ string getAPrimaryQlClass() { result = "???" }
+
+ /** Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs. */
+ string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
+ }
+
+ /** A token. */
+ class Token extends @dockerfile_token, AstNode {
+ /** Gets the value of this token. */
+ final string getValue() { dockerfile_tokeninfo(this, _, result) }
+
+ /** Gets a string representation of this element. */
+ final override string toString() { result = this.getValue() }
+
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Token" }
+ }
+
+ /** A reserved word. */
+ class ReservedWord extends @dockerfile_reserved_word, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ReservedWord" }
+ }
+
+ /** A class representing `add_instruction` nodes. */
+ class AddInstruction extends @dockerfile_add_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "AddInstruction" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_add_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_add_instruction_child(this, _, result) }
+ }
+
+ /** A class representing `arg_instruction` nodes. */
+ class ArgInstruction extends @dockerfile_arg_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ArgInstruction" }
+
+ /** Gets the node corresponding to the field `default`. */
+ final AstNode getDefault() { dockerfile_arg_instruction_default(this, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ final UnquotedString getName() { dockerfile_arg_instruction_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_arg_instruction_default(this, result) or
+ dockerfile_arg_instruction_def(this, result)
+ }
+ }
+
+ /** A class representing `cmd_instruction` nodes. */
+ class CmdInstruction extends @dockerfile_cmd_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "CmdInstruction" }
+
+ /** Gets the child of this node. */
+ final AstNode getChild() { dockerfile_cmd_instruction_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_cmd_instruction_def(this, result) }
+ }
+
+ /** A class representing `comment` tokens. */
+ class Comment extends @dockerfile_token_comment, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Comment" }
+ }
+
+ /** A class representing `copy_instruction` nodes. */
+ class CopyInstruction extends @dockerfile_copy_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "CopyInstruction" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_copy_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_copy_instruction_child(this, _, result) }
+ }
+
+ /** A class representing `cross_build_instruction` tokens. */
+ class CrossBuildInstruction extends @dockerfile_token_cross_build_instruction, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "CrossBuildInstruction" }
+ }
+
+ /** A class representing `double_quoted_string` nodes. */
+ class DoubleQuotedString extends @dockerfile_double_quoted_string, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "DoubleQuotedString" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_double_quoted_string_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_double_quoted_string_child(this, _, result)
+ }
+ }
+
+ /** A class representing `entrypoint_instruction` nodes. */
+ class EntrypointInstruction extends @dockerfile_entrypoint_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "EntrypointInstruction" }
+
+ /** Gets the child of this node. */
+ final AstNode getChild() { dockerfile_entrypoint_instruction_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_entrypoint_instruction_def(this, result)
+ }
+ }
+
+ /** A class representing `env_instruction` nodes. */
+ class EnvInstruction extends @dockerfile_env_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "EnvInstruction" }
+
+ /** Gets the `i`th child of this node. */
+ final EnvPair getChild(int i) { dockerfile_env_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_env_instruction_child(this, _, result) }
+ }
+
+ /** A class representing `env_pair` nodes. */
+ class EnvPair extends @dockerfile_env_pair, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "EnvPair" }
+
+ /** Gets the node corresponding to the field `name`. */
+ final UnquotedString getName() { dockerfile_env_pair_def(this, result) }
+
+ /** Gets the node corresponding to the field `value`. */
+ final AstNode getValue() { dockerfile_env_pair_value(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_env_pair_def(this, result) or dockerfile_env_pair_value(this, result)
+ }
+ }
+
+ /** A class representing `escape_sequence` tokens. */
+ class EscapeSequence extends @dockerfile_token_escape_sequence, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "EscapeSequence" }
+ }
+
+ /** A class representing `expansion` nodes. */
+ class Expansion extends @dockerfile_expansion, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Expansion" }
+
+ /** Gets the child of this node. */
+ final Variable getChild() { dockerfile_expansion_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_expansion_def(this, result) }
+ }
+
+ /** A class representing `expose_instruction` nodes. */
+ class ExposeInstruction extends @dockerfile_expose_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ExposeInstruction" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_expose_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_expose_instruction_child(this, _, result)
+ }
+ }
+
+ /** A class representing `expose_port` tokens. */
+ class ExposePort extends @dockerfile_token_expose_port, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ExposePort" }
+ }
+
+ /** A class representing `from_instruction` nodes. */
+ class FromInstruction extends @dockerfile_from_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "FromInstruction" }
+
+ /** Gets the node corresponding to the field `as`. */
+ final ImageAlias getAs() { dockerfile_from_instruction_as(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_from_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_from_instruction_as(this, result) or
+ dockerfile_from_instruction_child(this, _, result)
+ }
+ }
+
+ /** A class representing `healthcheck_instruction` nodes. */
+ class HealthcheckInstruction extends @dockerfile_healthcheck_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "HealthcheckInstruction" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_healthcheck_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_healthcheck_instruction_child(this, _, result)
+ }
+ }
+
+ /** A class representing `heredoc_block` nodes. */
+ class HeredocBlock extends @dockerfile_heredoc_block, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "HeredocBlock" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_heredoc_block_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_heredoc_block_child(this, _, result) }
+ }
+
+ /** A class representing `heredoc_end` tokens. */
+ class HeredocEnd extends @dockerfile_token_heredoc_end, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "HeredocEnd" }
+ }
+
+ /** A class representing `heredoc_line` tokens. */
+ class HeredocLine extends @dockerfile_token_heredoc_line, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "HeredocLine" }
+ }
+
+ /** A class representing `heredoc_marker` tokens. */
+ class HeredocMarker extends @dockerfile_token_heredoc_marker, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "HeredocMarker" }
+ }
+
+ /** A class representing `image_alias` nodes. */
+ class ImageAlias extends @dockerfile_image_alias, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ImageAlias" }
+
+ /** Gets the `i`th child of this node. */
+ final Expansion getChild(int i) { dockerfile_image_alias_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_image_alias_child(this, _, result) }
+ }
+
+ /** A class representing `image_digest` nodes. */
+ class ImageDigest extends @dockerfile_image_digest, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ImageDigest" }
+
+ /** Gets the `i`th child of this node. */
+ final Expansion getChild(int i) { dockerfile_image_digest_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_image_digest_child(this, _, result) }
+ }
+
+ /** A class representing `image_name` nodes. */
+ class ImageName extends @dockerfile_image_name, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ImageName" }
+
+ /** Gets the `i`th child of this node. */
+ final Expansion getChild(int i) { dockerfile_image_name_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_image_name_child(this, _, result) }
+ }
+
+ /** A class representing `image_spec` nodes. */
+ class ImageSpec extends @dockerfile_image_spec, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ImageSpec" }
+
+ /** Gets the node corresponding to the field `digest`. */
+ final ImageDigest getDigest() { dockerfile_image_spec_digest(this, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ final ImageName getName() { dockerfile_image_spec_def(this, result) }
+
+ /** Gets the node corresponding to the field `tag`. */
+ final ImageTag getTag() { dockerfile_image_spec_tag(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_image_spec_digest(this, result) or
+ dockerfile_image_spec_def(this, result) or
+ dockerfile_image_spec_tag(this, result)
+ }
+ }
+
+ /** A class representing `image_tag` nodes. */
+ class ImageTag extends @dockerfile_image_tag, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ImageTag" }
+
+ /** Gets the `i`th child of this node. */
+ final Expansion getChild(int i) { dockerfile_image_tag_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_image_tag_child(this, _, result) }
+ }
+
+ /** A class representing `json_string` nodes. */
+ class JsonString extends @dockerfile_json_string, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "JsonString" }
+
+ /** Gets the `i`th child of this node. */
+ final EscapeSequence getChild(int i) { dockerfile_json_string_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_json_string_child(this, _, result) }
+ }
+
+ /** A class representing `json_string_array` nodes. */
+ class JsonStringArray extends @dockerfile_json_string_array, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "JsonStringArray" }
+
+ /** Gets the `i`th child of this node. */
+ final JsonString getChild(int i) { dockerfile_json_string_array_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_json_string_array_child(this, _, result)
+ }
+ }
+
+ /** A class representing `label_instruction` nodes. */
+ class LabelInstruction extends @dockerfile_label_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "LabelInstruction" }
+
+ /** Gets the `i`th child of this node. */
+ final LabelPair getChild(int i) { dockerfile_label_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_label_instruction_child(this, _, result)
+ }
+ }
+
+ /** A class representing `label_pair` nodes. */
+ class LabelPair extends @dockerfile_label_pair, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "LabelPair" }
+
+ /** Gets the node corresponding to the field `key`. */
+ final AstNode getKey() { dockerfile_label_pair_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `value`. */
+ final AstNode getValue() { dockerfile_label_pair_def(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_label_pair_def(this, result, _) or dockerfile_label_pair_def(this, _, result)
+ }
+ }
+
+ /** A class representing `line_continuation` tokens. */
+ class LineContinuation extends @dockerfile_token_line_continuation, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "LineContinuation" }
+ }
+
+ /** A class representing `maintainer_instruction` tokens. */
+ class MaintainerInstruction extends @dockerfile_token_maintainer_instruction, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "MaintainerInstruction" }
+ }
+
+ /** A class representing `mount_param` nodes. */
+ class MountParam extends @dockerfile_mount_param, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "MountParam" }
+
+ /** Gets the node corresponding to the field `name`. */
+ final string getName() {
+ exists(int value | dockerfile_mount_param_def(this, value) | (result = "mount" and value = 0))
+ }
+
+ /** Gets the node corresponding to the field `value`. */
+ final AstNode getValue(int i) { dockerfile_mount_param_value(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_mount_param_value(this, _, result) }
+ }
+
+ /** A class representing `mount_param_param` tokens. */
+ class MountParamParam extends @dockerfile_token_mount_param_param, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "MountParamParam" }
+ }
+
+ /** A class representing `onbuild_instruction` nodes. */
+ class OnbuildInstruction extends @dockerfile_onbuild_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "OnbuildInstruction" }
+
+ /** Gets the child of this node. */
+ final AstNode getChild() { dockerfile_onbuild_instruction_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_onbuild_instruction_def(this, result) }
+ }
+
+ /** A class representing `param` tokens. */
+ class Param extends @dockerfile_token_param, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Param" }
+ }
+
+ /** A class representing `path` nodes. */
+ class Path extends @dockerfile_path, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Path" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_path_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_path_child(this, _, result) }
+ }
+
+ /** A class representing `run_instruction` nodes. */
+ class RunInstruction extends @dockerfile_run_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "RunInstruction" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_run_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_run_instruction_child(this, _, result) }
+ }
+
+ /** A class representing `shell_command` nodes. */
+ class ShellCommand extends @dockerfile_shell_command, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ShellCommand" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_shell_command_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_shell_command_child(this, _, result) }
+ }
+
+ /** A class representing `shell_fragment` nodes. */
+ class ShellFragment extends @dockerfile_shell_fragment, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ShellFragment" }
+
+ /** Gets the `i`th child of this node. */
+ final HeredocMarker getChild(int i) { dockerfile_shell_fragment_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_shell_fragment_child(this, _, result) }
+ }
+
+ /** A class representing `shell_instruction` nodes. */
+ class ShellInstruction extends @dockerfile_shell_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "ShellInstruction" }
+
+ /** Gets the child of this node. */
+ final JsonStringArray getChild() { dockerfile_shell_instruction_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_shell_instruction_def(this, result) }
+ }
+
+ /** A class representing `single_quoted_string` nodes. */
+ class SingleQuotedString extends @dockerfile_single_quoted_string, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "SingleQuotedString" }
+
+ /** Gets the `i`th child of this node. */
+ final EscapeSequence getChild(int i) { dockerfile_single_quoted_string_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_single_quoted_string_child(this, _, result)
+ }
+ }
+
+ /** A class representing `source_file` nodes. */
+ class SourceFile extends @dockerfile_source_file, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "SourceFile" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_source_file_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_source_file_child(this, _, result) }
+ }
+
+ /** A class representing `stopsignal_instruction` nodes. */
+ class StopsignalInstruction extends @dockerfile_stopsignal_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "StopsignalInstruction" }
+
+ /** Gets the `i`th child of this node. */
+ final Expansion getChild(int i) { dockerfile_stopsignal_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_stopsignal_instruction_child(this, _, result)
+ }
+ }
+
+ /** A class representing `unquoted_string` nodes. */
+ class UnquotedString extends @dockerfile_unquoted_string, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "UnquotedString" }
+
+ /** Gets the `i`th child of this node. */
+ final Expansion getChild(int i) { dockerfile_unquoted_string_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_unquoted_string_child(this, _, result) }
+ }
+
+ /** A class representing `user_instruction` nodes. */
+ class UserInstruction extends @dockerfile_user_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "UserInstruction" }
+
+ /** Gets the node corresponding to the field `group`. */
+ final UnquotedString getGroup() { dockerfile_user_instruction_group(this, result) }
+
+ /** Gets the node corresponding to the field `user`. */
+ final UnquotedString getUser() { dockerfile_user_instruction_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_user_instruction_group(this, result) or
+ dockerfile_user_instruction_def(this, result)
+ }
+ }
+
+ /** A class representing `variable` tokens. */
+ class Variable extends @dockerfile_token_variable, Token {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "Variable" }
+ }
+
+ /** A class representing `volume_instruction` nodes. */
+ class VolumeInstruction extends @dockerfile_volume_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "VolumeInstruction" }
+
+ /** Gets the `i`th child of this node. */
+ final AstNode getChild(int i) { dockerfile_volume_instruction_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() {
+ dockerfile_volume_instruction_child(this, _, result)
+ }
+ }
+
+ /** A class representing `workdir_instruction` nodes. */
+ class WorkdirInstruction extends @dockerfile_workdir_instruction, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ final override string getAPrimaryQlClass() { result = "WorkdirInstruction" }
+
+ /** Gets the child of this node. */
+ final Path getChild() { dockerfile_workdir_instruction_def(this, result) }
+
+ /** Gets a field or child node of this node. */
+ final override AstNode getAFieldOrChild() { dockerfile_workdir_instruction_def(this, result) }
+ }
+}
diff --git a/iac/ql/lib/codeql/iac/aws/CloudFormation.qll b/iac/ql/lib/codeql/iac/aws/CloudFormation.qll
new file mode 100644
index 000000000000..2c03a435c814
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/aws/CloudFormation.qll
@@ -0,0 +1,522 @@
+private import codeql.iac.YAML
+private import codeql.files.FileSystem
+
+/**
+ * AWS CloudFormation.
+ */
+module CloudFormation {
+ /**
+ * An AWS CloudFormation Variable node.
+ *
+ * This is used as a base class for other CloudFormation nodes that can be
+ * referenced by other nodes.
+ */
+ abstract class Variable extends YamlNode {
+ abstract string getName();
+
+ override string toString() { result = "CloudFormation Variable `" + this.getName() + "`" }
+ }
+
+ /**
+ * Find a CloudFormation variable by reference node.
+ */
+ Variable findByReference(YamlNode node) {
+ exists(Variable vars |
+ // Same file reference
+ vars.getFile().getAbsolutePath() = node.getFile().getAbsolutePath() and
+ (
+ // Support for JSON References
+ vars.getName() = node.(YamlMapping).lookup("Ref").(YamlString).getValue()
+ or
+ // Support for YAML References
+ vars.getName() = node.(YamlScalar).getValue()
+ )
+ |
+ result = vars
+ )
+ }
+
+ /**
+ * An AWS CloudFormation file node.
+ */
+ class Document extends YamlNode, YamlDocument, YamlMapping {
+ Document() { exists(this.lookup("AWSTemplateFormatVersion")) }
+
+ override string toString() { result = "CloudFormation Document" }
+
+ Resource getResources() { result = this.lookup("Resources").getAChild() }
+
+ Output getOutputs() { result = this.lookup("Outputs").getAChild() }
+ }
+
+ /**
+ * An AWS CloudFormation Resource node.
+ */
+ class Resource extends Variable, YamlNode, YamlMapping {
+ private Document document;
+
+ Resource() { this = document.lookup("Resources").getAChild() }
+
+ override string toString() { result = "CloudFormation Resource" }
+
+ override string getName() {
+ exists(YamlNode pair, string key_name |
+ pair = this.getParentNode().(YamlMapping).getChild(_) and
+ key_name = pair.(YamlString).getValue()
+ |
+ result = pair.toString()
+ )
+ }
+
+ /**
+ * Get the resource type.
+ */
+ string getType() { result = this.lookup("Type").(YamlString).getValue() }
+
+ /**
+ * Get the resource properties.
+ */
+ ResourceProperties getProperties() { result = this.lookup("Properties") }
+ }
+
+ /**
+ * An AWS CloudFormation Resource Properties node.
+ */
+ class ResourceProperties extends YamlNode, YamlMapping {
+ private Resource resource;
+
+ ResourceProperties() { this = resource.lookup("Properties") }
+
+ override string toString() { result = "CloudFormation Resource Properties" }
+
+ /**
+ * Get the resource property value.
+ */
+ YamlNode getProperty(string key) { result = this.lookup(key) }
+ }
+
+ /**
+ * An AWS CloudFormation Outputs node.
+ */
+ class Output extends YamlNode, YamlMapping {
+ private Document document;
+
+ Output() { this = document.lookup("Outputs").getAChild() }
+
+ override string toString() { result = "CloudFormation Outputs" }
+ }
+
+ /**
+ * An AWS CloudFormation S3 Bucket.
+ */
+ class S3Bucket extends Resource {
+ S3Bucket() { this.getType() = "AWS::S3::Bucket" }
+
+ override string toString() { result = "CloudFormation S3 Bucket" }
+
+ /**
+ * Get Bucket Policy if it exists.
+ */
+ S3BucketPolicy getBucketPolicy() {
+ exists(S3BucketPolicy policies | policies.getBucket() = this | result = policies)
+ }
+
+ /**
+ * Get the bucket access control.
+ */
+ string getAccessControl() {
+ result = this.getProperties().getProperty("AccessControl").(YamlString).getValue()
+ }
+ }
+
+ /**
+ * An AWS CloudFormation S3 Bucket Policy.
+ */
+ class S3BucketPolicy extends Resource {
+ S3BucketPolicy() { this.getType() = "AWS::S3::BucketPolicy" }
+
+ override string toString() { result = "CloudFormation S3 Bucket Policy" }
+
+ /**
+ * Get the bucket policy.
+ */
+ YamlNode getBucket() {
+ result = findByReference(this.getProperties().getProperty("Bucket"))
+ // exists(Variable node, S3Bucket buckets |
+ // node = this.getProperties().getProperty("Bucket") and
+ // // Support for JSON References
+ // exists(YamlMapping map | map = node |
+ // buckets.getName() = map.lookup("Ref").(YamlString).getValue()
+ // )
+ // or
+ // // Support for YAML References
+ // exists(YamlScalar scalar | scalar.getValue().matches("^!Ref .*$") |
+ // buckets.getName() = scalar.getValue()
+ // )
+ // |
+ // result = buckets
+ // )
+ }
+ }
+ class LambdaFunction extends Resource {
+ LambdaFunction() { this.getType() = "AWS::Lambda::Function" }
+
+ override string toString() { result = "CloudFormation Lambda Function" }
+
+ /**
+ * Get the Lambda function runtime.
+ */
+ string getRuntime() {
+ result = this.getProperties().getProperty("Runtime").(YamlString).getValue()
+ }
+ /**
+ * get principal
+ */
+ string getPrincipal() {
+ result = this.getProperties().getProperty("Principal").(YamlString).getValue()
+ }
+
+ }
+
+ class EC2SecurityGroup extends Resource {
+ EC2SecurityGroup() { this.getType() = "AWS::EC2::SecurityGroup" }
+
+ override string toString() { result = "CloudFormation EC2 Security Group" }
+
+ /**
+ * Get the security group egress rules.
+ */
+ YamlNode getSgEgress() {
+ result = this.getProperties().getProperty("SecurityGroupEgress")
+ }
+ YamlNode getEgressCidrIp() {
+ result = this.getSgEgress().getAChildNode().(YamlMapping).lookup("CidrIp")
+ }
+ YamlNode getEgressFromPort() {
+ result = this.getSgEgress().getAChildNode().(YamlMapping).lookup("FromPort")
+ }
+ YamlNode getEgressToPort() {
+ result = this.getSgEgress().getAChildNode().(YamlMapping).lookup("ToPort")
+ }
+ YamlNode getEgressDesc() {
+ result = this.getSgEgress().getAChildNode().(YamlMapping).lookup("Description")
+ }
+
+ /**
+ * Get the security group ingress rules.
+ */
+ YamlNode getSgIngress() {
+ result = this.getProperties().getProperty("SecurityGroupIngress")
+ }
+ YamlNode getIngressCidrIp() {
+ result = this.getSgIngress().getAChildNode().(YamlMapping).lookup("CidrIp")
+ }
+ YamlNode getIngressFromPort() {
+ result = this.getSgIngress().getAChildNode().(YamlMapping).lookup("FromPort")
+ }
+ YamlNode getIngressToPort() {
+ result = this.getSgIngress().getAChildNode().(YamlMapping).lookup("ToPort")
+ }
+ YamlNode getIngressDesc() {
+ result = this.getSgIngress().getAChildNode().(YamlMapping).lookup("Description")
+ }
+ }
+
+ class EC2SecurityGroupEgress extends Resource {
+ EC2SecurityGroupEgress() { this.getType() = "AWS::EC2::SecurityGroupEgress" }
+
+ override string toString() { result = "CloudFormation EC2 Security Group Egress" }
+
+ /**
+ * Get the security group ingress CIDR IP.
+ */
+ YamlNode getCidrIp() {
+ result = this.getProperties().getProperty("CidrIp")
+ }
+
+ /**
+ * Get the security group ingress from port.
+ */
+ YamlNode getFromPort() {
+ result = this.getProperties().getProperty("FromPort")
+ }
+ YamlNode getToPort() {
+ result = this.getProperties().getProperty("ToPort")
+ }
+ }
+
+
+
+ class EC2SecurityGroupIngress extends Resource {
+ EC2SecurityGroupIngress() { this.getType() = "AWS::EC2::SecurityGroupIngress" }
+
+ override string toString() { result = "CloudFormation EC2 Security Group Ingress" }
+
+ /**
+ * Get the security group ingress CIDR IP.
+ */
+ YamlNode getCidrIp() {
+ result = this.getProperties().getProperty("CidrIp")
+ }
+
+ /**
+ * Get the security group ingress from port.
+ */
+ YamlNode getFromPort() {
+ result = this.getProperties().getProperty("FromPort")
+ }
+ YamlNode getToPort() {
+ result = this.getProperties().getProperty("ToPort")
+ }
+ }
+
+ class IAMRole extends Resource {
+ IAMRole() { this.getType() = "AWS::IAM::Role" }
+
+ override string toString() { result = "CloudFormation IAM Role" }
+
+ string getProperty(string key) { result = this.getProperties().getProperty(key).toString() }
+
+ /**
+ * Get the IAM role policies.
+ */
+ IAMStatement getPolicy() {
+ result = this.getProperties().getProperty("Policies").getAChild().getAChild()
+
+ /*
+ exists(YamlNode policies
+ | policies = this.getProperties().getAChildNode()
+ | result = policies and policies.toString() = "Statement" )
+ */
+ }
+ }
+ class IAMStatement extends YamlNode {
+ IAMStatement(){ this.getAChild().toString() = "Statement"}
+
+ YamlNode getAction() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Action")
+ }
+ YamlNode getEffect() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Effect")
+ }
+ YamlNode getResource() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Resource")
+ }
+ }
+
+ class ECSService extends Resource {
+ ECSService() { this.getType() = "AWS::ECS::Service" }
+ YamlNode getNetworkConfiguration() {
+ result = this.getProperties().getProperty("NetworkConfiguration")
+ }
+
+ /**
+ * Get ecs service platform version
+ */
+ YamlNode getPlatformVersion() {
+ result = this.getProperties().getProperty("PlatformVersion")
+ }
+
+ TaskDefinition getTaskDefinition() {
+ result = this.getProperties().getProperty("TaskDefinition")
+ }
+ }
+
+ class ECSTaskSet extends Resource {
+ ECSTaskSet() { this.getType() = "AWS::ECS::TaskSet" }
+
+ override string toString() { result = "CloudFormation ECS Task Set" }
+
+ /**
+ * Get the task set network configuration.
+ */
+ YamlNode getNetworkConfiguration() {
+ result = this.getProperties().getProperty("NetworkConfiguration")
+ }
+
+ YamlNode getAssignPublicIp() {
+ result = this.getNetworkConfiguration().getAChild().(YamlMapping).lookup("AssignPublicIp")
+ }
+ }
+
+ class ECSNetworkConfiguration extends YamlNode {
+ ECSNetworkConfiguration() { this.getAChild().toString() = "NetworkConfiguration" }
+
+ YamlNode getAwsvpcConfiguration() {
+ result = this.getAChild().(YamlMapping).lookup("AwsvpcConfiguration")
+ }
+ YamlNode getAssignPublicIp() {
+ result = this.getAwsvpcConfiguration().(YamlMapping).lookup("AssignPublicIp")
+ }
+ }
+
+ class TaskDefinition extends Resource {
+ TaskDefinition() { this.getType() = "AWS::ECS::TaskDefinition" }
+
+ override string toString() { result = "CloudFormation ECS Task Definition" }
+
+ /**
+ * Get the task definition container definitions.
+ */
+ ContainerDefinition getContainerDefinitions() {
+ result = this.getProperties().getProperty("ContainerDefinitions")
+ }
+ /**
+ * Get network mode
+ */
+ YamlNode getNetworkMode() {
+ result = this.getProperties().getProperty("NetworkMode")
+ }
+
+ /**
+ * get PidMode
+ *
+ */
+ YamlNode getPidMode() {
+ result = this.getProperties().getProperty("PidMode")
+ }
+ /**
+ * get IPCMode
+ */
+ YamlNode getIpcMode() {
+ result = this.getProperties().getProperty("IpcMode")
+ }
+ /**
+ * get Volumes
+ */
+ YamlNode getVolumes() {
+ result = this.getProperties().getProperty("Volumes")
+ }
+ /**
+ * get PlacementConstraints
+ */
+ YamlNode getPlacementConstraints() {
+ result = this.getProperties().getProperty("PlacementConstraints")
+ }
+ /**
+ * get RequiresCompatibilities
+ */
+ YamlNode getRequiresCompatibilities() {
+ result = this.getProperties().getProperty("RequiresCompatibilities")
+ }
+ /**
+ * get Cpu
+ */
+ YamlNode getCpu() {
+ result = this.getProperties().getProperty("Cpu")
+ }
+ /**
+ * get Memory
+ */
+ YamlNode getMemory() {
+ result = this.getProperties().getProperty("Memory")
+ }
+ /**
+ * get ExecutionRoleArn
+ */
+ YamlNode getExecutionRoleArn() {
+ result = this.getProperties().getProperty("ExecutionRoleArn")
+ }
+
+ /**
+ * get logConfiguration
+ */
+ YamlNode getLogConfiguration() {
+ result = this.getProperties().getProperty("LogConfiguration")
+ }
+
+ /**
+ * get Secrets from ContainerDefinitions
+ */
+ YamlNode getSecrets() {
+ result = this.getContainerDefinitions().getAChild().(YamlMapping).lookup("Secrets")
+ }
+ YamlNode getRuntimePlatform() {
+ result = this.getProperties().getProperty("RuntimePlatform").(YamlMapping).lookup("OperatingSystemFamily")
+ }
+ }
+
+ class ECSCluster extends Resource {
+ ECSCluster() { this.getType() = "AWS::ECS::Cluster" }
+
+ override string toString() { result = "CloudFormation ECS Cluster" }
+
+ /** checks if container insights is enabled in container settings */
+ YamlNode getContainerInsights() {
+ result = this.getProperties().getProperty("ClusterSettings").getAChild().(YamlMapping).lookup("Value")
+ }
+ }
+
+ class ContainerDefinition extends YamlNode
+ {
+ ContainerDefinition() { this.getAChild().toString() = "ContainerDefinitions" }
+
+ YamlNode getName() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Name")
+ }
+
+ YamlNode getNetworkConfiguration() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("NetworkConfiguration")
+ }
+ YamlNode getnetworkconfigurationAwsvpcConfiguration() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("AwsvpcConfiguration")
+ }
+ YamlNode getImage() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Image")
+ }
+ YamlNode getMemory() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Memory")
+ }
+ YamlNode getMemoryReservation() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("MemoryReservation")
+ }
+ YamlNode getCpu() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Cpu")
+ }
+ YamlNode getEssential() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Essential")
+ }
+ YamlNode getPortMappings() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("PortMappings")
+ }
+ YamlNode getVolumesFrom() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("VolumesFrom")
+ }
+ YamlNode getEnvironment() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Environment")
+ }
+ YamlNode getSecrets() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Secrets")
+ }
+ YamlNode getLogConfiguration() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("LogConfiguration")
+ }
+ YamlNode getHealthCheck() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("HealthCheck")
+ }
+ YamlNode getEntryPoint() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("EntryPoint")
+ }
+ YamlNode getCommand() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Command")
+ }
+ YamlNode getWorkingDirectory() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("WorkingDirectory")
+ }
+ string getPrivileged() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("Privileged").toString()
+ }
+
+ string getReadOnlyRootFilesystem() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("ReadOnlyRootFilesystem").toString()
+ }
+ YamlNode getLinuxParametersCapabilities() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("LinuxParameters")
+ }
+
+ YamlNode getUser() {
+ result = this.getAChild().getAChild().(YamlMapping).lookup("User")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/iac/ql/lib/codeql/iac/azure/ARM.qll b/iac/ql/lib/codeql/iac/azure/ARM.qll
new file mode 100644
index 000000000000..a5739e6dc346
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/azure/ARM.qll
@@ -0,0 +1,89 @@
+private import codeql.iac.YAML
+private import codeql.files.FileSystem
+
+module ARM {
+ /**
+ * Azure Resource Manager (ARM) templates file.
+ */
+ class Document extends YamlNode, YamlDocument, YamlMapping {
+ Document() {
+ // Check the filename
+ this.getFile().getBaseName() = ["azuredeploy.json"]
+ or
+ // Check the schema
+ yamlToString(this.lookup("$schema")).regexpMatch(".*schema\\.management\\.azure\\.com.*")
+ }
+
+ string getVersion() {
+ result = this.lookup("contentVersion").toString().regexpReplaceAll("(\"|')", "")
+ }
+
+ Resource getResources() { result = this.lookup("resources").(YamlCollection).getAChild() }
+
+ Resource getResource(string name_or_type) {
+ exists(Resource resource | resource = this.getResources() |
+ resource.getName() = name_or_type
+ or
+ resource.getType() = name_or_type and
+ result = resource
+ )
+ }
+ }
+
+ /**
+ * Azure Resource Manager (ARM) resource.
+ */
+ class Resource extends YamlMapping {
+ private Document arm;
+
+ Resource() { arm.lookup("resources").getAChildNode() = this }
+
+ /**
+ * Get the resource type.
+ */
+ string getType() { result = this.lookup("type").toString().regexpReplaceAll("(\"|')", "") }
+
+ /**
+ * Get the resource name.
+ */
+ string getName() { result = this.lookup("name").toString().regexpReplaceAll("(\"|')", "") }
+
+ /**
+ * Get the resource properties.
+ */
+ ResourceProperties getProperties() { result = this.lookup("properties") }
+ }
+
+ /**
+ * Azure Resource Manager (ARM) resource properties.
+ */
+ class ResourceProperties extends YamlMapping {
+ private Resource resource;
+
+ ResourceProperties() { resource.lookup("properties") = this }
+
+ /**
+ * Get the property with the given name.
+ */
+ YamlNode getProperty(string name) { result = this.lookup(name) }
+
+ SecurityRule getSecurityRules() {
+ result = this.getProperty("securityRules").(YamlSequence).getAChild()
+ }
+ }
+
+ class SecurityRule extends YamlMapping {
+ private ResourceProperties properties;
+
+ SecurityRule() { properties.getProperty("securityRules").(YamlSequence).getAChildNode() = this }
+
+ /**
+ * Get the security rule name.
+ */
+ string getName() { result = this.lookup("name").toString().regexpReplaceAll("(\"|')", "") }
+
+ YamlNode getProperty(string name) {
+ result = this.lookup("properties").(YamlMapping).lookup(name)
+ }
+ }
+}
diff --git a/iac/ql/lib/codeql/iac/azure/Pipelines.qll b/iac/ql/lib/codeql/iac/azure/Pipelines.qll
new file mode 100644
index 000000000000..1a7269b3679b
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/azure/Pipelines.qll
@@ -0,0 +1,153 @@
+private import codeql.iac.YAML
+private import codeql.files.FileSystem
+
+module AzurePipelines {
+ /**
+ * Azure DevOps Pipeline file.
+ */
+ class Document extends YamlNode, YamlDocument, YamlMapping {
+ Document() {
+ // Check the filename
+ this.getFile().getBaseName() = ["azure-pipelines.yml", "azure-pipelines.yaml"]
+ }
+
+ override string toString() { result = "Azure DevOps Pipeline" }
+
+ /**
+ * Get the pipeline pool.
+ */
+ Pool getPool() { result = this.lookup("pool") }
+
+ /**
+ * Get the pipeline variables.
+ */
+ Variable getVariables() { result = this.lookup("variables").getAChild() }
+
+ /**
+ * Get the pipeline variable with the given name.
+ */
+ YamlValue getVariable(string name) {
+ exists(Variable var | var = this.getVariables() and var.getName() = name |
+ result = var.getValue()
+ )
+ }
+
+ /**
+ * Get the pipeline steps.
+ */
+ Step getSteps() { result = this.lookup("steps").getAChild() }
+
+ /**
+ * Get the pipeline task steps.
+ */
+ Task getTaskSteps() { result = this.getSteps().(Task) }
+
+ /**
+ * Get the pipeline script steps.
+ */
+ Script getScriptSteps() { result = this.getSteps().(Script) }
+ }
+
+ /**
+ * Azure DevOps Pipeline pool.
+ *
+ * https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/pool
+ */
+ class Pool extends YamlNode, YamlMapping {
+ private Document pipeline;
+
+ Pool() { pipeline.lookup("pool") = this }
+
+ /**
+ * Get the pool name.
+ */
+ string getName() { result = yamlToString(this.lookup("name")) }
+
+ /**
+ * Get the pool VM image.
+ */
+ string getVmImage() { result = yamlToString(this.lookup("vmImage")) }
+
+ /**
+ * Get the pool demands.
+ */
+ string getDemands() { result = yamlToString(this.lookup("demands")) }
+ }
+
+ /**
+ * Azure DevOps Pipeline variables.
+ *
+ * https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables
+ */
+ class Variable extends YamlNode, YamlMapping {
+ private Document document;
+
+ Variable() { document.lookup("variables").getChild(_) = this }
+
+ override string toString() { result = "Variable '" + this.getName() + "'" }
+
+ /**
+ * Get the variable name.
+ */
+ string getName() { result = yamlToString(this.lookup("name")) }
+
+ /**
+ * Get the variable value.
+ */
+ YamlValue getValue() { result = this.lookup("value") }
+ }
+
+ /**
+ * Azure DevOps Pipeline step.
+ *
+ * https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/steps
+ */
+ class Step extends YamlNode, YamlMapping {
+ private Document pipeline;
+
+ Step() { pipeline.lookup("steps").getAChildNode() = this }
+
+ override string toString() { result = "Azure DevOps Pipeline step" }
+
+ /**
+ * Get the step display name.
+ */
+ string displayName() { result = yamlToString(this.lookup("displayName")) }
+
+ /**
+ * Get the step type based on the presence of a `task` or `script` key.
+ */
+ string getType() {
+ exists(this.lookup("task")) and result = "task"
+ or
+ exists(this.lookup("script")) and result = "script"
+ }
+ }
+
+ /**
+ * Azure DevOps Pipeline task step.
+ */
+ class Task extends Step {
+ Task() { this.getType() = "task" }
+
+ /**
+ * Get the task name.
+ */
+ string getName() { result = yamlToString(this.lookup("task")) }
+ }
+
+ class TaskInputs extends YamlNode, YamlMapping {
+ private Task task;
+
+ TaskInputs() { task.lookup("inputs") = this }
+
+ YamlValue getInput(string name) { result = this.lookup(name) }
+ }
+
+ /**
+ * Azure DevOps Pipeline script step.
+ */
+ class Script extends Step {
+ Script() { this.getType() = "script" }
+ }
+}
diff --git a/iac/ql/lib/codeql/iac/compose/Compose.qll b/iac/ql/lib/codeql/iac/compose/Compose.qll
new file mode 100644
index 000000000000..496d123c4483
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/compose/Compose.qll
@@ -0,0 +1,58 @@
+private import codeql.iac.YAML
+private import codeql.files.FileSystem
+
+module Compose {
+ /**
+ * A Compose file node.
+ */
+ private class Node extends YamlNode {
+ Node() {
+ this.getFile().getBaseName() =
+ [
+ // Compose
+ "compose.yml", "compose.yaml",
+ // Docker
+ "docker-compose.yml", "docker-compose.yaml",
+ // Podman
+ "podman-compose.yml", "podman-compose.yaml",
+ ]
+ }
+ }
+
+ /**
+ * Docker / Podman Compose file.
+ */
+ class Document extends Node, YamlDocument, YamlMapping {
+ /**
+ * Returns the version of the Compose file.
+ */
+ string getApiVersion() {
+ result = this.lookup("version").toString().regexpReplaceAll("('|\")", "")
+ }
+
+ /**
+ * Returns the services defined in the Compose file.
+ */
+ Service getServices() { result = this.lookup("services").getAChildNode() }
+ }
+
+ /**
+ * A service defined in a Compose file.
+ */
+ class Service extends YamlMapping {
+ Document compose;
+
+ /**
+ * Compose Service
+ */
+ Service() { compose.lookup("services").getAChildNode() = this }
+
+ /**
+ * Returns the name of the service.
+ */
+ string getName() {
+ result = this.lookup("container_name").toString()
+ // TODO get parent key name
+ }
+ }
+}
diff --git a/iac/ql/lib/codeql/iac/containers/Containers.qll b/iac/ql/lib/codeql/iac/containers/Containers.qll
new file mode 100644
index 000000000000..86060eabc955
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/containers/Containers.qll
@@ -0,0 +1,15 @@
+private import codeql.Locations
+private import codeql.files.FileSystem
+private import codeql.iac.ast.internal.AstNodes
+private import codeql.iac.ast.Container
+
+/**
+ * A container definition.
+ */
+class ContainerDefinition extends ContainerAstNode, TSourceFile {
+ private DOCKERFILE::SourceFile sourceFile;
+
+ override string getAPrimaryQlClass() { result = "ContainerDefinition" }
+
+ ContainerDefinition() { this = TSourceFile(sourceFile) }
+}
diff --git a/iac/ql/lib/codeql/iac/containers/Images.qll b/iac/ql/lib/codeql/iac/containers/Images.qll
new file mode 100644
index 000000000000..3ee20a1371fd
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/containers/Images.qll
@@ -0,0 +1,62 @@
+private import codeql.iac.ast.internal.AstNodes
+private import codeql.iac.ast.Container
+private import codeql.Locations
+private import codeql.files.FileSystem
+
+/**
+ * A container image.
+ */
+class ContainerImage extends ContainerAstNode, TImageSpec {
+ private DOCKERFILE::ImageSpec imageSpec;
+
+ override string getAPrimaryQlClass() { result = "Image" }
+
+ ContainerImage() { this = TImageSpec(imageSpec) }
+
+ ContainerImageName getName() { toContainerTreeSitter(result) = imageSpec.getName() }
+
+ ContainerImageTag getTag() { toContainerTreeSitter(result) = imageSpec.getTag() }
+}
+
+/**
+ * A container image name.
+ */
+class ContainerImageName extends ContainerAstNode, TImageName {
+ private DOCKERFILE::ImageName imageName;
+
+ override string getAPrimaryQlClass() { result = "ImageName" }
+
+ ContainerImageName() { this = TImageName(imageName) }
+
+ /**
+ * Returns the name of the image.
+ */
+ string getValue() { none() }
+}
+
+/**
+ * A container image tag.
+ */
+class ContainerImageTag extends ContainerAstNode, TImageTag {
+ private DOCKERFILE::ImageTag imageTag;
+
+ override string getAPrimaryQlClass() { result = "ImageTag" }
+
+ ContainerImageTag() { this = TImageTag(imageTag) }
+
+ /**
+ * Returns the value of the tag.
+ */
+ string getValue() { none() }
+}
+
+/**
+ * A container image alias.
+ */
+class ContainerImageAlias extends ContainerAstNode, TImageAlias {
+ private DOCKERFILE::ImageAlias imageAlias;
+
+ override string getAPrimaryQlClass() { result = "ImageAlias" }
+
+ ContainerImageAlias() { this = TImageAlias(imageAlias) }
+}
diff --git a/iac/ql/lib/codeql/iac/helmcharts/HelmChart.qll b/iac/ql/lib/codeql/iac/helmcharts/HelmChart.qll
new file mode 100644
index 000000000000..956647f242fa
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/helmcharts/HelmChart.qll
@@ -0,0 +1,181 @@
+private import codeql.iac.YAML
+private import codeql.files.FileSystem
+
+module HelmChart {
+ private class Node extends YamlNode {
+ Node() {
+ // Chart file itself
+ this.getFile().getExtension() = ["yml", "yaml"]
+ }
+ }
+
+ /**
+ * Helm Chart document.
+ *
+ * https://helm.sh/docs/topics/charts/
+ */
+ class Document extends Node, YamlDocument, YamlMapping {
+ Document() { exists(this.lookup("apiVersion")) }
+
+ override string toString() { result = "HelmChart Document" }
+
+ /**
+ * Get the API version of the Helm Chart.
+ */
+ string getApiVersion() { result = yamlToString(this.lookup("apiVersion")) }
+
+ /**
+ * Get the Kubernetes version that this chart is compatible with.
+ */
+ string getKubernetesVersion() { result = yamlToString(this.lookup("kubeVersion")) }
+
+ /**
+ * Get the Helm Chart application version.
+ */
+ string getVersion() { result = yamlToString(this.lookup("version")) }
+
+ /**
+ * Get the Helm Chart name.
+ */
+ string getName() { result = yamlToString(this.lookup("name")) }
+
+ /**
+ * Get the Helm Chart type.
+ */
+ string getType() { result = yamlToString(this.lookup("type")) }
+
+ /**
+ * Get the Helm Chart spec.
+ */
+ Spec getSpec() { result = this.lookup("spec") }
+
+ /**
+ * Get the Helm Chart dependencies.
+ */
+ Dependency getDependencies() { result = this.lookup("dependencies").(YamlSequence).getAChild() }
+
+ /**
+ * Get the Helm Chart data.
+ */
+ HelmData getData() { result = this.lookup("data") }
+
+ /**
+ * Get the Helm Chart values.
+ */
+ HelmChartValues::Document getValues() { none() }
+ }
+
+ /**
+ * Helm Chart spec.
+ */
+ class Spec extends Node, YamlMapping {
+ private HelmChart::Document helm;
+
+ Spec() { helm.lookup("spec") = this }
+
+ boolean getPrivileged() { result = this.lookup("privileged").(YamlBool).getBoolValue() }
+
+ SecurityContext getSecurityContext() { result = this.lookup("securityContext") }
+
+ Container getContainers() { result = this.lookup("containers").(YamlSequence).getAChild() }
+ }
+
+ class HelmData extends Node, YamlMapping {
+ private HelmChart::Document helm;
+
+ HelmData() { helm.lookup("data") = this }
+
+ override string toString() { result = "HelmData" }
+
+ Node getName(string name) { result = this.lookup(name) }
+ }
+
+ /**
+ * Helm Chart container.
+ */
+ class Container extends Node, YamlMapping {
+ private HelmChart::Spec spec;
+
+ Container() { spec.lookup("containers").getAChild() = this }
+
+ override string toString() { result = "Container" }
+
+ /**
+ * Get the container name.
+ */
+ string getName() { result = yamlToString(this.lookup("name")) }
+
+ /**
+ * Get the container image.
+ */
+ string getImage() { result = yamlToString(this.lookup("image")) }
+
+ /**
+ * Get the container command.
+ */
+ YamlSequence getCommand() { result = this.lookup("command").(YamlSequence) }
+
+ /**
+ * Get the container security context.
+ */
+ SecurityContext getSecurityContext() { result = this.lookup("securityContext") }
+ }
+
+ /**
+ * Helm Chart security context.
+ */
+ class SecurityContext extends Node, YamlMapping {
+ private HelmChart::Spec spec;
+ private HelmChart::Container container;
+
+ SecurityContext() {
+ spec.lookup("securityContext") = this or container.lookup("securityContext") = this
+ }
+
+ override string toString() { result = "SecurityContext" }
+
+ /**
+ * Get the container runAsUser.
+ */
+ int getRunAsUser() { result = this.lookup("runAsUser").(YamlInteger).getIntValue() }
+
+ /**
+ * Get the container runAsGroup.
+ */
+ int getRunAsGroup() { result = this.lookup("runAsUser").(YamlInteger).getIntValue() }
+
+ /**
+ * Get the container privilege.
+ */
+ boolean getPrivileged() { result = this.lookup("privileged").(YamlBool).getBoolValue() }
+
+ /**
+ * Get the container allowPrivilegeEscalation.
+ */
+ boolean getAllowPrivilegeEscalation() {
+ result = this.lookup("allowPrivilegeEscalation").(YamlBool).getBoolValue()
+ }
+ }
+
+ class Dependency extends Node, YamlMapping {
+ private HelmChart::Document helm;
+
+ Dependency() { helm.lookup("dependencies").(YamlSequence).getAChild() = this }
+
+ override string toString() { result = this.getName() }
+
+ string getName() { result = yamlToString(this.lookup("name")) }
+
+ string getVersion() { result = yamlToString(this.lookup("version")) }
+
+ string getRepository() { result = yamlToString(this.lookup("repository")) }
+ }
+}
+
+module HelmChartValues {
+ class Document extends YamlMapping {
+ Document() { this.getFile().getBaseName() = ["values.yaml", "values.yml"] }
+
+ override string toString() { result = "HelmChart Value Document" }
+ }
+}
diff --git a/iac/ql/lib/codeql/iac/ideContextual/IDEContextual.qll b/iac/ql/lib/codeql/iac/ideContextual/IDEContextual.qll
new file mode 100644
index 000000000000..0e58b1d878be
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ideContextual/IDEContextual.qll
@@ -0,0 +1,19 @@
+private import codeql.files.FileSystem
+
+/**
+ * Returns an appropriately encoded version of a filename `name`
+ * passed by the VS Code extension in order to coincide with the
+ * output of `.getFile()` on locatable entities.
+ */
+cached
+File getFileBySourceArchiveName(string name) {
+ // The name provided for a file in the source archive by the VS Code extension
+ // has some differences from the absolute path in the database:
+ // 1. colons are replaced by underscores
+ // 2. there's a leading slash, even for Windows paths: "C:/foo/bar" ->
+ // "/C_/foo/bar"
+ // 3. double slashes in UNC prefixes are replaced with a single slash
+ // We can handle 2 and 3 together by unconditionally adding a leading slash
+ // before replacing double slashes.
+ name = ("/" + result.getAbsolutePath().replaceAll(":", "_")).replaceAll("//", "/")
+}
diff --git a/iac/ql/lib/codeql/iac/ideContextual/printAstAst.qll b/iac/ql/lib/codeql/iac/ideContextual/printAstAst.qll
new file mode 100644
index 000000000000..a2486685d7cd
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ideContextual/printAstAst.qll
@@ -0,0 +1,103 @@
+/**
+ * Provides queries to pretty-print a QL abstract syntax tree as a graph.
+ *
+ * This representation is based on the user-facing AST implementation.
+ *
+ * By default, this will print the AST for all nodes in the database. To change
+ * this behavior, extend `PrintASTConfiguration` and override `shouldPrintNode`
+ * to hold for only the AST nodes you wish to view.
+ */
+
+import codeql.iac.ast.Hcl
+import codeql.iac.ast.Container
+private import codeql.Locations
+import codeql.iac.ast.internal.AstNodes
+
+/**
+ * The query can extend this class to control which nodes are printed.
+ */
+class PrintAstConfiguration extends string {
+ PrintAstConfiguration() { this = "PrintAstConfiguration" }
+
+ /**
+ * Holds if the given node should be printed.
+ */
+ predicate shouldPrintNode(HclAstNode n) { any() }
+}
+
+/**
+ * Gets the `i`th child of parent.
+ * The ordering is location based and pretty arbitrary.
+ */
+HclAstNode getAstChild(PrintAstNode parent, int i) {
+ result =
+ rank[i](HclAstNode child, Location l |
+ child.getParent() = parent and
+ child.getLocation() = l
+ |
+ child
+ order by
+ l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
+ )
+}
+
+/**
+ * A node in the output tree.
+ */
+class PrintAstNode extends HclAstNode {
+ PrintAstNode() { shouldPrintNode(this) }
+
+ string getProperty(string key) {
+ key = "semmle.label" and
+ result = "[" + concat(this.getAPrimaryQlClass(), ", ") + "] " + this.toString()
+ or
+ key = "semmle.order" and
+ result =
+ any(int i |
+ this =
+ rank[i](PrintAstNode p, Location l, File f |
+ l = p.getLocation() and
+ f = l.getFile()
+ |
+ p order by f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn()
+ )
+ ).toString()
+ }
+
+ /**
+ * Gets the child node that is accessed using the predicate `edgeName`.
+ */
+ PrintAstNode getChild(string edgeName) { result = this.getAChild(edgeName) }
+}
+
+private predicate shouldPrintNode(HclAstNode n) {
+ exists(PrintAstConfiguration config | config.shouldPrintNode(n))
+}
+
+/**
+ * Holds if `node` belongs to the output tree, and its property `key` has the
+ * given `value`.
+ */
+query predicate nodes(PrintAstNode node, string key, string value) { value = node.getProperty(key) }
+
+/**
+ * Holds if `target` is a child of `source` in the AST, and property `key` of
+ * the edge has the given `value`.
+ */
+query predicate edges(PrintAstNode source, PrintAstNode target, string key, string value) {
+ target = source.getChild(_) and
+ (
+ key = "semmle.label" and
+ value = strictconcat(string name | source.getChild(name) = target | name, "/")
+ or
+ key = "semmle.order" and
+ value = target.getProperty("semmle.order")
+ )
+}
+
+/**
+ * Holds if property `key` of the graph has the given `value`.
+ */
+query predicate graphProperties(string key, string value) {
+ key = "semmle.graphKind" and value = "tree"
+}
diff --git a/iac/ql/lib/codeql/iac/ideContextual/printAstGenerated.qll b/iac/ql/lib/codeql/iac/ideContextual/printAstGenerated.qll
new file mode 100644
index 000000000000..bc1b0c835bf5
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/ideContextual/printAstGenerated.qll
@@ -0,0 +1,109 @@
+/**
+ * Provides queries to pretty-print an HCL abstract syntax tree as a graph.
+ *
+ * This representation is based on the TreeSitter auto-generated AST.
+ *
+ * By default, this will print the AST for all nodes in the database. To change
+ * this behavior, extend `PrintASTConfiguration` and override `shouldPrintNode`
+ * to hold for only the AST nodes you wish to view.
+ */
+
+import codeql.iac.ast.internal.TreeSitter::HCL
+private import codeql.Locations
+
+/**
+ * The query can extend this class to control which nodes are printed.
+ */
+class PrintAstConfiguration extends string {
+ PrintAstConfiguration() { this = "PrintAstConfiguration" }
+
+ /**
+ * Holds if the given node should be printed.
+ */
+ predicate shouldPrintNode(AstNode n) {
+ not n instanceof Comment and
+ not n instanceof ReservedWord
+ }
+}
+
+/**
+ * Gets the `i`th child of parent.
+ * The ordering is location based and pretty arbitrary.
+ */
+AstNode getAstChild(PrintAstNode parent, int i) {
+ result =
+ rank[i](AstNode child, Location l |
+ child.getParent() = parent and
+ child.getLocation() = l
+ |
+ child
+ order by
+ l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
+ )
+}
+
+/**
+ * A node in the output tree.
+ */
+class PrintAstNode extends AstNode {
+ PrintAstNode() { shouldPrintNode(this) }
+
+ string getProperty(string key) {
+ key = "semmle.label" and
+ result = "[" + concat(this.getAPrimaryQlClass(), ", ") + "] " + this.toString()
+ or
+ key = "semmle.order" and
+ result =
+ any(int i |
+ this =
+ rank[i](PrintAstNode p, Location l, File f |
+ l = p.getLocation() and
+ f = l.getFile()
+ |
+ p order by f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn()
+ )
+ ).toString()
+ }
+
+ /**
+ * Gets the child node that is accessed using the predicate `edgeName`.
+ */
+ PrintAstNode getChild(string edgeName) {
+ exists(int i |
+ result = getAstChild(this, i) and
+ edgeName = i.toString()
+ )
+ }
+}
+
+private predicate shouldPrintNode(AstNode n) {
+ exists(PrintAstConfiguration config | config.shouldPrintNode(n))
+}
+
+/**
+ * Holds if `node` belongs to the output tree, and its property `key` has the
+ * given `value`.
+ */
+query predicate nodes(PrintAstNode node, string key, string value) { value = node.getProperty(key) }
+
+/**
+ * Holds if `target` is a child of `source` in the AST, and property `key` of
+ * the edge has the given `value`.
+ */
+query predicate edges(PrintAstNode source, PrintAstNode target, string key, string value) {
+ target = source.getChild(_) and
+ (
+ key = "semmle.label" and
+ value = strictconcat(string name | source.getChild(name) = target | name, "/")
+ or
+ key = "semmle.order" and
+ value = target.getProperty("semmle.order")
+ )
+}
+
+/**
+ * Holds if property `key` of the graph has the given `value`.
+ */
+query predicate graphProperties(string key, string value) {
+ key = "semmle.graphKind" and value = "tree"
+}
diff --git a/iac/ql/lib/codeql/iac/openapi/OpenApi.qll b/iac/ql/lib/codeql/iac/openapi/OpenApi.qll
new file mode 100644
index 000000000000..2c656782bfe9
--- /dev/null
+++ b/iac/ql/lib/codeql/iac/openapi/OpenApi.qll
@@ -0,0 +1,178 @@
+private import codeql.iac.YAML
+private import codeql.files.FileSystem
+
+module OpenApi {
+ /**
+ * OpenAPI Node
+ */
+ private class Node extends YamlNode {
+ Node() {
+ this.getLocation().getFile().getBaseName() =
+ [
+ // Swagger JSON / YAML
+ "swagger.yml", "swagger.yaml", "swagger.json",
+ // OpenAPI JSON / YAML
+ "openapi.yml", "openapi.yaml", "openapi.json",
+ ]
+ or
+ // Swagger JSON 2.0
+ exists(this.(YamlMapping).lookup("swagger"))
+ }
+ }
+
+ /**
+ * OpenAPI document.
+ */
+ class Document extends Node, YamlDocument, YamlMapping {
+ override string toString() { result = "OpenApi Document" }
+
+ /**
+ * Get the OpenAPI info.
+ */
+ Info getInfo() { result = this.lookup("info") }
+
+ /**
+ * Get the OpenAPI version.
+ */
+ string getApiVersion() { result = yamlToString(this.lookup("openapi")) }
+
+ /**
+ * Get the OpenAPI host.
+ */
+ string getHost() { result = yamlToString(this.lookup("host")) }
+
+ /**
+ * Get the OpenAPI schemes.
+ */
+ string getSchemes() {
+ result = yamlToString(this.lookup("schemes").(YamlCollection).getAChild())
+ }
+
+ /**
+ * Get the base path.
+ */
+ string getBasePath() { result = yamlToString(this.lookup("basePath")) }
+
+ /**
+ * Get the full base path.
+ */
+ string getFullBasePath() {
+ // No scheme
+ not exists(this.getSchemes()) and
+ result = this.getHost() + this.getBasePath()
+ or
+ // With scheme
+ result = this.getSchemes() + "://" + this.getHost() + this.getBasePath()
+ }
+
+ /**
+ * Get the OpenAPI consumes.
+ */
+ string getProduces() { result = yamlToString(this.lookup("produces")) }
+
+ /**
+ * Get the OpenAPI servers.
+ */
+ Servers getServers() { result = this.lookup("servers") }
+
+ /**
+ * Get the OpenAPI definitions.
+ */
+ Definitions getDefinitions() { result = this.lookup("definitions") }
+
+ /**
+ * Get the OpenAPI paths.
+ */
+ Path getPaths() { result = this.lookup("paths") }
+ }
+
+ /**
+ * OpenAPI info.
+ */
+ class Info extends YamlMapping {
+ private Document oapi;
+
+ Info() { oapi.lookup("info") = this }
+
+ override string toString() { result = "OpenApi Information" }
+
+ /**
+ * Get the API title.
+ */
+ string getTitle() { result = yamlToString(this.lookup("title")) }
+
+ /**
+ * Get the API description.
+ */
+ string getDescription() { result = yamlToString(this.lookup("description")) }
+
+ /**
+ * Get the API version.
+ */
+ string getVersion() { result = yamlToString(this.lookup("version")) }
+ }
+
+ /**
+ * OpenAPI servers.
+ */
+ class Servers extends YamlSequence {
+ private Document oapi;
+
+ Servers() { oapi.lookup("servers") = this }
+
+ override string toString() { result = "OpenApi Servers" }
+
+ /**
+ * Get a server from the servers list.
+ */
+ Server getServers() { result = this.getAChild() }
+ }
+
+ /**
+ * OpenAPI server.
+ */
+ class Server extends Node, YamlMapping {
+ private Servers oapis;
+
+ Server() { oapis.getAChild() = this }
+
+ override string toString() { result = "OpenApi Server" }
+
+ /**
+ * Get the server URL.
+ */
+ string getUrl() { result = yamlToString(this.lookup("url")) }
+
+ /**
+ * Get the server description.
+ */
+ string getDescription() { result = yamlToString(this.lookup("description")) }
+ }
+
+ /**
+ * OpenAPI paths.
+ */
+ class Path extends Node, YamlMapping {
+ private Document oapi;
+
+ Path() { oapi.lookup("paths") = this }
+
+ override string toString() { result = "OpenApi Path" }
+
+ /**
+ * Get all paths.
+ */
+ YamlNode getPaths() { result = this.getAChild().(YamlString) }
+ }
+
+ /**
+ * OpenAPI definitions.
+ */
+ class Definitions extends YamlMapping {
+ private Document oapi;
+
+ Definitions() { oapi.lookup("definitions") = this }
+
+ override string toString() { result = "OpenApi Definitions" }
+ }
+}
diff --git a/iac/ql/lib/hcl.qll b/iac/ql/lib/hcl.qll
new file mode 100644
index 000000000000..dd36db1a7889
--- /dev/null
+++ b/iac/ql/lib/hcl.qll
@@ -0,0 +1,25 @@
+/**
+ * Comprehensive analysis capabilities for HCL (HashiCorp Configuration Language) files.
+ * This library includes AST analysis, resource definitions, security checks, provider-specific
+ * functionality, and support for Terraform configurations.
+ *
+ * The library offers the following main components:
+ * - AST nodes and expressions for analyzing HCL syntax and structure
+ * - Resource and data source analysis for infrastructure components
+ * - Variable, local, and constant value tracking
+ * - Provider-specific classes for AWS, Azure, GCP, and other cloud providers
+ * - Security analysis for detecting hardcoded secrets and misconfigurations
+ * - Terraform-specific functionality for modules, state, and workflows
+ */
+
+import codeql.Locations
+import codeql.files.FileSystem
+import codeql.hcl.ast.AstNodes
+import codeql.hcl.Resources
+import codeql.hcl.Constants
+import codeql.hcl.Locals
+import codeql.hcl.Terraform
+// providers
+import codeql.hcl.Providers
+// security
+import codeql.hcl.Security
diff --git a/iac/ql/lib/iac.dbscheme b/iac/ql/lib/iac.dbscheme
new file mode 100644
index 000000000000..6b6bd681890e
--- /dev/null
+++ b/iac/ql/lib/iac.dbscheme
@@ -0,0 +1,955 @@
+// CodeQL database schema for HCL
+// Automatically generated from the tree-sitter grammar; do not edit
+
+/*- Files and folders -*/
+
+/**
+ * The location of an element.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `file`.
+ * For more information, see
+ * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @file | @folder
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*- Empty location -*/
+
+empty_location(
+ int location: @location_default ref
+);
+
+/*- Source location prefix -*/
+
+/**
+ * The source location of the snapshot.
+ */
+sourceLocationPrefix(string prefix : string ref);
+
+/*- Diagnostic messages -*/
+
+diagnostics(
+ unique int id: @diagnostic,
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*- Diagnostic messages: severity -*/
+
+case @diagnostic.severity of
+ 10 = @diagnostic_debug
+| 20 = @diagnostic_info
+| 30 = @diagnostic_warning
+| 40 = @diagnostic_error
+;
+
+/*- YAML -*/
+
+#keyset[parent, idx]
+yaml (unique int id: @yaml_node,
+ int kind: int ref,
+ int parent: @yaml_node_parent ref,
+ int idx: int ref,
+ string tag: string ref,
+ string tostring: string ref);
+
+case @yaml_node.kind of
+ 0 = @yaml_scalar_node
+| 1 = @yaml_mapping_node
+| 2 = @yaml_sequence_node
+| 3 = @yaml_alias_node
+;
+
+@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node;
+
+@yaml_node_parent = @yaml_collection_node | @file;
+
+yaml_anchors (unique int node: @yaml_node ref,
+ string anchor: string ref);
+
+yaml_aliases (unique int alias: @yaml_alias_node ref,
+ string target: string ref);
+
+yaml_scalars (unique int scalar: @yaml_scalar_node ref,
+ int style: int ref,
+ string value: string ref);
+
+yaml_errors (unique int id: @yaml_error,
+ string message: string ref);
+
+yaml_comments (unique int id: @yaml_comment,
+ string text: string ref,
+ string tostring: string ref);
+
+yaml_locations(unique int locatable: @yaml_locatable ref,
+ int location: @location_default ref);
+
+@yaml_locatable = @yaml_node | @yaml_error | @yaml_comment;
+
+/*- HCL dbscheme -*/
+@hcl_underscore_collection_value = @hcl_object | @hcl_tuple
+
+@hcl_underscore_expr_term = @hcl_for_expr | @hcl_function_call | @hcl_get_attr_expr | @hcl_index_expr | @hcl_parenthesized_expr | @hcl_splat_expr | @hcl_underscore_collection_value | @hcl_underscore_literal_value | @hcl_underscore_operation | @hcl_underscore_template_expr | @hcl_variable_expr
+
+@hcl_underscore_expression = @hcl_conditional | @hcl_underscore_expr_term
+
+@hcl_underscore_literal_value = @hcl_string_lit | @hcl_token_bool_lit | @hcl_token_null_lit | @hcl_token_numeric_lit
+
+@hcl_underscore_operation = @hcl_binary_operation | @hcl_unary_operation
+
+@hcl_underscore_splat = @hcl_attr_splat | @hcl_full_splat
+
+@hcl_underscore_template_directive = @hcl_template_for | @hcl_template_if
+
+@hcl_underscore_template_expr = @hcl_heredoc_template | @hcl_quoted_template
+
+#keyset[hcl_attr_splat, index]
+hcl_attr_splat_element(
+ int hcl_attr_splat: @hcl_attr_splat ref,
+ int index: int ref,
+ unique int element: @hcl_get_attr ref
+);
+
+hcl_attr_splat_def(
+ unique int id: @hcl_attr_splat
+);
+
+hcl_attribute_def(
+ unique int id: @hcl_attribute,
+ int key__: @hcl_token_identifier ref,
+ int val: @hcl_underscore_expression ref
+);
+
+case @hcl_binary_operation.operator of
+ 0 = @hcl_binary_operation_bangequal
+| 1 = @hcl_binary_operation_percent
+| 2 = @hcl_binary_operation_ampersandampersand
+| 3 = @hcl_binary_operation_star
+| 4 = @hcl_binary_operation_plus
+| 5 = @hcl_binary_operation_minus
+| 6 = @hcl_binary_operation_slash
+| 7 = @hcl_binary_operation_langle
+| 8 = @hcl_binary_operation_langleequal
+| 9 = @hcl_binary_operation_equalequal
+| 10 = @hcl_binary_operation_rangle
+| 11 = @hcl_binary_operation_rangleequal
+| 12 = @hcl_binary_operation_pipepipe
+;
+
+
+hcl_binary_operation_def(
+ unique int id: @hcl_binary_operation,
+ int left: @hcl_underscore_expr_term ref,
+ int operator: int ref,
+ int right: @hcl_underscore_expr_term ref
+);
+
+hcl_block_body(
+ unique int hcl_block: @hcl_block ref,
+ unique int body: @hcl_body ref
+);
+
+@hcl_block_label_type = @hcl_string_lit | @hcl_token_identifier
+
+#keyset[hcl_block, index]
+hcl_block_label(
+ int hcl_block: @hcl_block ref,
+ int index: int ref,
+ unique int label: @hcl_block_label_type ref
+);
+
+hcl_block_def(
+ unique int id: @hcl_block,
+ int type__: @hcl_token_identifier ref
+);
+
+@hcl_body_child_type = @hcl_attribute | @hcl_block
+
+#keyset[hcl_body, index]
+hcl_body_child(
+ int hcl_body: @hcl_body ref,
+ int index: int ref,
+ unique int child: @hcl_body_child_type ref
+);
+
+hcl_body_def(
+ unique int id: @hcl_body
+);
+
+hcl_conditional_def(
+ unique int id: @hcl_conditional,
+ int alternative: @hcl_underscore_expression ref,
+ int body: @hcl_underscore_expression ref,
+ int condition: @hcl_underscore_expression ref
+);
+
+@hcl_config_file_child_type = @hcl_body | @hcl_object
+
+hcl_config_file_child(
+ unique int hcl_config_file: @hcl_config_file ref,
+ unique int child: @hcl_config_file_child_type ref
+);
+
+hcl_config_file_def(
+ unique int id: @hcl_config_file
+);
+
+@hcl_for_expr_child_type = @hcl_for_object_expr | @hcl_for_tuple_expr
+
+hcl_for_expr_def(
+ unique int id: @hcl_for_expr,
+ int child: @hcl_for_expr_child_type ref
+);
+
+hcl_for_object_expr_condition(
+ unique int hcl_for_object_expr: @hcl_for_object_expr ref,
+ unique int condition: @hcl_underscore_expression ref
+);
+
+#keyset[hcl_for_object_expr, index]
+hcl_for_object_expr_target(
+ int hcl_for_object_expr: @hcl_for_object_expr ref,
+ int index: int ref,
+ unique int target: @hcl_token_identifier ref
+);
+
+hcl_for_object_expr_child(
+ unique int hcl_for_object_expr: @hcl_for_object_expr ref,
+ unique int child: @hcl_token_ellipsis ref
+);
+
+hcl_for_object_expr_def(
+ unique int id: @hcl_for_object_expr,
+ int iter: @hcl_underscore_expression ref,
+ int key__: @hcl_underscore_expression ref,
+ int val: @hcl_underscore_expression ref
+);
+
+hcl_for_tuple_expr_condition(
+ unique int hcl_for_tuple_expr: @hcl_for_tuple_expr ref,
+ unique int condition: @hcl_underscore_expression ref
+);
+
+#keyset[hcl_for_tuple_expr, index]
+hcl_for_tuple_expr_target(
+ int hcl_for_tuple_expr: @hcl_for_tuple_expr ref,
+ int index: int ref,
+ unique int target: @hcl_token_identifier ref
+);
+
+hcl_for_tuple_expr_def(
+ unique int id: @hcl_for_tuple_expr,
+ int expr: @hcl_underscore_expression ref,
+ int iter: @hcl_underscore_expression ref
+);
+
+@hcl_full_splat_element_type = @hcl_get_attr | @hcl_index
+
+#keyset[hcl_full_splat, index]
+hcl_full_splat_element(
+ int hcl_full_splat: @hcl_full_splat ref,
+ int index: int ref,
+ unique int element: @hcl_full_splat_element_type ref
+);
+
+hcl_full_splat_def(
+ unique int id: @hcl_full_splat
+);
+
+#keyset[hcl_function_call, index]
+hcl_function_call_argument(
+ int hcl_function_call: @hcl_function_call ref,
+ int index: int ref,
+ unique int argument: @hcl_underscore_expression ref
+);
+
+hcl_function_call_child(
+ unique int hcl_function_call: @hcl_function_call ref,
+ unique int child: @hcl_token_ellipsis ref
+);
+
+hcl_function_call_def(
+ unique int id: @hcl_function_call,
+ int function: @hcl_token_identifier ref
+);
+
+hcl_get_attr_def(
+ unique int id: @hcl_get_attr,
+ int key__: @hcl_token_identifier ref
+);
+
+hcl_get_attr_expr_def(
+ unique int id: @hcl_get_attr_expr,
+ int expr: @hcl_underscore_expr_term ref,
+ int key__: @hcl_token_identifier ref
+);
+
+case @hcl_heredoc_template.start of
+ 0 = @hcl_heredoc_template_langlelangle
+| 1 = @hcl_heredoc_template_langlelangleminus
+;
+
+
+@hcl_heredoc_template_child_type = @hcl_template_interpolation | @hcl_token_template_literal | @hcl_underscore_template_directive
+
+#keyset[hcl_heredoc_template, index]
+hcl_heredoc_template_child(
+ int hcl_heredoc_template: @hcl_heredoc_template ref,
+ int index: int ref,
+ unique int child: @hcl_heredoc_template_child_type ref
+);
+
+hcl_heredoc_template_def(
+ unique int id: @hcl_heredoc_template,
+ int start: int ref
+);
+
+@hcl_index_index_type = @hcl_token_numeric_lit | @hcl_underscore_expression
+
+hcl_index_def(
+ unique int id: @hcl_index,
+ int index: @hcl_index_index_type ref
+);
+
+@hcl_index_expr_index_type = @hcl_token_numeric_lit | @hcl_underscore_expression
+
+hcl_index_expr_def(
+ unique int id: @hcl_index_expr,
+ int expr: @hcl_underscore_expr_term ref,
+ int index: @hcl_index_expr_index_type ref
+);
+
+#keyset[hcl_object, index]
+hcl_object_element(
+ int hcl_object: @hcl_object ref,
+ int index: int ref,
+ unique int element: @hcl_object_elem ref
+);
+
+hcl_object_def(
+ unique int id: @hcl_object
+);
+
+hcl_object_elem_def(
+ unique int id: @hcl_object_elem,
+ int key__: @hcl_underscore_expression ref,
+ int val: @hcl_underscore_expression ref
+);
+
+hcl_parenthesized_expr_def(
+ unique int id: @hcl_parenthesized_expr,
+ int child: @hcl_underscore_expression ref
+);
+
+@hcl_quoted_template_child_type = @hcl_template_interpolation | @hcl_token_template_literal | @hcl_underscore_template_directive
+
+#keyset[hcl_quoted_template, index]
+hcl_quoted_template_child(
+ int hcl_quoted_template: @hcl_quoted_template ref,
+ int index: int ref,
+ unique int child: @hcl_quoted_template_child_type ref
+);
+
+hcl_quoted_template_def(
+ unique int id: @hcl_quoted_template
+);
+
+hcl_splat_expr_def(
+ unique int id: @hcl_splat_expr,
+ int expr: @hcl_underscore_expr_term ref,
+ int splat: @hcl_underscore_splat ref
+);
+
+hcl_string_lit_child(
+ unique int hcl_string_lit: @hcl_string_lit ref,
+ unique int child: @hcl_token_template_literal ref
+);
+
+hcl_string_lit_def(
+ unique int id: @hcl_string_lit
+);
+
+@hcl_template_for_body_type = @hcl_template_interpolation | @hcl_token_template_literal | @hcl_underscore_template_directive
+
+#keyset[hcl_template_for, index]
+hcl_template_for_body(
+ int hcl_template_for: @hcl_template_for ref,
+ int index: int ref,
+ unique int body: @hcl_template_for_body_type ref
+);
+
+#keyset[hcl_template_for, index]
+hcl_template_for_target(
+ int hcl_template_for: @hcl_template_for ref,
+ int index: int ref,
+ unique int target: @hcl_token_identifier ref
+);
+
+hcl_template_for_def(
+ unique int id: @hcl_template_for,
+ int iter: @hcl_underscore_expression ref
+);
+
+@hcl_template_if_alternative_type = @hcl_template_interpolation | @hcl_token_template_literal | @hcl_underscore_template_directive
+
+#keyset[hcl_template_if, index]
+hcl_template_if_alternative(
+ int hcl_template_if: @hcl_template_if ref,
+ int index: int ref,
+ unique int alternative: @hcl_template_if_alternative_type ref
+);
+
+@hcl_template_if_body_type = @hcl_template_interpolation | @hcl_token_template_literal | @hcl_underscore_template_directive
+
+#keyset[hcl_template_if, index]
+hcl_template_if_body(
+ int hcl_template_if: @hcl_template_if ref,
+ int index: int ref,
+ unique int body: @hcl_template_if_body_type ref
+);
+
+hcl_template_if_def(
+ unique int id: @hcl_template_if,
+ int condition: @hcl_underscore_expression ref
+);
+
+hcl_template_interpolation_expr(
+ unique int hcl_template_interpolation: @hcl_template_interpolation ref,
+ unique int expr: @hcl_underscore_expression ref
+);
+
+hcl_template_interpolation_def(
+ unique int id: @hcl_template_interpolation
+);
+
+#keyset[hcl_tuple, index]
+hcl_tuple_element(
+ int hcl_tuple: @hcl_tuple ref,
+ int index: int ref,
+ unique int element: @hcl_underscore_expression ref
+);
+
+hcl_tuple_def(
+ unique int id: @hcl_tuple
+);
+
+case @hcl_unary_operation.operator of
+ 0 = @hcl_unary_operation_bang
+| 1 = @hcl_unary_operation_minus
+;
+
+
+hcl_unary_operation_def(
+ unique int id: @hcl_unary_operation,
+ int operand: @hcl_underscore_expr_term ref,
+ int operator: int ref
+);
+
+hcl_variable_expr_def(
+ unique int id: @hcl_variable_expr,
+ int name: @hcl_token_identifier ref
+);
+
+hcl_tokeninfo(
+ unique int id: @hcl_token,
+ int kind: int ref,
+ string value: string ref
+);
+
+case @hcl_token.kind of
+ 0 = @hcl_reserved_word
+| 1 = @hcl_token_bool_lit
+| 2 = @hcl_token_comment
+| 3 = @hcl_token_ellipsis
+| 4 = @hcl_token_identifier
+| 5 = @hcl_token_null_lit
+| 6 = @hcl_token_numeric_lit
+| 7 = @hcl_token_template_literal
+;
+
+
+@hcl_ast_node = @hcl_attr_splat | @hcl_attribute | @hcl_binary_operation | @hcl_block | @hcl_body | @hcl_conditional | @hcl_config_file | @hcl_for_expr | @hcl_for_object_expr | @hcl_for_tuple_expr | @hcl_full_splat | @hcl_function_call | @hcl_get_attr | @hcl_get_attr_expr | @hcl_heredoc_template | @hcl_index | @hcl_index_expr | @hcl_object | @hcl_object_elem | @hcl_parenthesized_expr | @hcl_quoted_template | @hcl_splat_expr | @hcl_string_lit | @hcl_template_for | @hcl_template_if | @hcl_template_interpolation | @hcl_token | @hcl_tuple | @hcl_unary_operation | @hcl_variable_expr
+
+hcl_ast_node_location(
+ unique int node: @hcl_ast_node ref,
+ int loc: @location_default ref
+);
+
+#keyset[parent, parent_index]
+hcl_ast_node_parent(
+ unique int node: @hcl_ast_node ref,
+ int parent: @hcl_ast_node ref,
+ int parent_index: int ref
+);
+
+/*- DOCKERFILE dbscheme -*/
+@dockerfile_add_instruction_child_type = @dockerfile_heredoc_block | @dockerfile_path | @dockerfile_token_param
+
+#keyset[dockerfile_add_instruction, index]
+dockerfile_add_instruction_child(
+ int dockerfile_add_instruction: @dockerfile_add_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_add_instruction_child_type ref
+);
+
+dockerfile_add_instruction_def(
+ unique int id: @dockerfile_add_instruction
+);
+
+@dockerfile_arg_instruction_default_type = @dockerfile_double_quoted_string | @dockerfile_single_quoted_string | @dockerfile_unquoted_string
+
+dockerfile_arg_instruction_default(
+ unique int dockerfile_arg_instruction: @dockerfile_arg_instruction ref,
+ unique int default: @dockerfile_arg_instruction_default_type ref
+);
+
+dockerfile_arg_instruction_def(
+ unique int id: @dockerfile_arg_instruction,
+ int name: @dockerfile_unquoted_string ref
+);
+
+@dockerfile_cmd_instruction_child_type = @dockerfile_json_string_array | @dockerfile_shell_command
+
+dockerfile_cmd_instruction_def(
+ unique int id: @dockerfile_cmd_instruction,
+ int child: @dockerfile_cmd_instruction_child_type ref
+);
+
+@dockerfile_copy_instruction_child_type = @dockerfile_heredoc_block | @dockerfile_path | @dockerfile_token_param
+
+#keyset[dockerfile_copy_instruction, index]
+dockerfile_copy_instruction_child(
+ int dockerfile_copy_instruction: @dockerfile_copy_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_copy_instruction_child_type ref
+);
+
+dockerfile_copy_instruction_def(
+ unique int id: @dockerfile_copy_instruction
+);
+
+@dockerfile_double_quoted_string_child_type = @dockerfile_expansion | @dockerfile_token_escape_sequence
+
+#keyset[dockerfile_double_quoted_string, index]
+dockerfile_double_quoted_string_child(
+ int dockerfile_double_quoted_string: @dockerfile_double_quoted_string ref,
+ int index: int ref,
+ unique int child: @dockerfile_double_quoted_string_child_type ref
+);
+
+dockerfile_double_quoted_string_def(
+ unique int id: @dockerfile_double_quoted_string
+);
+
+@dockerfile_entrypoint_instruction_child_type = @dockerfile_json_string_array | @dockerfile_shell_command
+
+dockerfile_entrypoint_instruction_def(
+ unique int id: @dockerfile_entrypoint_instruction,
+ int child: @dockerfile_entrypoint_instruction_child_type ref
+);
+
+#keyset[dockerfile_env_instruction, index]
+dockerfile_env_instruction_child(
+ int dockerfile_env_instruction: @dockerfile_env_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_env_pair ref
+);
+
+dockerfile_env_instruction_def(
+ unique int id: @dockerfile_env_instruction
+);
+
+@dockerfile_env_pair_value_type = @dockerfile_double_quoted_string | @dockerfile_single_quoted_string | @dockerfile_unquoted_string
+
+dockerfile_env_pair_value(
+ unique int dockerfile_env_pair: @dockerfile_env_pair ref,
+ unique int value: @dockerfile_env_pair_value_type ref
+);
+
+dockerfile_env_pair_def(
+ unique int id: @dockerfile_env_pair,
+ int name: @dockerfile_unquoted_string ref
+);
+
+dockerfile_expansion_def(
+ unique int id: @dockerfile_expansion,
+ int child: @dockerfile_token_variable ref
+);
+
+@dockerfile_expose_instruction_child_type = @dockerfile_expansion | @dockerfile_token_expose_port
+
+#keyset[dockerfile_expose_instruction, index]
+dockerfile_expose_instruction_child(
+ int dockerfile_expose_instruction: @dockerfile_expose_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_expose_instruction_child_type ref
+);
+
+dockerfile_expose_instruction_def(
+ unique int id: @dockerfile_expose_instruction
+);
+
+dockerfile_from_instruction_as(
+ unique int dockerfile_from_instruction: @dockerfile_from_instruction ref,
+ unique int as: @dockerfile_image_alias ref
+);
+
+@dockerfile_from_instruction_child_type = @dockerfile_image_spec | @dockerfile_token_param
+
+#keyset[dockerfile_from_instruction, index]
+dockerfile_from_instruction_child(
+ int dockerfile_from_instruction: @dockerfile_from_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_from_instruction_child_type ref
+);
+
+dockerfile_from_instruction_def(
+ unique int id: @dockerfile_from_instruction
+);
+
+@dockerfile_healthcheck_instruction_child_type = @dockerfile_cmd_instruction | @dockerfile_token_param
+
+#keyset[dockerfile_healthcheck_instruction, index]
+dockerfile_healthcheck_instruction_child(
+ int dockerfile_healthcheck_instruction: @dockerfile_healthcheck_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_healthcheck_instruction_child_type ref
+);
+
+dockerfile_healthcheck_instruction_def(
+ unique int id: @dockerfile_healthcheck_instruction
+);
+
+@dockerfile_heredoc_block_child_type = @dockerfile_token_heredoc_end | @dockerfile_token_heredoc_line
+
+#keyset[dockerfile_heredoc_block, index]
+dockerfile_heredoc_block_child(
+ int dockerfile_heredoc_block: @dockerfile_heredoc_block ref,
+ int index: int ref,
+ unique int child: @dockerfile_heredoc_block_child_type ref
+);
+
+dockerfile_heredoc_block_def(
+ unique int id: @dockerfile_heredoc_block
+);
+
+#keyset[dockerfile_image_alias, index]
+dockerfile_image_alias_child(
+ int dockerfile_image_alias: @dockerfile_image_alias ref,
+ int index: int ref,
+ unique int child: @dockerfile_expansion ref
+);
+
+dockerfile_image_alias_def(
+ unique int id: @dockerfile_image_alias
+);
+
+#keyset[dockerfile_image_digest, index]
+dockerfile_image_digest_child(
+ int dockerfile_image_digest: @dockerfile_image_digest ref,
+ int index: int ref,
+ unique int child: @dockerfile_expansion ref
+);
+
+dockerfile_image_digest_def(
+ unique int id: @dockerfile_image_digest
+);
+
+#keyset[dockerfile_image_name, index]
+dockerfile_image_name_child(
+ int dockerfile_image_name: @dockerfile_image_name ref,
+ int index: int ref,
+ unique int child: @dockerfile_expansion ref
+);
+
+dockerfile_image_name_def(
+ unique int id: @dockerfile_image_name
+);
+
+dockerfile_image_spec_digest(
+ unique int dockerfile_image_spec: @dockerfile_image_spec ref,
+ unique int digest: @dockerfile_image_digest ref
+);
+
+dockerfile_image_spec_tag(
+ unique int dockerfile_image_spec: @dockerfile_image_spec ref,
+ unique int tag: @dockerfile_image_tag ref
+);
+
+dockerfile_image_spec_def(
+ unique int id: @dockerfile_image_spec,
+ int name: @dockerfile_image_name ref
+);
+
+#keyset[dockerfile_image_tag, index]
+dockerfile_image_tag_child(
+ int dockerfile_image_tag: @dockerfile_image_tag ref,
+ int index: int ref,
+ unique int child: @dockerfile_expansion ref
+);
+
+dockerfile_image_tag_def(
+ unique int id: @dockerfile_image_tag
+);
+
+#keyset[dockerfile_json_string, index]
+dockerfile_json_string_child(
+ int dockerfile_json_string: @dockerfile_json_string ref,
+ int index: int ref,
+ unique int child: @dockerfile_token_escape_sequence ref
+);
+
+dockerfile_json_string_def(
+ unique int id: @dockerfile_json_string
+);
+
+#keyset[dockerfile_json_string_array, index]
+dockerfile_json_string_array_child(
+ int dockerfile_json_string_array: @dockerfile_json_string_array ref,
+ int index: int ref,
+ unique int child: @dockerfile_json_string ref
+);
+
+dockerfile_json_string_array_def(
+ unique int id: @dockerfile_json_string_array
+);
+
+#keyset[dockerfile_label_instruction, index]
+dockerfile_label_instruction_child(
+ int dockerfile_label_instruction: @dockerfile_label_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_label_pair ref
+);
+
+dockerfile_label_instruction_def(
+ unique int id: @dockerfile_label_instruction
+);
+
+@dockerfile_label_pair_key_type = @dockerfile_double_quoted_string | @dockerfile_single_quoted_string | @dockerfile_unquoted_string
+
+@dockerfile_label_pair_value_type = @dockerfile_double_quoted_string | @dockerfile_single_quoted_string | @dockerfile_unquoted_string
+
+dockerfile_label_pair_def(
+ unique int id: @dockerfile_label_pair,
+ int key__: @dockerfile_label_pair_key_type ref,
+ int value: @dockerfile_label_pair_value_type ref
+);
+
+case @dockerfile_mount_param.name of
+ 0 = @dockerfile_mount_param_mount
+;
+
+
+@dockerfile_mount_param_value_type = @dockerfile_reserved_word | @dockerfile_token_mount_param_param
+
+#keyset[dockerfile_mount_param, index]
+dockerfile_mount_param_value(
+ int dockerfile_mount_param: @dockerfile_mount_param ref,
+ int index: int ref,
+ unique int value: @dockerfile_mount_param_value_type ref
+);
+
+dockerfile_mount_param_def(
+ unique int id: @dockerfile_mount_param,
+ int name: int ref
+);
+
+@dockerfile_onbuild_instruction_child_type = @dockerfile_add_instruction | @dockerfile_arg_instruction | @dockerfile_cmd_instruction | @dockerfile_copy_instruction | @dockerfile_entrypoint_instruction | @dockerfile_env_instruction | @dockerfile_expose_instruction | @dockerfile_from_instruction | @dockerfile_healthcheck_instruction | @dockerfile_label_instruction | @dockerfile_onbuild_instruction | @dockerfile_run_instruction | @dockerfile_shell_instruction | @dockerfile_stopsignal_instruction | @dockerfile_token_cross_build_instruction | @dockerfile_token_maintainer_instruction | @dockerfile_user_instruction | @dockerfile_volume_instruction | @dockerfile_workdir_instruction
+
+dockerfile_onbuild_instruction_def(
+ unique int id: @dockerfile_onbuild_instruction,
+ int child: @dockerfile_onbuild_instruction_child_type ref
+);
+
+@dockerfile_path_child_type = @dockerfile_expansion | @dockerfile_token_heredoc_marker
+
+#keyset[dockerfile_path, index]
+dockerfile_path_child(
+ int dockerfile_path: @dockerfile_path ref,
+ int index: int ref,
+ unique int child: @dockerfile_path_child_type ref
+);
+
+dockerfile_path_def(
+ unique int id: @dockerfile_path
+);
+
+@dockerfile_run_instruction_child_type = @dockerfile_heredoc_block | @dockerfile_json_string_array | @dockerfile_mount_param | @dockerfile_shell_command | @dockerfile_token_param
+
+#keyset[dockerfile_run_instruction, index]
+dockerfile_run_instruction_child(
+ int dockerfile_run_instruction: @dockerfile_run_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_run_instruction_child_type ref
+);
+
+dockerfile_run_instruction_def(
+ unique int id: @dockerfile_run_instruction
+);
+
+@dockerfile_shell_command_child_type = @dockerfile_shell_fragment | @dockerfile_token_line_continuation
+
+#keyset[dockerfile_shell_command, index]
+dockerfile_shell_command_child(
+ int dockerfile_shell_command: @dockerfile_shell_command ref,
+ int index: int ref,
+ unique int child: @dockerfile_shell_command_child_type ref
+);
+
+dockerfile_shell_command_def(
+ unique int id: @dockerfile_shell_command
+);
+
+#keyset[dockerfile_shell_fragment, index]
+dockerfile_shell_fragment_child(
+ int dockerfile_shell_fragment: @dockerfile_shell_fragment ref,
+ int index: int ref,
+ unique int child: @dockerfile_token_heredoc_marker ref
+);
+
+dockerfile_shell_fragment_def(
+ unique int id: @dockerfile_shell_fragment
+);
+
+dockerfile_shell_instruction_def(
+ unique int id: @dockerfile_shell_instruction,
+ int child: @dockerfile_json_string_array ref
+);
+
+#keyset[dockerfile_single_quoted_string, index]
+dockerfile_single_quoted_string_child(
+ int dockerfile_single_quoted_string: @dockerfile_single_quoted_string ref,
+ int index: int ref,
+ unique int child: @dockerfile_token_escape_sequence ref
+);
+
+dockerfile_single_quoted_string_def(
+ unique int id: @dockerfile_single_quoted_string
+);
+
+@dockerfile_source_file_child_type = @dockerfile_add_instruction | @dockerfile_arg_instruction | @dockerfile_cmd_instruction | @dockerfile_copy_instruction | @dockerfile_entrypoint_instruction | @dockerfile_env_instruction | @dockerfile_expose_instruction | @dockerfile_from_instruction | @dockerfile_healthcheck_instruction | @dockerfile_label_instruction | @dockerfile_onbuild_instruction | @dockerfile_run_instruction | @dockerfile_shell_instruction | @dockerfile_stopsignal_instruction | @dockerfile_token_cross_build_instruction | @dockerfile_token_maintainer_instruction | @dockerfile_user_instruction | @dockerfile_volume_instruction | @dockerfile_workdir_instruction
+
+#keyset[dockerfile_source_file, index]
+dockerfile_source_file_child(
+ int dockerfile_source_file: @dockerfile_source_file ref,
+ int index: int ref,
+ unique int child: @dockerfile_source_file_child_type ref
+);
+
+dockerfile_source_file_def(
+ unique int id: @dockerfile_source_file
+);
+
+#keyset[dockerfile_stopsignal_instruction, index]
+dockerfile_stopsignal_instruction_child(
+ int dockerfile_stopsignal_instruction: @dockerfile_stopsignal_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_expansion ref
+);
+
+dockerfile_stopsignal_instruction_def(
+ unique int id: @dockerfile_stopsignal_instruction
+);
+
+#keyset[dockerfile_unquoted_string, index]
+dockerfile_unquoted_string_child(
+ int dockerfile_unquoted_string: @dockerfile_unquoted_string ref,
+ int index: int ref,
+ unique int child: @dockerfile_expansion ref
+);
+
+dockerfile_unquoted_string_def(
+ unique int id: @dockerfile_unquoted_string
+);
+
+dockerfile_user_instruction_group(
+ unique int dockerfile_user_instruction: @dockerfile_user_instruction ref,
+ unique int group: @dockerfile_unquoted_string ref
+);
+
+dockerfile_user_instruction_def(
+ unique int id: @dockerfile_user_instruction,
+ int user: @dockerfile_unquoted_string ref
+);
+
+@dockerfile_volume_instruction_child_type = @dockerfile_json_string_array | @dockerfile_path
+
+#keyset[dockerfile_volume_instruction, index]
+dockerfile_volume_instruction_child(
+ int dockerfile_volume_instruction: @dockerfile_volume_instruction ref,
+ int index: int ref,
+ unique int child: @dockerfile_volume_instruction_child_type ref
+);
+
+dockerfile_volume_instruction_def(
+ unique int id: @dockerfile_volume_instruction
+);
+
+dockerfile_workdir_instruction_def(
+ unique int id: @dockerfile_workdir_instruction,
+ int child: @dockerfile_path ref
+);
+
+dockerfile_tokeninfo(
+ unique int id: @dockerfile_token,
+ int kind: int ref,
+ string value: string ref
+);
+
+case @dockerfile_token.kind of
+ 0 = @dockerfile_reserved_word
+| 1 = @dockerfile_token_comment
+| 2 = @dockerfile_token_cross_build_instruction
+| 3 = @dockerfile_token_escape_sequence
+| 4 = @dockerfile_token_expose_port
+| 5 = @dockerfile_token_heredoc_end
+| 6 = @dockerfile_token_heredoc_line
+| 7 = @dockerfile_token_heredoc_marker
+| 8 = @dockerfile_token_line_continuation
+| 9 = @dockerfile_token_maintainer_instruction
+| 10 = @dockerfile_token_mount_param_param
+| 11 = @dockerfile_token_param
+| 12 = @dockerfile_token_variable
+;
+
+
+@dockerfile_ast_node = @dockerfile_add_instruction | @dockerfile_arg_instruction | @dockerfile_cmd_instruction | @dockerfile_copy_instruction | @dockerfile_double_quoted_string | @dockerfile_entrypoint_instruction | @dockerfile_env_instruction | @dockerfile_env_pair | @dockerfile_expansion | @dockerfile_expose_instruction | @dockerfile_from_instruction | @dockerfile_healthcheck_instruction | @dockerfile_heredoc_block | @dockerfile_image_alias | @dockerfile_image_digest | @dockerfile_image_name | @dockerfile_image_spec | @dockerfile_image_tag | @dockerfile_json_string | @dockerfile_json_string_array | @dockerfile_label_instruction | @dockerfile_label_pair | @dockerfile_mount_param | @dockerfile_onbuild_instruction | @dockerfile_path | @dockerfile_run_instruction | @dockerfile_shell_command | @dockerfile_shell_fragment | @dockerfile_shell_instruction | @dockerfile_single_quoted_string | @dockerfile_source_file | @dockerfile_stopsignal_instruction | @dockerfile_token | @dockerfile_unquoted_string | @dockerfile_user_instruction | @dockerfile_volume_instruction | @dockerfile_workdir_instruction
+
+dockerfile_ast_node_location(
+ unique int node: @dockerfile_ast_node ref,
+ int loc: @location_default ref
+);
+
+#keyset[parent, parent_index]
+dockerfile_ast_node_parent(
+ unique int node: @dockerfile_ast_node ref,
+ int parent: @dockerfile_ast_node ref,
+ int parent_index: int ref
+);
+
diff --git a/iac/ql/lib/iac.dbscheme.stats b/iac/ql/lib/iac.dbscheme.stats
new file mode 100644
index 000000000000..a9aacbc3eb85
--- /dev/null
+++ b/iac/ql/lib/iac.dbscheme.stats
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/iac/ql/lib/iac.qll b/iac/ql/lib/iac.qll
new file mode 100644
index 000000000000..d29338c0eb68
--- /dev/null
+++ b/iac/ql/lib/iac.qll
@@ -0,0 +1,21 @@
+import codeql.Locations
+import codeql.files.FileSystem
+import codeql.iac.Dependencies
+// Azure
+import codeql.iac.azure.ARM
+import codeql.iac.azure.Pipelines
+// AWS
+import codeql.iac.aws.CloudFormation
+// Containers / Docker
+import codeql.iac.containers.Containers
+import codeql.iac.containers.Images
+// Compose
+import codeql.iac.compose.Compose
+// HelmCharts
+import codeql.iac.helmcharts.HelmChart
+// Terraform / HCL
+import hcl
+// OpenAPI / Swagger
+import codeql.iac.openapi.OpenApi
+// YAML
+import codeql.iac.YAML
diff --git a/iac/ql/lib/ide-contextual-queries/printAst.ql b/iac/ql/lib/ide-contextual-queries/printAst.ql
new file mode 100644
index 000000000000..1db55cb007ed
--- /dev/null
+++ b/iac/ql/lib/ide-contextual-queries/printAst.ql
@@ -0,0 +1,26 @@
+/**
+ * @name Print AST
+ * @description Produces a representation of a file's Abstract Syntax Tree.
+ * This query is used by the VS Code extension.
+ * @id iac/print-ast
+ * @kind graph
+ * @tags ide-contextual-queries/print-ast
+ */
+
+// Switch between these imports to switch between viewing the internal AST or the user-facing AST layer
+import codeql.iac.ideContextual.printAstGenerated
+//import codeql.iac.ideContextual.printAstAst
+import codeql.iac.ideContextual.IDEContextual
+
+/**
+ * Gets the source file to generate an AST from.
+ */
+external string selectedSourceFile();
+
+// Overrides the configuration to print only nodes in the selected source file.
+class Cfg extends PrintAstConfiguration {
+ override predicate shouldPrintNode(AstNode n) {
+ super.shouldPrintNode(n) and
+ n.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
+ }
+}
diff --git a/iac/ql/lib/qlpack.yml b/iac/ql/lib/qlpack.yml
new file mode 100644
index 000000000000..7ccbd8beedb7
--- /dev/null
+++ b/iac/ql/lib/qlpack.yml
@@ -0,0 +1,13 @@
+---
+library: true
+warnOnImplicitThis: true
+name: advanced-security/iac-all
+version: 0.5.1
+dependencies:
+ codeql/util: ^1.0.12
+ codeql/yaml: ^1.0.25
+dbscheme: iac.dbscheme
+extractor: iac
+upgrades: upgrades
+groups:
+ - iac
diff --git a/iac/ql/lib/test/TestUtilities/InlineExpectationsTest.qll b/iac/ql/lib/test/TestUtilities/InlineExpectationsTest.qll
new file mode 100644
index 000000000000..f021148d1f56
--- /dev/null
+++ b/iac/ql/lib/test/TestUtilities/InlineExpectationsTest.qll
@@ -0,0 +1,40 @@
+/**
+ * Inline expectation tests for HCL.
+ * See `shared/util/codeql/util/test/InlineExpectationsTest.qll`
+ */
+
+private import hcl as HCL
+private import codeql.util.test.InlineExpectationsTest
+
+private module Impl implements InlineExpectationsTestSig {
+ private import codeql.hcl.ast.internal.TreeSitter as TS
+
+ private newtype TExpectationComment = MkExpectationComment(TS::HCL::Comment comment)
+
+ /**
+ * Represents a line comment.
+ */
+ class ExpectationComment extends TExpectationComment {
+ TS::HCL::Comment comment;
+
+ ExpectationComment() {
+ this = MkExpectationComment(comment) and comment.getValue().matches("#%")
+ }
+
+ /** Returns the contents of the given comment, _without_ the preceding comment marker (`#`). */
+ string getContents() {
+ /* TODO: Fix working with `//` comments as well */
+ result = comment.getValue().suffix(1)
+ }
+
+ /** Gets a textual representation of this element. */
+ string toString() { result = comment.toString() }
+
+ /** Gets the location of this comment. */
+ Location getLocation() { result = comment.getLocation() }
+ }
+
+ class Location = HCL::Location;
+}
+
+import Make
diff --git a/iac/ql/src/codeql-pack.lock.yml b/iac/ql/src/codeql-pack.lock.yml
new file mode 100644
index 000000000000..69ce09bcb67b
--- /dev/null
+++ b/iac/ql/src/codeql-pack.lock.yml
@@ -0,0 +1,8 @@
+---
+lockVersion: 1.0.0
+dependencies:
+ codeql/util:
+ version: 1.0.12
+ codeql/yaml:
+ version: 1.0.25
+compiled: false
diff --git a/iac/ql/src/codeql-suites/iac-code-scanning.qls b/iac/ql/src/codeql-suites/iac-code-scanning.qls
new file mode 100644
index 000000000000..5d6011d5e6b2
--- /dev/null
+++ b/iac/ql/src/codeql-suites/iac-code-scanning.qls
@@ -0,0 +1,19 @@
+- description: Standard Code Scanning queries for IaC
+- queries: .
+
+- include:
+ kind:
+ - problem
+ - path-problem
+ tags contain:
+ - security
+ - maintainability
+
+- include:
+ kind:
+ - diagnostic
+
+- exclude:
+ tags contain:
+ - experimental
+ - testing
diff --git a/iac/ql/src/diagnostics/Dependencies.ql b/iac/ql/src/diagnostics/Dependencies.ql
new file mode 100644
index 000000000000..e7b07a623bb5
--- /dev/null
+++ b/iac/ql/src/diagnostics/Dependencies.ql
@@ -0,0 +1,11 @@
+/**
+ * @name Dependencies
+ * @description List all dependencies.
+ * @kind diagnostic
+ * @id iac/diagnostics/dependencies
+ */
+
+import iac
+
+from Dependency dependency
+select dependency, "Dependency used " + dependency.getName()
diff --git a/iac/ql/src/diagnostics/ExtractionErrors.ql b/iac/ql/src/diagnostics/ExtractionErrors.ql
new file mode 100644
index 000000000000..15da749be6db
--- /dev/null
+++ b/iac/ql/src/diagnostics/ExtractionErrors.ql
@@ -0,0 +1,18 @@
+/**
+ * @name Extraction errors
+ * @description List all extraction errors for files in the source code directory.
+ * @kind diagnostic
+ * @id iac/diagnostics/extraction-errors
+ */
+
+import iac
+import codeql.iac.Diagnostics
+
+/** Gets the SARIF severity to associate an error. */
+int getSeverity() { result = 2 }
+
+from ExtractionError error, File f
+where
+ f = error.getLocation().getFile() and
+ exists(f.getRelativePath())
+select error, "Extraction failed in " + f + " with error " + error.getMessage(), getSeverity()
diff --git a/iac/ql/src/diagnostics/SuccessfullyExtractedFiles.ql b/iac/ql/src/diagnostics/SuccessfullyExtractedFiles.ql
new file mode 100644
index 000000000000..f75a87074c3f
--- /dev/null
+++ b/iac/ql/src/diagnostics/SuccessfullyExtractedFiles.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Successfully extracted files
+ * @description Lists all files in the source code directory that were extracted
+ * without encountering an error in the file.
+ * @kind diagnostic
+ * @id iac/diagnostics/successfully-extracted-files
+ * @tags successfully-extracted-files
+ */
+
+import iac
+import codeql.iac.Diagnostics
+
+from File f
+where
+ not exists(ExtractionError e | e.getLocation().getFile() = f) and
+ exists(f.getRelativePath())
+select f, ""
diff --git a/iac/ql/src/qlpack.yml b/iac/ql/src/qlpack.yml
new file mode 100644
index 000000000000..461f8ef35b4f
--- /dev/null
+++ b/iac/ql/src/qlpack.yml
@@ -0,0 +1,10 @@
+name: advanced-security/iac-queries
+version: 0.5.1
+groups:
+ - iac
+ - queries
+dependencies:
+ advanced-security/iac-all: ${workspace}
+extractor: iac
+warnOnImplicitThis: true
+defaultSuiteFile: codeql-suites/iac-code-scanning.qls
diff --git a/iac/ql/src/queries.xml b/iac/ql/src/queries.xml
new file mode 100644
index 000000000000..d10416ac96db
--- /dev/null
+++ b/iac/ql/src/queries.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/iac/ql/src/security/ARM/AllowSSH.ql b/iac/ql/src/security/ARM/AllowSSH.ql
new file mode 100644
index 000000000000..bc54a0ef35d4
--- /dev/null
+++ b/iac/ql/src/security/ARM/AllowSSH.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Allow SSH access from the Internet
+ * @description Allow SSH access from the Internet
+ * @kind problem
+ * @severity warning
+ * @security-severity 4.0
+ * @precision very-high
+ * @id iac/arm/allow-ssh-from-internet
+ * @tags security
+ * experimental
+ */
+
+import iac
+
+from ARM::Resource resource, ARM::SecurityRule security
+where
+ security = resource.getProperties().getSecurityRules() and
+ security.getProperty("destinationPortRange").toString() = "\"22\""
+select security, "test"
diff --git a/iac/ql/src/security/CloudFormation/ECS/ContainerInsights.ql b/iac/ql/src/security/CloudFormation/ECS/ContainerInsights.ql
new file mode 100644
index 000000000000..04eb16295779
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/ContainerInsights.ql
@@ -0,0 +1,18 @@
+/**
+ * @name ECS clusters should use Container Insights
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/container-insights
+ * @tags security
+ * aws/ecs/12
+ * NIST/800-53/AU-6(3)
+ * NIST/800-53/AU-6(4)
+ * NIST/800-53/CA-7
+ * NIST/800-53/SI-2
+ */
+
+import iac
+
+from CloudFormation::ECSCluster cluster
+where not cluster.getContainerInsights().toString() = "'enabled'"
+select cluster, "ECS Cluster should have cluster settings enabled"
\ No newline at end of file
diff --git a/iac/ql/src/security/CloudFormation/ECS/LatestVersion.ql b/iac/ql/src/security/CloudFormation/ECS/LatestVersion.ql
new file mode 100644
index 000000000000..16daf67f8e7d
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/LatestVersion.ql
@@ -0,0 +1,33 @@
+/**
+ * @name ECS Fargate services should run on the latest Fargate platform version
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/latest-version
+ * @tags security
+ * experimental
+ * aws/ecs/10
+ * NIST/800-53/SI-2
+ * NIST/800-53/SI-2(2)
+ * NIST/800-53/SI-2(4)
+ * NIST/800-53/SI-2(5)
+ * PCI-DSS/4.0.1
+ * PCI-DSS/6.3.3
+ *
+ */
+
+// need to figure out how to make !Ref to be recognized, then and this should be possible to be used properly, used as "Experimental for now
+import iac
+from CloudFormation::ECSService ecs, CloudFormation::TaskDefinition td
+//where ecs.getPlatformVersion().toString() = ["'LATEST'", "'1.4.0'"] or ecs.getPlatformVersion().toString() = ["'LATEST'", "'1.0.0'"]
+//where td.getRuntimePlatform().toString() = "'LINUX'" or td.getRuntimePlatform().toString() = "'WINDOWS'"
+where
+ ((ecs.getPlatformVersion().toString() = ["'LATEST'", "'1.4.0'"] or not exists(ecs.getPlatformVersion()))
+ and
+ (td.getRuntimePlatform().toString() ="'LINUX'" or not exists(td.getRuntimePlatform())) )
+ or
+ ((ecs.getPlatformVersion().toString() = ["'LATEST'", "'1.0.0'"] or not exists(ecs.getPlatformVersion()))
+ and
+ (exists(td.getRuntimePlatform()) and td.getRuntimePlatform().toString() !="'LINUX'"))
+
+select td, "ContainerDefinitions must have a log configuration"
+
diff --git a/iac/ql/src/security/CloudFormation/ECS/LogConfiguration.ql b/iac/ql/src/security/CloudFormation/ECS/LogConfiguration.ql
new file mode 100644
index 000000000000..91ce3efa1d77
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/LogConfiguration.ql
@@ -0,0 +1,25 @@
+/**
+ * @name ECS task definitions should have a logging configuration
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/log-configuration
+ * @tags security
+ * aws/ecs/9
+ * NIST/800-53/AC-4(26)
+ * NIST/800-53/AU-10
+ * NIST/800-53/AU-12
+ * NIST/800-53/AU-2
+ * NIST/800-53/AU-3
+ * NIST/800-53/AU-6(3)
+ * NIST/800-53/AU-6(4)
+ * NIST/800-53/CA-7
+ * NIST/800-53/SC-7(9)
+ * NIST/800-53/SI-7(8)
+ */
+
+import iac
+
+from CloudFormation::ContainerDefinition cd
+where not exists(cd.getLogConfiguration())
+select cd, "ContainerDefinitions must have a log configuration"
+
diff --git a/iac/ql/src/security/CloudFormation/ECS/NetworkMode.ql b/iac/ql/src/security/CloudFormation/ECS/NetworkMode.ql
new file mode 100644
index 000000000000..242a12db7642
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/NetworkMode.ql
@@ -0,0 +1,25 @@
+/**
+ * @name Amazon ECS task definitions should have secure networking modes and user definitions.
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/networkmode
+ * @tags security
+ * experimental
+ * /aws/config/ecs/1
+ * /NIST/800-53/AC-2(1)
+ * /NIST/800-53/AC-3
+ * /NIST/800-53/AC-3(15)
+ * /NIST/800-53/AC-3(7)
+ * /NIST/800-53/AC-5
+ * /NIST/800-53/AC-6
+ */
+
+import iac
+
+//Check for NetworkMode to not be host in taskdefinition, this is very much experimental -> Experimental
+from CloudFormation::ContainerDefinition cd, CloudFormation::TaskDefinition td
+where
+ (cd.getUser().toString() = "'root'" or cd.getPrivileged() = "true") and
+ td.getNetworkMode().toString() = "'host'"
+select td,
+ "ContainerDefinitions must not run as root or be privileged when networkmode Host is used"
diff --git a/iac/ql/src/security/CloudFormation/ECS/NonPriv.ql b/iac/ql/src/security/CloudFormation/ECS/NonPriv.ql
new file mode 100644
index 000000000000..b1562aa41127
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/NonPriv.ql
@@ -0,0 +1,20 @@
+/**
+ * @name ECS containers should run as non-privileged
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/non-priv
+ * @tags security
+ * aws/ecs/4
+ * NIST/800-53/AC-2(1)
+ * NIST/800-53/AC-3
+ * NIST/800-53/AC-3(15)
+ * NIST/800-53/AC-3(7)
+ * NIST/800-53/AC-5
+ * NIST/800-53/AC-6
+ */
+
+import iac
+
+from CloudFormation::ContainerDefinition cd
+where not cd.getPrivileged() = "false"
+select cd, "ContainerDefinitions must be explictly configured privileged mode to false"
diff --git a/iac/ql/src/security/CloudFormation/ECS/PidMode.ql b/iac/ql/src/security/CloudFormation/ECS/PidMode.ql
new file mode 100644
index 000000000000..7f63d055ff5e
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/PidMode.ql
@@ -0,0 +1,16 @@
+/**
+ * @name ECS task definitions should not share the host's process namespace
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/pidmode
+ * @tags security
+ * aws/ecs/3
+ * NIST/800-53/CA-9(1)
+ * NIST/800-53/CM-2
+ */
+
+import iac
+
+from CloudFormation::TaskDefinition td
+where not td.getPidMode().toString() = "task"
+select td, "PidMode should be \"task\" for ECS tasks"
diff --git a/iac/ql/src/security/CloudFormation/ECS/PublicIP.ql b/iac/ql/src/security/CloudFormation/ECS/PublicIP.ql
new file mode 100644
index 000000000000..e697e735707c
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/PublicIP.ql
@@ -0,0 +1,30 @@
+/**
+ * @name ECS task sets should not automatically assign public IP addresses
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/assignpublicip
+ * @tags security
+ * aws/ecs/2
+ * NIST/800-53/AC-21
+ * NIST/800-53/AC-3
+ * NIST/800-53/AC-3(7)
+ * NIST/800-53/AC-4
+ * NIST/800-53/AC-4(21)
+ * NIST/800-53/AC-6
+ * NIST/800-53/SC-7
+ * NIST/800-53/SC-7(11)
+ * NIST/800-53/SC-7(16)
+ * NIST/800-53/SC-7(20)
+ * NIST/800-53/SC-7(21)
+ * NIST/800-53/SC-7(3)
+ * NIST/800-53/SC-7(4)
+ * NIST/800-53/SC-7(9)
+ * PCI-DSS/4.0.1
+ * PCI-DSS/1.4.4
+ */
+
+import iac
+
+from CloudFormation::ECSNetworkConfiguration ecsnetwork
+where not ecsnetwork.getAssignPublicIp().toString() = ["'DISABLED'","DISABLED"]
+select ecsnetwork.getAssignPublicIp(), "AssignPublicIp should be \"DISABLED\" for ECS tasks"
\ No newline at end of file
diff --git a/iac/ql/src/security/CloudFormation/ECS/PublicIPTaskSet.ql b/iac/ql/src/security/CloudFormation/ECS/PublicIPTaskSet.ql
new file mode 100644
index 000000000000..a37772e0cf32
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/PublicIPTaskSet.ql
@@ -0,0 +1,17 @@
+/**
+ * @name ECS task sets should not automatically assign public IP addresses
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/assign-publicip-taskset
+ * @tags security
+ * cloudformation
+ * aws/ecs/16
+ * PCI-DSS/4.0.1
+ * PCI-DSS/1.4.4
+ */
+
+import iac
+
+from CloudFormation::ECSTaskSet ts
+//where not ts.getAssignPublicIp().toString() = ["'DISABLED'","DISABLED"]
+select ts, "AssignPublicIp must be \"DISABLED\" for ECS tasks"
diff --git a/iac/ql/src/security/CloudFormation/ECS/ReadOnlyRootFileSystem.ql b/iac/ql/src/security/CloudFormation/ECS/ReadOnlyRootFileSystem.ql
new file mode 100644
index 000000000000..df01aaf2aff3
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/ReadOnlyRootFileSystem.ql
@@ -0,0 +1,20 @@
+/**
+ * @name ECS containers should be limited to read-only access to root filesystems
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/read-only-root-filesystem
+ * @tags security
+ * aws/ecs/5
+ * NIST/800-53/AC-2(1)
+ * NIST/800-53/AC-3
+ * NIST/800-53/AC-3(15)
+ * NIST/800-53/AC-3(7)
+ * NIST/800-53/AC-5
+ * NIST/800-53/AC-6
+ */
+
+import iac
+
+from CloudFormation::ContainerDefinition cd
+where not cd.getReadOnlyRootFilesystem() = "true"
+select cd, "Containers must have explictly only read only root filesystem"
diff --git a/iac/ql/src/security/CloudFormation/ECS/Secrets.ql b/iac/ql/src/security/CloudFormation/ECS/Secrets.ql
new file mode 100644
index 000000000000..2dbbac517b5c
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/ECS/Secrets.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Secrets should not be passed as container environment variables
+ * @kind problem
+ * @problem.severity warning
+ * @id iac/ecs/secrets
+ * @tags security
+ * aws/ecs/8
+ * NIST/800-53/AC-2(1)
+ * NIST/800-53/AC-3
+ * NIST/800-53/AC-3(15)
+ * NIST/800-53/AC-3(7)
+ * NIST/800-53/AC-5
+ * NIST/800-53/AC-6
+ */
+
+import iac
+
+from CloudFormation::ContainerDefinition cd
+//where cd.getSecrets().getAChild().getAChild().toString() = ["'AWS_ACCESS_KEY_ID'", "'AWS_SECRET_ACCESS_KEY'", "'ECS_ENGINE_AUTH_DATA'"]
+select cd.getSecrets(), "Containers must not pass secret thorugh environment variables"
diff --git a/iac/ql/src/security/CloudFormation/S3/PublicBucket.ql b/iac/ql/src/security/CloudFormation/S3/PublicBucket.ql
new file mode 100644
index 000000000000..1bf27564e653
--- /dev/null
+++ b/iac/ql/src/security/CloudFormation/S3/PublicBucket.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Public S3 Bucket
+ * @description Public S3 Bucket
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 10.0
+ * @precision high
+ * @id cf/aws/storage-publicly-accessible
+ * @tags security
+ * aws
+ * cloudformation
+ * storage
+ */
+
+import iac
+
+from CloudFormation::S3Bucket bucket
+where bucket.getAccessControl() = "PublicRead"
+select bucket, "Public S3 Bucket resource"
diff --git a/iac/ql/src/security/Compose/PinnedMajorVersion.ql b/iac/ql/src/security/Compose/PinnedMajorVersion.ql
new file mode 100644
index 000000000000..33a2bc3f3978
--- /dev/null
+++ b/iac/ql/src/security/Compose/PinnedMajorVersion.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Using old version of Compose
+ * @description Compose files with pinned version
+ * @kind problem
+ * @severity warning
+ * @security-severity 1.0
+ * @precision very-high
+ * @id iac/compose/latest-images
+ * @tags maintainability
+ * experimental
+ */
+
+import iac
+
+from Compose::Document compose
+where compose.getApiVersion() = ["2", "3"]
+select compose, "pinned version of Compose spec"
diff --git a/iac/ql/src/security/Containers/LatestImages.ql b/iac/ql/src/security/Containers/LatestImages.ql
new file mode 100644
index 000000000000..8d0b26d0a168
--- /dev/null
+++ b/iac/ql/src/security/Containers/LatestImages.ql
@@ -0,0 +1,16 @@
+/**
+ * @name Using latest images
+ * @description Using the latest version of an image can lead to unexpected changes in your container.
+ * @kind problem
+ * @severity info
+ * @security-severity 2.0
+ * @precision high
+ * @id containers/docker/latest-images
+ * @tags security
+ */
+
+import iac
+
+from ContainerImage image
+where image.getTag().getValue() = ["latest", "main", "master"]
+select image, "using latest image"
diff --git a/iac/ql/src/security/Containers/UserNotSet.ql b/iac/ql/src/security/Containers/UserNotSet.ql
new file mode 100644
index 000000000000..c16554f274da
--- /dev/null
+++ b/iac/ql/src/security/Containers/UserNotSet.ql
@@ -0,0 +1,17 @@
+/**
+ * @name User not set
+ * @description User not set
+ * @kind problem
+ * @severity info
+ * @security-severity 2.0
+ * @precision low
+ * @id iac/containers/user-not-set
+ * @tags security
+ * experimental
+ */
+
+import iac
+
+from ContainerDefinition container
+// where not container.get
+select container, "user not set"
diff --git a/iac/ql/src/security/HelmCharts/CWE-265/PrivillagedPod.md b/iac/ql/src/security/HelmCharts/CWE-265/PrivillagedPod.md
new file mode 100644
index 000000000000..f42d2c10a64f
--- /dev/null
+++ b/iac/ql/src/security/HelmCharts/CWE-265/PrivillagedPod.md
@@ -0,0 +1,31 @@
+# Privileged Pod defined
+
+Helms charts allow a pod to be defined as privileged which has many security concerns. This allows the pod to run with root privileges. This is a security risk as it allows the pod to access the host system and other pods running on the same node.
+
+## Sample Code
+
+The following Helm chart defines a privileged pod:
+
+```yaml
+spec:
+ privileged: true
+ # or
+ containers:
+ - name: mycontainer
+ securityContext:
+ privileged: true
+```
+
+## Recommendation
+
+Ensure that the pod is not defined as privileged, remove the `privileged: true` line from the pod definition or define it as `privileged: false`:
+
+```yaml
+spec:
+ privileged: false
+ # or
+ containers:
+ - name: mycontainer
+ securityContext:
+ privileged: false
+```
diff --git a/iac/ql/src/security/HelmCharts/CWE-265/PrivillagedPod.ql b/iac/ql/src/security/HelmCharts/CWE-265/PrivillagedPod.ql
new file mode 100644
index 000000000000..d216b62c5992
--- /dev/null
+++ b/iac/ql/src/security/HelmCharts/CWE-265/PrivillagedPod.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Privillaged Pod enabled in Helm Chart
+ * @description Privillaged Pod enabled in Helm Chart
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 9.0
+ * @precision high
+ * @id hc/kubernetes/privileged-pod
+ * @tags security
+ * helmcharts
+ * external/cwe/cwe-265
+ */
+
+import iac
+
+from HelmChart::Spec spec, HelmChart::SecurityContext security_context, YamlMapping sink
+where
+ spec.getPrivileged() = true and sink = spec
+ or
+ security_context.getPrivileged() = true and sink = security_context
+select sink.lookup("privileged"), "privileged enabled"
diff --git a/iac/ql/src/security/HelmCharts/CWE-265/RunAsRoot.md b/iac/ql/src/security/HelmCharts/CWE-265/RunAsRoot.md
new file mode 100644
index 000000000000..befd15196d6b
--- /dev/null
+++ b/iac/ql/src/security/HelmCharts/CWE-265/RunAsRoot.md
@@ -0,0 +1,15 @@
+# Running Pod as Root
+
+## Overview
+
+Kubernetes allows a pod to be defined as running as root which has many security concerns. This allows the pod to run with root privileges. This is a security risk as it allows the pod to access the host system and other pods running on the same node.
+
+## Recommendation
+
+Ensure that the pod is not defined as running as root, replace the `runAsUser` and `runAsGroup` lines from the pod definition with a non-root user:
+
+```yaml
+securityContext:
+ runAsUser: 1000
+ runAsGroup: 1000
+```
diff --git a/iac/ql/src/security/HelmCharts/CWE-265/RunAsRoot.ql b/iac/ql/src/security/HelmCharts/CWE-265/RunAsRoot.ql
new file mode 100644
index 000000000000..7fe36a131897
--- /dev/null
+++ b/iac/ql/src/security/HelmCharts/CWE-265/RunAsRoot.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Pod running as rool in Helm Chart
+ * @description Pod running as rool in Helm Chart
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 8.0
+ * @precision high
+ * @id hc/kubernetes/pod-run-as-root
+ * @tags security
+ * helmcharts
+ * external/cwe/cwe-265
+ */
+
+import iac
+
+from HelmChart::SecurityContext security_context
+where
+ security_context.getRunAsUser() = 0
+ or
+ security_context.getRunAsGroup() = 0
+select security_context, "root user set"
diff --git a/iac/ql/src/security/HelmCharts/CWE-829/UntrustedRegistry.ql b/iac/ql/src/security/HelmCharts/CWE-829/UntrustedRegistry.ql
new file mode 100644
index 000000000000..1fa0ddb1bf87
--- /dev/null
+++ b/iac/ql/src/security/HelmCharts/CWE-829/UntrustedRegistry.ql
@@ -0,0 +1,42 @@
+/**
+ * @name Untrusted Registry used in Helm Chart
+ * @description Untrusted Registry used in Helm Chart
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 9.0
+ * @precision high
+ * @id hc/kubernetes/untrusted-registry
+ * @tags security
+ * helmcharts
+ * external/cwe/cwe-829
+ * experimental
+ */
+
+import iac
+
+bindingset[regs]
+private predicate allowedRegistries(string regs) {
+ exists(string reg |
+ reg in [
+ // Docker Hub
+ "docker\\.io",
+ // GitHub Container Registry
+ "ghcr\\.io",
+ // Amazon ECR
+ "[A-Za-z0-9]+\\.dkr\\.ecr\\.[A-Za-z0-9-]+\\.amazonaws\\.com",
+ // Azure Container Registry
+ "azurecr\\.io",
+ // Google Container Registry
+ "gcr\\.io",
+ ]
+ |
+ regs.regexpMatch("^" + reg + "/%")
+ )
+}
+
+from HelmChart::Container container
+where
+ not container.getImage().matches("%/%")
+ or
+ not allowedRegistries(container.getImage())
+select container.lookup("image"), "untrusted container registry"
diff --git a/iac/ql/src/security/HelmCharts/OldApiVersion.ql b/iac/ql/src/security/HelmCharts/OldApiVersion.ql
new file mode 100644
index 000000000000..a5bd434e34ae
--- /dev/null
+++ b/iac/ql/src/security/HelmCharts/OldApiVersion.ql
@@ -0,0 +1,18 @@
+/**
+ * @name Use of old API version
+ * @description Use of old HelmChart API version
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 4.0
+ * @precision very-high
+ * @id hc/kubernetes/old-api-version
+ * @tags security
+ * helmcharts
+ * experimental
+ */
+
+import iac
+
+from HelmChart::Document chart
+where chart.getApiVersion() = "v1"
+select chart, "old API version used"
diff --git a/iac/ql/src/security/OpenApi/HttpAllowed.ql b/iac/ql/src/security/OpenApi/HttpAllowed.ql
new file mode 100644
index 000000000000..e7c49af34150
--- /dev/null
+++ b/iac/ql/src/security/OpenApi/HttpAllowed.ql
@@ -0,0 +1,16 @@
+/**
+ * @name HTTP scheme is allowed
+ * @description HTTP scheme is allowed
+ * @kind problem
+ * @severity warning
+ * @security-severity 2.0
+ * @precision very-high
+ * @id openapi/web/http-allowed
+ * @tags security
+ */
+
+import iac
+
+from OpenApi::Document oapi
+where oapi.getSchemes() = "http"
+select oapi.lookup("schemes"), "http scheme is not secure"
diff --git a/iac/ql/src/security/Terraform/AWS/DeprecatedFeatures.ql b/iac/ql/src/security/Terraform/AWS/DeprecatedFeatures.ql
new file mode 100644
index 000000000000..045d6391b20d
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/DeprecatedFeatures.ql
@@ -0,0 +1,27 @@
+/**
+ * @name Terraform AWS Provider Deprecated Features
+ * @description Terraform AWS Provider Deprecated Features
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 3.0
+ * @precision high
+ * @id tf/aws/deprecated-features
+ * @tags maintenance
+ */
+
+import hcl
+
+abstract class DeprecatedFeatures extends Expr {
+ string getFeature() { result = "NA" }
+}
+
+class S3Logging extends DeprecatedFeatures {
+ S3Logging() {
+ exists(AWS::S3Bucket b | b.hasAttribute("logging") and this = b.getAttribute("logging"))
+ }
+
+ override string getFeature() { result = "logging" }
+}
+
+from DeprecatedFeatures features
+select features, "Deprecated feature \"" + features.getFeature() + "\" used"
diff --git a/iac/ql/src/security/Terraform/AWS/EKS/PublicCluster/PublicCluster.ql b/iac/ql/src/security/Terraform/AWS/EKS/PublicCluster/PublicCluster.ql
new file mode 100644
index 000000000000..0c859e4cd9ba
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/EKS/PublicCluster/PublicCluster.ql
@@ -0,0 +1,20 @@
+/**
+ * @name EKS Cluster Publically Accessible
+ * @description Ensure that EKS Clusters are not publically accessible
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 9.0
+ * @precision high
+ * @id tf/aws/eks-public-cluster
+ * @tags security
+ * terraform
+ * aws
+ */
+
+import hcl
+
+from AWS::EKSCluster cluster
+where
+ cluster.getEndpointPublicAccess() = true and
+ not exists(cluster.getPublicAccessCidrs())
+select cluster.getAttribute("vpc_config"), "EKS Cluster Publically Accessible"
diff --git a/iac/ql/src/security/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.ql b/iac/ql/src/security/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.ql
new file mode 100644
index 000000000000..53444fbb3265
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.ql
@@ -0,0 +1,18 @@
+/**
+ * @name EKS Unencrypted Secrets
+ * @description Ensure that EKS Clusters use encryption for secrets
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 8.0
+ * @precision high
+ * @id tf/aws/eks-unencrypted-secrets
+ * @tags security
+ * terraform
+ * aws
+ */
+
+import hcl
+
+from AWS::EKSCluster cluster
+where not exists(cluster.getEncryptionConfig())
+select cluster, "EKS Unencrypted Secrets"
diff --git a/iac/ql/src/security/Terraform/AWS/ESDisabledLogging.ql b/iac/ql/src/security/Terraform/AWS/ESDisabledLogging.ql
new file mode 100644
index 000000000000..452548f85b96
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/ESDisabledLogging.ql
@@ -0,0 +1,25 @@
+/**
+ * @name Elastic Search Logging Disabled
+ * @description Elastic Search Logging Disabled
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 6.0
+ * @precision high
+ * @id tf/aws/elastic-search-disabled-logging
+ * @tags security
+ */
+
+import hcl
+
+from Resource r
+where
+ r.getResourceType() = "aws_elasticsearch_domain" and
+ // Disabled by default (in-secure), if present the default if turned on (secure)
+ not r.hasAttribute("log_publishing_options")
+ or
+ exists(Block block |
+ block = r.getAttribute("log_publishing_options") and
+ r.getAttribute("enabled").(BooleanLiteral).getBool() = false and
+ block = r
+ )
+select r, "Logging Disabled"
diff --git a/iac/ql/src/security/Terraform/AWS/RDBUnencrypted.ql b/iac/ql/src/security/Terraform/AWS/RDBUnencrypted.ql
new file mode 100644
index 000000000000..f8e1c6f0b958
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/RDBUnencrypted.ql
@@ -0,0 +1,19 @@
+/**
+ * @name RDS Database Unencrypted
+ * @description RDS Database Unencrypted
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 8.0
+ * @precision high
+ * @id tf/aws/rds-database-unencrytped
+ * @tags security
+ */
+
+import hcl
+
+from Resource r
+where
+ r.getResourceType() = "aws_db_instance" and
+ not r.hasAttribute("storage_encrypted")
+// TODO: check if set to true
+select r, "S3 Bucket Unencrypted: \"" + r.getName() + "\""
diff --git a/iac/ql/src/security/Terraform/AWS/S3/DisableLogging/LoggingDisabled.ql b/iac/ql/src/security/Terraform/AWS/S3/DisableLogging/LoggingDisabled.ql
new file mode 100644
index 000000000000..178c8ad2134a
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/S3/DisableLogging/LoggingDisabled.ql
@@ -0,0 +1,23 @@
+/**
+ * @name S3 Bucket Logging Disabled
+ * @description S3 Bucket Logging Disabled
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 8.0
+ * @precision high
+ * @id tf/aws/storage-logging-disabled
+ * @tags security
+ * terraform
+ * aws
+ * storage
+ */
+
+import hcl
+
+from AWS::S3Bucket buckets
+where
+ // Disable by default
+ not exists(buckets.getLogging()) and
+ // Only checks buckets that are themselves used for storing logs
+ not buckets.getAclValue() = "log-delivery-write"
+select buckets, "Logging disabled for: \"" + buckets.getName() + "\""
diff --git a/iac/ql/src/security/Terraform/AWS/S3/PublicBucket/PublicBucket.ql b/iac/ql/src/security/Terraform/AWS/S3/PublicBucket/PublicBucket.ql
new file mode 100644
index 000000000000..6f14def4faa9
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/S3/PublicBucket/PublicBucket.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Public S3 Bucket
+ * @description Public S3 Bucket
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 10.0
+ * @precision high
+ * @id tf/aws/storage-publicly-accessible
+ * @tags security
+ * terraform
+ * aws
+ * storage
+ */
+
+import hcl
+
+from AWS::S3Bucket buckets
+where
+ // Check for public-read
+ buckets.getAclValue() = "public-read"
+select buckets, "Public S3 Bucket resource"
diff --git a/iac/ql/src/security/Terraform/AWS/S3/PublicBucketAccessEnabled/PublicBucketAccessEnabled.ql b/iac/ql/src/security/Terraform/AWS/S3/PublicBucketAccessEnabled/PublicBucketAccessEnabled.ql
new file mode 100644
index 000000000000..ec121ef6a4f2
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/S3/PublicBucketAccessEnabled/PublicBucketAccessEnabled.ql
@@ -0,0 +1,20 @@
+/**
+ * @name S3 Access Policy Allows Public Buckets
+ * @description S3 Access Policy Allows Public Buckets
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 5.0
+ * @precision high
+ * @id tf/aws/s3-public-access-disabled
+ * @tags security
+ * terraform
+ */
+
+import hcl
+
+from Resource r
+where
+ r.getResourceType() = "aws_s3_bucket_public_access_block" and
+ not r.hasAttribute("restrict_public_buckets")
+// TODO: check if set to try
+select r, "Access Policy"
diff --git a/iac/ql/src/security/Terraform/AWS/S3/UnencryptedBucket/UnencryptedBucket.ql b/iac/ql/src/security/Terraform/AWS/S3/UnencryptedBucket/UnencryptedBucket.ql
new file mode 100644
index 000000000000..ca708ad36ff0
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/S3/UnencryptedBucket/UnencryptedBucket.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Unencrytped S3 Bucket
+ * @description Unencrytped S3 Bucket
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 6.0
+ * @precision high
+ * @id tf/aws/storage-unencrypted
+ * @tags security
+ * terraform
+ * aws
+ * storage
+ */
+
+import hcl
+
+from AWS::S3Bucket buckets
+where
+ // Default is unencrypted
+ not exists(buckets.getEncryptionConfiguration())
+select buckets, "S3 Bucket Unencrypted: \"" + buckets.getName() + "\""
diff --git a/iac/ql/src/security/Terraform/AWS/S3/VersioningDisabled/VersioningDisabled.ql b/iac/ql/src/security/Terraform/AWS/S3/VersioningDisabled/VersioningDisabled.ql
new file mode 100644
index 000000000000..a9d3a483a9f8
--- /dev/null
+++ b/iac/ql/src/security/Terraform/AWS/S3/VersioningDisabled/VersioningDisabled.ql
@@ -0,0 +1,24 @@
+/**
+ * @name S3 Bucket Versioning Disabled
+ * @description S3 Bucket Versioning Disabled
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 6.0
+ * @precision high
+ * @id tf/aws/storage-versioning-disabled
+ * @tags security
+ * terraform
+ * aws
+ * storage
+ */
+
+import hcl
+
+from AWS::S3Bucket buckets
+where
+ // Disable by default
+ not exists(buckets.getVersioning())
+ or
+ // If disabled
+ buckets.getVersioningValue() = false
+select buckets, "Versioning disabled for: \"" + buckets.getName() + "\""
diff --git a/iac/ql/src/security/Terraform/Alicloud/Bucket/PublicBucket.ql b/iac/ql/src/security/Terraform/Alicloud/Bucket/PublicBucket.ql
new file mode 100644
index 000000000000..a189e51bd499
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Alicloud/Bucket/PublicBucket.ql
@@ -0,0 +1,24 @@
+/**
+ * @name Alicloud Public Bucket
+ * @description Alicloud Public Bucket
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 10.0
+ * @precision high
+ * @id tf/alicloud/storage-publicly-accessible
+ * @tags security
+ * terraform
+ * alicloud
+ * storage
+ */
+
+import hcl
+
+from Resource r
+where
+ r.getResourceType() = "alicloud_oss_bucket" and
+ exists(StringLiteral str |
+ str = r.getAttribute("acl") and
+ str.getValue() = "public-read-write"
+ )
+select r, "Public and Writeable"
diff --git a/iac/ql/src/security/Terraform/Azure/Database/DbGeoBackup.ql b/iac/ql/src/security/Terraform/Azure/Database/DbGeoBackup.ql
new file mode 100644
index 000000000000..b41b582037e4
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Azure/Database/DbGeoBackup.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Azure Database Geo Backup Unset or Disabled
+ * @description Azure Database Geo Backup Unset or Disabled
+ * @kind problem
+ * @problem.severity info
+ * @security-severity 2.0
+ * @precision low
+ * @id tf/azure/database-geo-backup-unset-or-disabled
+ * @tags security
+ */
+
+import hcl
+
+from Azure::Database resource, Expr sink
+where
+ not resource.hasAttribute("geo_redundant_backup_enabled") and sink = resource
+ or
+ resource.getGeoRedundantBackupEnabled() = false and
+ sink = resource.getAttribute("geo_redundant_backup_enabled")
+select sink, "Geo Backup Disabled"
diff --git a/iac/ql/src/security/Terraform/Azure/Database/InfrastructureEncryptionDisabled.ql b/iac/ql/src/security/Terraform/Azure/Database/InfrastructureEncryptionDisabled.ql
new file mode 100644
index 000000000000..12dbeaf2c846
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Azure/Database/InfrastructureEncryptionDisabled.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Azure Database Geo Backup Unset or Disabled
+ * @description Azure Database Geo Backup Unset or Disabled
+ * @kind problem
+ * @problem.severity info
+ * @security-severity 7.0
+ * @precision high
+ * @id tf/azure/database-unencrypted
+ * @tags security
+ * terraform
+ * azure
+ * database
+ */
+
+import hcl
+
+from Azure::Database resource, Expr sink
+where
+ not resource.hasAttribute("infrastructure_encryption_enabled") and sink = resource
+ or
+ resource.getInfrastructureEncryptionEnabled() = false and
+ sink = resource.getAttribute("infrastructure_encryption_enabled")
+select sink, "Geo Backup Disabled"
diff --git a/iac/ql/src/security/Terraform/Azure/Database/SSLDisabled.ql b/iac/ql/src/security/Terraform/Azure/Database/SSLDisabled.ql
new file mode 100644
index 000000000000..4efdd0eab0f9
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Azure/Database/SSLDisabled.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Azure Service TLS/SSL Disable
+ * @description Azure Service TLS/SSL Disable
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 10.0
+ * @precision high
+ * @id tf/azure/database-tls-disable
+ * @tags security
+ * terraform
+ * azure
+ * database
+ */
+
+import hcl
+
+from Azure::Database database
+where database.getSslEnforcementEnabled() = false
+select database.getAttribute("ssl_enforcement_enabled"), "TLS/SSL Disabled"
diff --git a/iac/ql/src/security/Terraform/Azure/Database/WeakEncryption.ql b/iac/ql/src/security/Terraform/Azure/Database/WeakEncryption.ql
new file mode 100644
index 000000000000..4eeed8e14eaf
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Azure/Database/WeakEncryption.ql
@@ -0,0 +1,22 @@
+/**
+ * @name Azure Service TLS/SSL Weak Encryption
+ * @description Azure Service TLS/SSL Weak Encryption
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 4.0
+ * @precision high
+ * @id tf/azure/database-weak-encryption
+ * @tags security
+ * terraform
+ * azure
+ * database
+ */
+
+import hcl
+
+from Resource resource, Expr attr
+where
+ resource.getResourceType() = ["azurerm_mysql_server", "azurerm_postgresql_server"] and
+ attr = resource.getAttribute("ssl_minimal_tls_version_enforced") and
+ attr.(StringLiteral).getValue() = ["TLS1_0", "TLS1_1"]
+select attr, "Weak Encryption"
diff --git a/iac/ql/src/security/Terraform/Azure/KeyVault/WeakVaultKey/WeakVaultKey.md b/iac/ql/src/security/Terraform/Azure/KeyVault/WeakVaultKey/WeakVaultKey.md
new file mode 100644
index 000000000000..e2e0e23061aa
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Azure/KeyVault/WeakVaultKey/WeakVaultKey.md
@@ -0,0 +1,34 @@
+# Weak Azure Key Vault Key
+
+When using Azure Key Vault, it is important to ensure that the keys used to encrypt the data are strong enough to withstand brute force attacks.
+This policy checks that the key size is at least 2048 bits.
+
+## Examples
+
+Here are the good and bad ways of doing this.
+
+### Bad
+
+The following example will fail the policy check as the key size is too small.
+
+```hcl
+resource "azurerm_key_vault_key" "example" {
+ name = "example-key"
+ key_vault_id = azurerm_key_vault.example.id
+ key_type = "RSA"
+ key_size = 1024
+}
+```
+
+### Good
+
+The following example will pass the policy check as the key size is large enough.
+
+```hcl
+resource "azurerm_key_vault_key" "example" {
+ name = "example-key"
+ key_vault_id = azurerm_key_vault.example.id
+ key_type = "RSA"
+ key_size = 2048
+}
+```
diff --git a/iac/ql/src/security/Terraform/Azure/KeyVault/WeakVaultKey/WeakVaultKey.ql b/iac/ql/src/security/Terraform/Azure/KeyVault/WeakVaultKey/WeakVaultKey.ql
new file mode 100644
index 000000000000..fb10e4506219
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Azure/KeyVault/WeakVaultKey/WeakVaultKey.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Weak Azure Key Vault Key
+ * @description Weak Azure Key Vault Key
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 8.0
+ * @precision high
+ * @id tf/azure/vault-weak-key
+ * @tags security
+ * terraform
+ * azure
+ * vault
+ */
+
+import hcl
+
+from Azure::KeyVaultKey key
+where
+ key.getKeyType() = "RSA" and
+ key.getKeySize() < 2048
+select key.getAttribute("key_size"), "weak key size"
diff --git a/iac/ql/src/security/Terraform/Azure/ManagedDisk/ManagedStorageUnencrypted.ql b/iac/ql/src/security/Terraform/Azure/ManagedDisk/ManagedStorageUnencrypted.ql
new file mode 100644
index 000000000000..ae3d60dd4701
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Azure/ManagedDisk/ManagedStorageUnencrypted.ql
@@ -0,0 +1,25 @@
+/**
+ * @name Azure Managed Storage is Unencrypted
+ * @description Azure Storage is Unencrypted
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 6.0
+ * @precision high
+ * @id tf/azure/storage-unencrypted
+ * @tags security
+ * terraform
+ * azure
+ * storage
+ */
+
+import hcl
+
+// https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_disk
+from Azure::ManagedDisk managed_disk, Azure::ManagedDiskEncryptionSettings settings, Expr sink
+where
+ not exists(managed_disk.getEncryptionSettings()) and sink = managed_disk
+ or
+ settings = managed_disk.getEncryptionSettings() and
+ settings.getEnabled() = false and
+ sink = settings
+select sink, "Azure Storage is Unencrypted for '" + managed_disk.getName() + "'"
diff --git a/iac/ql/src/security/Terraform/Azure/ManagedDisk/PublicAccess.ql b/iac/ql/src/security/Terraform/Azure/ManagedDisk/PublicAccess.ql
new file mode 100644
index 000000000000..8867e18bf58f
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Azure/ManagedDisk/PublicAccess.ql
@@ -0,0 +1,22 @@
+/**
+ * @name Azure Managed Storage is Public
+ * @description Azure Managed Storage is Public
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 10.0
+ * @precision high
+ * @id tf/azure/storage-publicly-accessible
+ * @tags security
+ * terraform
+ * azure
+ * storage
+ */
+
+import hcl
+
+// https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_disk
+from Azure::StorageContainer managed_disk
+where
+ managed_disk.getContainerAccessType() = "blob" and
+ managed_disk.getProperty("publicAccess").(StringLiteral).getValue() = "blob"
+select managed_disk, "Azure Storage is Unencrypted for '" + managed_disk.getName() + "'"
diff --git a/iac/ql/src/security/Terraform/Azure/SecurityCenterDisabledNotifications.ql b/iac/ql/src/security/Terraform/Azure/SecurityCenterDisabledNotifications.ql
new file mode 100644
index 000000000000..22f080aa9b0b
--- /dev/null
+++ b/iac/ql/src/security/Terraform/Azure/SecurityCenterDisabledNotifications.ql
@@ -0,0 +1,18 @@
+/**
+ * @name Azure Security Center Disabled Notifications
+ * @description Azure Security Center Disabled Notifications
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 3.0
+ * @precision high
+ * @id tf/azure/security-center-disabled-notifications
+ * @tags security
+ */
+
+import hcl
+
+from Azure::SecurityCenterContact contact
+where
+ contact.getAlertNotifications() = false and
+ contact.getAlertsToAdmins() = false
+select contact, "disabled notifications"
diff --git a/iac/ql/src/security/Terraform/EmbeddedScripts.ql b/iac/ql/src/security/Terraform/EmbeddedScripts.ql
new file mode 100644
index 000000000000..1a879d98b610
--- /dev/null
+++ b/iac/ql/src/security/Terraform/EmbeddedScripts.ql
@@ -0,0 +1,22 @@
+/**
+ * @name Embedded Scripts
+ * @description Embedded Scripts
+ * @kind problem
+ * @problem.severity info
+ * @security-severity 2.0
+ * @precision high
+ * @id tf/all/machine-embedded-scripts
+ * @tags security
+ * experimental
+ */
+
+import hcl
+
+from Resource resource, HereDoc data
+where
+ resource.getResourceType() = "aws_instance" and
+ data = resource.getAttribute("user_data")
+ or
+ resource.getResourceType() = "null_resource" and
+ data = resource.getAttribute("command")
+select data, "Embedded script found"
diff --git a/iac/ql/src/security/Terraform/GCP/ContainerCluster/ABACEnabled.ql b/iac/ql/src/security/Terraform/GCP/ContainerCluster/ABACEnabled.ql
new file mode 100644
index 000000000000..2210ed3ce745
--- /dev/null
+++ b/iac/ql/src/security/Terraform/GCP/ContainerCluster/ABACEnabled.ql
@@ -0,0 +1,16 @@
+/**
+ * @name GCP ABAC Permissions enabled
+ * @description GCP ABAC Permissions enabled
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 8.0
+ * @precision high
+ * @id tf/gcp/abac-enabled
+ * @tags security
+ */
+
+import hcl
+
+from GCP::ContainerCluster resource, Expr attr
+where attr = resource.getAttribute("enable_legacy_abac")
+select attr, "ABAC Enabled"
diff --git a/iac/ql/src/security/Terraform/GCP/ContainerCluster/ClusterPodSecurityPolicy.ql b/iac/ql/src/security/Terraform/GCP/ContainerCluster/ClusterPodSecurityPolicy.ql
new file mode 100644
index 000000000000..5e46465ffa52
--- /dev/null
+++ b/iac/ql/src/security/Terraform/GCP/ContainerCluster/ClusterPodSecurityPolicy.ql
@@ -0,0 +1,18 @@
+/**
+ * @name Cluster Pod Security Policy (legacy)
+ * @description Cluster Pod Security Policy (legacy)
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 5.0
+ * @precision high
+ * @id tf/gcp/cluster-pod-security-policy
+ * @tags security
+ */
+
+import hcl
+
+from GCP::ContainerCluster resource, GCP::PodSecurityPolicyConfig pspc
+where
+ pspc = resource.getPodSecurityPolicyConfig() and
+ pspc.getEnabled() = "false"
+select resource, "No Pod Security Policy Defined"
diff --git a/iac/ql/src/security/Terraform/GCP/ContainerCluster/PublicControlPlane.ql b/iac/ql/src/security/Terraform/GCP/ContainerCluster/PublicControlPlane.ql
new file mode 100644
index 000000000000..7b8e16ba2973
--- /dev/null
+++ b/iac/ql/src/security/Terraform/GCP/ContainerCluster/PublicControlPlane.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Cluster Control Plane Publicly Accessible
+ * @description Cluster Control Plane Publicly Accessible
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 5.0
+ * @precision high
+ * @id tf/gcp/cluster-control-plane-publicly-accessible
+ * @tags security
+ */
+
+import hcl
+
+from GCP::ContainerCluster resource, Block config, Block cidr_blocks
+where
+ config = resource.getAuthorizedNetworks() and
+ cidr_blocks = config.getAttribute("cidr_blocks") and
+ cidr_blocks.getAttribute("cidr_block").(StringLiteral).getValue() = "0.0.0.0/0"
+select cidr_blocks, "No Pod Security Policy Defined"
diff --git a/iac/ql/src/security/Terraform/GCP/Storage/PublicBucket.ql b/iac/ql/src/security/Terraform/GCP/Storage/PublicBucket.ql
new file mode 100644
index 000000000000..0fb18b776fa8
--- /dev/null
+++ b/iac/ql/src/security/Terraform/GCP/Storage/PublicBucket.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Public Storage Bucket
+ * @description Public Storage Bucket
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 10.0
+ * @precision high
+ * @id tf/gcp/storage-publicly-accessible
+ * @tags security
+ * terraform
+ * gcp
+ * storage
+ */
+
+import hcl
+
+from GCP::StorageBucket bucket, GCP::StorageBucketAccessControl acl
+where
+ acl.getBucket() = bucket and
+ acl.getEntity() = "allUsers"
+select bucket, "Public Storage Bucket."
diff --git a/iac/ql/src/security/Terraform/HardcodedPasswords.ql b/iac/ql/src/security/Terraform/HardcodedPasswords.ql
new file mode 100644
index 000000000000..6b8c68eac60e
--- /dev/null
+++ b/iac/ql/src/security/Terraform/HardcodedPasswords.ql
@@ -0,0 +1,15 @@
+/**
+ * @name Hardcoded Passwords
+ * @description Hardcoded Passwords
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 8.0
+ * @precision high
+ * @id tf/all/hardcoded-passwords
+ * @tags security
+ */
+
+import hcl
+
+from PasswordSinks sinks
+select sinks, "Hardcoded Password"
diff --git a/iac/ql/test/codeql-pack.lock.yml b/iac/ql/test/codeql-pack.lock.yml
new file mode 100644
index 000000000000..69ce09bcb67b
--- /dev/null
+++ b/iac/ql/test/codeql-pack.lock.yml
@@ -0,0 +1,8 @@
+---
+lockVersion: 1.0.0
+dependencies:
+ codeql/util:
+ version: 1.0.12
+ codeql/yaml:
+ version: 1.0.25
+compiled: false
diff --git a/iac/ql/test/library-tests/aws/cloudformation/AST.expected b/iac/ql/test/library-tests/aws/cloudformation/AST.expected
new file mode 100644
index 000000000000..16219c80b8b7
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/AST.expected
@@ -0,0 +1,61 @@
+cloudformation
+| IAMRole.yml:1:1:18:74 | CloudFormation Document |
+| IngressEgressResources.yml:1:1:19:21 | CloudFormation Document |
+| ecs.yml:1:1:92:40 | CloudFormation Document |
+| lambda.yml:1:1:21:18 | CloudFormation Document |
+| s3.json:1:1:77:1 | CloudFormation Document |
+| s3.yml:1:1:42:59 | CloudFormation Document |
+| securitygroup.yml:1:1:16:24 | CloudFormation Document |
+resources
+| IAMRole.yml:5:5:18:74 | CloudFormation IAM Role |
+| IngressEgressResources.yml:7:5:13:2 | CloudFormation EC2 Security Group Ingress |
+| IngressEgressResources.yml:14:5:19:21 | CloudFormation EC2 Security Group Egress |
+| ecs.yml:6:5:38:2 | CloudFormation ECS Task Set |
+| ecs.yml:39:5:66:2 | CloudFormation ECS Task Definition |
+| ecs.yml:67:5:92:40 | CloudFormation Resource |
+| lambda.yml:5:5:21:18 | CloudFormation Lambda Function |
+| s3.json:4:21:15:9 | CloudFormation S3 Bucket |
+| s3.json:16:25:47:9 | CloudFormation S3 Bucket Policy |
+| s3.yml:4:5:12:2 | CloudFormation S3 Bucket |
+| s3.yml:13:5:28:28 | CloudFormation S3 Bucket Policy |
+| securitygroup.yml:7:5:16:24 | CloudFormation EC2 Security Group |
+resourceProperties
+| IAMRole.yml:7:7:18:74 | CloudFormation Resource Properties |
+| IngressEgressResources.yml:9:7:13:2 | CloudFormation Resource Properties |
+| IngressEgressResources.yml:16:7:19:21 | CloudFormation Resource Properties |
+| ecs.yml:8:7:38:2 | CloudFormation Resource Properties |
+| ecs.yml:41:7:66:2 | CloudFormation Resource Properties |
+| ecs.yml:70:7:92:40 | CloudFormation Resource Properties |
+| lambda.yml:7:7:21:18 | CloudFormation Resource Properties |
+| s3.json:6:27:12:13 | CloudFormation Resource Properties |
+| s3.json:18:27:46:13 | CloudFormation Resource Properties |
+| s3.yml:6:7:10:4 | CloudFormation Resource Properties |
+| s3.yml:15:7:28:28 | CloudFormation Resource Properties |
+| securitygroup.yml:9:7:16:24 | CloudFormation Resource Properties |
+s3
+| s3.json:4:21:15:9 | CloudFormation S3 Bucket |
+| s3.yml:4:5:12:2 | CloudFormation S3 Bucket |
+s3Policy
+| s3.yml:4:5:12:2 | CloudFormation S3 Bucket | s3.yml:13:5:28:28 | CloudFormation S3 Bucket Policy |
+lambda
+| lambda.yml:5:5:21:18 | CloudFormation Lambda Function |
+ec2SecurityGroup
+| securitygroup.yml:7:5:16:24 | CloudFormation EC2 Security Group |
+ec2SecurityGroupIngress
+| IngressEgressResources.yml:7:5:13:2 | CloudFormation EC2 Security Group Ingress |
+ec2SecurityGroupEgress
+| IngressEgressResources.yml:14:5:19:21 | CloudFormation EC2 Security Group Egress |
+iamRole
+| IAMRole.yml:5:5:18:74 | CloudFormation IAM Role |
+iamStatement
+| IAMRole.yml:9:9:16:6 | Version ... -10-17' |
+| s3.yml:16:9:28:6 | Id: MyPolicy |
+ecsService
+| ecs.yml:67:5:92:40 | CloudFormation Resource |
+ecsCluster
+ecsTaskSet
+| ecs.yml:6:5:38:2 | CloudFormation ECS Task Set |
+taskDefinition
+| ecs.yml:39:5:66:2 | CloudFormation ECS Task Definition |
+containerDefinition
+| ecs.yml:41:7:66:2 | CloudFormation Resource Properties |
diff --git a/iac/ql/test/library-tests/aws/cloudformation/AST.ql b/iac/ql/test/library-tests/aws/cloudformation/AST.ql
new file mode 100644
index 000000000000..e0919febb82a
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/AST.ql
@@ -0,0 +1,27 @@
+private import iac
+
+query predicate cloudformation(CloudFormation::Document n) { any() }
+
+query predicate resources(CloudFormation::Resource n) { any() }
+
+query predicate resourceProperties(CloudFormation::ResourceProperties n) { any() }
+
+query predicate s3(CloudFormation::S3Bucket n) { any() }
+
+query predicate s3Policy(CloudFormation::S3Bucket n, CloudFormation::S3BucketPolicy p) {
+ p = n.getBucketPolicy()
+}
+
+query predicate lambda(CloudFormation::LambdaFunction n) { any() }
+
+query predicate ec2SecurityGroup(CloudFormation::EC2SecurityGroup n) { any() }
+query predicate ec2SecurityGroupIngress(CloudFormation::EC2SecurityGroupIngress n) { any() }
+query predicate ec2SecurityGroupEgress(CloudFormation::EC2SecurityGroupEgress n) { any() }
+query predicate iamRole(CloudFormation::IAMRole n) { any() }
+query predicate iamStatement(CloudFormation::IAMStatement p){ any() }
+
+query predicate ecsService(CloudFormation::ECSService p){ any() }
+query predicate ecsCluster(CloudFormation::ECSCluster p){ any() }
+query predicate ecsTaskSet(CloudFormation::ECSTaskSet p){ any() }
+query predicate taskDefinition(CloudFormation::TaskDefinition p){ any() }
+query predicate containerDefinition(CloudFormation::ContainerDefinition p){ any() }
diff --git a/iac/ql/test/library-tests/aws/cloudformation/IAMRole.yml b/iac/ql/test/library-tests/aws/cloudformation/IAMRole.yml
new file mode 100644
index 000000000000..de7cc306c48b
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/IAMRole.yml
@@ -0,0 +1,18 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Security: Account Enable Guardduty'
+Resources:
+ LambdaRole:
+ Type: 'AWS::IAM::Role'
+ Properties:
+ RoleName: SecurityGuardDutyLambdaRole
+ AssumeRolePolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service: 'lambda.amazonaws.com'
+ Action:
+ - 'sts:AssumeRole'
+ Path: '/'
+ ManagedPolicyArns:
+ - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
\ No newline at end of file
diff --git a/iac/ql/test/library-tests/aws/cloudformation/IngressEgressResources.yml b/iac/ql/test/library-tests/aws/cloudformation/IngressEgressResources.yml
new file mode 100644
index 000000000000..26caecbb7568
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/IngressEgressResources.yml
@@ -0,0 +1,22 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: PositionAndJourney Statistics Service
+
+Resources:
+ IngressInbound:
+ Type: 'AWS::EC2::SecurityGroupIngress'
+ Properties:
+ IpProtocol: tcp
+ CidrIp: 0.0.0.0/0
+ FromPort: '6370'
+ ToPort: '6379'
+ EgressOutbound:
+ Type: 'AWS::EC2::SecurityGroupEgress'
+ Properties:
+ IpProtocol: tcp
+ CidrIp: 0.0.0.0/0
+ FromPort: '6370'
+ ToPort: '6379'
+
+
+
diff --git a/iac/ql/test/library-tests/aws/cloudformation/ecs.yml b/iac/ql/test/library-tests/aws/cloudformation/ecs.yml
new file mode 100644
index 000000000000..c22758eaf612
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/ecs.yml
@@ -0,0 +1,92 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+Parameters:
+Resources:
+ TaskSet:
+ Type: 'AWS::ECS::TaskSet'
+ Properties:
+ Cluster: !Ref 'Cluster'
+ Service: !Ref 'Service'
+ TaskDefinition: !Ref 'TaskDefinition'
+ LaunchType: 'FARGATE'
+ NetworkConfiguration:
+ AwsVpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref 'SecurityGroup'
+ Subnets:
+ - !Ref 'SubnetA'
+ - !Ref 'SubnetB'
+ - !Ref 'SubnetC'
+ LoadBalancers:
+ - ContainerName: !Ref 'ContainerName'
+ ContainerPort: !Ref 'ContainerPort'
+ TargetGroupArn: !Ref 'TargetGroup'
+ PlatformVersion: 'LATEST'
+ Scale: !Ref 'Scale'
+ ServiceRegistries:
+ - ContainerName: !Ref 'ContainerName'
+ ContainerPort: !Ref 'ContainerPort'
+ RegistryArn: !Ref 'ServiceDiscoveryRegistry'
+ HealthCheckGracePeriodSeconds: 90
+ SchedulingStrategy: 'REPLICA'
+ ServiceDiscovery: !Ref 'ServiceDiscovery'
+
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Ref Container
+ Name: !Ref Component
+ PortMappings:
+ - ContainerPort: !Ref ContainerPort
+ LogConfiguration:
+ LogDriver: 'awslogs'
+ Options:
+ awslogs-create-group: true
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Sub '/fargate/logs'
+ awslogs-stream-prefix: !Ref Component
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: DummyListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: 'ecs-fargate-ws-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref PrivateA
+ - !Ref PrivateB
+ - !Ref PrivateC
+ ServiceName: !Ref Service
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
diff --git a/iac/ql/test/library-tests/aws/cloudformation/lambda.yml b/iac/ql/test/library-tests/aws/cloudformation/lambda.yml
new file mode 100644
index 000000000000..162edf61435d
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/lambda.yml
@@ -0,0 +1,21 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Security: Account Enable Guardduty'
+Resources:
+ LambdaFunctionEnableGuardDuty:
+ Type: 'AWS::Lambda::Function'
+ Properties:
+ Code:
+ ZipFile: |
+ 'use strict';
+ const AWS = require('aws-sdk');
+ const response = require('cfn-response');
+ Handler: 'index.handler'
+ MemorySize: 128
+ Role: !GetAtt 'LambdaRole.Arn'
+ Runtime: 'nodejs16.x'
+ Tags:
+ - Key: 'wcar-service'
+ Value: 'provisioning-security-guardduty'
+ - Key: 'team'
+ Value: 'sec-ops'
+ Timeout: 120
\ No newline at end of file
diff --git a/iac/ql/test/library-tests/aws/cloudformation/misc.json b/iac/ql/test/library-tests/aws/cloudformation/misc.json
new file mode 100644
index 000000000000..2a136c2393c1
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/misc.json
@@ -0,0 +1,5 @@
+{
+ "random": {
+ "json": "file"
+ }
+}
\ No newline at end of file
diff --git a/iac/ql/test/library-tests/aws/cloudformation/s3.json b/iac/ql/test/library-tests/aws/cloudformation/s3.json
new file mode 100644
index 000000000000..f287968df8f8
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/s3.json
@@ -0,0 +1,77 @@
+{
+ "AWSTemplateFormatVersion": "2010-09-09",
+ "Resources": {
+ "S3Bucket": {
+ "Type": "AWS::S3::Bucket",
+ "Properties": {
+ "AccessControl": "PublicRead",
+ "WebsiteConfiguration": {
+ "IndexDocument": "index.html",
+ "ErrorDocument": "error.html"
+ }
+ },
+ "DeletionPolicy": "Retain",
+ "UpdateReplacePolicy": "Retain"
+ },
+ "BucketPolicy": {
+ "Type": "AWS::S3::BucketPolicy",
+ "Properties": {
+ "PolicyDocument": {
+ "Id": "MyPolicy",
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "PublicReadForGetBucketObjects",
+ "Effect": "Allow",
+ "Principal": "*",
+ "Action": "s3:GetObject",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ },
+ "/*"
+ ]
+ ]
+ }
+ }
+ ]
+ },
+ "Bucket": {
+ "Ref": "S3Bucket"
+ }
+ }
+ }
+ },
+ "Outputs": {
+ "WebsiteURL": {
+ "Value": {
+ "Fn::GetAtt": [
+ "S3Bucket",
+ "WebsiteURL"
+ ]
+ },
+ "Description": "URL for website hosted on S3"
+ },
+ "S3BucketSecureURL": {
+ "Value": {
+ "Fn::Join": [
+ "",
+ [
+ "https://",
+ {
+ "Fn::GetAtt": [
+ "S3Bucket",
+ "DomainName"
+ ]
+ }
+ ]
+ ]
+ },
+ "Description": "Name of S3 bucket to hold website content"
+ }
+ }
+}
\ No newline at end of file
diff --git a/iac/ql/test/library-tests/aws/cloudformation/s3.yml b/iac/ql/test/library-tests/aws/cloudformation/s3.yml
new file mode 100644
index 000000000000..7135563031a8
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/s3.yml
@@ -0,0 +1,42 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ S3Bucket:
+ Type: "AWS::S3::Bucket"
+ Properties:
+ AccessControl: PublicRead
+ WebsiteConfiguration:
+ IndexDocument: index.html
+ ErrorDocument: error.html
+ DeletionPolicy: Retain
+ UpdateReplacePolicy: Retain
+ BucketPolicy:
+ Type: "AWS::S3::BucketPolicy"
+ Properties:
+ PolicyDocument:
+ Id: MyPolicy
+ Version: 2012-10-17
+ Statement:
+ - Sid: PublicReadForGetBucketObjects
+ Effect: Allow
+ Principal: "*"
+ Action: "s3:GetObject"
+ Resource: !Join
+ - ""
+ - - "arn:aws:s3:::"
+ - !Ref S3Bucket
+ - /*
+ Bucket: !Ref S3Bucket
+Outputs:
+ WebsiteURL:
+ Value: !GetAtt
+ - S3Bucket
+ - WebsiteURL
+ Description: URL for website hosted on S3
+ S3BucketSecureURL:
+ Value: !Join
+ - ""
+ - - "https://"
+ - !GetAtt
+ - S3Bucket
+ - DomainName
+ Description: Name of S3 bucket to hold website content
diff --git a/iac/ql/test/library-tests/aws/cloudformation/securitygroup.yml b/iac/ql/test/library-tests/aws/cloudformation/securitygroup.yml
new file mode 100644
index 000000000000..ffe3af1c07fb
--- /dev/null
+++ b/iac/ql/test/library-tests/aws/cloudformation/securitygroup.yml
@@ -0,0 +1,16 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Metadata:
+ Description: 'AWS CloudFormation Template To fs2 messaging test'
+
+Resources:
+ SecurityGroup:
+ Type: AWS::EC2::SecurityGroup
+ Properties:
+ GroupDescription: "Vpc Security Group"
+ SecurityGroupEgress:
+ - IpProtocol: "tcp"
+ CidrIp: "0.0.0.0/0"
+ FromPort: 1337
+ ToPort: 1337
+ Description: "For RDS"
+ VpcId: !Ref VpcId
diff --git a/iac/ql/test/library-tests/azure/arm/AST.expected b/iac/ql/test/library-tests/azure/arm/AST.expected
new file mode 100644
index 000000000000..4a901433c7de
--- /dev/null
+++ b/iac/ql/test/library-tests/azure/arm/AST.expected
@@ -0,0 +1,16 @@
+armAstNodes
+| azuredeploy.json:1:1:237:1 | { |
+armResource
+| azuredeploy.json:89:9:100:9 | { |
+| azuredeploy.json:101:9:124:9 | { |
+| azuredeploy.json:125:9:151:9 | { |
+| azuredeploy.json:152:9:177:9 | { |
+| azuredeploy.json:178:9:220:9 | { |
+| azuredeploy.json:221:9:235:9 | { |
+armResourceProps
+| azuredeploy.json:94:27:99:13 | { |
+| azuredeploy.json:107:27:123:13 | { |
+| azuredeploy.json:133:27:150:13 | { |
+| azuredeploy.json:161:27:176:13 | { |
+| azuredeploy.json:186:27:219:13 | { |
+| azuredeploy.json:229:27:234:13 | { |
diff --git a/iac/ql/test/library-tests/azure/arm/AST.ql b/iac/ql/test/library-tests/azure/arm/AST.ql
new file mode 100644
index 000000000000..4e60098807ca
--- /dev/null
+++ b/iac/ql/test/library-tests/azure/arm/AST.ql
@@ -0,0 +1,7 @@
+private import iac
+
+query predicate armAstNodes(ARM::Document n) { any() }
+
+query predicate armResource(ARM::Resource n) { any() }
+
+query predicate armResourceProps(ARM::ResourceProperties n) { any() }
diff --git a/iac/ql/test/library-tests/azure/arm/azuredeploy.json b/iac/ql/test/library-tests/azure/arm/azuredeploy.json
new file mode 100644
index 000000000000..5672cd0fee2d
--- /dev/null
+++ b/iac/ql/test/library-tests/azure/arm/azuredeploy.json
@@ -0,0 +1,237 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "adminUsername": {
+ "type": "string",
+ "metadata": {
+ "description": "Username for the Virtual Machine."
+ }
+ },
+ "dnsNameForPublicIP": {
+ "type": "string",
+ "metadata": {
+ "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
+ }
+ },
+ "vmSize": {
+ "type": "string",
+ "defaultValue": "Standard_F1",
+ "metadata": {
+ "description": "VM size for the Docker host."
+ }
+ },
+ "ubuntuOSVersion": {
+ "type": "string",
+ "defaultValue": "18.04-LTS",
+ "metadata": {
+ "description": "The Ubuntu version for deploying the Docker containers. This will pick a fully patched image of this given Ubuntu version. Allowed values: 15.10, 16.04.0-LTS, 18.04-LTS"
+ },
+ "allowedValues": [
+ "14.04.5-LTS",
+ "16.04-LTS",
+ "18.04-LTS"
+ ]
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Location for all resources."
+ }
+ },
+ "authenticationType": {
+ "type": "string",
+ "defaultValue": "sshPublicKey",
+ "allowedValues": [
+ "sshPublicKey",
+ "password"
+ ],
+ "metadata": {
+ "description": "Type of authentication to use on the Virtual Machine. SSH key is recommended."
+ }
+ },
+ "adminPasswordOrKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "SSH Key or password for the Virtual Machine. SSH key is recommended."
+ }
+ }
+ },
+ "variables": {
+ "imagePublisher": "Canonical",
+ "imageOffer": "UbuntuServer",
+ "nicName": "myVMNicD",
+ "extensionName": "DockerExtension",
+ "addressPrefix": "10.0.0.0/16",
+ "subnetName": "Subnet",
+ "subnetPrefix": "10.0.0.0/24",
+ "diskStorageType": "StandardSSD_LRS",
+ "publicIPAddressName": "myPublicIPD",
+ "publicIPAddressType": "Dynamic",
+ "vmName": "MyDockerVM",
+ "virtualNetworkName": "MyVNETD",
+ "subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('subnetName'))]",
+ "linuxConfiguration": {
+ "disablePasswordAuthentication": true,
+ "ssh": {
+ "publicKeys": [
+ {
+ "path": "[concat('/home/', parameters('adminUsername'), '/.ssh/authorized_keys')]",
+ "keyData": "[parameters('adminPasswordOrKey')]"
+ }
+ ]
+ }
+ },
+ "networkSecurityGroupName": "default-NSG"
+ },
+ "resources": [
+ {
+ "apiVersion": "2020-06-01",
+ "type": "Microsoft.Network/publicIPAddresses",
+ "name": "[variables('publicIPAddressName')]",
+ "location": "[parameters('location')]",
+ "properties": {
+ "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
+ "dnsSettings": {
+ "domainNameLabel": "[parameters('dnsNameForPublicIP')]"
+ }
+ }
+ },
+ {
+ "comments": "Default Network Security Group for template",
+ "type": "Microsoft.Network/networkSecurityGroups",
+ "apiVersion": "2020-06-01",
+ "name": "[variables('networkSecurityGroupName')]",
+ "location": "[parameters('location')]",
+ "properties": {
+ "securityRules": [
+ {
+ "name": "default-allow-22",
+ "properties": {
+ "priority": 1000,
+ "access": "Allow",
+ "direction": "Inbound",
+ "destinationPortRange": "22",
+ "protocol": "Tcp",
+ "sourceAddressPrefix": "*",
+ "sourcePortRange": "*",
+ "destinationAddressPrefix": "*"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2020-06-01",
+ "type": "Microsoft.Network/virtualNetworks",
+ "name": "[variables('virtualNetworkName')]",
+ "location": "[parameters('location')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]"
+ ],
+ "properties": {
+ "addressSpace": {
+ "addressPrefixes": [
+ "[variables('addressPrefix')]"
+ ]
+ },
+ "subnets": [
+ {
+ "name": "[variables('subnetName')]",
+ "properties": {
+ "addressPrefix": "[variables('subnetPrefix')]",
+ "networkSecurityGroup": {
+ "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2020-06-01",
+ "type": "Microsoft.Network/networkInterfaces",
+ "name": "[variables('nicName')]",
+ "location": "[parameters('location')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
+ "[resourceId('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
+ ],
+ "properties": {
+ "ipConfigurations": [
+ {
+ "name": "ipconfig1",
+ "properties": {
+ "privateIPAllocationMethod": "Dynamic",
+ "publicIPAddress": {
+ "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
+ },
+ "subnet": {
+ "id": "[variables('subnetRef')]"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2019-12-01",
+ "type": "Microsoft.Compute/virtualMachines",
+ "name": "[variables('vmName')]",
+ "location": "[parameters('location')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
+ ],
+ "properties": {
+ "hardwareProfile": {
+ "vmSize": "[parameters('vmSize')]"
+ },
+ "osProfile": {
+ "computerName": "[variables('vmName')]",
+ "adminUsername": "[parameters('adminUsername')]",
+ "adminPassword": "[parameters('adminPasswordOrKey')]",
+ "linuxConfiguration": "[if(equals(parameters('authenticationType'), 'password'), json('null'), variables('linuxConfiguration'))]"
+ },
+ "storageProfile": {
+ "imageReference": {
+ "publisher": "[variables('imagePublisher')]",
+ "offer": "[variables('imageOffer')]",
+ "sku": "[parameters('ubuntuOSVersion')]",
+ "version": "latest"
+ },
+ "osDisk": {
+ "name": "[concat(variables('vmName'),'_OSDisk')]",
+ "caching": "ReadWrite",
+ "createOption": "FromImage",
+ "managedDisk": {
+ "storageAccountType": "[variables('diskStorageType')]"
+ }
+ }
+ },
+ "networkProfile": {
+ "networkInterfaces": [
+ {
+ "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Compute/virtualMachines/extensions",
+ "name": "[concat(variables('vmName'),'/', variables('extensionName'))]",
+ "apiVersion": "2019-12-01",
+ "location": "[parameters('location')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Compute/virtualMachines/', variables('vmName'))]"
+ ],
+ "properties": {
+ "publisher": "Microsoft.Azure.Extensions",
+ "type": "DockerExtension",
+ "typeHandlerVersion": "1.0",
+ "autoUpgradeMinorVersion": true
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/iac/ql/test/library-tests/azure/pipelines/AST.expected b/iac/ql/test/library-tests/azure/pipelines/AST.expected
new file mode 100644
index 000000000000..f6c44af8a897
--- /dev/null
+++ b/iac/ql/test/library-tests/azure/pipelines/AST.expected
@@ -0,0 +1,15 @@
+adopipeline
+| azure-pipelines.yml:1:1:33:26 | Azure DevOps Pipeline |
+adopipelineVariable
+| azure-pipelines.yml:16:5:17:24 | Variable 'one' |
+adopipelineSteps
+| azure-pipelines.yml:20:5:25:2 | Azure DevOps Pipeline step |
+| azure-pipelines.yml:25:5:30:2 | Azure DevOps Pipeline step |
+| azure-pipelines.yml:30:5:33:26 | Azure DevOps Pipeline step |
+adopipelinePool
+| azure-pipelines.yml:5:3:5:25 | vmImage ... -latest |
+adopipelineTask
+| azure-pipelines.yml:20:5:25:2 | Azure DevOps Pipeline step |
+adopipelineScript
+| azure-pipelines.yml:25:5:30:2 | Azure DevOps Pipeline step |
+| azure-pipelines.yml:30:5:33:26 | Azure DevOps Pipeline step |
diff --git a/iac/ql/test/library-tests/azure/pipelines/AST.ql b/iac/ql/test/library-tests/azure/pipelines/AST.ql
new file mode 100644
index 000000000000..ca02be0e106d
--- /dev/null
+++ b/iac/ql/test/library-tests/azure/pipelines/AST.ql
@@ -0,0 +1,13 @@
+private import iac
+
+query predicate adopipeline(AzurePipelines::Document n) { any() }
+
+query predicate adopipelineVariable(AzurePipelines::Variable n) { any() }
+
+query predicate adopipelineSteps(AzurePipelines::Step n) { any() }
+
+query predicate adopipelinePool(AzurePipelines::Pool n) { any() }
+
+query predicate adopipelineTask(AzurePipelines::Task n) { any() }
+
+query predicate adopipelineScript(AzurePipelines::Script n) { any() }
diff --git a/iac/ql/test/library-tests/azure/pipelines/azure-pipelines.yml b/iac/ql/test/library-tests/azure/pipelines/azure-pipelines.yml
new file mode 100644
index 000000000000..47741c915d24
--- /dev/null
+++ b/iac/ql/test/library-tests/azure/pipelines/azure-pipelines.yml
@@ -0,0 +1,33 @@
+trigger:
+ - main
+
+pool:
+ vmImage: ubuntu-latest
+strategy:
+ matrix:
+ Python38:
+ python.version: "3.8"
+ Python39:
+ python.version: "3.9"
+ Python310:
+ python.version: "3.10"
+
+variables:
+ - name: one
+ value: initialValue
+
+steps:
+ - task: UsePythonVersion@0
+ inputs:
+ versionSpec: "$(python.version)"
+ displayName: "Use Python $(python.version)"
+
+ - script: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+ displayName: "Install dependencies"
+
+ - script: |
+ pip install pytest pytest-azurepipelines
+ pytest
+ displayName: "pytest"
diff --git a/iac/ql/test/library-tests/containers/ast/AST.expected b/iac/ql/test/library-tests/containers/ast/AST.expected
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/library-tests/containers/ast/AST.ql b/iac/ql/test/library-tests/containers/ast/AST.ql
new file mode 100644
index 000000000000..f5f4eccee0e8
--- /dev/null
+++ b/iac/ql/test/library-tests/containers/ast/AST.ql
@@ -0,0 +1,3 @@
+private import codeql.iac.ast.Container
+
+query predicate containerAstNodes(ContainerAstNode n) { any() }
diff --git a/iac/ql/test/library-tests/containers/ast/Dockerfile b/iac/ql/test/library-tests/containers/ast/Dockerfile
new file mode 100644
index 000000000000..6b7cb200b8bf
--- /dev/null
+++ b/iac/ql/test/library-tests/containers/ast/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.11-alpine
+
+COPY . .
+
+RUN pip install -r requirements.txt
+
+CMD ["python", "myapp.py"]
diff --git a/iac/ql/test/library-tests/hcl/ast/AST.expected b/iac/ql/test/library-tests/hcl/ast/AST.expected
new file mode 100644
index 000000000000..c1cb30da1b69
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/ast/AST.expected
@@ -0,0 +1,97 @@
+| sample.hcl:1:1:1:9 | Identifier |
+| sample.hcl:1:1:10:1 | terraform |
+| sample.hcl:1:1:32:1 | ??? |
+| sample.hcl:1:1:32:1 | ??? |
+| sample.hcl:2:3:2:20 | Identifier |
+| sample.hcl:2:3:7:3 | required_providers |
+| sample.hcl:2:3:9:31 | ??? |
+| sample.hcl:3:5:3:11 | Identifier |
+| sample.hcl:3:5:6:5 | ??? |
+| sample.hcl:3:5:6:5 | Attribute |
+| sample.hcl:3:15:6:5 | Object |
+| sample.hcl:4:7:4:12 | Identifier |
+| sample.hcl:4:7:4:12 | source |
+| sample.hcl:4:7:4:35 | ObjectElement |
+| sample.hcl:4:17:4:35 | hashicorp/azurerm |
+| sample.hcl:4:18:4:34 | Expr |
+| sample.hcl:5:7:5:13 | Identifier |
+| sample.hcl:5:7:5:13 | version |
+| sample.hcl:5:7:5:26 | ObjectElement |
+| sample.hcl:5:17:5:26 | ~> 3.0.2 |
+| sample.hcl:5:18:5:25 | Expr |
+| sample.hcl:9:3:9:18 | Identifier |
+| sample.hcl:9:3:9:31 | Attribute |
+| sample.hcl:9:22:9:31 | >= 1.1.0 |
+| sample.hcl:9:23:9:30 | Expr |
+| sample.hcl:12:1:12:8 | Identifier |
+| sample.hcl:12:1:14:1 | variable |
+| sample.hcl:12:10:12:20 | disk_size |
+| sample.hcl:12:11:12:19 | Expr |
+| sample.hcl:13:3:13:9 | Identifier |
+| sample.hcl:13:3:13:15 | ??? |
+| sample.hcl:13:3:13:15 | Attribute |
+| sample.hcl:13:13:13:15 | 1 |
+| sample.hcl:13:14:13:14 | Expr |
+| sample.hcl:16:1:16:8 | Identifier |
+| sample.hcl:16:1:18:1 | provider |
+| sample.hcl:16:10:16:18 | azurerm |
+| sample.hcl:16:11:16:17 | Expr |
+| sample.hcl:17:3:17:10 | Identifier |
+| sample.hcl:17:3:17:13 | ??? |
+| sample.hcl:17:3:17:13 | features |
+| sample.hcl:20:1:20:8 | Identifier |
+| sample.hcl:20:1:23:1 | resource azurerm_resource_group rg |
+| sample.hcl:20:10:20:33 | azurerm_resource_group |
+| sample.hcl:20:11:20:32 | Expr |
+| sample.hcl:20:35:20:38 | rg |
+| sample.hcl:20:36:20:37 | Expr |
+| sample.hcl:21:3:21:6 | Identifier |
+| sample.hcl:21:3:21:32 | Attribute |
+| sample.hcl:21:3:22:22 | ??? |
+| sample.hcl:21:14:21:32 | myTFResourceGroup |
+| sample.hcl:21:15:21:31 | Expr |
+| sample.hcl:22:3:22:10 | Identifier |
+| sample.hcl:22:3:22:22 | Attribute |
+| sample.hcl:22:14:22:22 | westus2 |
+| sample.hcl:22:15:22:21 | Expr |
+| sample.hcl:25:1:25:8 | Identifier |
+| sample.hcl:25:1:32:1 | resource azurerm_managed_disk example1 |
+| sample.hcl:25:10:25:31 | azurerm_managed_disk |
+| sample.hcl:25:11:25:30 | Expr |
+| sample.hcl:25:33:25:42 | example1 |
+| sample.hcl:25:34:25:41 | Expr |
+| sample.hcl:26:3:26:6 | Identifier |
+| sample.hcl:26:3:26:36 | Attribute |
+| sample.hcl:26:3:31:38 | ??? |
+| sample.hcl:26:26:26:36 | acctestmd |
+| sample.hcl:26:27:26:35 | Expr |
+| sample.hcl:27:3:27:10 | Identifier |
+| sample.hcl:27:3:27:64 | Attribute |
+| sample.hcl:27:26:27:47 | Identifier |
+| sample.hcl:27:26:27:47 | azurerm_resource_group |
+| sample.hcl:27:26:27:55 | azurerm_resource_group.example |
+| sample.hcl:27:26:27:64 | azurerm_resource_group.example.location |
+| sample.hcl:27:49:27:55 | Identifier |
+| sample.hcl:27:57:27:64 | Identifier |
+| sample.hcl:28:3:28:21 | Identifier |
+| sample.hcl:28:3:28:60 | Attribute |
+| sample.hcl:28:26:28:47 | Identifier |
+| sample.hcl:28:26:28:47 | azurerm_resource_group |
+| sample.hcl:28:26:28:55 | azurerm_resource_group.example |
+| sample.hcl:28:26:28:60 | azurerm_resource_group.example.name |
+| sample.hcl:28:49:28:55 | Identifier |
+| sample.hcl:28:57:28:60 | Identifier |
+| sample.hcl:29:3:29:22 | Identifier |
+| sample.hcl:29:3:29:39 | Attribute |
+| sample.hcl:29:26:29:39 | Standard_LRS |
+| sample.hcl:29:27:29:38 | Expr |
+| sample.hcl:30:3:30:15 | Identifier |
+| sample.hcl:30:3:30:32 | Attribute |
+| sample.hcl:30:26:30:32 | Empty |
+| sample.hcl:30:27:30:31 | Expr |
+| sample.hcl:31:3:31:14 | Identifier |
+| sample.hcl:31:3:31:38 | Attribute |
+| sample.hcl:31:26:31:28 | Identifier |
+| sample.hcl:31:26:31:28 | var |
+| sample.hcl:31:26:31:38 | var.disk_size |
+| sample.hcl:31:30:31:38 | Identifier |
diff --git a/iac/ql/test/library-tests/hcl/ast/AST.ql b/iac/ql/test/library-tests/hcl/ast/AST.ql
new file mode 100644
index 000000000000..a6a94c92e3f1
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/ast/AST.ql
@@ -0,0 +1,3 @@
+private import codeql.hcl.AST
+
+query predicate hclAstNodes(HclAstNode n) { any() }
diff --git a/iac/ql/test/library-tests/hcl/ast/Attribute.expected b/iac/ql/test/library-tests/hcl/ast/Attribute.expected
new file mode 100644
index 000000000000..030a969f7321
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/ast/Attribute.expected
@@ -0,0 +1,14 @@
+attributeExpr
+| sample.hcl:3:5:6:5 | Attribute |
+| sample.hcl:9:3:9:31 | Attribute |
+| sample.hcl:13:3:13:15 | Attribute |
+| sample.hcl:21:3:21:32 | Attribute |
+| sample.hcl:22:3:22:22 | Attribute |
+| sample.hcl:26:3:26:36 | Attribute |
+| sample.hcl:27:3:27:64 | Attribute |
+| sample.hcl:28:3:28:60 | Attribute |
+| sample.hcl:29:3:29:39 | Attribute |
+| sample.hcl:30:3:30:32 | Attribute |
+| sample.hcl:31:3:31:38 | Attribute |
+attributeRef
+| sample.hcl:31:3:31:38 | Attribute | sample.hcl:13:13:13:15 | 1 |
diff --git a/iac/ql/test/library-tests/hcl/ast/Attribute.ql b/iac/ql/test/library-tests/hcl/ast/Attribute.ql
new file mode 100644
index 000000000000..d8825e0857a4
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/ast/Attribute.ql
@@ -0,0 +1,5 @@
+private import hcl
+
+query predicate attributeExpr(Attribute a) { exists(a.getExpr()) }
+
+query predicate attributeRef(Attribute a, Expr expr) { expr = a.getReference() }
diff --git a/iac/ql/test/library-tests/hcl/ast/sample.hcl b/iac/ql/test/library-tests/hcl/ast/sample.hcl
new file mode 100644
index 000000000000..d783c61ce833
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/ast/sample.hcl
@@ -0,0 +1,32 @@
+terraform {
+ required_providers {
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "~> 3.0.2"
+ }
+ }
+
+ required_version = ">= 1.1.0"
+}
+
+variable "disk_size" {
+ default = "1"
+}
+
+provider "azurerm" {
+ features {}
+}
+
+resource "azurerm_resource_group" "rg" {
+ name = "myTFResourceGroup"
+ location = "westus2"
+}
+
+resource "azurerm_managed_disk" "example1" {
+ name = "acctestmd"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = var.disk_size
+}
\ No newline at end of file
diff --git a/iac/ql/test/library-tests/hcl/aws/AST.expected b/iac/ql/test/library-tests/hcl/aws/AST.expected
new file mode 100644
index 000000000000..a0e70e2671e5
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/aws/AST.expected
@@ -0,0 +1,14 @@
+resource
+| S3.hcl:1:1:3:1 | resource aws_s3_bucket example |
+| S3.hcl:5:1:8:1 | resource aws_s3_bucket_acl example |
+| S3.hcl:10:1:12:1 | resource aws_s3_bucket log_bucket |
+| S3.hcl:14:1:17:1 | resource aws_s3_bucket_acl log_bucket_acl |
+| S3.hcl:19:1:24:1 | resource aws_s3_bucket_logging example |
+buckets
+| S3.hcl:1:1:3:1 | resource aws_s3_bucket example |
+| S3.hcl:10:1:12:1 | resource aws_s3_bucket log_bucket |
+bucketsLogging
+| S3.hcl:1:1:3:1 | resource aws_s3_bucket example | S3.hcl:19:1:24:1 | resource aws_s3_bucket_logging example |
+bucketsAcl
+| S3.hcl:1:1:3:1 | resource aws_s3_bucket example | S3.hcl:5:1:8:1 | resource aws_s3_bucket_acl example |
+| S3.hcl:10:1:12:1 | resource aws_s3_bucket log_bucket | S3.hcl:14:1:17:1 | resource aws_s3_bucket_acl log_bucket_acl |
diff --git a/iac/ql/test/library-tests/hcl/aws/AST.ql b/iac/ql/test/library-tests/hcl/aws/AST.ql
new file mode 100644
index 000000000000..9ef3581e9202
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/aws/AST.ql
@@ -0,0 +1,11 @@
+import hcl
+
+query predicate resource(AWS::AwsResource n) { any() }
+
+query predicate buckets(AWS::S3Bucket n) { any() }
+
+query predicate bucketsLogging(AWS::S3Bucket n, AWS::S3BucketLogging log) {
+ any() and log = n.getLogging()
+}
+
+query predicate bucketsAcl(AWS::S3Bucket n, AWS::S3BucketAcl acl) { any() and acl = n.getAcl() }
diff --git a/iac/ql/test/library-tests/hcl/aws/S3.hcl b/iac/ql/test/library-tests/hcl/aws/S3.hcl
new file mode 100644
index 000000000000..05c881ff4428
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/aws/S3.hcl
@@ -0,0 +1,24 @@
+resource "aws_s3_bucket" "example" {
+ bucket = "my-tf-example-bucket"
+}
+
+resource "aws_s3_bucket_acl" "example" {
+ bucket = aws_s3_bucket.example.id
+ acl = "private"
+}
+
+resource "aws_s3_bucket" "log_bucket" {
+ bucket = "my-tf-log-bucket"
+}
+
+resource "aws_s3_bucket_acl" "log_bucket_acl" {
+ bucket = aws_s3_bucket.log_bucket.id
+ acl = "log-delivery-write"
+}
+
+resource "aws_s3_bucket_logging" "example" {
+ bucket = aws_s3_bucket.example.id
+
+ target_bucket = aws_s3_bucket.log_bucket.id
+ target_prefix = "log/"
+}
diff --git a/iac/ql/test/library-tests/hcl/azure/azure.expected b/iac/ql/test/library-tests/hcl/azure/azure.expected
new file mode 100644
index 000000000000..4485f0a7bcc2
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/azure/azure.expected
@@ -0,0 +1,13 @@
+azure
+| databases.hcl:1:1:4:1 | ResourceGroup example-resources |
+| databases.hcl:6:1:11:1 | Database myexamplesqlserver |
+| databases.hcl:13:1:18:1 | Database mariadb-svr |
+| databases.hcl:20:1:25:1 | Database example-sqlserver |
+| storage.hcl:1:1:4:1 | ResourceGroup example-resources |
+| storage.hcl:6:1:13:1 | ManagedDisk acctestmd |
+azureManagedDisk
+| storage.hcl:6:1:13:1 | ManagedDisk acctestmd |
+azureDatabases
+| databases.hcl:6:1:11:1 | Database myexamplesqlserver |
+| databases.hcl:13:1:18:1 | Database mariadb-svr |
+| databases.hcl:20:1:25:1 | Database example-sqlserver |
diff --git a/iac/ql/test/library-tests/hcl/azure/azure.ql b/iac/ql/test/library-tests/hcl/azure/azure.ql
new file mode 100644
index 000000000000..98832b5acf73
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/azure/azure.ql
@@ -0,0 +1,7 @@
+import hcl
+
+query predicate azure(Azure::AzureResource r) { any() }
+
+query predicate azureManagedDisk(Azure::ManagedDisk md) { any() }
+
+query predicate azureDatabases(Azure::Database db) { any() }
diff --git a/iac/ql/test/library-tests/hcl/azure/databases.hcl b/iac/ql/test/library-tests/hcl/azure/databases.hcl
new file mode 100644
index 000000000000..9f6cd66d2b64
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/azure/databases.hcl
@@ -0,0 +1,25 @@
+resource "azurerm_resource_group" "example" {
+ name = "example-resources"
+ location = "West Europe"
+}
+
+resource "azurerm_sql_server" "example" {
+ name = "myexamplesqlserver"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ version = "12.0"
+}
+
+resource "azurerm_mariadb_server" "example" {
+ name = "mariadb-svr"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ version = "10.2"
+}
+
+resource "azurerm_mssql_server" "example" {
+ name = "example-sqlserver"
+ resource_group_name = azurerm_resource_group.example.name
+ location = azurerm_resource_group.example.location
+ version = "12.0"
+}
diff --git a/iac/ql/test/library-tests/hcl/azure/storage.hcl b/iac/ql/test/library-tests/hcl/azure/storage.hcl
new file mode 100644
index 000000000000..05db553a9628
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/azure/storage.hcl
@@ -0,0 +1,13 @@
+resource "azurerm_resource_group" "example" {
+ name = "example-resources"
+ location = "West Europe"
+}
+
+resource "azurerm_managed_disk" "example1" {
+ name = "acctestmd"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+}
diff --git a/iac/ql/test/library-tests/hcl/gcp/gcp.expected b/iac/ql/test/library-tests/hcl/gcp/gcp.expected
new file mode 100644
index 000000000000..8085d930c397
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/gcp/gcp.expected
@@ -0,0 +1,9 @@
+gcp
+| storage.tf:1:1:5:1 | resource google_storage_bucket_access_control public_rule |
+| storage.tf:7:1:10:1 | resource google_storage_bucket bucket |
+storage
+| storage.tf:7:1:10:1 | resource google_storage_bucket bucket |
+accessControl
+| storage.tf:1:1:5:1 | resource google_storage_bucket_access_control public_rule |
+storageAccess
+| storage.tf:7:1:10:1 | resource google_storage_bucket bucket | storage.tf:1:1:5:1 | resource google_storage_bucket_access_control public_rule |
diff --git a/iac/ql/test/library-tests/hcl/gcp/gcp.ql b/iac/ql/test/library-tests/hcl/gcp/gcp.ql
new file mode 100644
index 000000000000..aa71d3fc3730
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/gcp/gcp.ql
@@ -0,0 +1,11 @@
+import hcl
+
+query predicate gcp(GCP::GcpResource r) { any() }
+
+query predicate storage(GCP::StorageBucket b) { any() }
+
+query predicate accessControl(GCP::StorageBucketAccessControl r) { any() }
+
+query predicate storageAccess(GCP::StorageBucket b, GCP::StorageBucketAccessControl r) {
+ any() and b.getAccessControl() = r
+}
diff --git a/iac/ql/test/library-tests/hcl/gcp/storage.tf b/iac/ql/test/library-tests/hcl/gcp/storage.tf
new file mode 100644
index 000000000000..d31669b00fdc
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/gcp/storage.tf
@@ -0,0 +1,10 @@
+resource "google_storage_bucket_access_control" "public_rule" {
+ bucket = google_storage_bucket.bucket.name
+ role = "READER"
+ entity = "allUsers"
+}
+
+resource "google_storage_bucket" "bucket" {
+ name = "static-content-bucket"
+ location = "US"
+}
diff --git a/iac/ql/test/library-tests/hcl/helm/helm.expected b/iac/ql/test/library-tests/hcl/helm/helm.expected
new file mode 100644
index 000000000000..45d6b253c7a5
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/helm/helm.expected
@@ -0,0 +1,8 @@
+helm
+| provider.tf:21:1:31:1 | resource helm_release nginx-ingress-controller |
+provider
+| provider.tf:1:1:19:1 | provider |
+release
+| provider.tf:21:1:31:1 | resource helm_release nginx-ingress-controller |
+releaseSets
+| provider.tf:21:1:31:1 | resource helm_release nginx-ingress-controller | provider.tf:27:3:30:3 | set |
diff --git a/iac/ql/test/library-tests/hcl/helm/helm.ql b/iac/ql/test/library-tests/hcl/helm/helm.ql
new file mode 100644
index 000000000000..9473960dcffb
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/helm/helm.ql
@@ -0,0 +1,11 @@
+import hcl
+
+query predicate helm(TerraformHelmChart::HelmResource r) { any() }
+
+query predicate provider(TerraformHelmChart::HelmProvider p) { any() }
+
+query predicate release(TerraformHelmChart::Release r) { any() }
+
+query predicate releaseSets(TerraformHelmChart::Release r, TerraformHelmChart::ReleaseSet s) {
+ r.getSets() = s
+}
diff --git a/iac/ql/test/library-tests/hcl/helm/provider.tf b/iac/ql/test/library-tests/hcl/helm/provider.tf
new file mode 100644
index 000000000000..d315e96ae6c4
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/helm/provider.tf
@@ -0,0 +1,31 @@
+provider "helm" {
+ kubernetes {
+ config_path = "~/.kube/config"
+ }
+
+ # localhost registry with password protection
+ registry {
+ url = "oci://localhost:5000"
+ username = "username"
+ password = "password"
+ }
+
+ # private registry
+ registry {
+ url = "oci://private.registry"
+ username = "username"
+ password = "password"
+ }
+}
+
+resource "helm_release" "nginx_ingress" {
+ name = "nginx-ingress-controller"
+
+ repository = "https://charts.bitnami.com/bitnami"
+ chart = "nginx-ingress-controller"
+
+ set {
+ name = "service.type"
+ value = "ClusterIP"
+ }
+}
diff --git a/iac/ql/test/library-tests/hcl/terraform/Dependencies.expected b/iac/ql/test/library-tests/hcl/terraform/Dependencies.expected
new file mode 100644
index 000000000000..b10e469b5dd8
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/terraform/Dependencies.expected
@@ -0,0 +1,8 @@
+dependencies
+| providers.tf:5:15:8:5 | hashicorp/azurerm@3.0.0 |
+| providers.tf:9:14:9:22 | hashicorp/time@0.7.2 |
+| providers.tf:10:14:10:22 | hashicorp/random@3.3.1 |
+semver
+| providers.tf:5:15:8:5 | hashicorp/azurerm@3.0.0 | =3.0.0 |
+| providers.tf:9:14:9:22 | hashicorp/time@0.7.2 | ~>0.7.2 |
+| providers.tf:10:14:10:22 | hashicorp/random@3.3.1 | ~>3.3.1 |
diff --git a/iac/ql/test/library-tests/hcl/terraform/Dependencies.ql b/iac/ql/test/library-tests/hcl/terraform/Dependencies.ql
new file mode 100644
index 000000000000..03451cff8808
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/terraform/Dependencies.ql
@@ -0,0 +1,5 @@
+import hcl
+
+query predicate dependencies(Dependency d) { any() }
+
+query predicate semver(Dependency d, SemanticVersion v) { d.getSemanticVersion() = v }
diff --git a/iac/ql/test/library-tests/hcl/terraform/Providers.expected b/iac/ql/test/library-tests/hcl/terraform/Providers.expected
new file mode 100644
index 000000000000..b6c72d22dab2
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/terraform/Providers.expected
@@ -0,0 +1,8 @@
+files
+| providers.tf:0:0:0:0 | providers.tf |
+terraformSettings
+| providers.tf:3:1:12:1 | terraform |
+requiredProvider
+| providers.tf:5:15:8:5 | RequiredProvider azurerm |
+| providers.tf:9:14:9:22 | RequiredProvider time |
+| providers.tf:10:14:10:22 | RequiredProvider random |
diff --git a/iac/ql/test/library-tests/hcl/terraform/Providers.ql b/iac/ql/test/library-tests/hcl/terraform/Providers.ql
new file mode 100644
index 000000000000..cb6ed7a30d94
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/terraform/Providers.ql
@@ -0,0 +1,8 @@
+private import codeql.hcl.Terraform
+private import codeql.iac.Dependencies
+
+query predicate files(Terraform::TerraformFile f) { any() }
+
+query predicate terraformSettings(Terraform::Terraform t) { any() }
+
+query predicate requiredProvider(Terraform::RequiredProvider p) { any() }
diff --git a/iac/ql/test/library-tests/hcl/terraform/providers.tf b/iac/ql/test/library-tests/hcl/terraform/providers.tf
new file mode 100644
index 000000000000..d7fe35bf0a3a
--- /dev/null
+++ b/iac/ql/test/library-tests/hcl/terraform/providers.tf
@@ -0,0 +1,18 @@
+# We strongly recommend using the required_providers block to set the
+# Azure Provider source and version being used
+terraform {
+ required_providers {
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "=3.0.0"
+ }
+ time = "~>0.7.2"
+ random = "~>3.3.1"
+ }
+}
+
+# Configure the Microsoft Azure Provider
+provider "azurerm" {
+ skip_provider_registration = true
+ features {}
+}
diff --git a/iac/ql/test/library-tests/helmcharts/ast/AST.expected b/iac/ql/test/library-tests/helmcharts/ast/AST.expected
new file mode 100644
index 000000000000..2ebc162105b6
--- /dev/null
+++ b/iac/ql/test/library-tests/helmcharts/ast/AST.expected
@@ -0,0 +1,26 @@
+helmchart
+| Chart.yml:1:1:30:26 | HelmChart Document |
+| random.yml:1:1:11:13 | HelmChart Document |
+helmchartValues
+| values.yaml:2:1:104:23 | HelmChart Value Document |
+| values.yaml:4:3:10:27 | HelmChart Value Document |
+| values.yaml:12:15:12:16 | HelmChart Value Document |
+| values.yaml:17:3:104:23 | HelmChart Value Document |
+| values.yaml:36:16:36:17 | HelmChart Value Document |
+| values.yaml:38:11:38:12 | HelmChart Value Document |
+| values.yaml:40:19:40:20 | HelmChart Value Document |
+| values.yaml:42:14:42:15 | HelmChart Value Document |
+| values.yaml:79:5:79:6 | HelmChart Value Document |
+| values.yaml:95:5:95:6 | HelmChart Value Document |
+helmchartAssignedValues
+helmchartSpec
+| Chart.yml:25:3:30:26 | securityContext: |
+helmchartContainers
+| Chart.yml:29:7:30:26 | Container |
+helmchartContainerSecurityContext
+| Chart.yml:26:5:28:2 | SecurityContext |
+helmchartDeps
+| Chart.yml:14:5:17:2 | nginx |
+| Chart.yml:17:5:19:53 | memcached |
+helmchartData
+| Chart.yml:22:3:22:26 | HelmData |
diff --git a/iac/ql/test/library-tests/helmcharts/ast/AST.ql b/iac/ql/test/library-tests/helmcharts/ast/AST.ql
new file mode 100644
index 000000000000..4537b0302c91
--- /dev/null
+++ b/iac/ql/test/library-tests/helmcharts/ast/AST.ql
@@ -0,0 +1,17 @@
+private import iac
+
+query predicate helmchart(HelmChart::Document n) { any() }
+
+query predicate helmchartValues(HelmChartValues::Document n) { any() }
+
+query predicate helmchartAssignedValues(HelmChart::Document doc) { exists(doc.getValues()) }
+
+query predicate helmchartSpec(HelmChart::Spec n) { any() }
+
+query predicate helmchartContainers(HelmChart::Container n) { any() }
+
+query predicate helmchartContainerSecurityContext(HelmChart::SecurityContext n) { any() }
+
+query predicate helmchartDeps(HelmChart::Dependency n) { any() }
+
+query predicate helmchartData(HelmChart::HelmData n) { any() }
diff --git a/iac/ql/test/library-tests/helmcharts/ast/Chart.yml b/iac/ql/test/library-tests/helmcharts/ast/Chart.yml
new file mode 100644
index 000000000000..979ebb0b6d7f
--- /dev/null
+++ b/iac/ql/test/library-tests/helmcharts/ast/Chart.yml
@@ -0,0 +1,30 @@
+apiVersion: v2
+name: hello-world
+description: A Helm chart for Kubernetes
+
+# app type
+type: application
+
+# version info
+version: 0.1.0
+appVersion: "1.16.0"
+
+# https://helm.sh/docs/helm/helm_dependency/
+dependencies:
+ - name: nginx
+ version: "1.2.3"
+ repository: "https://example.com/charts"
+ - name: memcached
+ version: "3.2.1"
+ repository: "https://another.example.com/charts"
+
+data:
+ SomeData: "Hello world"
+
+spec:
+ securityContext:
+ runAsUser: 1000
+ runAsGroup: 1000
+ containers:
+ - name: example1
+ image: busybox:1.28
diff --git a/iac/ql/test/library-tests/helmcharts/ast/misc.yml b/iac/ql/test/library-tests/helmcharts/ast/misc.yml
new file mode 100644
index 000000000000..85b62875a988
--- /dev/null
+++ b/iac/ql/test/library-tests/helmcharts/ast/misc.yml
@@ -0,0 +1,5 @@
+this:
+ is: a
+ random: file
+with:
+ random: content
diff --git a/iac/ql/test/library-tests/helmcharts/ast/random.yml b/iac/ql/test/library-tests/helmcharts/ast/random.yml
new file mode 100644
index 000000000000..bc5775b09af8
--- /dev/null
+++ b/iac/ql/test/library-tests/helmcharts/ast/random.yml
@@ -0,0 +1,11 @@
+apiVersion: v2
+kind: Chart
+metadata:
+ name: random
+ version: 0.1.0
+ appVersion: 1.16.0
+ description: A Helm chart for Kubernetes
+ type: application
+ maintainers:
+ - name: The Helm Team
+ email:
diff --git a/iac/ql/test/library-tests/helmcharts/ast/values.yaml b/iac/ql/test/library-tests/helmcharts/ast/values.yaml
new file mode 100644
index 000000000000..0fcc462dd909
--- /dev/null
+++ b/iac/ql/test/library-tests/helmcharts/ast/values.yaml
@@ -0,0 +1,104 @@
+# Default values for Traefik
+image:
+ # -- Traefik image host registry
+ registry: docker.io
+ # -- Traefik image repository
+ repository: traefik
+ # -- defaults to appVersion
+ tag: ""
+ # -- Traefik image pull policy
+ pullPolicy: IfNotPresent
+
+commonLabels: {}
+
+# Configure the deployment
+deployment:
+ # -- Enable deployment
+ enabled: true
+ # -- Deployment or DaemonSet
+ kind: Deployment
+ # -- Number of pods of the deployment (only applies when kind == Deployment)
+ replicas: 1
+ # -- Number of old history to retain to allow rollback (If not set, default Kubernetes value is set to 10)
+ # revisionHistoryLimit: 1
+ # -- Amount of time (in seconds) before Kubernetes will send the SIGKILL signal if Traefik does not shut down
+ terminationGracePeriodSeconds: 60
+ # -- The minimum number of seconds Traefik needs to be up and running before the DaemonSet/Deployment controller considers it available
+ minReadySeconds: 0
+ ## Override the liveness/readiness port. This is useful to integrate traefik
+ ## with an external Load Balancer that performs healthchecks.
+ ## Default: ports.traefik.port
+ # healthchecksPort: 9000
+ ## Override the liveness/readiness scheme. Useful for getting ping to
+ ## respond on websecure entryPoint.
+ # healthchecksScheme: HTTPS
+ # -- Additional deployment annotations (e.g. for jaeger-operator sidecar injection)
+ annotations: {}
+ # -- Additional deployment labels (e.g. for filtering deployment by custom labels)
+ labels: {}
+ # -- Additional pod annotations (e.g. for mesh injection or prometheus scraping)
+ podAnnotations: {}
+ # -- Additional Pod labels (e.g. for filtering Pod by custom labels)
+ podLabels: {}
+ # -- Additional containers (e.g. for metric offloading sidecars)
+ additionalContainers:
+ []
+ # https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=host
+ # - name: socat-proxy
+ # image: alpine/socat:1.0.5
+ # args: ["-s", "-u", "udp-recv:8125", "unix-sendto:/socket/socket"]
+ # volumeMounts:
+ # - name: dsdsocket
+ # mountPath: /socket
+ # -- Additional volumes available for use with initContainers and additionalContainers
+ additionalVolumes:
+ []
+ # - name: dsdsocket
+ # hostPath:
+ # path: /var/run/statsd-exporter
+ # -- Additional initContainers (e.g. for setting file permission as shown below)
+ initContainers:
+ []
+ # The "volume-permissions" init container is required if you run into permission issues.
+ # Related issue: https://github.com/traefik/traefik-helm-chart/issues/396
+ # - name: volume-permissions
+ # image: busybox:latest
+ # command: ["sh", "-c", "touch /data/acme.json; chmod -v 600 /data/acme.json"]
+ # securityContext:
+ # runAsNonRoot: true
+ # runAsGroup: 65532
+ # runAsUser: 65532
+ # volumeMounts:
+ # - name: data
+ # mountPath: /data
+ # -- Use process namespace sharing
+ shareProcessNamespace: false
+ # -- Custom pod DNS policy. Apply if `hostNetwork: true`
+ # dnsPolicy: ClusterFirstWithHostNet
+ dnsConfig:
+ {}
+ # nameservers:
+ # - 192.0.2.1 # this is an example
+ # searches:
+ # - ns1.svc.cluster-domain.example
+ # - my.dns.search.suffix
+ # options:
+ # - name: ndots
+ # value: "2"
+ # - name: edns0
+ # -- Additional imagePullSecrets
+ imagePullSecrets:
+ []
+ # - name: myRegistryKeySecretName
+ # -- Pod lifecycle actions
+ lifecycle:
+ {}
+ # preStop:
+ # exec:
+ # command: ["/bin/sh", "-c", "sleep 40"]
+ # postStart:
+ # httpGet:
+ # path: /ping
+ # port: 9000
+ # host: localhost
+ # scheme: HTTP
diff --git a/iac/ql/test/library-tests/openapi/ast/AST.expected b/iac/ql/test/library-tests/openapi/ast/AST.expected
new file mode 100644
index 000000000000..567f2ec29d29
--- /dev/null
+++ b/iac/ql/test/library-tests/openapi/ast/AST.expected
@@ -0,0 +1,10 @@
+oapi
+| swagger.yml:1:1:24:31 | OpenApi Document |
+| uber.json:1:1:370:1 | OpenApi Document |
+oapiInfo
+| swagger.yml:3:3:5:17 | OpenApi Information |
+| uber.json:3:13:7:5 | OpenApi Information |
+oapiServers
+| swagger.yml:7:3:10:87 | OpenApi Servers |
+oapiPaths
+| swagger.yml:12:3:24:31 | OpenApi Path |
diff --git a/iac/ql/test/library-tests/openapi/ast/AST.ql b/iac/ql/test/library-tests/openapi/ast/AST.ql
new file mode 100644
index 000000000000..88fe9723e6c7
--- /dev/null
+++ b/iac/ql/test/library-tests/openapi/ast/AST.ql
@@ -0,0 +1,9 @@
+private import iac::OpenApi
+
+query predicate oapi(Document n) { any() }
+
+query predicate oapiInfo(Info n) { any() }
+
+query predicate oapiServers(Servers n) { any() }
+
+query predicate oapiPaths(Path n) { any() }
diff --git a/iac/ql/test/library-tests/openapi/ast/README.md b/iac/ql/test/library-tests/openapi/ast/README.md
new file mode 100644
index 000000000000..67fd23465ad0
--- /dev/null
+++ b/iac/ql/test/library-tests/openapi/ast/README.md
@@ -0,0 +1,3 @@
+# Resources
+
+- https://github.com/OAI/OpenAPI-Specification/blob/9dff244e5708fbe16e768738f4f17cf3fddf4066/examples/v2.0/json/uber.json
diff --git a/iac/ql/test/library-tests/openapi/ast/swagger.yml b/iac/ql/test/library-tests/openapi/ast/swagger.yml
new file mode 100644
index 000000000000..8a975fa281ca
--- /dev/null
+++ b/iac/ql/test/library-tests/openapi/ast/swagger.yml
@@ -0,0 +1,24 @@
+openapi: 3.0.0
+info:
+ title: Sample API
+ description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
+ version: 0.1.9
+servers:
+ - url: http://api.example.com/v1
+ description: Optional server description, e.g. Main (production) server
+ - url: http://staging-api.example.com
+ description: Optional server description, e.g. Internal staging server for testing
+paths:
+ /users:
+ get:
+ summary: Returns a list of users.
+ description: Optional extended description in CommonMark or HTML.
+ responses:
+ "200": # status code
+ description: A JSON array of user names
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
diff --git a/iac/ql/test/library-tests/openapi/ast/uber.json b/iac/ql/test/library-tests/openapi/ast/uber.json
new file mode 100644
index 000000000000..e1272b232846
--- /dev/null
+++ b/iac/ql/test/library-tests/openapi/ast/uber.json
@@ -0,0 +1,370 @@
+{
+ "swagger": "2.0",
+ "info": {
+ "title": "Uber API",
+ "description": "Move your app forward with the Uber API",
+ "version": "1.0.0"
+ },
+ "host": "api.uber.com",
+ "schemes": [
+ "https"
+ ],
+ "basePath": "/v1",
+ "produces": [
+ "application/json"
+ ],
+ "paths": {
+ "/products": {
+ "get": {
+ "summary": "Product Types",
+ "description": "The Products endpoint returns information about the Uber products offered at a given location. The response includes the display name and other details about each product, and lists the products in the proper display order.",
+ "parameters": [
+ {
+ "name": "latitude",
+ "in": "query",
+ "description": "Latitude component of location.",
+ "required": true,
+ "type": "number",
+ "format": "double"
+ },
+ {
+ "name": "longitude",
+ "in": "query",
+ "description": "Longitude component of location.",
+ "required": true,
+ "type": "number",
+ "format": "double"
+ }
+ ],
+ "tags": [
+ "Products"
+ ],
+ "responses": {
+ "200": {
+ "description": "An array of products",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Product"
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
+ "/estimates/price": {
+ "get": {
+ "summary": "Price Estimates",
+ "description": "The Price Estimates endpoint returns an estimated price range for each product offered at a given location. The price estimate is provided as a formatted string with the full price range and the localized currency symbol.
The response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code for situations requiring currency conversion. When surge is active for a particular product, its surge_multiplier will be greater than 1, but the price estimate already factors in this multiplier.",
+ "parameters": [
+ {
+ "name": "start_latitude",
+ "in": "query",
+ "description": "Latitude component of start location.",
+ "required": true,
+ "type": "number",
+ "format": "double"
+ },
+ {
+ "name": "start_longitude",
+ "in": "query",
+ "description": "Longitude component of start location.",
+ "required": true,
+ "type": "number",
+ "format": "double"
+ },
+ {
+ "name": "end_latitude",
+ "in": "query",
+ "description": "Latitude component of end location.",
+ "required": true,
+ "type": "number",
+ "format": "double"
+ },
+ {
+ "name": "end_longitude",
+ "in": "query",
+ "description": "Longitude component of end location.",
+ "required": true,
+ "type": "number",
+ "format": "double"
+ }
+ ],
+ "tags": [
+ "Estimates"
+ ],
+ "responses": {
+ "200": {
+ "description": "An array of price estimates by product",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/PriceEstimate"
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
+ "/estimates/time": {
+ "get": {
+ "summary": "Time Estimates",
+ "description": "The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs.",
+ "parameters": [
+ {
+ "name": "start_latitude",
+ "in": "query",
+ "description": "Latitude component of start location.",
+ "required": true,
+ "type": "number",
+ "format": "double"
+ },
+ {
+ "name": "start_longitude",
+ "in": "query",
+ "description": "Longitude component of start location.",
+ "required": true,
+ "type": "number",
+ "format": "double"
+ },
+ {
+ "name": "customer_uuid",
+ "in": "query",
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique customer identifier to be used for experience customization."
+ },
+ {
+ "name": "product_id",
+ "in": "query",
+ "type": "string",
+ "description": "Unique identifier representing a specific product for a given latitude & longitude."
+ }
+ ],
+ "tags": [
+ "Estimates"
+ ],
+ "responses": {
+ "200": {
+ "description": "An array of products",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Product"
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
+ "/me": {
+ "get": {
+ "summary": "User Profile",
+ "description": "The User Profile endpoint returns information about the Uber user that has authorized with the application.",
+ "tags": [
+ "User"
+ ],
+ "responses": {
+ "200": {
+ "description": "Profile information for a user",
+ "schema": {
+ "$ref": "#/definitions/Profile"
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
+ "/history": {
+ "get": {
+ "summary": "User Activity",
+ "description": "The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.
The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary.",
+ "parameters": [
+ {
+ "name": "offset",
+ "in": "query",
+ "type": "integer",
+ "format": "int32",
+ "description": "Offset the list of returned results by this amount. Default is zero."
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "type": "integer",
+ "format": "int32",
+ "description": "Number of items to retrieve. Default is 5, maximum is 100."
+ }
+ ],
+ "tags": [
+ "User"
+ ],
+ "responses": {
+ "200": {
+ "description": "History information for the given user",
+ "schema": {
+ "$ref": "#/definitions/Activities"
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "Product": {
+ "properties": {
+ "product_id": {
+ "type": "string",
+ "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles."
+ },
+ "description": {
+ "type": "string",
+ "description": "Description of product."
+ },
+ "display_name": {
+ "type": "string",
+ "description": "Display name of product."
+ },
+ "capacity": {
+ "type": "string",
+ "description": "Capacity of product. For example, 4 people."
+ },
+ "image": {
+ "type": "string",
+ "description": "Image URL representing the product."
+ }
+ }
+ },
+ "PriceEstimate": {
+ "properties": {
+ "product_id": {
+ "type": "string",
+ "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles"
+ },
+ "currency_code": {
+ "type": "string",
+ "description": "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code."
+ },
+ "display_name": {
+ "type": "string",
+ "description": "Display name of product."
+ },
+ "estimate": {
+ "type": "string",
+ "description": "Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or \"Metered\" for TAXI."
+ },
+ "low_estimate": {
+ "type": "number",
+ "description": "Lower bound of the estimated price."
+ },
+ "high_estimate": {
+ "type": "number",
+ "description": "Upper bound of the estimated price."
+ },
+ "surge_multiplier": {
+ "type": "number",
+ "description": "Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier."
+ }
+ }
+ },
+ "Profile": {
+ "properties": {
+ "first_name": {
+ "type": "string",
+ "description": "First name of the Uber user."
+ },
+ "last_name": {
+ "type": "string",
+ "description": "Last name of the Uber user."
+ },
+ "email": {
+ "type": "string",
+ "description": "Email address of the Uber user"
+ },
+ "picture": {
+ "type": "string",
+ "description": "Image URL of the Uber user."
+ },
+ "promo_code": {
+ "type": "string",
+ "description": "Promo code of the Uber user."
+ }
+ }
+ },
+ "Activity": {
+ "properties": {
+ "uuid": {
+ "type": "string",
+ "description": "Unique identifier for the activity"
+ }
+ }
+ },
+ "Activities": {
+ "properties": {
+ "offset": {
+ "type": "integer",
+ "format": "int32",
+ "description": "Position in pagination."
+ },
+ "limit": {
+ "type": "integer",
+ "format": "int32",
+ "description": "Number of items to retrieve (100 max)."
+ },
+ "count": {
+ "type": "integer",
+ "format": "int32",
+ "description": "Total number of items available."
+ },
+ "history": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Activity"
+ }
+ }
+ }
+ },
+ "Error": {
+ "properties": {
+ "code": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "message": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "string"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/iac/ql/test/library-tests/yaml/ast/AST.expected b/iac/ql/test/library-tests/yaml/ast/AST.expected
new file mode 100644
index 000000000000..a196729928f8
--- /dev/null
+++ b/iac/ql/test/library-tests/yaml/ast/AST.expected
@@ -0,0 +1,37 @@
+| Chart.yml:1:1:1:10 | apiVersion |
+| Chart.yml:1:1:7:21 | HelmChart Document |
+| Chart.yml:1:13:1:14 | v2 |
+| Chart.yml:2:1:2:4 | name |
+| Chart.yml:2:7:2:17 | hello-world |
+| Chart.yml:3:1:3:11 | description |
+| Chart.yml:3:14:3:40 | A Helm ... ernetes |
+| Chart.yml:5:1:5:4 | type |
+| Chart.yml:5:7:5:17 | application |
+| Chart.yml:6:1:6:7 | version |
+| Chart.yml:6:10:6:14 | 0.1.0 |
+| Chart.yml:7:1:7:10 | appVersion |
+| Chart.yml:7:13:7:20 | "1.16.0" |
+| sample.json:1:1:8:1 | { |
+| sample.json:2:5:2:10 | "this" |
+| sample.json:2:13:2:16 | "is" |
+| sample.json:3:5:3:7 | "a" |
+| sample.json:3:10:6:5 | { |
+| sample.json:4:9:4:16 | "sample" |
+| sample.json:4:19:4:24 | "json" |
+| sample.json:5:9:5:14 | "file" |
+| sample.json:5:17:5:17 | 4 |
+| sample.json:7:5:7:13 | "testing" |
+| sample.json:7:16:7:19 | true |
+| sample.yml:1:1:1:4 | this |
+| sample.yml:1:1:6:11 | this: is |
+| sample.yml:1:7:1:8 | is |
+| sample.yml:2:1:2:1 | a |
+| sample.yml:3:3:3:6 | test |
+| sample.yml:3:3:4:13 | test: yaml |
+| sample.yml:3:9:3:12 | yaml |
+| sample.yml:4:3:4:6 | file |
+| sample.yml:4:9:4:12 | true |
+| sample.yml:5:1:5:4 | with |
+| sample.yml:6:3:6:7 | lines |
+| sample.yml:6:3:6:11 | lines: 7 |
+| sample.yml:6:10:6:10 | 7 |
diff --git a/iac/ql/test/library-tests/yaml/ast/AST.ql b/iac/ql/test/library-tests/yaml/ast/AST.ql
new file mode 100644
index 000000000000..e91d0986c6e0
--- /dev/null
+++ b/iac/ql/test/library-tests/yaml/ast/AST.ql
@@ -0,0 +1,3 @@
+private import iac
+
+query predicate yamlAstNodes(YamlNode n) { any() }
diff --git a/iac/ql/test/library-tests/yaml/ast/Chart.yml b/iac/ql/test/library-tests/yaml/ast/Chart.yml
new file mode 100644
index 000000000000..fce6b29e33a2
--- /dev/null
+++ b/iac/ql/test/library-tests/yaml/ast/Chart.yml
@@ -0,0 +1,7 @@
+apiVersion: v2
+name: hello-world
+description: A Helm chart for Kubernetes
+
+type: application
+version: 0.1.0
+appVersion: "1.16.0"
diff --git a/iac/ql/test/library-tests/yaml/ast/sample.json b/iac/ql/test/library-tests/yaml/ast/sample.json
new file mode 100644
index 000000000000..ced66775f7ff
--- /dev/null
+++ b/iac/ql/test/library-tests/yaml/ast/sample.json
@@ -0,0 +1,8 @@
+{
+ "this": "is",
+ "a": {
+ "sample": "json",
+ "file": 4
+ },
+ "testing": true
+}
\ No newline at end of file
diff --git a/iac/ql/test/library-tests/yaml/ast/sample.yml b/iac/ql/test/library-tests/yaml/ast/sample.yml
new file mode 100644
index 000000000000..b3ead40857ac
--- /dev/null
+++ b/iac/ql/test/library-tests/yaml/ast/sample.yml
@@ -0,0 +1,6 @@
+this: is
+a:
+ test: yaml
+ file: true
+with:
+ lines: 7
diff --git a/iac/ql/test/qlpack.yml b/iac/ql/test/qlpack.yml
new file mode 100644
index 000000000000..293ada09563e
--- /dev/null
+++ b/iac/ql/test/qlpack.yml
@@ -0,0 +1,10 @@
+name: advanced-security/iac-tests
+groups: [iac, test]
+dependencies:
+dependencies:
+ advanced-security/iac-all: ${workspace}
+ advanced-security/iac-queries: ${workspace}
+extractor: iac
+extractor: iac
+tests: .
+warnOnImplicitThis: true
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/ContainerInsights.expected b/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/ContainerInsights.expected
new file mode 100644
index 000000000000..3ae18e18ff93
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/ContainerInsights.expected
@@ -0,0 +1 @@
+| ecs.yml:41:5:47:2 | CloudFormation ECS Cluster | ECS Cluster should have cluster settings enabled |
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/ContainerInsights.qlref b/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/ContainerInsights.qlref
new file mode 100644
index 000000000000..73c892c3a8d7
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/ContainerInsights.qlref
@@ -0,0 +1 @@
+security/CloudFormation/ECS/ContainerInsights.ql
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/ecs.yml b/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/ecs.yml
new file mode 100644
index 000000000000..febb0c75378c
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/ecs.yml
@@ -0,0 +1,71 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+
+Resources:
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Sub '${image}:${version}'
+ Name: !Ref Component
+ PortMappings:
+ - ContainerPort: 443
+ LogConfiguration:
+ LogDriver: 'awslogs'
+ Options:
+ awslogs-create-group: true
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Sub '/fargate/${Component}'
+ awslogs-stream-prefix: !Ref Component
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Secrets:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ - Name: 'AWS_ACCESS_KEY_ID'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-key-id
+ Name: 'AWS_SECRET_ACCESS_KEY'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-secret-key
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+
+ ClusterService:
+ Type: 'AWS::ECS::Cluster'
+ Properties:
+ ClusterName: !Sub 'group${UserPartition}-ecs-fargate-ws-cluster'
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: ListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: !Sub 'group$-ecs-fargate-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref Subnet1
+ ServiceName: !Sub '${RegionalPrefix}-${Component}-${Version}-${Launch}'
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/something.tf b/iac/ql/test/queries-tests/CloudFormation/ECS/ContainerInsights/something.tf
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/LogConfiguration.expected b/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/LogConfiguration.expected
new file mode 100644
index 000000000000..04b574badc24
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/LogConfiguration.expected
@@ -0,0 +1 @@
+| ecs.yml:11:7:33:2 | CloudFormation Resource Properties | ContainerDefinitions must have a log configuration |
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/LogConfiguration.qlref b/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/LogConfiguration.qlref
new file mode 100644
index 000000000000..725b86ff30cc
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/LogConfiguration.qlref
@@ -0,0 +1 @@
+security/CloudFormation/ECS/LogConfiguration.ql
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/ecs.yml b/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/ecs.yml
new file mode 100644
index 000000000000..d885c525a3bd
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/ecs.yml
@@ -0,0 +1,64 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+
+Resources:
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Sub '${image}:${version}'
+ Name: !Ref Component
+ PortMappings:
+ - ContainerPort: 443
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Secrets:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ - Name: 'AWS_ACCESS_KEY_ID'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-key-id
+ Name: 'AWS_SECRET_ACCESS_KEY'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-secret-key
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+
+ ClusterService:
+ Type: 'AWS::ECS::Cluster'
+ Properties:
+ ClusterName: !Sub 'group${UserPartition}-ecs-fargate-ws-cluster'
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: ListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: !Sub 'group$-ecs-fargate-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref Subnet1
+ ServiceName: !Sub '${RegionalPrefix}-${Component}-${Version}-${Launch}'
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/something.tf b/iac/ql/test/queries-tests/CloudFormation/ECS/LogConfiguration/something.tf
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/NetworkMode.expected b/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/NetworkMode.expected
new file mode 100644
index 000000000000..7a0c9fbdac12
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/NetworkMode.expected
@@ -0,0 +1 @@
+| ecs.yml:9:5:42:2 | CloudFormation ECS Task Definition | ContainerDefinitions must not run as root or be privileged when networkmode Host is used |
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/NetworkMode.qlref b/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/NetworkMode.qlref
new file mode 100644
index 000000000000..e2d716c8e955
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/NetworkMode.qlref
@@ -0,0 +1 @@
+security/CloudFormation/ECS/NetworkMode.ql
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/ecs.yml b/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/ecs.yml
new file mode 100644
index 000000000000..94e571499dbe
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/ecs.yml
@@ -0,0 +1,73 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+
+Resources:
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Sub '${image}:${version}'
+ Name: !Ref Component
+ User: 'root'
+ PortMappings:
+ - ContainerPort: 443
+ LogConfiguration:
+ LogDriver: 'awslogs'
+ Options:
+ awslogs-create-group: true
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Sub '/fargate/${Component}'
+ awslogs-stream-prefix: !Ref Component
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Secrets:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ - Name: 'AWS_ACCESS_KEY_ID'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-key-id
+ Name: 'AWS_SECRET_ACCESS_KEY'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-secret-key
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ NetworkMode: 'host'
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+
+ ClusterService:
+ Type: 'AWS::ECS::Cluster'
+ Properties:
+ ClusterName: !Sub 'group${UserPartition}-ecs-fargate-ws-cluster'
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: ListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: !Sub 'group$-ecs-fargate-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref Subnet1
+ ServiceName: !Sub '${RegionalPrefix}-${Component}-${Version}-${Launch}'
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/something.tf b/iac/ql/test/queries-tests/CloudFormation/ECS/NetworkMode/something.tf
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/NonPriv.expected b/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/NonPriv.expected
new file mode 100644
index 000000000000..184828a1d3bb
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/NonPriv.expected
@@ -0,0 +1 @@
+| ecs.yml:11:7:40:2 | CloudFormation Resource Properties | ContainerDefinitions must be explictly configured privileged mode to false |
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/NonPriv.qlref b/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/NonPriv.qlref
new file mode 100644
index 000000000000..1559cdaeda0c
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/NonPriv.qlref
@@ -0,0 +1 @@
+security/CloudFormation/ECS/NonPriv.ql
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/ecs.yml b/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/ecs.yml
new file mode 100644
index 000000000000..febb0c75378c
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/ecs.yml
@@ -0,0 +1,71 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+
+Resources:
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Sub '${image}:${version}'
+ Name: !Ref Component
+ PortMappings:
+ - ContainerPort: 443
+ LogConfiguration:
+ LogDriver: 'awslogs'
+ Options:
+ awslogs-create-group: true
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Sub '/fargate/${Component}'
+ awslogs-stream-prefix: !Ref Component
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Secrets:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ - Name: 'AWS_ACCESS_KEY_ID'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-key-id
+ Name: 'AWS_SECRET_ACCESS_KEY'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-secret-key
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+
+ ClusterService:
+ Type: 'AWS::ECS::Cluster'
+ Properties:
+ ClusterName: !Sub 'group${UserPartition}-ecs-fargate-ws-cluster'
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: ListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: !Sub 'group$-ecs-fargate-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref Subnet1
+ ServiceName: !Sub '${RegionalPrefix}-${Component}-${Version}-${Launch}'
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/something.tf b/iac/ql/test/queries-tests/CloudFormation/ECS/NonPriv/something.tf
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/ecs.yml b/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/ecs.yml
new file mode 100644
index 000000000000..febb0c75378c
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/ecs.yml
@@ -0,0 +1,71 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+
+Resources:
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Sub '${image}:${version}'
+ Name: !Ref Component
+ PortMappings:
+ - ContainerPort: 443
+ LogConfiguration:
+ LogDriver: 'awslogs'
+ Options:
+ awslogs-create-group: true
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Sub '/fargate/${Component}'
+ awslogs-stream-prefix: !Ref Component
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Secrets:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ - Name: 'AWS_ACCESS_KEY_ID'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-key-id
+ Name: 'AWS_SECRET_ACCESS_KEY'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-secret-key
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+
+ ClusterService:
+ Type: 'AWS::ECS::Cluster'
+ Properties:
+ ClusterName: !Sub 'group${UserPartition}-ecs-fargate-ws-cluster'
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: ListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: !Sub 'group$-ecs-fargate-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref Subnet1
+ ServiceName: !Sub '${RegionalPrefix}-${Component}-${Version}-${Launch}'
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/pidmode.expected b/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/pidmode.expected
new file mode 100644
index 000000000000..031a38c9aff6
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/pidmode.expected
@@ -0,0 +1 @@
+| ecs.yml:9:5:40:2 | CloudFormation ECS Task Definition | PidMode should be "task" for ECS tasks |
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/pidmode.qlref b/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/pidmode.qlref
new file mode 100644
index 000000000000..984385f1fe8c
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/pidmode.qlref
@@ -0,0 +1 @@
+security/CloudFormation/ECS/PidMode.ql
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/something.tf b/iac/ql/test/queries-tests/CloudFormation/ECS/PidMode/something.tf
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/PublicIP.expected b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/PublicIP.expected
new file mode 100644
index 000000000000..0c5bb23db84b
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/PublicIP.expected
@@ -0,0 +1 @@
+| ecs.yml:61:27:61:35 | 'ENABLED' | AssignPublicIp should be "DISABLED" for ECS tasks |
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/PublicIP.qlref b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/PublicIP.qlref
new file mode 100644
index 000000000000..bf35faaf1042
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/PublicIP.qlref
@@ -0,0 +1 @@
+security/CloudFormation/ECS/PublicIP.ql
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/ecs.yml b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/ecs.yml
new file mode 100644
index 000000000000..febb0c75378c
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/ecs.yml
@@ -0,0 +1,71 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+
+Resources:
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Sub '${image}:${version}'
+ Name: !Ref Component
+ PortMappings:
+ - ContainerPort: 443
+ LogConfiguration:
+ LogDriver: 'awslogs'
+ Options:
+ awslogs-create-group: true
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Sub '/fargate/${Component}'
+ awslogs-stream-prefix: !Ref Component
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Secrets:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ - Name: 'AWS_ACCESS_KEY_ID'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-key-id
+ Name: 'AWS_SECRET_ACCESS_KEY'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-secret-key
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+
+ ClusterService:
+ Type: 'AWS::ECS::Cluster'
+ Properties:
+ ClusterName: !Sub 'group${UserPartition}-ecs-fargate-ws-cluster'
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: ListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: !Sub 'group$-ecs-fargate-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref Subnet1
+ ServiceName: !Sub '${RegionalPrefix}-${Component}-${Version}-${Launch}'
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/something.tf b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIP/something.tf
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/PublicIPTaskSet.expected b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/PublicIPTaskSet.expected
new file mode 100644
index 000000000000..02f86ade9193
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/PublicIPTaskSet.expected
@@ -0,0 +1 @@
+| ecs.yml:5:5:37:2 | CloudFormation ECS Task Set | AssignPublicIp must be "DISABLED" for ECS tasks |
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/PublicIPTaskSet.qlref b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/PublicIPTaskSet.qlref
new file mode 100644
index 000000000000..42cce83f5b88
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/PublicIPTaskSet.qlref
@@ -0,0 +1 @@
+security/CloudFormation/ECS/PublicIPTaskSet.ql
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/ecs.yml b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/ecs.yml
new file mode 100644
index 000000000000..e1a0a3b96534
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/ecs.yml
@@ -0,0 +1,100 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+Resources:
+ TaskSet:
+ Type: 'AWS::ECS::TaskSet'
+ Properties:
+ Cluster: !Ref 'Cluster'
+ Service: !Ref 'Service'
+ TaskDefinition: !Ref 'TaskDefinition'
+ LaunchType: 'FARGATE'
+ NetworkConfiguration:
+ AwsVpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref 'SecurityGroup'
+ Subnets:
+ - !Ref 'SubnetA'
+ - !Ref 'SubnetB'
+ - !Ref 'SubnetC'
+ LoadBalancers:
+ - ContainerName: !Ref 'ContainerName'
+ ContainerPort: !Ref 'ContainerPort'
+ TargetGroupArn: !Ref 'TargetGroup'
+ PlatformVersion: 'LATEST'
+ Scale: !Ref 'Scale'
+ ServiceRegistries:
+ - ContainerName: !Ref 'ContainerName'
+ ContainerPort: !Ref 'ContainerPort'
+ RegistryArn: !Ref 'ServiceDiscoveryRegistry'
+ HealthCheckGracePeriodSeconds: 90
+ SchedulingStrategy: 'REPLICA'
+ ServiceDiscovery: !Ref 'ServiceDiscovery'
+
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Sub '${image}:${version}'
+ Name: !Ref Component
+ PortMappings:
+ - ContainerPort: 443
+ LogConfiguration:
+ LogDriver: 'awslogs'
+ Options:
+ awslogs-create-group: true
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Sub '/fargate/${Component}'
+ awslogs-stream-prefix: !Ref Component
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Secrets:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ - Name: 'AWS_ACCESS_KEY_ID'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-key-id
+ Name: 'AWS_SECRET_ACCESS_KEY'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-secret-key
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+
+ ClusterService:
+ Type: 'AWS::ECS::Cluster'
+ Properties:
+ ClusterName: !Sub 'group${UserPartition}-ecs-fargate-ws-cluster'
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: ListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: !Sub 'group$-ecs-fargate-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref Subnet1
+ ServiceName: !Sub '${RegionalPrefix}-${Component}-${Version}-${Launch}'
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/something.tf b/iac/ql/test/queries-tests/CloudFormation/ECS/PublicIPTaskSet/something.tf
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/ReadOnlyFileSystem.expected b/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/ReadOnlyFileSystem.expected
new file mode 100644
index 000000000000..c7538c6f23a4
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/ReadOnlyFileSystem.expected
@@ -0,0 +1 @@
+| ecs.yml:11:7:40:2 | CloudFormation Resource Properties | Containers must have explictly only read only root filesystem |
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/ReadOnlyFileSystem.qlref b/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/ReadOnlyFileSystem.qlref
new file mode 100644
index 000000000000..9873b52cff00
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/ReadOnlyFileSystem.qlref
@@ -0,0 +1 @@
+security/CloudFormation/ECS/ReadOnlyRootFileSystem.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/ecs.yml b/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/ecs.yml
new file mode 100644
index 000000000000..febb0c75378c
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/ecs.yml
@@ -0,0 +1,71 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+
+Resources:
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Sub '${image}:${version}'
+ Name: !Ref Component
+ PortMappings:
+ - ContainerPort: 443
+ LogConfiguration:
+ LogDriver: 'awslogs'
+ Options:
+ awslogs-create-group: true
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Sub '/fargate/${Component}'
+ awslogs-stream-prefix: !Ref Component
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Secrets:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ - Name: 'AWS_ACCESS_KEY_ID'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-key-id
+ Name: 'AWS_SECRET_ACCESS_KEY'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-secret-key
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+
+ ClusterService:
+ Type: 'AWS::ECS::Cluster'
+ Properties:
+ ClusterName: !Sub 'group${UserPartition}-ecs-fargate-ws-cluster'
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: ListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: !Sub 'group$-ecs-fargate-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref Subnet1
+ ServiceName: !Sub '${RegionalPrefix}-${Component}-${Version}-${Launch}'
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/something.tf b/iac/ql/test/queries-tests/CloudFormation/ECS/ReadOnlyFileSystem/something.tf
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/Secrets.expected b/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/Secrets.expected
new file mode 100644
index 000000000000..f1a99c5cd3cd
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/Secrets.expected
@@ -0,0 +1 @@
+| ecs.yml:27:13:31:6 | - Name: ... KEY_ID' | Containers must not pass secret thorugh environment variables |
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/Secrets.qlref b/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/Secrets.qlref
new file mode 100644
index 000000000000..e7a27ce49524
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/Secrets.qlref
@@ -0,0 +1 @@
+security/CloudFormation/ECS/Secrets.ql
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/ecs.yml b/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/ecs.yml
new file mode 100644
index 000000000000..febb0c75378c
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/ecs.yml
@@ -0,0 +1,71 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: 'Infrastructure for the ECS Fargate deployment pattern workshop service.'
+
+Resources:
+ #------------------------------------------------
+ # ECS task definition
+ #------------------------------------------------
+ ComponentTaskDefinition:
+ Type: 'AWS::ECS::TaskDefinition'
+ Properties:
+ ContainerDefinitions:
+ - Image: !Sub '${image}:${version}'
+ Name: !Ref Component
+ PortMappings:
+ - ContainerPort: 443
+ LogConfiguration:
+ LogDriver: 'awslogs'
+ Options:
+ awslogs-create-group: true
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Sub '/fargate/${Component}'
+ awslogs-stream-prefix: !Ref Component
+ Environment:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ Secrets:
+ # Normally mandatory (but e.g. SHORTNAME and FLAVOUR may not always be applicable)
+ - Name: 'AWS_ACCESS_KEY_ID'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-key-id
+ Name: 'AWS_SECRET_ACCESS_KEY'
+ ValueFrom: arn:aws:secretsmanager:region:account-id:secret:aws-secret-key
+ Cpu: 512
+ ExecutionRoleArn: !GetAtt ComponentTaskDefinitionExecutionRole.Arn
+ TaskRoleArn: !GetAtt ComponentTaskDefinitionRole.Arn
+ Family: !Ref Component
+ Memory: 1024
+ RequiresCompatibilities:
+ - 'FARGATE'
+
+
+ ClusterService:
+ Type: 'AWS::ECS::Cluster'
+ Properties:
+ ClusterName: !Sub 'group${UserPartition}-ecs-fargate-ws-cluster'
+ #------------------------------------------------
+ # ECS Service
+ #------------------------------------------------
+ ComponentEcsService:
+ Type: 'AWS::ECS::Service'
+ DependsOn: ListenerRule
+ Properties:
+ Cluster:
+ Fn::ImportValue: !Sub 'group$-ecs-fargate-cluster-arn'
+ DesiredCount: 1
+ LaunchType: 'FARGATE'
+ LoadBalancers:
+ - ContainerName: !Ref Component
+ ContainerPort: !Ref ContainerPort
+ TargetGroupArn: !Ref ComponentTargetGroup
+ NetworkConfiguration:
+ AwsvpcConfiguration:
+ AssignPublicIp: 'ENABLED'
+ SecurityGroups:
+ - !Ref ComponentEcsServiceSecurityGroup
+ - !Ref CiSecurityGroupId
+ Subnets:
+ - !Ref Subnet1
+ ServiceName: !Sub '${RegionalPrefix}-${Component}-${Version}-${Launch}'
+ TaskDefinition: !Ref ComponentTaskDefinition
+ PropagateTags: 'TASK_DEFINITION'
+ # Note: The value has been lowered from the recommended 180, for production use please choose this value wisely.
+ HealthCheckGracePeriodSeconds: 90
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/something.tf b/iac/ql/test/queries-tests/CloudFormation/ECS/Secrets/something.tf
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/CloudFormation/S3/PublicBucket/PublicBucket.expected b/iac/ql/test/queries-tests/CloudFormation/S3/PublicBucket/PublicBucket.expected
new file mode 100644
index 000000000000..b04b1894a600
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/S3/PublicBucket/PublicBucket.expected
@@ -0,0 +1 @@
+| s3.yml:4:5:12:2 | CloudFormation S3 Bucket | Public S3 Bucket resource |
diff --git a/iac/ql/test/queries-tests/CloudFormation/S3/PublicBucket/PublicBucket.qlref b/iac/ql/test/queries-tests/CloudFormation/S3/PublicBucket/PublicBucket.qlref
new file mode 100644
index 000000000000..38fd97080273
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/S3/PublicBucket/PublicBucket.qlref
@@ -0,0 +1 @@
+security/CloudFormation/S3/PublicBucket.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/CloudFormation/S3/PublicBucket/s3.yml b/iac/ql/test/queries-tests/CloudFormation/S3/PublicBucket/s3.yml
new file mode 100644
index 000000000000..3e65a84f9a7a
--- /dev/null
+++ b/iac/ql/test/queries-tests/CloudFormation/S3/PublicBucket/s3.yml
@@ -0,0 +1,28 @@
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+ S3Bucket:
+ Type: "AWS::S3::Bucket"
+ Properties:
+ AccessControl: PublicRead
+ WebsiteConfiguration:
+ IndexDocument: index.html
+ ErrorDocument: error.html
+ DeletionPolicy: Retain
+ UpdateReplacePolicy: Retain
+ BucketPolicy:
+ Type: "AWS::S3::BucketPolicy"
+ Properties:
+ PolicyDocument:
+ Id: MyPolicy
+ Version: 2012-10-17
+ Statement:
+ - Sid: PublicReadForGetBucketObjects
+ Effect: Allow
+ Principal: "*"
+ Action: "s3:GetObject"
+ Resource: !Join
+ - ""
+ - - "arn:aws:s3:::"
+ - !Ref S3Bucket
+ - /*
+ Bucket: !Ref S3Bucket
diff --git a/iac/ql/test/queries-tests/HelmCharts/CWE-265/Chart.yml b/iac/ql/test/queries-tests/HelmCharts/CWE-265/Chart.yml
new file mode 100644
index 000000000000..78a91d89a4bf
--- /dev/null
+++ b/iac/ql/test/queries-tests/HelmCharts/CWE-265/Chart.yml
@@ -0,0 +1,32 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: security-context-demo
+spec:
+ privileged: true
+
+ securityContext:
+ runAsUser: 0
+ runAsGroup: 0
+ volumes:
+ - name: sec-ctx-vol
+ emptyDir: {}
+ containers:
+ - name: sec-ctx-demo
+ image: busybox:1.28
+ command: ["sh", "-c", "sleep 1h"]
+ volumeMounts:
+ - name: sec-ctx-vol
+ mountPath: /data/demo
+ securityContext:
+ allowPrivilegeEscalation: false
+
+ - name: sec-ctx-demo2
+ image: busybox:1.28
+ command: ["sh", "-c", "sleep 1h"]
+ volumeMounts:
+ - name: sec-ctx-vol
+ mountPath: /data/demo
+ securityContext:
+ runAsUser: 0
+ privileged: true
diff --git a/iac/ql/test/queries-tests/HelmCharts/CWE-265/PrivillagedPod.expected b/iac/ql/test/queries-tests/HelmCharts/CWE-265/PrivillagedPod.expected
new file mode 100644
index 000000000000..d54f1eabb6f9
--- /dev/null
+++ b/iac/ql/test/queries-tests/HelmCharts/CWE-265/PrivillagedPod.expected
@@ -0,0 +1,2 @@
+| Chart.yml:6:15:6:18 | true | privileged enabled |
+| Chart.yml:32:21:32:24 | true | privileged enabled |
diff --git a/iac/ql/test/queries-tests/HelmCharts/CWE-265/PrivillagedPod.qlref b/iac/ql/test/queries-tests/HelmCharts/CWE-265/PrivillagedPod.qlref
new file mode 100644
index 000000000000..429bd0764cdd
--- /dev/null
+++ b/iac/ql/test/queries-tests/HelmCharts/CWE-265/PrivillagedPod.qlref
@@ -0,0 +1 @@
+security/HelmCharts/CWE-265/PrivillagedPod.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/HelmCharts/CWE-265/RunAsRoot.expected b/iac/ql/test/queries-tests/HelmCharts/CWE-265/RunAsRoot.expected
new file mode 100644
index 000000000000..8eba680d0b98
--- /dev/null
+++ b/iac/ql/test/queries-tests/HelmCharts/CWE-265/RunAsRoot.expected
@@ -0,0 +1,2 @@
+| Chart.yml:9:5:11:2 | SecurityContext | root user set |
+| Chart.yml:31:9:32:25 | SecurityContext | root user set |
diff --git a/iac/ql/test/queries-tests/HelmCharts/CWE-265/RunAsRoot.qlref b/iac/ql/test/queries-tests/HelmCharts/CWE-265/RunAsRoot.qlref
new file mode 100644
index 000000000000..37ef0f8e947d
--- /dev/null
+++ b/iac/ql/test/queries-tests/HelmCharts/CWE-265/RunAsRoot.qlref
@@ -0,0 +1 @@
+security/HelmCharts/CWE-265/RunAsRoot.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/HelmCharts/CWE-829/Chart.yml b/iac/ql/test/queries-tests/HelmCharts/CWE-829/Chart.yml
new file mode 100644
index 000000000000..ffadbc9932cb
--- /dev/null
+++ b/iac/ql/test/queries-tests/HelmCharts/CWE-829/Chart.yml
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: security-context-demo
+spec:
+ containers:
+ # safe
+ - name: safe1
+ image: busybox:1.28
+ # safe
+ - name: safe2
+ image: ghcr.io/github/busybox:1.28
+ # safe
+ - name: safe3
+ image: ACCOUNT.dkr.ecr.us-west-2.amazonaws.com/busybox:1.28
+
+ # fake registry name with typo
+ - name: fake1
+ image: gchr.io/github/busybox:1.28
diff --git a/iac/ql/test/queries-tests/HelmCharts/CWE-829/UntrustedRegistry.expected b/iac/ql/test/queries-tests/HelmCharts/CWE-829/UntrustedRegistry.expected
new file mode 100644
index 000000000000..fe1522f81e19
--- /dev/null
+++ b/iac/ql/test/queries-tests/HelmCharts/CWE-829/UntrustedRegistry.expected
@@ -0,0 +1,4 @@
+| Chart.yml:9:14:9:25 | busybox:1.28 | untrusted container registry |
+| Chart.yml:12:14:12:40 | ghcr.io ... ox:1.28 | untrusted container registry |
+| Chart.yml:15:14:15:65 | ACCOUNT ... ox:1.28 | untrusted container registry |
+| Chart.yml:19:14:19:40 | gchr.io ... ox:1.28 | untrusted container registry |
diff --git a/iac/ql/test/queries-tests/HelmCharts/CWE-829/UntrustedRegistry.qlref b/iac/ql/test/queries-tests/HelmCharts/CWE-829/UntrustedRegistry.qlref
new file mode 100644
index 000000000000..487c4e93029b
--- /dev/null
+++ b/iac/ql/test/queries-tests/HelmCharts/CWE-829/UntrustedRegistry.qlref
@@ -0,0 +1 @@
+security/HelmCharts/CWE-829/UntrustedRegistry.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/DeprecatedFeatures/DeprecatedFeatures.expected b/iac/ql/test/queries-tests/Terraform/AWS/DeprecatedFeatures/DeprecatedFeatures.expected
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/DeprecatedFeatures/DeprecatedFeatures.qlref b/iac/ql/test/queries-tests/Terraform/AWS/DeprecatedFeatures/DeprecatedFeatures.qlref
new file mode 100644
index 000000000000..538130750c2c
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/DeprecatedFeatures/DeprecatedFeatures.qlref
@@ -0,0 +1 @@
+security/Terraform/AWS/DeprecatedFeatures.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/DeprecatedFeatures/bucket.hcl b/iac/ql/test/queries-tests/Terraform/AWS/DeprecatedFeatures/bucket.hcl
new file mode 100644
index 000000000000..24d9f6dba0f6
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/DeprecatedFeatures/bucket.hcl
@@ -0,0 +1,19 @@
+resource "aws_s3_bucket" "logs" {
+ bucket = "logs"
+ acl = "private"
+ versioning {
+ enabled = true
+ }
+ logging {
+ target_bucket = aws_s3_bucket.logs.arn
+ target_prefix = "logs/"
+ }
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "aws:kms"
+ kms_master_key_id = aws_kms_key.logs_key.arn
+ }
+ }
+ }
+}
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/EKS/PublicCluster/PublicCluster.expected b/iac/ql/test/queries-tests/Terraform/AWS/EKS/PublicCluster/PublicCluster.expected
new file mode 100644
index 000000000000..8b88dfd62dff
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/EKS/PublicCluster/PublicCluster.expected
@@ -0,0 +1 @@
+| eks.tf:4:3:6:3 | vpc_config | EKS Cluster Publically Accessible |
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/EKS/PublicCluster/PublicCluster.qlref b/iac/ql/test/queries-tests/Terraform/AWS/EKS/PublicCluster/PublicCluster.qlref
new file mode 100644
index 000000000000..8b5fbe1056c6
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/EKS/PublicCluster/PublicCluster.qlref
@@ -0,0 +1 @@
+security/Terraform/AWS/EKS/PublicCluster/PublicCluster.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/EKS/PublicCluster/eks.tf b/iac/ql/test/queries-tests/Terraform/AWS/EKS/PublicCluster/eks.tf
new file mode 100644
index 000000000000..b48c059404be
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/EKS/PublicCluster/eks.tf
@@ -0,0 +1,16 @@
+// Insecure
+resource "aws_eks_cluster" "cluster" {
+ name = "my-cluster"
+ vpc_config {
+ endpoint_public_access = true
+ }
+}
+
+// Secure
+resource "aws_eks_cluster" "cluster-secure" {
+ name = "cluster-secure"
+ vpc_config {
+ endpoint_public_access = true
+ public_access_cidrs = ["10.2.0.0/8"]
+ }
+}
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.expected b/iac/ql/test/queries-tests/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.expected
new file mode 100644
index 000000000000..ce69297e4f9d
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.expected
@@ -0,0 +1 @@
+| eks.tf:2:1:8:1 | resource aws_eks_cluster cluster | EKS Unencrypted Secrets |
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.qlref b/iac/ql/test/queries-tests/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.qlref
new file mode 100644
index 000000000000..c6c4dd14d6b9
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.qlref
@@ -0,0 +1 @@
+security/Terraform/AWS/EKS/UnencryptedSecrets/UnencryptedSecrets.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/EKS/UnencryptedSecrets/eks.tf b/iac/ql/test/queries-tests/Terraform/AWS/EKS/UnencryptedSecrets/eks.tf
new file mode 100644
index 000000000000..38deb6ae7b04
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/EKS/UnencryptedSecrets/eks.tf
@@ -0,0 +1,22 @@
+// Insecure
+resource "aws_eks_cluster" "cluster" {
+ name = "my-cluster"
+ role_arn = aws_iam_role.example.arn
+ vpc_config {
+ endpoint_public_access = true
+ }
+}
+
+// Secure
+resource "aws_eks_cluster" "cluster-secure" {
+ name = "cluster-secure"
+ role_arn = aws_iam_role.example.arn
+ encryption_config {
+ provider = "awskms"
+ resources = ["secrets"]
+ }
+ vpc_config {
+ endpoint_public_access = true
+ public_access_cidrs = ["10.2.0.0/8"]
+ }
+}
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/DisableLogging.expected b/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/DisableLogging.expected
new file mode 100644
index 000000000000..44e8e972125e
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/DisableLogging.expected
@@ -0,0 +1 @@
+| S3-old.hcl:1:1:3:1 | resource aws_s3_bucket data | Logging disabled for: "data" |
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/DisableLogging.qlref b/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/DisableLogging.qlref
new file mode 100644
index 000000000000..003f1493b407
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/DisableLogging.qlref
@@ -0,0 +1 @@
+security/Terraform/AWS/S3/DisableLogging/LoggingDisabled.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/S3-new.hcl b/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/S3-new.hcl
new file mode 100644
index 000000000000..af33bb7bdc1c
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/S3-new.hcl
@@ -0,0 +1,24 @@
+resource "aws_s3_bucket" "example" {
+ bucket = "my-tf-example-bucket"
+}
+
+resource "aws_s3_bucket_acl" "example" {
+ bucket = aws_s3_bucket.example.id
+ acl = "private"
+}
+
+resource "aws_s3_bucket" "log_bucket" {
+ bucket = "my-tf-log-bucket"
+}
+
+resource "aws_s3_bucket_acl" "log_bucket_acl" {
+ bucket = aws_s3_bucket.log_bucket.id
+ acl = "log-delivery-write"
+}
+
+resource "aws_s3_bucket_logging" "example" {
+ bucket = aws_s3_bucket.example.id
+
+ target_bucket = aws_s3_bucket.log_bucket.id
+ target_prefix = "log/"
+}
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/S3-old.hcl b/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/S3-old.hcl
new file mode 100644
index 000000000000..f1100cefd02b
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/S3/DisableLogging/S3-old.hcl
@@ -0,0 +1,11 @@
+resource "aws_s3_bucket" "data" {
+ bucket = "${local.resource_prefix.value}-data"
+}
+
+resource "aws_s3_bucket" "data_science" {
+ bucket = "${local.resource_prefix.value}-data-science"
+ // old attribute block
+ logging {
+ target_bucket = aws_s3_bucket.logs.id
+ }
+}
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/PublicBucket.expected b/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/PublicBucket.expected
new file mode 100644
index 000000000000..f5a20c9d94fa
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/PublicBucket.expected
@@ -0,0 +1,2 @@
+| S3-new.hcl:11:1:13:1 | resource aws_s3_bucket example2 | Public S3 Bucket resource |
+| S3-old.hcl:5:1:9:1 | resource aws_s3_bucket data2 | Public S3 Bucket resource |
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/PublicBucket.qlref b/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/PublicBucket.qlref
new file mode 100644
index 000000000000..8aba0952f61a
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/PublicBucket.qlref
@@ -0,0 +1 @@
+security/Terraform/AWS/S3/PublicBucket/PublicBucket.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/S3-new.hcl b/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/S3-new.hcl
new file mode 100644
index 000000000000..43b886da51aa
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/S3-new.hcl
@@ -0,0 +1,19 @@
+resource "aws_s3_bucket" "example1" {
+ bucket = "my-tf-example1-bucket"
+}
+
+resource "aws_s3_bucket_acl" "example1" {
+ bucket = aws_s3_bucket.example1.id
+ acl = "private"
+}
+
+
+resource "aws_s3_bucket" "example2" {
+ bucket = "my-tf-example2-bucket"
+}
+
+
+resource "aws_s3_bucket_acl" "example2" {
+ bucket = aws_s3_bucket.example2.id
+ acl = "public-read"
+}
diff --git a/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/S3-old.hcl b/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/S3-old.hcl
new file mode 100644
index 000000000000..41253ba206d3
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/AWS/S3/PublicBucket/S3-old.hcl
@@ -0,0 +1,15 @@
+resource "aws_s3_bucket" "data1" {
+ bucket = "${local.resource_prefix.value}-data"
+}
+
+resource "aws_s3_bucket" "data2" {
+ bucket = "${local.resource_prefix.value}-data-science"
+ // old attribute block
+ acl = "public-read"
+}
+
+resource "aws_s3_bucket" "data3" {
+ bucket = "${local.resource_prefix.value}-data-science"
+ // old attribute block
+ acl = "private"
+}
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/ManagedStorageUnencrypted/ManagedStorageUnencrypted.expected b/iac/ql/test/queries-tests/Terraform/Azure/ManagedStorageUnencrypted/ManagedStorageUnencrypted.expected
new file mode 100644
index 000000000000..8ea668c45935
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/ManagedStorageUnencrypted/ManagedStorageUnencrypted.expected
@@ -0,0 +1,2 @@
+| storage.hcl:6:1:13:1 | ManagedDisk acctestmd | Azure Storage is Unencrypted for 'acctestmd' |
+| storage.hcl:23:3:25:3 | ManagedDiskEncryptionSettings | Azure Storage is Unencrypted for 'acctestmd' |
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/ManagedStorageUnencrypted/ManagedStorageUnencrypted.qlref b/iac/ql/test/queries-tests/Terraform/Azure/ManagedStorageUnencrypted/ManagedStorageUnencrypted.qlref
new file mode 100644
index 000000000000..c21de0907ae7
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/ManagedStorageUnencrypted/ManagedStorageUnencrypted.qlref
@@ -0,0 +1 @@
+security/Terraform/Azure/ManagedDisk/ManagedStorageUnencrypted.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/ManagedStorageUnencrypted/storage.hcl b/iac/ql/test/queries-tests/Terraform/Azure/ManagedStorageUnencrypted/storage.hcl
new file mode 100644
index 000000000000..ac318e5b2854
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/ManagedStorageUnencrypted/storage.hcl
@@ -0,0 +1,39 @@
+resource "azurerm_resource_group" "example" {
+ name = "example-resources"
+ location = "West Europe"
+}
+
+resource "azurerm_managed_disk" "example1" {
+ name = "acctestmd"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+}
+
+resource "azurerm_managed_disk" "example2" {
+ name = "acctestmd"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+
+ encryption_settings {
+ enabled = false
+ }
+}
+
+resource "azurerm_managed_disk" "example3" {
+ name = "acctestmd"
+ location = azurerm_resource_group.example.location
+ resource_group_name = azurerm_resource_group.example.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = "1"
+
+ encryption_settings {
+ enabled = true
+ }
+}
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/SecurityCenterDisabledNotifications/SecurityCenterDisabledNotifications.expected b/iac/ql/test/queries-tests/Terraform/Azure/SecurityCenterDisabledNotifications/SecurityCenterDisabledNotifications.expected
new file mode 100644
index 000000000000..19a599cf60cb
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/SecurityCenterDisabledNotifications/SecurityCenterDisabledNotifications.expected
@@ -0,0 +1 @@
+| security_center.hcl:5:1:9:1 | resource azurerm_security_center_contact contact | disabled notifications |
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/SecurityCenterDisabledNotifications/SecurityCenterDisabledNotifications.qlref b/iac/ql/test/queries-tests/Terraform/Azure/SecurityCenterDisabledNotifications/SecurityCenterDisabledNotifications.qlref
new file mode 100644
index 000000000000..29f58067d255
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/SecurityCenterDisabledNotifications/SecurityCenterDisabledNotifications.qlref
@@ -0,0 +1 @@
+security/Terraform/Azure/SecurityCenterDisabledNotifications.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/SecurityCenterDisabledNotifications/security_center.hcl b/iac/ql/test/queries-tests/Terraform/Azure/SecurityCenterDisabledNotifications/security_center.hcl
new file mode 100644
index 000000000000..0d62417125d7
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/SecurityCenterDisabledNotifications/security_center.hcl
@@ -0,0 +1,9 @@
+resource "azurerm_security_center_subscription_pricing" "pricing" {
+ tier = "Free"
+}
+
+resource "azurerm_security_center_contact" "contact" {
+ alert_notifications = false
+ alerts_to_admins = false
+ email = "some@email.com"
+}
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/Storage/PublicAccess/PublicAccess.expected b/iac/ql/test/queries-tests/Terraform/Azure/Storage/PublicAccess/PublicAccess.expected
new file mode 100644
index 000000000000..51ad6466bbc4
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/Storage/PublicAccess/PublicAccess.expected
@@ -0,0 +1 @@
+| storage.tf:9:1:15:1 | resource azurerm_storage_container insecure | Azure Storage is Unencrypted for 'insecure' |
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/Storage/PublicAccess/PublicAccess.qlref b/iac/ql/test/queries-tests/Terraform/Azure/Storage/PublicAccess/PublicAccess.qlref
new file mode 100644
index 000000000000..1923acd9b1cc
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/Storage/PublicAccess/PublicAccess.qlref
@@ -0,0 +1 @@
+security/Terraform/Azure/ManagedDisk/PublicAccess.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/Storage/PublicAccess/storage.tf b/iac/ql/test/queries-tests/Terraform/Azure/Storage/PublicAccess/storage.tf
new file mode 100644
index 000000000000..615414a803d8
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/Storage/PublicAccess/storage.tf
@@ -0,0 +1,15 @@
+
+# secure
+resource "azurerm_storage_container" "secure" {
+ name = "secure-storage-container"
+ container_access_type = "private"
+}
+
+# insecure
+resource "azurerm_storage_container" "insecure" {
+ name = "insecure-storage-container"
+ container_access_type = "blob"
+ properties = {
+ "publicAccess" = "blob"
+ }
+}
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/WeakVaultKey/WeakVaultKey.expected b/iac/ql/test/queries-tests/Terraform/Azure/WeakVaultKey/WeakVaultKey.expected
new file mode 100644
index 000000000000..7aa4504812c2
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/WeakVaultKey/WeakVaultKey.expected
@@ -0,0 +1 @@
+| key_vault_key.hcl:5:18:5:21 | 1024 | weak key size |
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/WeakVaultKey/WeakVaultKey.qlref b/iac/ql/test/queries-tests/Terraform/Azure/WeakVaultKey/WeakVaultKey.qlref
new file mode 100644
index 000000000000..11d6318050d0
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/WeakVaultKey/WeakVaultKey.qlref
@@ -0,0 +1 @@
+security/Terraform/Azure/KeyVault/WeakVaultKey/WeakVaultKey.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/Azure/WeakVaultKey/key_vault_key.hcl b/iac/ql/test/queries-tests/Terraform/Azure/WeakVaultKey/key_vault_key.hcl
new file mode 100644
index 000000000000..5f74efb288ef
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/Azure/WeakVaultKey/key_vault_key.hcl
@@ -0,0 +1,13 @@
+resource "azurerm_key_vault_key" "generated" {
+ name = "generated-certificate"
+ key_vault_id = azurerm_key_vault.example.id
+ key_type = "RSA"
+ key_size = 1024
+}
+
+resource "azurerm_key_vault_key" "generated2" {
+ name = "generated-certificate"
+ key_vault_id = azurerm_key_vault.example.id
+ key_type = "RSA"
+ key_size = 2048
+}
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/ABACEnabled/ABACEnabled.expected b/iac/ql/test/queries-tests/Terraform/GCP/ABACEnabled/ABACEnabled.expected
new file mode 100644
index 000000000000..0714d797d435
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/ABACEnabled/ABACEnabled.expected
@@ -0,0 +1 @@
+| rbac_enabled.hcl:6:24:6:27 | true | ABAC Enabled |
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/ABACEnabled/ABACEnabled.qlref b/iac/ql/test/queries-tests/Terraform/GCP/ABACEnabled/ABACEnabled.qlref
new file mode 100644
index 000000000000..63905416bd5d
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/ABACEnabled/ABACEnabled.qlref
@@ -0,0 +1 @@
+security/Terraform/GCP/ContainerCluster/ABACEnabled.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/ABACEnabled/rbac_enabled.hcl b/iac/ql/test/queries-tests/Terraform/GCP/ABACEnabled/rbac_enabled.hcl
new file mode 100644
index 000000000000..81b7acb75d77
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/ABACEnabled/rbac_enabled.hcl
@@ -0,0 +1,7 @@
+resource "google_container_cluster" "workload_cluster" {
+ name = "terragoat-${var.environment}-cluster"
+ logging_service = "none"
+ location = var.region
+ initial_node_count = 1
+ enable_legacy_abac = true
+}
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/ClusterPodSecurityPolicy/ClusterPodSecurityPolicy.expected b/iac/ql/test/queries-tests/Terraform/GCP/ClusterPodSecurityPolicy/ClusterPodSecurityPolicy.expected
new file mode 100644
index 000000000000..7d25542a9ba4
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/ClusterPodSecurityPolicy/ClusterPodSecurityPolicy.expected
@@ -0,0 +1 @@
+| pods.hcl:7:1:15:1 | resource google_container_cluster cluster2 | No Pod Security Policy Defined |
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/ClusterPodSecurityPolicy/ClusterPodSecurityPolicy.qlref b/iac/ql/test/queries-tests/Terraform/GCP/ClusterPodSecurityPolicy/ClusterPodSecurityPolicy.qlref
new file mode 100644
index 000000000000..3deab24ee92f
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/ClusterPodSecurityPolicy/ClusterPodSecurityPolicy.qlref
@@ -0,0 +1 @@
+security/Terraform/GCP/ContainerCluster/ClusterPodSecurityPolicy.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/ClusterPodSecurityPolicy/pods.hcl b/iac/ql/test/queries-tests/Terraform/GCP/ClusterPodSecurityPolicy/pods.hcl
new file mode 100644
index 000000000000..7c7a92be14ca
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/ClusterPodSecurityPolicy/pods.hcl
@@ -0,0 +1,25 @@
+resource "google_container_cluster" "cluster1" {
+ name = "cluster1"
+ location = "us-central1"
+ initial_node_count = 1
+}
+
+resource "google_container_cluster" "cluster2" {
+ name = "cluster2"
+ location = "us-central1"
+ initial_node_count = 1
+
+ pod_security_policy_config {
+ enabled = "false"
+ }
+}
+
+resource "google_container_cluster" "cluster3" {
+ name = "cluster3"
+ location = "us-central1"
+ initial_node_count = 1
+
+ pod_security_policy_config {
+ enabled = "true"
+ }
+}
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/PublicControlPlane/PublicControlPlane.expected b/iac/ql/test/queries-tests/Terraform/GCP/PublicControlPlane/PublicControlPlane.expected
new file mode 100644
index 000000000000..4136156566bd
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/PublicControlPlane/PublicControlPlane.expected
@@ -0,0 +1 @@
+| gke.hcl:13:5:15:5 | cidr_blocks | No Pod Security Policy Defined |
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/PublicControlPlane/PublicControlPlane.qlref b/iac/ql/test/queries-tests/Terraform/GCP/PublicControlPlane/PublicControlPlane.qlref
new file mode 100644
index 000000000000..851b3a6db898
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/PublicControlPlane/PublicControlPlane.qlref
@@ -0,0 +1 @@
+security/Terraform/GCP/ContainerCluster/PublicControlPlane.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/PublicControlPlane/gke.hcl b/iac/ql/test/queries-tests/Terraform/GCP/PublicControlPlane/gke.hcl
new file mode 100644
index 000000000000..d7a9141979cd
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/PublicControlPlane/gke.hcl
@@ -0,0 +1,17 @@
+resource "google_container_cluster" "workload_cluster" {
+ name = "terragoat-${var.environment}-cluster"
+ logging_service = "none"
+ location = var.region
+ initial_node_count = 1
+
+ enable_legacy_abac = true
+ monitoring_service = "none"
+ remove_default_node_pool = true
+ network = google_compute_network.vpc.name
+ subnetwork = google_compute_subnetwork.public-subnetwork.name
+ master_authorized_networks_config {
+ cidr_blocks {
+ cidr_block = "0.0.0.0/0"
+ }
+ }
+}
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/Storage/PublicAccess/PublicAccess.expected b/iac/ql/test/queries-tests/Terraform/GCP/Storage/PublicAccess/PublicAccess.expected
new file mode 100644
index 000000000000..b4107adb4c47
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/Storage/PublicAccess/PublicAccess.expected
@@ -0,0 +1 @@
+| storage.tf:7:1:10:1 | resource google_storage_bucket bucket | Public Storage Bucket. |
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/Storage/PublicAccess/PublicAccess.qlref b/iac/ql/test/queries-tests/Terraform/GCP/Storage/PublicAccess/PublicAccess.qlref
new file mode 100644
index 000000000000..5e70e1dbce4f
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/Storage/PublicAccess/PublicAccess.qlref
@@ -0,0 +1 @@
+security/Terraform/GCP/Storage/PublicBucket.ql
\ No newline at end of file
diff --git a/iac/ql/test/queries-tests/Terraform/GCP/Storage/PublicAccess/storage.tf b/iac/ql/test/queries-tests/Terraform/GCP/Storage/PublicAccess/storage.tf
new file mode 100644
index 000000000000..d31669b00fdc
--- /dev/null
+++ b/iac/ql/test/queries-tests/Terraform/GCP/Storage/PublicAccess/storage.tf
@@ -0,0 +1,10 @@
+resource "google_storage_bucket_access_control" "public_rule" {
+ bucket = google_storage_bucket.bucket.name
+ role = "READER"
+ entity = "allUsers"
+}
+
+resource "google_storage_bucket" "bucket" {
+ name = "static-content-bucket"
+ location = "US"
+}
diff --git a/iac/rust-toolchain.toml b/iac/rust-toolchain.toml
new file mode 100644
index 000000000000..00e88ca78ef5
--- /dev/null
+++ b/iac/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.85"
+profile = "minimal"
+components = ["rustfmt"]
diff --git a/iac/scripts/README.md b/iac/scripts/README.md
new file mode 100644
index 000000000000..5fe971800eb9
--- /dev/null
+++ b/iac/scripts/README.md
@@ -0,0 +1,9 @@
+# Scripts
+
+## Coverage Script
+
+**Generate coverage report for docs:**
+
+```bash
+uv run ./scripts/create-coverage.py report --markdown -o ./docs/coverage.md
+```
diff --git a/iac/scripts/create-coverage.py b/iac/scripts/create-coverage.py
new file mode 100644
index 000000000000..011c6d463135
--- /dev/null
+++ b/iac/scripts/create-coverage.py
@@ -0,0 +1,240 @@
+#!/usr/bin/env python3
+# /// script
+# requires-python = ">=3.12"
+# dependencies = [
+# "ghastoolkit"
+# ]
+# ///
+
+import os
+import csv
+import json
+import yaml
+import logging
+from dataclasses import dataclass
+from typing import Optional, List
+from ghastoolkit import CodeQL, CodeQLPack
+from ghastoolkit.utils.cli import CommandLine
+
+logger = logging.getLogger(__name__)
+
+def generateCsv(csvfile, rules, src, src_suite, display: bool = True):
+ headers = ["ID", "Name", "Suite", "Severity"]
+ if display:
+ print(f"Source / Queries :: {src} :: {src_suite}")
+
+
+ with open("coverage.csv", "w") as csvfile:
+ writer = csv.writer(csvfile)
+ writer.writerow(headers)
+
+ for rule in rules:
+ id = rule.get("id")
+ props = rule.get("properties", {})
+ name = props.get("name")
+
+ if display:
+ print(f"Rule :: {id}")
+
+ severity = props.get("security-severity", {})
+
+ writer.writerow([id, name, "code-scanning", severity])
+
+def generateMarkdown(rules, suite: str = "code-scanning") -> str:
+ markdown = """\
+| Suite | Query ID | Severity |
+| ------------- | ---------------------------------------------------- | -------- |
+"""
+
+ for rule in rules:
+ id = rule.get("id")
+ props = rule.get("properties", {})
+ severity = props.get("security-severity", "NA")
+
+ markdown += f"| {suite} | {id:<52} | {severity:<8} |\n"
+ return markdown
+
+@dataclass
+class Rule:
+ id: str
+ name: str
+ description: Optional[str] = None
+ severity: Optional[float] = None
+ tags: Optional[List[str]] = None
+ impacts: Optional[List[str]] = None
+
+ required: Optional[List[str]] = None
+
+ def __post_init__(self):
+ if self.severity:
+ self.severity = float(self.severity)
+
+ def __str__(self) -> str:
+ return f"Rule({self.id}, '{self.severity}')"
+
+ @staticmethod
+ def load(path: str) -> Optional["Rule"]:
+ """Load the query from the path."""
+ if not os.path.exists(path):
+ raise Exception(f"Query path not found: {path}")
+ with open(path, "r") as handle:
+ query = yaml.safe_load(handle)
+ return Rule(**query)
+
+
+class CoverageCommandLine(CommandLine):
+ def arguments(self):
+ self.addModes(["report", "rules"])
+
+ parser = self.parser.add_argument_group("coverage")
+ parser.add_argument("--pull-request", help="Output to pull_request")
+ parser.add_argument("--csv", action="store_true", help="Output as csv")
+ parser.add_argument("--markdown", action="store_true", help="Output as markdown")
+ parser.add_argument("-o", "--output", help="Output file")
+
+ parser.add_argument("--rules", default="./scripts/rules", help="Path to query rules folder")
+
+ def run(self):
+ arguments = self.parse_args()
+
+ if arguments.mode == "report":
+ self.runReport(arguments)
+ elif arguments.mode == "rules":
+ self.runRules(arguments)
+
+ def generateQueries(self, codeql: CodeQL, src_suite: str):
+ sarif = json.loads(codeql.runCommand("generate", "query-help", "--format", "sarif-latest", src_suite))
+ rules = sarif.get("runs", [{}])[0].get("tool", {}).get("driver", {}).get("rules", [])
+ return rules
+
+
+ def runReport(self, arguments):
+ """Run and generate the report."""
+ codeql = CodeQL()
+
+ src = CodeQLPack("ql/src")
+ src_suite = os.path.join(src.path, src.default_suite)
+
+ rules = self.generateQueries(codeql, src_suite)
+
+ generateCsv("coverage.csv", rules, src, src_suite, display=not arguments.markdown)
+
+ markdown = generateMarkdown(rules)
+
+ if arguments.output:
+ if not os.path.exists(arguments.output):
+ raise Exception(f"Markdown output file not found: {arguments.output}")
+ with open(arguments.output, "r") as handle:
+ content = handle.read()
+
+ start_marker = ""
+ end_marker = ""
+ start_index = content.find(start_marker)
+ end_index = content.find(end_marker)
+ if start_index == -1 or end_index == -1:
+ raise Exception("Markers not found in markdown file")
+
+ # Replace the content between the markers
+ new_content = content[:start_index + len(start_marker)] + "\n" + markdown + "\n" + content[end_index:]
+ with open(arguments.output, "w") as handle:
+ handle.write(new_content)
+
+ else:
+ print("""# Code Scanning Coverage Report\n\nThis report shows the coverage of Code Scanning rules for the current repository.\n\n""")
+ print(markdown)
+
+ def runRules(self, arguments):
+ """Run and generate the queries."""
+
+ codeql = CodeQL()
+
+ src = CodeQLPack("ql/src")
+ src_suite = os.path.join(src.path, src.default_suite)
+
+ # load the queries and config
+ rules = []
+ config = {}
+
+ if not os.path.exists(arguments.rules):
+ logger.error("Queries folder not found.")
+ raise Exception("Queries folder not found.")
+ config_path = os.path.join(arguments.rules, "config.yml")
+ with open(config_path, "r") as handle:
+ config = yaml.safe_load(handle)
+
+ for root, dirs, files in os.walk(arguments.rules):
+ for query_file in files:
+ if query_file == "config.yml":
+ continue
+
+ rules.append(Rule.load(os.path.join(root, query_file)))
+
+ logger.info(f"Loaded {len(rules)} rules.")
+ # list of all of the queries included in the pack
+ queries = self.generateQueries(codeql, src_suite)
+ queries_ids = []
+
+ for query in queries:
+ id = query.get("id")
+ # {language}/{cloud_provider}/{category}
+ try:
+ id_lang, id_provider, id_name = id.split("/")
+ except ValueError:
+ # TODO :: handle this better
+ logger.error(f" - invalid rule id `{id}`")
+ continue
+ queries_ids.append(id)
+
+ # print(f"Rule :: {id_lang} :: {id_provider} :: {id_name}")
+ # check: language
+ if id_lang not in config.get("languages", []):
+ logger.error(f"{id} :: language `{id_lang}` not included in config")
+ # check: cloud provider
+ if id_provider not in config.get("providers", []):
+ logger.error(f"{id} :: provider `{id_provider}` not included in config")
+
+ # find the rule in the rules list
+ rule = next((r for r in rules if r.name == id_name), None)
+ # check: name
+ if not rule:
+ logger.debug(f"{id} :: rule `{id}` not found in rules")
+ continue
+
+ # check: severity score
+ severity_score = float(query.get("properties", {}).get("security-severity", 0))
+ if not severity_score or severity_score == "":
+ logger.error(f"{id} :: severity not set")
+ elif severity_score != rule.severity:
+ logger.error(f"{id} :: severity `{severity_score}` does not match rule `{rule.severity}`")
+
+ # tags
+ tags = query.get("properties", {}).get("tags", [])
+
+ # check: provider tags
+ if id_provider not in tags:
+ logger.error(f"{id} :: provider `{id_provider}` not in tags")
+ # check: language tags
+ alias = config.get("languages_aliases", {}).get(id_lang)
+ if alias and alias not in tags:
+ logger.error(f"{id} :: language alias `{alias}` not in tags")
+ elif not alias and id_lang not in tags:
+ logger.error(f"{id} :: language `{id_lang}` not in tags")
+ # TODO check: category id tags
+ # if rule.id not in tags:
+ # logger.error(f"{id} :: category id `{rule.id}` not in tags")
+ # check: rule tags
+ for tag in rule.tags:
+ if tag not in tags:
+ logger.error(f"{id} :: tag `{tag}` not in tags")
+
+ for rule in rules:
+ # check: required
+ if rule.required:
+ for required in rule.required:
+ fullname = f"{required}/{rule.name}"
+ if fullname not in queries_ids:
+ logger.error(f"{fullname} :: not found in queries")
+
+if __name__ == "__main__":
+ cli = CoverageCommandLine()
+ cli.run()
diff --git a/iac/scripts/create-coverage.sh b/iac/scripts/create-coverage.sh
new file mode 100644
index 000000000000..fdf8e4f847ef
--- /dev/null
+++ b/iac/scripts/create-coverage.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+pip install ghastoolkit
+
+python3 ./scripts/create-coverage.py $@
diff --git a/iac/scripts/create-extractor-pack.ps1 b/iac/scripts/create-extractor-pack.ps1
new file mode 100644
index 000000000000..65b4dfc348a4
--- /dev/null
+++ b/iac/scripts/create-extractor-pack.ps1
@@ -0,0 +1,14 @@
+cargo build --release
+
+cargo run --release -p ql-generator -- --dbscheme ql/src/ql.dbscheme --library ql/src/codeql_ql/ast/internal/TreeSitter.qll
+codeql query format -i ql\src\codeql_ql\ast\internal\TreeSitter.qll
+
+if (Test-Path -Path extractor-pack) {
+ rm -Recurse -Force extractor-pack
+}
+mkdir extractor-pack | Out-Null
+cp codeql-extractor.yml, ql\src\ql.dbscheme, ql\src\ql.dbscheme.stats extractor-pack
+cp -Recurse tools extractor-pack
+mkdir extractor-pack\tools\win64 | Out-Null
+cp target\release\ql-extractor.exe extractor-pack\tools\win64\extractor.exe
+cp target\release\ql-autobuilder.exe extractor-pack\tools\win64\autobuilder.exe
diff --git a/iac/scripts/create-extractor-pack.sh b/iac/scripts/create-extractor-pack.sh
new file mode 100644
index 000000000000..7449383d749e
--- /dev/null
+++ b/iac/scripts/create-extractor-pack.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+set -eux
+CARGO=cargo
+if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ platform="linux64"
+ if which cross; then
+ CARGO=cross
+ fi
+elif [[ "$OSTYPE" == "darwin"* ]]; then
+ platform="osx64"
+else
+ echo "Unknown OS"
+ exit 1
+fi
+
+if which codeql >/dev/null; then
+ CODEQL_BINARY="codeql"
+elif gh codeql >/dev/null; then
+ CODEQL_BINARY="gh codeql"
+else
+ gh extension install github/gh-codeql
+ CODEQL_BINARY="gh codeql"
+fi
+
+echo "Update submodules..."
+git submodule update --init --recursive
+
+cargo build --release
+cargo run --release --bin codeql-extractor-iac -- generate --dbscheme ql/lib/iac.dbscheme --library ql/lib/codeql/iac/ast/internal/TreeSitter.qll
+$CODEQL_BINARY query format -i ql/lib/codeql/iac/ast/internal/TreeSitter.qll
+
+rm -rf extractor-pack
+mkdir -p extractor-pack
+cp -r codeql-extractor.yml downgrades tools ql/lib/iac.dbscheme ql/lib/iac.dbscheme.stats extractor-pack/
+mkdir -p extractor-pack/tools/${platform}
+cp target/release/codeql-extractor-iac extractor-pack/tools/${platform}/extractor
diff --git a/iac/scripts/rules/compute/compute-unencrypted.yml b/iac/scripts/rules/compute/compute-unencrypted.yml
new file mode 100644
index 000000000000..b43fc0c0b992
--- /dev/null
+++ b/iac/scripts/rules/compute/compute-unencrypted.yml
@@ -0,0 +1,7 @@
+id: IAC-050
+name: compute-unencrypted
+severity: 7.0
+
+tags:
+ - security
+ - compute
diff --git a/iac/scripts/rules/config.yml b/iac/scripts/rules/config.yml
new file mode 100644
index 000000000000..4dcd7c73deec
--- /dev/null
+++ b/iac/scripts/rules/config.yml
@@ -0,0 +1,25 @@
+# rule: {language}/{cloud_provider}/{category}
+languages:
+ - tf # terraform
+ - bicep
+ - cf # cloudformation
+ - hc # helmchart(s)
+ - actions
+ - openapi
+ - containers
+
+languages_aliases:
+ tf: terraform
+ cf: cloudformation
+ hc: helmcharts
+
+providers:
+ - all # impact all providers
+ - azure
+ - aws
+ - gcp
+ - github # actions / hcl provider
+ - kubernetes
+ - alicloud
+ - web # for openapi
+ - docker
diff --git a/iac/scripts/rules/database/database-tls-disabled.yml b/iac/scripts/rules/database/database-tls-disabled.yml
new file mode 100644
index 000000000000..582fb5f27549
--- /dev/null
+++ b/iac/scripts/rules/database/database-tls-disabled.yml
@@ -0,0 +1,7 @@
+id: IAC-010
+name: database-tls-disabled
+severity: 7.0
+
+tags:
+ - security
+ - database
diff --git a/iac/scripts/rules/database/database-unencrypted.yml b/iac/scripts/rules/database/database-unencrypted.yml
new file mode 100644
index 000000000000..68375ed5cf4d
--- /dev/null
+++ b/iac/scripts/rules/database/database-unencrypted.yml
@@ -0,0 +1,7 @@
+id: IAC-011
+name: database-unencrypted
+severity: 7.0
+
+tags:
+ - security
+ - database
diff --git a/iac/scripts/rules/database/database-weak-encryption.yml b/iac/scripts/rules/database/database-weak-encryption.yml
new file mode 100644
index 000000000000..895617750c54
--- /dev/null
+++ b/iac/scripts/rules/database/database-weak-encryption.yml
@@ -0,0 +1,7 @@
+id: IAC-012
+name: database-weak-encryption
+severity: 4.0
+
+tags:
+ - security
+ - database
diff --git a/iac/scripts/rules/kubernetes/kubernetes-privileged-pod.yml b/iac/scripts/rules/kubernetes/kubernetes-privileged-pod.yml
new file mode 100644
index 000000000000..9b143d12908d
--- /dev/null
+++ b/iac/scripts/rules/kubernetes/kubernetes-privileged-pod.yml
@@ -0,0 +1,7 @@
+id: IAC-050
+name: kubernetes-privileged-pod
+severity: 10.0
+
+tags:
+ - security
+ - kubernetes
diff --git a/iac/scripts/rules/storage/storage-logging-disabled.yml b/iac/scripts/rules/storage/storage-logging-disabled.yml
new file mode 100644
index 000000000000..592b3d8606ea
--- /dev/null
+++ b/iac/scripts/rules/storage/storage-logging-disabled.yml
@@ -0,0 +1,7 @@
+id: IAC-003
+name: storage-logging-disabled
+severity: 8.0
+
+tags:
+ - security
+ - storage
diff --git a/iac/scripts/rules/storage/storage-publicly-accessible.yml b/iac/scripts/rules/storage/storage-publicly-accessible.yml
new file mode 100644
index 000000000000..60b2f8019e5d
--- /dev/null
+++ b/iac/scripts/rules/storage/storage-publicly-accessible.yml
@@ -0,0 +1,15 @@
+id: IAC-001
+name: storage-publicly-accessible
+description: "Cloud Storage buckets should not be publicly accessible to prevent unauthorized access to data."
+severity: 10.0
+
+tags:
+ - security
+ - storage
+
+required:
+ - cf/aws
+ - tf/aws
+ - tf/azure
+ - tf/gcp
+ - tf/alicloud
diff --git a/iac/scripts/rules/storage/storage-tls-disabled.yml b/iac/scripts/rules/storage/storage-tls-disabled.yml
new file mode 100644
index 000000000000..cab2093a5a62
--- /dev/null
+++ b/iac/scripts/rules/storage/storage-tls-disabled.yml
@@ -0,0 +1,7 @@
+id: IAC-010
+name: storage-tls-disabled
+severity: 9.0
+
+tags:
+ - security
+ - storage
diff --git a/iac/scripts/rules/storage/storage-unencrypted.yml b/iac/scripts/rules/storage/storage-unencrypted.yml
new file mode 100644
index 000000000000..4beae29dbda5
--- /dev/null
+++ b/iac/scripts/rules/storage/storage-unencrypted.yml
@@ -0,0 +1,11 @@
+id: IAC-004
+name: storage-unencrypted
+severity: 6.0
+
+tags:
+ - security
+ - storage
+
+required:
+ - tf/aws
+ - tf/azure
diff --git a/iac/scripts/rules/storage/storage-versioning-disabled.yml b/iac/scripts/rules/storage/storage-versioning-disabled.yml
new file mode 100644
index 000000000000..6d13bbfb5a93
--- /dev/null
+++ b/iac/scripts/rules/storage/storage-versioning-disabled.yml
@@ -0,0 +1,8 @@
+id: IAC-002
+name: storage-versioning-disabled
+description: "Cloud Storage buckets should have versioning enabled to prevent accidental deletion of data."
+severity: 6.0
+
+tags:
+ - security
+ - storage
diff --git a/iac/scripts/rules/vault/vault-weak-key.yml b/iac/scripts/rules/vault/vault-weak-key.yml
new file mode 100644
index 000000000000..ebb93e2050cc
--- /dev/null
+++ b/iac/scripts/rules/vault/vault-weak-key.yml
@@ -0,0 +1,7 @@
+id: IAC-020
+name: vault-weak-key
+severity: 8.0
+
+tags:
+ - security
+ - vault
diff --git a/iac/scripts/run-tests.sh b/iac/scripts/run-tests.sh
new file mode 100644
index 000000000000..e78ca974fb78
--- /dev/null
+++ b/iac/scripts/run-tests.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# get the argument
+TESTS_DIR=$1
+if [ -z "$TESTS_DIR" ]; then
+ TESTS_DIR="ql/test"
+fi
+
+if which codeql >/dev/null; then
+ CODEQL_BINARY="codeql"
+elif gh codeql >/dev/null; then
+ CODEQL_BINARY="gh codeql"
+else
+ gh extension install github/gh-codeql
+ CODEQL_BINARY="gh codeql"
+fi
+
+$CODEQL_BINARY pack install ql/test
+
+echo "Running tests in $TESTS_DIR"
+
+$CODEQL_BINARY test run \
+ -j 0 \
+ --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition \
+ --search-path ./extractor-pack \
+ "$TESTS_DIR"
diff --git a/iac/tools/autobuild.cmd b/iac/tools/autobuild.cmd
new file mode 100644
index 000000000000..55c3e82cf5cc
--- /dev/null
+++ b/iac/tools/autobuild.cmd
@@ -0,0 +1,5 @@
+@echo off
+
+type NUL && "%CODEQL_EXTRACTOR_IAC_ROOT%\tools\%CODEQL_PLATFORM%\extractor" autobuild
+
+exit /b %ERRORLEVEL%
\ No newline at end of file
diff --git a/iac/tools/autobuild.sh b/iac/tools/autobuild.sh
new file mode 100644
index 000000000000..9c26192c60f9
--- /dev/null
+++ b/iac/tools/autobuild.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec "${CODEQL_EXTRACTOR_IAC_ROOT}/tools/${CODEQL_PLATFORM}/extractor" autobuild
\ No newline at end of file
diff --git a/iac/tools/index-files.cmd b/iac/tools/index-files.cmd
new file mode 100644
index 000000000000..d5a3a306fa90
--- /dev/null
+++ b/iac/tools/index-files.cmd
@@ -0,0 +1,8 @@
+@echo off
+
+type NUL && "%CODEQL_EXTRACTOR_IAC_ROOT%\tools\win64\extractor.exe" extract ^
+ --file-list "%1" ^
+ --source-archive-dir "%CODEQL_EXTRACTOR_IAC_SOURCE_ARCHIVE_DIR%" ^
+ --output-dir "%CODEQL_EXTRACTOR_IAC_TRAP_DIR%"
+
+exit /b %ERRORLEVEL%
diff --git a/iac/tools/index-files.sh b/iac/tools/index-files.sh
new file mode 100644
index 000000000000..e88671ef988d
--- /dev/null
+++ b/iac/tools/index-files.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -eu
+
+exec "${CODEQL_EXTRACTOR_IAC_ROOT}/tools/${CODEQL_PLATFORM}/extractor" \
+ extract \
+ --file-list "$1" \
+ --source-archive-dir "$CODEQL_EXTRACTOR_IAC_SOURCE_ARCHIVE_DIR" \
+ --output-dir "$CODEQL_EXTRACTOR_IAC_TRAP_DIR"
\ No newline at end of file
diff --git a/iac/tools/pre-finalize.cmd b/iac/tools/pre-finalize.cmd
new file mode 100644
index 000000000000..e32c42249dd5
--- /dev/null
+++ b/iac/tools/pre-finalize.cmd
@@ -0,0 +1,14 @@
+@echo off
+
+type NUL && "%CODEQL_DIST%\codeql" database index-files ^
+ --include=*.yml ^
+ --include=*.yaml ^
+ --include=*.json ^
+ --include=*.jsonc ^
+ --include=*.jsonl ^
+ --size-limit=5m ^
+ --language yaml ^
+ -- ^
+ "%CODEQL_EXTRACTOR_IAC_WIP_DATABASE%"
+
+exit /b %ERRORLEVEL%
\ No newline at end of file
diff --git a/iac/tools/pre-finalize.sh b/iac/tools/pre-finalize.sh
new file mode 100644
index 000000000000..a881cb508072
--- /dev/null
+++ b/iac/tools/pre-finalize.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -eu
+
+"$CODEQL_DIST/codeql" database index-files \
+ "--include=**/*.yml" \
+ "--include=**/*.yaml" \
+ "--include=**/*.json" \
+ "--include=**/*.jsonc" \
+ "--include=**/*.jsonl" \
+ --size-limit=5m \
+ --language yaml \
+ -- \
+ "$CODEQL_EXTRACTOR_IAC_WIP_DATABASE"
\ No newline at end of file
diff --git a/iac/tools/qltest.cmd b/iac/tools/qltest.cmd
new file mode 100644
index 000000000000..c960b58a298b
--- /dev/null
+++ b/iac/tools/qltest.cmd
@@ -0,0 +1,12 @@
+@echo off
+
+type NUL && "%CODEQL_DIST%\codeql.exe" database index-files ^
+ --prune=**/*.testproj ^
+ --include-extension=.hcl ^
+ --include-extension=.tf ^
+ --size-limit=5m ^
+ --language=hcl ^
+ --working-dir=. ^
+ "%CODEQL_EXTRACTOR_IAC_WIP_DATABASE%"
+
+exit /b %ERRORLEVEL%
diff --git a/iac/tools/qltest.sh b/iac/tools/qltest.sh
new file mode 100644
index 000000000000..62bb316caa0a
--- /dev/null
+++ b/iac/tools/qltest.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+set -eu
+
+"${CODEQL_DIST}/codeql" database index-files \
+ --prune="**/*.testproj" \
+ --include-extension=.hcl \
+ --include-extension=.tf \
+ --include-extension=.tfvars \
+ --include-extension=.bicep \
+ --size-limit=5m \
+ --language=iac \
+ --working-dir=.\
+ "$CODEQL_EXTRACTOR_IAC_WIP_DATABASE"
+
+exec "${CODEQL_DIST}/codeql" database index-files \
+ --prune="**/*.testproj" \
+ --include-extension=.yml \
+ --include-extension=.yaml \
+ --include-extension=.json \
+ --size-limit=5m \
+ --language=yaml \
+ --working-dir=.\
+ "$CODEQL_EXTRACTOR_IAC_WIP_DATABASE"
\ No newline at end of file