Trivy’s custom checks let you define your own security rules, but the real power comes from understanding how to write those rules in Rego, the policy language of Open Policy Agent (OPA).
Let’s see custom checks in action. Imagine you’re using a custom Docker image for your application and you want to ensure it never has the latest tag exposed in your Kubernetes deployments. This is a common security practice to avoid unexpected image updates.
Here’s a policy.rego file that implements this rule:
package kubernetes.custom
import data.kubernetes.ingress
deny[msg] {
# Iterate over all pods in the cluster
pod := input.kubernetes.pod[_]
# Check each container within the pod
container := pod.spec.containers[_]
# Check if the image name ends with ":latest"
startswith(container.image, "docker.io/") # Assuming images are pulled from docker.io
endswith(container.image, ":latest")
msg := sprintf("Pod %v in namespace %v uses a container image with the 'latest' tag, which is disallowed.", [pod.metadata.name, pod.metadata.namespace])
}
And here’s a sample input.json representing a Kubernetes pod:
{
"kubernetes": {
"pod": [
{
"metadata": {
"name": "my-app-pod",
"namespace": "default"
},
"spec": {
"containers": [
{
"name": "app-container",
"image": "docker.io/my-company/my-app:v1.2.0"
},
{
"name": "sidecar-container",
"image": "docker.io/my-company/sidecar:latest"
}
]
}
},
{
"metadata": {
"name": "another-pod",
"namespace": "kube-system"
},
"spec": {
"containers": [
{
"name": "kube-proxy",
"image": "k8s.gcr.io/kube-proxy:v1.21.0"
}
]
}
}
]
}
}
If you were to run Trivy with this policy and input, it would flag the my-app-pod as violating the rule:
trivy k8s --config trivy-custom-checks.conf --input input.json --policy policy.rego
(Note: The exact command might vary slightly depending on how you integrate custom checks with Trivy’s various scanning modes. For simplicity, we’re showing a conceptual input.json and policy.rego.)
The core problem this solves is preventing "drift" and ensuring reproducibility in your containerized environments. By disallowing :latest, you force explicit version pinning, which is crucial for:
- Predictable Deployments: You know exactly which image version is being deployed, reducing the chance of unexpected behavior due to an automatically updated
latesttag. - Easier Rollbacks: If a deployment goes wrong, you can confidently roll back to a specific, known-good version.
- Auditing and Compliance: Demonstrating that your deployments are controlled and auditable.
Internally, Rego works by defining rules that evaluate to true or false. Trivy, when using Rego policies, feeds the scanned data (like Kubernetes manifests or container images) into the Rego engine as the input document. Your policy then queries this input to find violations.
The deny[msg] rule in our example is a common pattern. It means: "If the conditions within this rule are met, then deny this action and output the message msg."
input.kubernetes.pod[_]iterates through all items in thepodarray within thekubernetesobject of theinput. The_is a wildcard meaning "any element."pod.spec.containers[_]does the same for containers within each pod.startswith(container.image, "docker.io/")andendswith(container.image, ":latest")are built-in Rego functions that check string patterns.sprintfis used to format the output message with dynamic values from the input.
The power of Rego lies in its expressive query language and its ability to define complex relationships. You can check for missing labels, specific environment variables, insecure configurations, and much more.
One aspect that trips people up initially is how Rego handles data and context. When you’re writing a policy, you’re not just checking a single value; you’re often navigating a nested data structure. The input document provided by Trivy mirrors the structure of the scanned artifact. For Kubernetes manifests, this means you’re dealing with the JSON representation of the API objects. For container images, Trivy might provide vulnerability data, file system contents, or package lists. Understanding this structure is key to writing effective queries. For instance, if you were scanning a container image for specific files, your input would contain file system information, and your Rego policy would query input.filesystem or similar structures.
The next step beyond simple checks like this is to start combining multiple conditions, perhaps checking for an image tag and a specific label on the Kubernetes resource, or checking for a vulnerable package and ensuring a specific mitigation is not present.