Documents, data, and packages
input, data, and how Rego loads the world.
In short. Rego never "runs a script against a cluster." It evaluates rules against documents: JSON (or YAML-as-JSON) trees. The document under inspection is usually `input`.
Rego never "runs a script against a cluster." It evaluates rules against documents: JSON (or YAML-as-JSON) trees. The document under inspection is usually input. Facts you load ahead of time — org charts, approved registries, exception lists — live under data. Packages namespace rules so data.kubernetes.admission.deny does not collide with data.terraform.deny.
If you treat Rego like Python with odd syntax, you will fight the language. If you treat it like a query over nested maps and sets, the same patterns scale from one ConstraintTemplate to a multi-repo library.
input vs data
Admission controllers typically put the admission review payload in input. CI tools like Conftest put the file you are scanning in input. data comes from files OPA loads (opa run -b bundle) or from Kubernetes resources Gatekeeper syncs. Confusing the two produces policies that work in unit tests (where you stuffed everything into input) and fail in production.
printf '%s\n' 'package example' 'deny contains msg if {' ' input.kind == "Pod"' ' not startswith(input.spec.containers[_].image, data.allowed.registry)' ' msg := "image registry not allowed"' '}' > /tmp/ex.regoprintf '{"allowed":{"registry":"ghcr.io/acme/"}}' > /tmp/data.jsonecho '{"kind":"Pod","spec":{"containers":[{"image":"evil/x:1"}]}}' \| opa eval -f pretty -I -d /tmp/ex.rego -d /tmp/data.json 'data.example.deny'
{"evil/x:1 image registry not allowed"}
Packages and rule names
package kubernetes.admission defines where rules live in the data document. Entry points like deny or violation are conventions your engine expects — Gatekeeper looks for violation, Conftest often uses deny. Name helpers clearly (is_privileged, has_valid_label) and keep them pure.
Going deeper
Bundles package Rego + data as versioned artifacts. Sign and pin them. A cluster on bundle 2026.07.01 and CI on main will disagree — that disagreement is how "it passed CI" incidents happen. Treat bundle promotion like container image promotion.
Document schemas for input in each PEP: AdmissionReview versions differ; Conftest file wrappers differ. Provide JSON Schema or example fixtures next to policies so authors stop guessing field paths.
Avoid gigantic data trees synced from the whole cluster. Sync only kinds you query. Oversync hurts latency and memory and encourages accidental coupling to irrelevant objects.
When debugging, dump the exact input document your PEP sees — not the YAML you think you applied. Helm wrappers, defaulting webhooks, and Kyverno mutators change shapes before OPA/Gatekeeper evaluate.
Try this
Write a tiny policy that denies Pods missing metadata.labels.app, load it with opa eval, and feed both good and bad input.
printf '%s\n' 'package k8s' 'deny contains msg if {' ' input.kind == "Pod"' ' not input.metadata.labels.app' ' msg := "missing app label"' '}' > /tmp/label.regoecho '{"kind":"Pod","metadata":{"labels":{}}}' | opa eval -f raw -I -d /tmp/label.rego 'count(data.k8s.deny)'
1
Takeaway
Rego evaluates packages over input and data documents. Get the document model right and every later pattern — tests, Gatekeeper, bundles — gets easier.
Next: make those rules testable with opa test and CI fixtures.