From d2c55ef5f3e26a46c02ed00e1147b3414c6864db Mon Sep 17 00:00:00 2001 From: chanel-y Date: Tue, 15 Sep 2026 11:47:10 -0700 Subject: [PATCH 1/3] Add Kubernetes and Compose YAML models Add structural Kubernetes workload and RBAC classes and expand Compose services with environment, secret, image, volume, capability, and runtime settings. Include focused library fixtures for workload, RBAC, and both Compose environment forms. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8eab11c3-3153-4036-9847-28a2f615835f --- iac/ql/lib/codeql/iac/compose/Compose.qll | 81 ++++++- .../lib/codeql/iac/kubernetes/Kubernetes.qll | 218 ++++++++++++++++++ iac/ql/lib/iac.qll | 3 + iac/ql/test/library-tests/compose/ast/AST.ql | 5 + .../compose/ast/compose-list.yaml | 11 + .../library-tests/compose/ast/compose.yaml | 18 ++ .../test/library-tests/kubernetes/ast/AST.ql | 13 ++ .../kubernetes/ast/workloads.yaml | 54 +++++ 8 files changed, 401 insertions(+), 2 deletions(-) create mode 100644 iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll create mode 100644 iac/ql/test/library-tests/compose/ast/AST.ql create mode 100644 iac/ql/test/library-tests/compose/ast/compose-list.yaml create mode 100644 iac/ql/test/library-tests/compose/ast/compose.yaml create mode 100644 iac/ql/test/library-tests/kubernetes/ast/AST.ql create mode 100644 iac/ql/test/library-tests/kubernetes/ast/workloads.yaml diff --git a/iac/ql/lib/codeql/iac/compose/Compose.qll b/iac/ql/lib/codeql/iac/compose/Compose.qll index 496d123c4483..f0905bb353dc 100644 --- a/iac/ql/lib/codeql/iac/compose/Compose.qll +++ b/iac/ql/lib/codeql/iac/compose/Compose.qll @@ -34,6 +34,12 @@ module Compose { * Returns the services defined in the Compose file. */ Service getServices() { result = this.lookup("services").getAChildNode() } + + YamlValue getNetworks() { result = this.lookup("networks") } + + YamlValue getVolumes() { result = this.lookup("volumes") } + + YamlValue getSecrets() { result = this.lookup("secrets") } } /** @@ -51,8 +57,79 @@ module Compose { * Returns the name of the service. */ string getName() { - result = this.lookup("container_name").toString() - // TODO get parent key name + result = yamlToString(this.lookup("container_name")) + or + exists(YamlMapping services, YamlValue key, YamlValue value | + services = compose.lookup("services") and + services.maps(key, value) and + value = this and + result = key.toString() + ) + } + + string getImage() { result = yamlToString(this.lookup("image")) } + + YamlValue getBuild() { result = this.lookup("build") } + + YamlValue getEnvironment() { result = this.lookup("environment") } + + EnvironmentEntry getEnvironmentEntries() { + result = this.lookup("environment").(YamlSequence).getAChild() + or + result = this.lookup("environment").(YamlMapping).getAChild() + } + + YamlValue getSecrets() { result = this.lookup("secrets") } + + YamlValue getVolumes() { result = this.lookup("volumes") } + + YamlValue getCapAdd() { result = this.lookup("cap_add") } + + YamlValue getCapDrop() { result = this.lookup("cap_drop") } + + YamlValue getPrivileged() { result = this.lookup("privileged") } + + YamlValue getReadOnly() { result = this.lookup("read_only") } + + YamlValue getUser() { result = this.lookup("user") } + + YamlValue getPid() { result = this.lookup("pid") } + + YamlValue getNetworkMode() { result = this.lookup("network_mode") } + + YamlValue getDevices() { result = this.lookup("devices") } + } + + class EnvironmentEntry extends YamlNode { + EnvironmentEntry() { + exists(Service service | + service.lookup("environment").(YamlSequence).getAChildNode() = this + ) + or + exists(Service service | + service.lookup("environment").(YamlMapping).getAChildNode() = this + ) + } + + string getName() { + result = this.(YamlString).getValue() + or + exists(YamlMapping environment, YamlValue key, YamlValue value | + environment.getAChildNode() = this and + environment.maps(key, value) and + value = this and + result = key.toString() + ) + } + + YamlValue getValue() { + result = this.(YamlMapping).lookup("value") + or + result = this.(YamlMapping).lookup("value_from") + or + result = this.(YamlMapping).lookup("value") + or + result = this } } } diff --git a/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll b/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll new file mode 100644 index 000000000000..97516328f9c2 --- /dev/null +++ b/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll @@ -0,0 +1,218 @@ +private import codeql.iac.YAML +private import codeql.files.FileSystem + +/** + * Structural model for Kubernetes YAML manifests. + * + * The model intentionally covers locally observable workload and RBAC + * structure. It does not attempt to resolve admission policies, rendered Helm + * values, or cluster-level defaults. + */ +module YamlKubernetes { + class Document extends YamlNode, YamlDocument, YamlMapping { + Document() { + this.getFile().getExtension() = ["yml", "yaml"] and + exists(this.lookup("apiVersion")) and + exists(this.lookup("kind")) + } + + override string toString() { result = "Kubernetes " + this.getKind() + " document" } + + string getApiVersion() { result = yamlToString(this.lookup("apiVersion")) } + + string getKind() { result = yamlToString(this.lookup("kind")) } + + Metadata getMetadata() { result = this.lookup("metadata") } + + YamlMapping getSpec() { result = this.lookup("spec") } + + PodSpec getPodSpec() { + result = this.getSpec().(PodSpec) + or + result = this.getSpec().lookup("template").(YamlMapping).lookup("spec").(PodSpec) + or + result = this.getSpec().lookup("jobTemplate").(YamlMapping).lookup("spec").(YamlMapping) + .lookup("template").(YamlMapping).lookup("spec").(PodSpec) + } + + Container getContainers() { result = this.getPodSpec().getContainers() } + + Container getInitContainers() { result = this.getPodSpec().getInitContainers() } + + Container getEphemeralContainers() { result = this.getPodSpec().getEphemeralContainers() } + } + + class Metadata extends YamlNode, YamlMapping { + Metadata() { exists(Document document | document.lookup("metadata") = this) } + + string getName() { result = yamlToString(this.lookup("name")) } + + string getNamespace() { result = yamlToString(this.lookup("namespace")) } + + YamlValue getLabels() { result = this.lookup("labels") } + + YamlValue getAnnotations() { result = this.lookup("annotations") } + } + + class PodSpec extends YamlNode, YamlMapping { + PodSpec() { + exists(Document document | document.getSpec() = this) + or + exists(YamlMapping template | template.lookup("spec") = this) + } + + Container getContainers() { result = this.lookup("containers").(YamlSequence).getAChild() } + + Container getInitContainers() { + result = this.lookup("initContainers").(YamlSequence).getAChild() + } + + Container getEphemeralContainers() { + result = this.lookup("ephemeralContainers").(YamlSequence).getAChild() + } + + SecurityContext getSecurityContext() { result = this.lookup("securityContext") } + + YamlValue getServiceAccountName() { result = this.lookup("serviceAccountName") } + + YamlValue getAutomountServiceAccountToken() { + result = this.lookup("automountServiceAccountToken") + } + + Volume getVolumes() { result = this.lookup("volumes").(YamlSequence).getAChild() } + + YamlValue getHostNetwork() { result = this.lookup("hostNetwork") } + + YamlValue getHostPid() { result = this.lookup("hostPID") } + + YamlValue getHostIpc() { result = this.lookup("hostIPC") } + } + + class Container extends YamlNode, YamlMapping { + Container() { + exists(PodSpec pod | pod.lookup("containers").(YamlSequence).getAChildNode() = this) + or + exists(PodSpec pod | pod.lookup("initContainers").(YamlSequence).getAChildNode() = this) + or + exists(PodSpec pod | pod.lookup("ephemeralContainers").(YamlSequence).getAChildNode() = this) + } + + string getName() { result = yamlToString(this.lookup("name")) } + + string getImage() { result = yamlToString(this.lookup("image")) } + + SecurityContext getSecurityContext() { result = this.lookup("securityContext") } + + YamlValue getCommand() { result = this.lookup("command") } + + YamlValue getArgs() { result = this.lookup("args") } + + YamlValue getEnv() { result = this.lookup("env") } + + EnvEntry getEnvironmentEntries() { result = this.lookup("env").(YamlSequence).getAChild() } + + YamlValue getEnvFrom() { result = this.lookup("envFrom") } + + YamlValue getVolumeMounts() { result = this.lookup("volumeMounts") } + + YamlValue getPorts() { result = this.lookup("ports") } + } + + class EnvEntry extends YamlNode, YamlMapping { + EnvEntry() { + exists(Container container | container.lookup("env").(YamlSequence).getAChildNode() = this) + } + + string getName() { result = yamlToString(this.lookup("name")) } + + YamlValue getValue() { result = this.lookup("value") } + + YamlMapping getValueFrom() { result = this.lookup("valueFrom") } + + YamlMapping getSecretKeyRef() { result = this.getValueFrom().lookup("secretKeyRef") } + + YamlMapping getConfigMapKeyRef() { result = this.getValueFrom().lookup("configMapKeyRef") } + } + + class SecurityContext extends YamlNode, YamlMapping { + SecurityContext() { + exists(PodSpec pod | pod.lookup("securityContext") = this) + or + exists(Container container | container.lookup("securityContext") = this) + } + + YamlValue getPrivileged() { result = this.lookup("privileged") } + + YamlValue getAllowPrivilegeEscalation() { + result = this.lookup("allowPrivilegeEscalation") + } + + YamlValue getRunAsUser() { result = this.lookup("runAsUser") } + + YamlValue getRunAsGroup() { result = this.lookup("runAsGroup") } + + YamlValue getRunAsNonRoot() { result = this.lookup("runAsNonRoot") } + + YamlValue getReadOnlyRootFilesystem() { + result = this.lookup("readOnlyRootFilesystem") + } + + YamlValue getCapabilities() { result = this.lookup("capabilities") } + + YamlValue getSeccompProfile() { result = this.lookup("seccompProfile") } + } + + class Volume extends YamlNode, YamlMapping { + Volume() { exists(PodSpec pod | pod.lookup("volumes").(YamlSequence).getAChildNode() = this) } + + string getName() { result = yamlToString(this.lookup("name")) } + + YamlValue getHostPath() { result = this.lookup("hostPath") } + + YamlValue getProjected() { result = this.lookup("projected") } + + YamlValue getSecret() { result = this.lookup("secret") } + + YamlValue getConfigMap() { result = this.lookup("configMap") } + } + + class Role extends Document { + Role() { this.getKind() = ["Role", "ClusterRole"] } + + Rule getRules() { result = this.getSpec().lookup("rules").(YamlSequence).getAChild() } + } + + class Rule extends YamlNode, YamlMapping { + Rule() { exists(Role role | role.getSpec().lookup("rules").(YamlSequence).getAChildNode() = this) } + + YamlValue getApiGroups() { result = this.lookup("apiGroups") } + + YamlValue getResources() { result = this.lookup("resources") } + + YamlValue getVerbs() { result = this.lookup("verbs") } + + YamlValue getResourceNames() { result = this.lookup("resourceNames") } + } + + class RoleBinding extends Document { + RoleBinding() { this.getKind() = ["RoleBinding", "ClusterRoleBinding"] } + + YamlMapping getRoleRef() { result = this.getSpec().lookup("roleRef") } + + Subject getSubjects() { result = this.getSpec().lookup("subjects").(YamlSequence).getAChild() } + } + + class Subject extends YamlNode, YamlMapping { + Subject() { + exists(RoleBinding binding | + binding.getSpec().lookup("subjects").(YamlSequence).getAChildNode() = this + ) + } + + string getKind() { result = yamlToString(this.lookup("kind")) } + + string getName() { result = yamlToString(this.lookup("name")) } + + string getNamespace() { result = yamlToString(this.lookup("namespace")) } + } +} diff --git a/iac/ql/lib/iac.qll b/iac/ql/lib/iac.qll index f5258b4b39dd..62838ba9a842 100644 --- a/iac/ql/lib/iac.qll +++ b/iac/ql/lib/iac.qll @@ -12,6 +12,8 @@ import codeql.iac.containers.Containers import codeql.iac.containers.Images // Compose import codeql.iac.compose.Compose +// Kubernetes +import codeql.iac.kubernetes.Kubernetes // HelmCharts import codeql.iac.helmcharts.HelmChart // Terraform / HCL @@ -20,3 +22,4 @@ import hcl import codeql.iac.openapi.OpenApi // YAML import codeql.iac.YAML +import codeql.iac.YamlDocumentClassification diff --git a/iac/ql/test/library-tests/compose/ast/AST.ql b/iac/ql/test/library-tests/compose/ast/AST.ql new file mode 100644 index 000000000000..7f16160e10e6 --- /dev/null +++ b/iac/ql/test/library-tests/compose/ast/AST.ql @@ -0,0 +1,5 @@ +private import iac + +query predicate documents(Compose::Document n) { any() } +query predicate services(Compose::Service n) { any() } +query predicate environment(Compose::EnvironmentEntry n) { any() } diff --git a/iac/ql/test/library-tests/compose/ast/compose-list.yaml b/iac/ql/test/library-tests/compose/ast/compose-list.yaml new file mode 100644 index 000000000000..0d853ba26cdd --- /dev/null +++ b/iac/ql/test/library-tests/compose/ast/compose-list.yaml @@ -0,0 +1,11 @@ +services: + worker: + image: example/worker:1.0 + environment: + - DEBUG=true + - API_TOKEN + secrets: + - worker-token +secrets: + worker-token: + file: ./worker-token.txt diff --git a/iac/ql/test/library-tests/compose/ast/compose.yaml b/iac/ql/test/library-tests/compose/ast/compose.yaml new file mode 100644 index 000000000000..74fe325175ec --- /dev/null +++ b/iac/ql/test/library-tests/compose/ast/compose.yaml @@ -0,0 +1,18 @@ +services: + web: + image: nginx:1.25 + environment: + PASSWORD: ${PASSWORD} + DEBUG: "true" + secrets: + - app-password + cap_drop: + - ALL + read_only: true + user: "1000:1000" + network_mode: bridge + volumes: + - ./web:/srv/web +secrets: + app-password: + file: ./password.txt diff --git a/iac/ql/test/library-tests/kubernetes/ast/AST.ql b/iac/ql/test/library-tests/kubernetes/ast/AST.ql new file mode 100644 index 000000000000..d265519566fc --- /dev/null +++ b/iac/ql/test/library-tests/kubernetes/ast/AST.ql @@ -0,0 +1,13 @@ +private import iac + +query predicate documents(YamlKubernetes::Document n) { any() } +query predicate metadata(YamlKubernetes::Metadata n) { any() } +query predicate podSpecs(YamlKubernetes::PodSpec n) { any() } +query predicate containers(YamlKubernetes::Container n) { any() } +query predicate env(YamlKubernetes::EnvEntry n) { any() } +query predicate security(YamlKubernetes::SecurityContext n) { any() } +query predicate volumes(YamlKubernetes::Volume n) { any() } +query predicate roles(YamlKubernetes::Role n) { any() } +query predicate rules(YamlKubernetes::Rule n) { any() } +query predicate bindings(YamlKubernetes::RoleBinding n) { any() } +query predicate subjects(YamlKubernetes::Subject n) { any() } diff --git a/iac/ql/test/library-tests/kubernetes/ast/workloads.yaml b/iac/ql/test/library-tests/kubernetes/ast/workloads.yaml new file mode 100644 index 000000000000..390918371b0b --- /dev/null +++ b/iac/ql/test/library-tests/kubernetes/ast/workloads.yaml @@ -0,0 +1,54 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: web + namespace: prod +spec: + replicas: 2 + template: + metadata: + labels: + app: web + spec: + serviceAccountName: web + automountServiceAccountToken: false + hostNetwork: false + containers: + - name: web + image: nginx:1.25 + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: app-secrets + key: password + securityContext: + privileged: false + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + volumes: + - name: config + configMap: + name: app-config +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: reader +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: reader-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: reader +subjects: + - kind: ServiceAccount + name: web + namespace: prod From 4e86ca029a68f5e4ed5968777affa7d1b6213210 Mon Sep 17 00:00:00 2001 From: chanel-y Date: Thu, 17 Sep 2026 12:26:56 -0700 Subject: [PATCH 2/3] Fix Kubernetes and Compose model relationships Restrict pod specs to actual workload locations, model top-level Kubernetes RBAC rules and subjects, and normalize Compose mapping and list environment entries. Add generated expected outputs and classify Kubernetes YAML through the shared document foundation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8eab11c3-3153-4036-9847-28a2f615835f --- .../codeql/iac/YamlDocumentClassification.qll | 4 +- iac/ql/lib/codeql/iac/compose/Compose.qll | 24 ++++------- .../lib/codeql/iac/kubernetes/Kubernetes.qll | 41 +++++++++++-------- .../library-tests/compose/ast/AST.expected | 13 ++++++ ...{compose-list.yaml => docker-compose.yaml} | 0 .../library-tests/kubernetes/ast/AST.expected | 31 ++++++++++++++ .../yaml-classification/AST.expected | 2 + 7 files changed, 83 insertions(+), 32 deletions(-) create mode 100644 iac/ql/test/library-tests/compose/ast/AST.expected rename iac/ql/test/library-tests/compose/ast/{compose-list.yaml => docker-compose.yaml} (100%) create mode 100644 iac/ql/test/library-tests/kubernetes/ast/AST.expected diff --git a/iac/ql/lib/codeql/iac/YamlDocumentClassification.qll b/iac/ql/lib/codeql/iac/YamlDocumentClassification.qll index 9829bfe845d7..bff776bff270 100644 --- a/iac/ql/lib/codeql/iac/YamlDocumentClassification.qll +++ b/iac/ql/lib/codeql/iac/YamlDocumentClassification.qll @@ -22,6 +22,7 @@ import iac private import codeql.iac.YAML private import codeql.iac.azure.Pipelines private import codeql.iac.helmcharts.HelmChart +private import codeql.iac.kubernetes.Kubernetes private import codeql.iac.compose.Compose private import codeql.iac.openapi.OpenApi private import codeql.iac.aws.CloudFormation @@ -69,7 +70,8 @@ module YamlDocumentClassification { ( doc instanceof AzurePipelines::Document and kind = "ado-pipeline" or - doc instanceof HelmChart::Document and kind = "kubernetes-helm" + (doc instanceof HelmChart::Document or doc instanceof YamlKubernetes::Document) and + kind = "kubernetes-helm" or doc instanceof Compose::Document and kind = "compose" or diff --git a/iac/ql/lib/codeql/iac/compose/Compose.qll b/iac/ql/lib/codeql/iac/compose/Compose.qll index f0905bb353dc..78b0cca8a045 100644 --- a/iac/ql/lib/codeql/iac/compose/Compose.qll +++ b/iac/ql/lib/codeql/iac/compose/Compose.qll @@ -100,36 +100,30 @@ module Compose { YamlValue getDevices() { result = this.lookup("devices") } } - class EnvironmentEntry extends YamlNode { + class EnvironmentEntry extends YamlValue { EnvironmentEntry() { exists(Service service | - service.lookup("environment").(YamlSequence).getAChildNode() = this + service.lookup("environment").(YamlSequence).getAChild() = this ) or exists(Service service | - service.lookup("environment").(YamlMapping).getAChildNode() = this + service.lookup("environment").(YamlMapping).getAChild() = this ) } string getName() { - result = this.(YamlString).getValue() - or exists(YamlMapping environment, YamlValue key, YamlValue value | - environment.getAChildNode() = this and environment.maps(key, value) and value = this and - result = key.toString() + result = yamlToString(key.(YamlString)) ) - } - - YamlValue getValue() { - result = this.(YamlMapping).lookup("value") - or - result = this.(YamlMapping).lookup("value_from") or - result = this.(YamlMapping).lookup("value") + result = this.(YamlString).getValue().regexpCapture("([^=]+)=.*", 1) or - result = this + result = this.(YamlString).getValue() and + not result.matches("%=%") } + + YamlValue getValue() { result = this } } } diff --git a/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll b/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll index 97516328f9c2..30f1cc123213 100644 --- a/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll +++ b/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll @@ -13,7 +13,8 @@ module YamlKubernetes { Document() { this.getFile().getExtension() = ["yml", "yaml"] and exists(this.lookup("apiVersion")) and - exists(this.lookup("kind")) + exists(this.lookup("kind")) and + this.lookup("kind").(YamlString).getValue() != "Chart" } override string toString() { result = "Kubernetes " + this.getKind() + " document" } @@ -26,14 +27,7 @@ module YamlKubernetes { YamlMapping getSpec() { result = this.lookup("spec") } - PodSpec getPodSpec() { - result = this.getSpec().(PodSpec) - or - result = this.getSpec().lookup("template").(YamlMapping).lookup("spec").(PodSpec) - or - result = this.getSpec().lookup("jobTemplate").(YamlMapping).lookup("spec").(YamlMapping) - .lookup("template").(YamlMapping).lookup("spec").(PodSpec) - } + PodSpec getPodSpec() { result.getDocument() = this } Container getContainers() { result = this.getPodSpec().getContainers() } @@ -56,9 +50,24 @@ module YamlKubernetes { class PodSpec extends YamlNode, YamlMapping { PodSpec() { - exists(Document document | document.getSpec() = this) + exists(Document document | + document.getKind() = "Pod" and + document.lookup("spec") = this + ) or - exists(YamlMapping template | template.lookup("spec") = this) + exists(Document document | + document.getKind() = + [ + "DaemonSet", "Deployment", "Job", "ReplicaSet", "ReplicationController", "StatefulSet", + ] and + document.lookup("spec").(YamlMapping).lookup("template").(YamlMapping).lookup("spec") = this + ) + or + exists(Document document | + document.getKind() = "CronJob" and + document.lookup("spec").(YamlMapping).lookup("jobTemplate").(YamlMapping).lookup("spec") + .(YamlMapping).lookup("template").(YamlMapping).lookup("spec") = this + ) } Container getContainers() { result = this.lookup("containers").(YamlSequence).getAChild() } @@ -179,11 +188,11 @@ module YamlKubernetes { class Role extends Document { Role() { this.getKind() = ["Role", "ClusterRole"] } - Rule getRules() { result = this.getSpec().lookup("rules").(YamlSequence).getAChild() } + Rule getRules() { result = this.lookup("rules").(YamlSequence).getAChild() } } class Rule extends YamlNode, YamlMapping { - Rule() { exists(Role role | role.getSpec().lookup("rules").(YamlSequence).getAChildNode() = this) } + Rule() { exists(Role role | role.lookup("rules").(YamlSequence).getAChildNode() = this) } YamlValue getApiGroups() { result = this.lookup("apiGroups") } @@ -197,15 +206,15 @@ module YamlKubernetes { class RoleBinding extends Document { RoleBinding() { this.getKind() = ["RoleBinding", "ClusterRoleBinding"] } - YamlMapping getRoleRef() { result = this.getSpec().lookup("roleRef") } + YamlMapping getRoleRef() { result = this.lookup("roleRef") } - Subject getSubjects() { result = this.getSpec().lookup("subjects").(YamlSequence).getAChild() } + Subject getSubjects() { result = this.lookup("subjects").(YamlSequence).getAChild() } } class Subject extends YamlNode, YamlMapping { Subject() { exists(RoleBinding binding | - binding.getSpec().lookup("subjects").(YamlSequence).getAChildNode() = this + binding.lookup("subjects").(YamlSequence).getAChildNode() = this ) } diff --git a/iac/ql/test/library-tests/compose/ast/AST.expected b/iac/ql/test/library-tests/compose/ast/AST.expected new file mode 100644 index 000000000000..9ebc41d7f4ee --- /dev/null +++ b/iac/ql/test/library-tests/compose/ast/AST.expected @@ -0,0 +1,13 @@ +documents +| compose.yaml:1:1:18:25 | services: | +| docker-compose.yaml:1:1:11:29 | services: | +services +| compose.yaml:3:5:15:23 | image: nginx:1.25 | +| docker-compose.yaml:3:5:8:21 | image: ... ker:1.0 | +environment +| compose.yaml:5:7:5:14 | PASSWORD | +| compose.yaml:5:17:5:27 | ${PASSWORD} | +| compose.yaml:6:7:6:11 | DEBUG | +| compose.yaml:6:14:6:19 | "true" | +| docker-compose.yaml:5:9:5:18 | DEBUG=true | +| docker-compose.yaml:6:9:6:17 | API_TOKEN | diff --git a/iac/ql/test/library-tests/compose/ast/compose-list.yaml b/iac/ql/test/library-tests/compose/ast/docker-compose.yaml similarity index 100% rename from iac/ql/test/library-tests/compose/ast/compose-list.yaml rename to iac/ql/test/library-tests/compose/ast/docker-compose.yaml diff --git a/iac/ql/test/library-tests/kubernetes/ast/AST.expected b/iac/ql/test/library-tests/kubernetes/ast/AST.expected new file mode 100644 index 000000000000..7b800c10241c --- /dev/null +++ b/iac/ql/test/library-tests/kubernetes/ast/AST.expected @@ -0,0 +1,31 @@ +documents +| workloads.yaml:1:1:32:29 | HelmChart Document | +| workloads.yaml:1:1:32:29 | Kubernetes Deployment document | +| workloads.yaml:34:1:41:27 | HelmChart Document | +| workloads.yaml:34:1:41:27 | Kubernetes ClusterRole document | +| workloads.yaml:43:1:54:20 | HelmChart Document | +| workloads.yaml:43:1:54:20 | Kubernetes ClusterRoleBinding document | +metadata +| workloads.yaml:4:3:5:18 | name: web | +| workloads.yaml:37:3:37:15 | name: reader | +| workloads.yaml:46:3:46:23 | name: reader-binding | +podSpecs +| workloads.yaml:13:7:32:29 | service ... me: web | +containers +| workloads.yaml:17:11:29:6 | name: web | +env +| workloads.yaml:20:15:25:10 | name: PASSWORD | +security +| workloads.yaml:26:13:29:6 | privileged: false | +volumes +| workloads.yaml:30:11:32:29 | name: config | +roles +| workloads.yaml:34:1:41:27 | HelmChart Document | +| workloads.yaml:34:1:41:27 | Kubernetes ClusterRole document | +rules +| workloads.yaml:39:5:41:27 | apiGroups: [""] | +bindings +| workloads.yaml:43:1:54:20 | HelmChart Document | +| workloads.yaml:43:1:54:20 | Kubernetes ClusterRoleBinding document | +subjects +| workloads.yaml:52:5:54:20 | kind: ServiceAccount | diff --git a/iac/ql/test/library-tests/yaml-classification/AST.expected b/iac/ql/test/library-tests/yaml-classification/AST.expected index c8d07e824bca..75de151586df 100644 --- a/iac/ql/test/library-tests/yaml-classification/AST.expected +++ b/iac/ql/test/library-tests/yaml-classification/AST.expected @@ -3,6 +3,7 @@ supportedYamlDocument | cloudformation.yaml:1:1:6:29 | CloudFormation Document | cloudformation | | compose.yaml:1:1:4:24 | version: "3.9" | compose | | deployment.yaml:1:1:8:29 | HelmChart Document | kubernetes-helm | +| deployment.yaml:1:1:8:29 | Kubernetes Deployment document | kubernetes-helm | | openapi.yaml:1:1:12:26 | OpenApi Document | openapi | allYamlDocuments | arm-template.yaml:1:1:5:24 | $schema ... .json#" | @@ -10,6 +11,7 @@ allYamlDocuments | cloudformation.yaml:1:1:6:29 | CloudFormation Document | | compose.yaml:1:1:4:24 | version: "3.9" | | deployment.yaml:1:1:8:29 | HelmChart Document | +| deployment.yaml:1:1:8:29 | Kubernetes Deployment document | | openapi.json:1:1:8:1 | OpenApi Document | | openapi.yaml:1:1:12:26 | OpenApi Document | | unrelated.yaml:1:1:4:8 | descrip ... ocument | From 2ea94b70ff1870c41c29e0654d39df342c20af23 Mon Sep 17 00:00:00 2001 From: chanelyoung Date: Sun, 20 Sep 2026 20:40:27 -0700 Subject: [PATCH 3/3] update annotations, update to use getA/getAn, add service and container and environment value to unit test --- iac/ql/lib/codeql/iac/azure/Pipelines.qll | 4 +- iac/ql/lib/codeql/iac/compose/Compose.qll | 112 +++++++-- .../lib/codeql/iac/kubernetes/Kubernetes.qll | 228 ++++++++++++++++-- .../library-tests/compose/ast/AST.expected | 23 +- iac/ql/test/library-tests/compose/ast/AST.ql | 13 + .../library-tests/compose/ast/compose.yaml | 1 + .../library-tests/kubernetes/ast/AST.expected | 15 ++ .../test/library-tests/kubernetes/ast/AST.ql | 36 +++ 8 files changed, 381 insertions(+), 51 deletions(-) diff --git a/iac/ql/lib/codeql/iac/azure/Pipelines.qll b/iac/ql/lib/codeql/iac/azure/Pipelines.qll index f5b029b95ec7..795313f1ee2b 100644 --- a/iac/ql/lib/codeql/iac/azure/Pipelines.qll +++ b/iac/ql/lib/codeql/iac/azure/Pipelines.qll @@ -409,7 +409,7 @@ module AzurePipelines { string getName() { result = yamlToString(this.lookup("name")) } /** - * Gets the referenced revision. + * Gets the referenced revision, if any. */ string getRef() { result = yamlToString(this.lookup("ref")) } } @@ -444,7 +444,7 @@ module AzurePipelines { string getSource() { result = yamlToString(this.lookup("source")) } /** - * Gets the branch selector + * Gets the branch selector, if any. */ string getBranch() { result = yamlToString(this.lookup("branch")) } } diff --git a/iac/ql/lib/codeql/iac/compose/Compose.qll b/iac/ql/lib/codeql/iac/compose/Compose.qll index 78b0cca8a045..ab5442681267 100644 --- a/iac/ql/lib/codeql/iac/compose/Compose.qll +++ b/iac/ql/lib/codeql/iac/compose/Compose.qll @@ -24,21 +24,37 @@ module Compose { */ class Document extends Node, YamlDocument, YamlMapping { /** - * Returns the version of the Compose file. + * Gets the version of the Compose file, if any. */ string getApiVersion() { result = this.lookup("version").toString().regexpReplaceAll("('|\")", "") } /** - * Returns the services defined in the Compose file. + * Gets a service defined in the Compose file, if any. */ - Service getServices() { result = this.lookup("services").getAChildNode() } + Service getAService() { result = this.lookup("services").getAChildNode() } + /** + * Gets a service defined in the Compose file, if any. + * + * Use `getAService` instead. + */ + Service getServices() { result = this.getAService() } + + /** + * Gets the network definitions, if any. + */ YamlValue getNetworks() { result = this.lookup("networks") } + /** + * Gets the volume definitions, if any. + */ YamlValue getVolumes() { result = this.lookup("volumes") } + /** + * Gets the secret definitions, if any. + */ YamlValue getSecrets() { result = this.lookup("secrets") } } @@ -54,68 +70,116 @@ module Compose { Service() { compose.lookup("services").getAChildNode() = this } /** - * Returns the name of the service. + * Gets the name of the service. */ string getName() { - result = yamlToString(this.lookup("container_name")) - or exists(YamlMapping services, YamlValue key, YamlValue value | services = compose.lookup("services") and services.maps(key, value) and value = this and - result = key.toString() + result = yamlToString(key) ) } + /** + * Gets the explicit container name, if any. + */ + string getContainerName() { result = yamlToString(this.lookup("container_name")) } + + /** + * Gets the container image, if any. + */ string getImage() { result = yamlToString(this.lookup("image")) } + /** + * Gets the build configuration, if any. + */ YamlValue getBuild() { result = this.lookup("build") } + /** + * Gets the environment definition, if any. + */ YamlValue getEnvironment() { result = this.lookup("environment") } - EnvironmentEntry getEnvironmentEntries() { + /** + * Gets an environment entry, if any. + */ + EnvironmentEntry getAnEnvironmentEntry() { result = this.lookup("environment").(YamlSequence).getAChild() or - result = this.lookup("environment").(YamlMapping).getAChild() + this.lookup("environment").(YamlMapping).maps(result, _) } + /** + * Gets the secret references, if any. + */ YamlValue getSecrets() { result = this.lookup("secrets") } + /** + * Gets the volume mounts, if any. + */ YamlValue getVolumes() { result = this.lookup("volumes") } + /** + * Gets the capabilities to add, if any. + */ YamlValue getCapAdd() { result = this.lookup("cap_add") } + /** + * Gets the capabilities to drop, if any. + */ YamlValue getCapDrop() { result = this.lookup("cap_drop") } + /** + * Gets the privileged setting, if any. + */ YamlValue getPrivileged() { result = this.lookup("privileged") } + /** + * Gets the read-only root filesystem setting, if any. + */ YamlValue getReadOnly() { result = this.lookup("read_only") } + /** + * Gets the user setting, if any. + */ YamlValue getUser() { result = this.lookup("user") } + /** + * Gets the PID mode, if any. + */ YamlValue getPid() { result = this.lookup("pid") } + /** + * Gets the network mode, if any. + */ YamlValue getNetworkMode() { result = this.lookup("network_mode") } + /** + * Gets the device mappings, if any. + */ YamlValue getDevices() { result = this.lookup("devices") } } + /** + * An environment entry defined for a Compose service. + */ class EnvironmentEntry extends YamlValue { EnvironmentEntry() { - exists(Service service | - service.lookup("environment").(YamlSequence).getAChild() = this - ) + exists(Service service | service.lookup("environment").(YamlSequence).getAChild() = this) or - exists(Service service | - service.lookup("environment").(YamlMapping).getAChild() = this + exists(Service service, YamlValue value | + service.lookup("environment").(YamlMapping).maps(this, value) ) } + /** + * Gets the environment variable name. + */ string getName() { - exists(YamlMapping environment, YamlValue key, YamlValue value | - environment.maps(key, value) and - value = this and - result = yamlToString(key.(YamlString)) + exists(Service service, YamlValue value | + service.lookup("environment").(YamlMapping).maps(this, value) and + result = yamlToString(this) ) or result = this.(YamlString).getValue().regexpCapture("([^=]+)=.*", 1) @@ -124,6 +188,16 @@ module Compose { not result.matches("%=%") } - YamlValue getValue() { result = this } + /** + * Gets the environment variable value. + */ + YamlValue getValue() { + exists(Service service | service.lookup("environment").(YamlMapping).maps(this, result)) + or + exists(Service service | + service.lookup("environment").(YamlSequence).getAChild() = this and + result = this + ) + } } } diff --git a/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll b/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll index 30f1cc123213..0acb42c99b48 100644 --- a/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll +++ b/iac/ql/lib/codeql/iac/kubernetes/Kubernetes.qll @@ -19,32 +19,68 @@ module YamlKubernetes { override string toString() { result = "Kubernetes " + this.getKind() + " document" } + /** + * Gets the Kubernetes API version. + */ string getApiVersion() { result = yamlToString(this.lookup("apiVersion")) } + /** + * Gets the Kubernetes resource kind. + */ string getKind() { result = yamlToString(this.lookup("kind")) } + /** + * Gets the resource metadata, if any. + */ Metadata getMetadata() { result = this.lookup("metadata") } + /** + * Gets the resource specification, if any. + */ YamlMapping getSpec() { result = this.lookup("spec") } + /** + * Gets the pod specification, if any. + */ PodSpec getPodSpec() { result.getDocument() = this } - Container getContainers() { result = this.getPodSpec().getContainers() } + /** + * Gets a regular container, if any. + */ + Container getAContainer() { result = this.getPodSpec().getAContainer() } - Container getInitContainers() { result = this.getPodSpec().getInitContainers() } + /** + * Gets an init container, if any. + */ + Container getAnInitContainer() { result = this.getPodSpec().getAnInitContainer() } - Container getEphemeralContainers() { result = this.getPodSpec().getEphemeralContainers() } + /** + * Gets an ephemeral container, if any. + */ + Container getAnEphemeralContainer() { result = this.getPodSpec().getAnEphemeralContainer() } } class Metadata extends YamlNode, YamlMapping { Metadata() { exists(Document document | document.lookup("metadata") = this) } + /** + * Gets the resource name, if any. + */ string getName() { result = yamlToString(this.lookup("name")) } + /** + * Gets the resource namespace, if any. + */ string getNamespace() { result = yamlToString(this.lookup("namespace")) } + /** + * Gets the resource labels, if any. + */ YamlValue getLabels() { result = this.lookup("labels") } + /** + * Gets the resource annotations, if any. + */ YamlValue getAnnotations() { result = this.lookup("annotations") } } @@ -57,43 +93,79 @@ module YamlKubernetes { or exists(Document document | document.getKind() = - [ - "DaemonSet", "Deployment", "Job", "ReplicaSet", "ReplicationController", "StatefulSet", - ] and + ["DaemonSet", "Deployment", "Job", "ReplicaSet", "ReplicationController", "StatefulSet",] and document.lookup("spec").(YamlMapping).lookup("template").(YamlMapping).lookup("spec") = this ) or exists(Document document | document.getKind() = "CronJob" and - document.lookup("spec").(YamlMapping).lookup("jobTemplate").(YamlMapping).lookup("spec") - .(YamlMapping).lookup("template").(YamlMapping).lookup("spec") = this + document + .lookup("spec") + .(YamlMapping) + .lookup("jobTemplate") + .(YamlMapping) + .lookup("spec") + .(YamlMapping) + .lookup("template") + .(YamlMapping) + .lookup("spec") = this ) } - Container getContainers() { result = this.lookup("containers").(YamlSequence).getAChild() } + /** + * Gets a regular container, if any. + */ + Container getAContainer() { result = this.lookup("containers").(YamlSequence).getAChild() } - Container getInitContainers() { + /** + * Gets an init container, if any. + */ + Container getAnInitContainer() { result = this.lookup("initContainers").(YamlSequence).getAChild() } - Container getEphemeralContainers() { + /** + * Gets an ephemeral container, if any. + */ + Container getAnEphemeralContainer() { result = this.lookup("ephemeralContainers").(YamlSequence).getAChild() } + /** + * Gets the pod security context, if any. + */ SecurityContext getSecurityContext() { result = this.lookup("securityContext") } + /** + * Gets the service account name, if any. + */ YamlValue getServiceAccountName() { result = this.lookup("serviceAccountName") } + /** + * Gets the service account token automount setting, if any. + */ YamlValue getAutomountServiceAccountToken() { result = this.lookup("automountServiceAccountToken") } - Volume getVolumes() { result = this.lookup("volumes").(YamlSequence).getAChild() } + /** + * Gets a volume, if any. + */ + Volume getAVolume() { result = this.lookup("volumes").(YamlSequence).getAChild() } + /** + * Gets the host network setting, if any. + */ YamlValue getHostNetwork() { result = this.lookup("hostNetwork") } + /** + * Gets the host PID namespace setting, if any. + */ YamlValue getHostPid() { result = this.lookup("hostPID") } + /** + * Gets the host IPC namespace setting, if any. + */ YamlValue getHostIpc() { result = this.lookup("hostIPC") } } @@ -106,24 +178,54 @@ module YamlKubernetes { exists(PodSpec pod | pod.lookup("ephemeralContainers").(YamlSequence).getAChildNode() = this) } + /** + * Gets the container name. + */ string getName() { result = yamlToString(this.lookup("name")) } + /** + * Gets the container image, if any. + */ string getImage() { result = yamlToString(this.lookup("image")) } + /** + * Gets the container security context, if any. + */ SecurityContext getSecurityContext() { result = this.lookup("securityContext") } + /** + * Gets the container command, if any. + */ YamlValue getCommand() { result = this.lookup("command") } + /** + * Gets the container arguments, if any. + */ YamlValue getArgs() { result = this.lookup("args") } + /** + * Gets the environment variable definitions, if any. + */ YamlValue getEnv() { result = this.lookup("env") } - EnvEntry getEnvironmentEntries() { result = this.lookup("env").(YamlSequence).getAChild() } + /** + * Gets an environment entry, if any. + */ + EnvEntry getAnEnvironmentEntry() { result = this.lookup("env").(YamlSequence).getAChild() } + /** + * Gets the environment sources, if any. + */ YamlValue getEnvFrom() { result = this.lookup("envFrom") } + /** + * Gets the volume mounts, if any. + */ YamlValue getVolumeMounts() { result = this.lookup("volumeMounts") } + /** + * Gets the container ports, if any. + */ YamlValue getPorts() { result = this.lookup("ports") } } @@ -132,14 +234,29 @@ module YamlKubernetes { exists(Container container | container.lookup("env").(YamlSequence).getAChildNode() = this) } + /** + * Gets the environment variable name. + */ string getName() { result = yamlToString(this.lookup("name")) } + /** + * Gets the literal environment variable value, if any. + */ YamlValue getValue() { result = this.lookup("value") } + /** + * Gets the environment variable value source, if any. + */ YamlMapping getValueFrom() { result = this.lookup("valueFrom") } + /** + * Gets the referenced secret key, if any. + */ YamlMapping getSecretKeyRef() { result = this.getValueFrom().lookup("secretKeyRef") } + /** + * Gets the referenced ConfigMap key, if any. + */ YamlMapping getConfigMapKeyRef() { result = this.getValueFrom().lookup("configMapKeyRef") } } @@ -150,78 +267,141 @@ module YamlKubernetes { exists(Container container | container.lookup("securityContext") = this) } + /** + * Gets the privileged setting, if any. + */ YamlValue getPrivileged() { result = this.lookup("privileged") } - YamlValue getAllowPrivilegeEscalation() { - result = this.lookup("allowPrivilegeEscalation") - } + /** + * Gets the privilege escalation setting, if any. + */ + YamlValue getAllowPrivilegeEscalation() { result = this.lookup("allowPrivilegeEscalation") } + /** + * Gets the user ID, if any. + */ YamlValue getRunAsUser() { result = this.lookup("runAsUser") } + /** + * Gets the group ID, if any. + */ YamlValue getRunAsGroup() { result = this.lookup("runAsGroup") } + /** + * Gets the non-root user requirement, if any. + */ YamlValue getRunAsNonRoot() { result = this.lookup("runAsNonRoot") } - YamlValue getReadOnlyRootFilesystem() { - result = this.lookup("readOnlyRootFilesystem") - } + /** + * Gets the read-only root filesystem setting, if any. + */ + YamlValue getReadOnlyRootFilesystem() { result = this.lookup("readOnlyRootFilesystem") } + /** + * Gets the Linux capabilities configuration, if any. + */ YamlValue getCapabilities() { result = this.lookup("capabilities") } + /** + * Gets the seccomp profile, if any. + */ YamlValue getSeccompProfile() { result = this.lookup("seccompProfile") } } class Volume extends YamlNode, YamlMapping { Volume() { exists(PodSpec pod | pod.lookup("volumes").(YamlSequence).getAChildNode() = this) } + /** + * Gets the volume name. + */ string getName() { result = yamlToString(this.lookup("name")) } + /** + * Gets the host path volume source, if any. + */ YamlValue getHostPath() { result = this.lookup("hostPath") } + /** + * Gets the projected volume source, if any. + */ YamlValue getProjected() { result = this.lookup("projected") } + /** + * Gets the secret volume source, if any. + */ YamlValue getSecret() { result = this.lookup("secret") } + /** + * Gets the ConfigMap volume source, if any. + */ YamlValue getConfigMap() { result = this.lookup("configMap") } } class Role extends Document { Role() { this.getKind() = ["Role", "ClusterRole"] } - Rule getRules() { result = this.lookup("rules").(YamlSequence).getAChild() } + /** + * Gets an RBAC rule, if any. + */ + Rule getARule() { result = this.lookup("rules").(YamlSequence).getAChild() } } class Rule extends YamlNode, YamlMapping { Rule() { exists(Role role | role.lookup("rules").(YamlSequence).getAChildNode() = this) } + /** + * Gets the API groups, if any. + */ YamlValue getApiGroups() { result = this.lookup("apiGroups") } + /** + * Gets the resources, if any. + */ YamlValue getResources() { result = this.lookup("resources") } + /** + * Gets the allowed verbs, if any. + */ YamlValue getVerbs() { result = this.lookup("verbs") } + /** + * Gets the resource names, if any. + */ YamlValue getResourceNames() { result = this.lookup("resourceNames") } } class RoleBinding extends Document { RoleBinding() { this.getKind() = ["RoleBinding", "ClusterRoleBinding"] } + /** + * Gets the referenced role. + */ YamlMapping getRoleRef() { result = this.lookup("roleRef") } - Subject getSubjects() { result = this.lookup("subjects").(YamlSequence).getAChild() } + /** + * Gets a binding subject, if any. + */ + Subject getASubject() { result = this.lookup("subjects").(YamlSequence).getAChild() } } class Subject extends YamlNode, YamlMapping { Subject() { - exists(RoleBinding binding | - binding.lookup("subjects").(YamlSequence).getAChildNode() = this - ) + exists(RoleBinding binding | binding.lookup("subjects").(YamlSequence).getAChildNode() = this) } + /** + * Gets the subject kind. + */ string getKind() { result = yamlToString(this.lookup("kind")) } + /** + * Gets the subject name. + */ string getName() { result = yamlToString(this.lookup("name")) } + /** + * Gets the subject namespace, if any. + */ string getNamespace() { result = yamlToString(this.lookup("namespace")) } } } diff --git a/iac/ql/test/library-tests/compose/ast/AST.expected b/iac/ql/test/library-tests/compose/ast/AST.expected index 9ebc41d7f4ee..441464136fc7 100644 --- a/iac/ql/test/library-tests/compose/ast/AST.expected +++ b/iac/ql/test/library-tests/compose/ast/AST.expected @@ -1,13 +1,24 @@ documents -| compose.yaml:1:1:18:25 | services: | +| compose.yaml:1:1:19:25 | services: | | docker-compose.yaml:1:1:11:29 | services: | services -| compose.yaml:3:5:15:23 | image: nginx:1.25 | +| compose.yaml:3:5:16:23 | contain ... ntainer | | docker-compose.yaml:3:5:8:21 | image: ... ker:1.0 | environment -| compose.yaml:5:7:5:14 | PASSWORD | -| compose.yaml:5:17:5:27 | ${PASSWORD} | -| compose.yaml:6:7:6:11 | DEBUG | -| compose.yaml:6:14:6:19 | "true" | +| compose.yaml:6:7:6:14 | PASSWORD | +| compose.yaml:7:7:7:11 | DEBUG | | docker-compose.yaml:5:9:5:18 | DEBUG=true | | docker-compose.yaml:6:9:6:17 | API_TOKEN | +documentServices +| compose.yaml:1:1:19:25 | services: | compose.yaml:3:5:16:23 | contain ... ntainer | +| docker-compose.yaml:1:1:11:29 | services: | docker-compose.yaml:3:5:8:21 | image: ... ker:1.0 | +serviceNames +| compose.yaml:3:5:16:23 | contain ... ntainer | web | +| docker-compose.yaml:3:5:8:21 | image: ... ker:1.0 | worker | +containerNames +| compose.yaml:3:5:16:23 | contain ... ntainer | web-container | +environmentValues +| compose.yaml:6:7:6:14 | PASSWORD | PASSWORD | compose.yaml:6:17:6:27 | ${PASSWORD} | +| compose.yaml:7:7:7:11 | DEBUG | DEBUG | compose.yaml:7:14:7:19 | "true" | +| docker-compose.yaml:5:9:5:18 | DEBUG=true | DEBUG | docker-compose.yaml:5:9:5:18 | DEBUG=true | +| docker-compose.yaml:6:9:6:17 | API_TOKEN | API_TOKEN | docker-compose.yaml:6:9:6:17 | API_TOKEN | diff --git a/iac/ql/test/library-tests/compose/ast/AST.ql b/iac/ql/test/library-tests/compose/ast/AST.ql index 7f16160e10e6..7ca4859aeff3 100644 --- a/iac/ql/test/library-tests/compose/ast/AST.ql +++ b/iac/ql/test/library-tests/compose/ast/AST.ql @@ -1,5 +1,18 @@ private import iac query predicate documents(Compose::Document n) { any() } + query predicate services(Compose::Service n) { any() } + query predicate environment(Compose::EnvironmentEntry n) { any() } + +query predicate documentServices(Compose::Document d, Compose::Service s) { s = d.getAService() } + +query predicate serviceNames(Compose::Service s, string name) { name = s.getName() } + +query predicate containerNames(Compose::Service s, string name) { name = s.getContainerName() } + +query predicate environmentValues(Compose::EnvironmentEntry entry, string name, YamlValue value) { + name = entry.getName() and + value = entry.getValue() +} diff --git a/iac/ql/test/library-tests/compose/ast/compose.yaml b/iac/ql/test/library-tests/compose/ast/compose.yaml index 74fe325175ec..f07475a00eab 100644 --- a/iac/ql/test/library-tests/compose/ast/compose.yaml +++ b/iac/ql/test/library-tests/compose/ast/compose.yaml @@ -1,5 +1,6 @@ services: web: + container_name: web-container image: nginx:1.25 environment: PASSWORD: ${PASSWORD} diff --git a/iac/ql/test/library-tests/kubernetes/ast/AST.expected b/iac/ql/test/library-tests/kubernetes/ast/AST.expected index 7b800c10241c..db1defc9c9f9 100644 --- a/iac/ql/test/library-tests/kubernetes/ast/AST.expected +++ b/iac/ql/test/library-tests/kubernetes/ast/AST.expected @@ -29,3 +29,18 @@ bindings | workloads.yaml:43:1:54:20 | Kubernetes ClusterRoleBinding document | subjects | workloads.yaml:52:5:54:20 | kind: ServiceAccount | +documentContainers +| workloads.yaml:1:1:32:29 | HelmChart Document | workloads.yaml:17:11:29:6 | name: web | +| workloads.yaml:1:1:32:29 | Kubernetes Deployment document | workloads.yaml:17:11:29:6 | name: web | +podContainers +| workloads.yaml:13:7:32:29 | service ... me: web | workloads.yaml:17:11:29:6 | name: web | +podVolumes +| workloads.yaml:13:7:32:29 | service ... me: web | workloads.yaml:30:11:32:29 | name: config | +containerEnvironment +| workloads.yaml:17:11:29:6 | name: web | workloads.yaml:20:15:25:10 | name: PASSWORD | +roleRules +| workloads.yaml:34:1:41:27 | HelmChart Document | workloads.yaml:39:5:41:27 | apiGroups: [""] | +| workloads.yaml:34:1:41:27 | Kubernetes ClusterRole document | workloads.yaml:39:5:41:27 | apiGroups: [""] | +bindingSubjects +| workloads.yaml:43:1:54:20 | HelmChart Document | workloads.yaml:52:5:54:20 | kind: ServiceAccount | +| workloads.yaml:43:1:54:20 | Kubernetes ClusterRoleBinding document | workloads.yaml:52:5:54:20 | kind: ServiceAccount | diff --git a/iac/ql/test/library-tests/kubernetes/ast/AST.ql b/iac/ql/test/library-tests/kubernetes/ast/AST.ql index d265519566fc..a22e1e050a1b 100644 --- a/iac/ql/test/library-tests/kubernetes/ast/AST.ql +++ b/iac/ql/test/library-tests/kubernetes/ast/AST.ql @@ -1,13 +1,49 @@ private import iac query predicate documents(YamlKubernetes::Document n) { any() } + query predicate metadata(YamlKubernetes::Metadata n) { any() } + query predicate podSpecs(YamlKubernetes::PodSpec n) { any() } + query predicate containers(YamlKubernetes::Container n) { any() } + query predicate env(YamlKubernetes::EnvEntry n) { any() } + query predicate security(YamlKubernetes::SecurityContext n) { any() } + query predicate volumes(YamlKubernetes::Volume n) { any() } + query predicate roles(YamlKubernetes::Role n) { any() } + query predicate rules(YamlKubernetes::Rule n) { any() } + query predicate bindings(YamlKubernetes::RoleBinding n) { any() } + query predicate subjects(YamlKubernetes::Subject n) { any() } + +query predicate documentContainers(YamlKubernetes::Document d, YamlKubernetes::Container container) { + container = d.getAContainer() +} + +query predicate podContainers(YamlKubernetes::PodSpec pod, YamlKubernetes::Container container) { + container = pod.getAContainer() +} + +query predicate podVolumes(YamlKubernetes::PodSpec pod, YamlKubernetes::Volume volume) { + volume = pod.getAVolume() +} + +query predicate containerEnvironment( + YamlKubernetes::Container container, YamlKubernetes::EnvEntry entry +) { + entry = container.getAnEnvironmentEntry() +} + +query predicate roleRules(YamlKubernetes::Role role, YamlKubernetes::Rule rule) { + rule = role.getARule() +} + +query predicate bindingSubjects(YamlKubernetes::RoleBinding binding, YamlKubernetes::Subject subject) { + subject = binding.getASubject() +}