Datalog Access Control [2026 Cheat Sheet] Reference
Bottom Line
Treat authorization as derived data: model identities, relations, and resource facts as tuples, then compute decisions from them. The safest baseline is default allow := false plus human-readable deny reasons and a repeatable OPA CLI loop.
Key Takeaways
- ›Start with
default allow := false; in policy engines, undefined is not the same as false. - ›Mix relations and attributes: tuples answer who-can-touch-what, request input adds runtime context.
- ›
opa fmt,opa check,opa test, andopa evalform the fastest local authoring loop. - ›Use
opa deps, partial eval, and optimized bundles only after the policy shape is stable.
Policy-as-logic works best when you treat authorization as a query problem, not a maze of application-side conditionals. This reference maps Datalog-style access control to the concrete commands most teams actually run today, using OPA and Rego as the practical baseline for authoring, testing, bundling, and debugging policy. Keep it open beside your editor: the goal here is fast recall, predictable defaults, and fewer permission bugs in production.
- Start with
default allow := false; in policy engines, undefined is not the same as false. - Mix relations and attributes; tuples answer who-can-touch-what, request input adds runtime context.
opa fmt,opa check,opa test, andopa evalform the fastest local authoring loop.- Use
opa deps, partial eval, and optimized bundles later; first stabilize the policy shape.
Policy Logic Primitives
Bottom Line
For granular access control, think in relations and derivations: facts go in, decisions come out. The boring-but-correct baseline is default allow := false, explicit allow rules, and a parallel set of human-readable deny reasons.
Pure Datalog is the mental model; Rego is the production-facing syntax many teams use to operationalize it in APIs, services, and CI. In practice, the winning pattern is to keep the model relational and the enforcement interface small.
Core mental model
- Facts are input tuples or stored data such as users, resource attributes, and relationship edges.
- Rules derive new relations such as
viewer,editor, orcan_refund. - Undefined is its own state. A rule not proving true is not automatically the same thing as
false. - Negation is easiest to reason about when your base facts are complete and your deny logic is explicit.
- Universal checks belong in policy too; every is useful when a condition must hold for all members of a set.
Canonical allow/deny shape
package http.authz
default allow := false
allow if {
count(deny) == 0
}
deny contains "tenant mismatch" if {
input.resource.tenant != input.user.tenant
}
deny contains "refund requires finance-admin" if {
input.action == "invoice.refund"
not "finance-admin" in input.user.roles
}- Keep the enforcement decision small: a single
allowboolean is easy to integrate. - Keep explanations separate: a
denyset makes support, logging, and review far easier. - Prefer named derived relations over giant inline conditions; they scale better as policies grow.
Official references: policy language and CLI reference.
Searchable Command Reference
This section is organized by purpose rather than alphabetically. Use the live filter to cut straight to eval, schema, bundle, partial, or any other command fragment.
Press / to focus, Esc to clear, and [ or ] to jump between major sections.
Authoring and validation
| Purpose | Command | Why it matters |
|---|---|---|
| Format source | opa fmt -w policy.rego | Writes canonical formatting back to disk before review. |
| Compile and validate | opa check -S --schema schema.json . | Runs compiler checks with strict mode and schema-aware validation. |
| Inspect syntax tree | opa parse policy.rego -f pretty | Useful when debugging how the parser sees a rule. |
Exploration, testing, and debugging
| Purpose | Command | Why it matters |
|---|---|---|
| Evaluate a decision | opa eval -d policy.rego -i input.json 'data.http.authz.allow' | Fastest way to execute a single policy query locally. |
| Open the REPL | opa run | Good for prototyping rules and poking at loaded data interactively. |
| Run as a server | opa run -s -c opa-config.yaml | Starts the HTTP API with runtime configuration. |
| Run tests | opa test ./policy | Executes discovered test rules under the given path. |
| Measure coverage | opa test --coverage ./policy | Shows how much of the policy surface your tests actually execute. |
| Trace dependencies | opa deps --data policy.rego data.http.authz.allow | Separates base documents from virtual documents before refactors. |
| Benchmark a query | opa bench -d policy.rego -i input.json 'data.http.authz.allow' | Measures hot-path decision cost after correctness is settled. |
Packaging and delivery
| Purpose | Command | Why it matters |
|---|---|---|
| Build a bundle | opa build -b . | Packages policy and data into the default bundle.tar.gz. |
| Build an optimized bundle | opa build -b . -O=1 -e http/authz/allow | Optimization requires at least one entrypoint when enabled. |
| Inspect bundle contents | opa inspect -a bundle.tar.gz | Summarizes packages, data locations, manifest data, and annotations. |
| Partial evaluation | opa eval -p -d policy.rego 'data.http.authz.allow' | Pre-computes the known parts of a decision for later specialization. |
opa fmt -w policy.rego
opa check -S --schema schema.json .
opa test ./policy
opa eval -d policy.rego -i input.json 'data.http.authz.allow'Configuration Patterns
Configuration is where a policy prototype turns into an operational service. OPA accepts JSON or YAML config files, and the runtime loads them with -c or --config-file.
Minimal runtime config
services:
controlplane:
url: "${BASE_URL}"
credentials:
bearer:
token: "${BEARER_TOKEN}"
bundles:
authz:
service: controlplane
resource: /bundles/authz.tar.gz
polling:
min_delay_seconds: 60
max_delay_seconds: 120
labels:
app: billing-api
env: prod
default_decision: /http/authz/allow- Environment substitution with
${...}is supported by the runtime path used by opa run. default_decisiondefines which decision is served from the base URL when no path is supplied.- Bundle polling defaults exist, but setting explicit min and max delays makes behavior easier to reason about.
- Prefer map-style keys for services and bundles; it avoids brittle index-based overrides later.
For sample requests, test fixtures, and policy examples that might contain user or tenant identifiers, redact them before publishing. TechBytes’ Data Masking Tool is a good fit for sanitizing auth payloads without destroying their structure.
Official references: configuration and bundle management.
Access Control Recipes
Granular authorization usually needs both relationship facts and request-time attributes. Avoid role-name explosion by deriving intermediate relations and then applying action- or tenant-specific checks on top.
Relationship plus attribute checks
package http.authz
default allow := false
viewer if {
some rel in data.relations
rel.user == input.user.id
rel.object == input.resource.id
rel.relation == "viewer"
}
editor if {
some rel in data.relations
rel.user == input.user.id
rel.object == input.resource.id
rel.relation == "editor"
}
allow if {
input.action == "doc.read"
viewer
}
allow if {
input.action == "doc.write"
editor
input.resource.tenant == input.user.tenant
}Output shapes that scale
- Boolean enforcement: expose a single
allowdecision to gateways and services. - Human-readable reasons: return a parallel deny set for audit logs, support tickets, and CI failures.
- Filtered collections: compute visible IDs or rows inside policy when the caller needs data scoping, not just yes-or-no checks.
- Derived relations: keep
viewer,editor,owner, and similar predicates as reusable building blocks.
Advanced Usage
Reach for advanced features once the policy model is stable and the performance bottleneck is real. They are powerful, but they add operational surface area.
High-leverage commands
opa depsshows which external documents and virtual rules feed a decision.opa eval -ppartially evaluates a policy when some inputs are intentionally unknown until request time.opa build -O=1 -e ...emits an optimized bundle and requires at least one entrypoint when optimization is enabled.opa inspect -averifies annotations and bundle structure before shipping policy artifacts.opa benchis the right place to measure decision cost after tests are already green.
opa deps --data policy.rego data.http.authz.allow
opa eval -p -d policy.rego 'data.http.authz.allow'
opa build -b . -O=1 -e http/authz/allow
opa inspect -a bundle.tar.gz
opa bench -d policy.rego -i input.json 'data.http.authz.allow'What to optimize last
- Do not start with bundle optimization before you have a clean test suite and stable rule names.
- Do not benchmark policy that is still changing semantically; you will optimize the wrong thing.
- Do not hide business semantics inside one giant rule body when reusable derived relations would make review easier.
Keyboard Shortcuts
The shortcuts below map to the live filter and section navigation in this cheat sheet.
| Shortcut | Action | When to use it |
|---|---|---|
/ | Focus the live filter | Jump straight to commands like eval, deps, or build. |
Esc | Clear the filter | Reset the command view without touching the mouse. |
[ | Previous section | Move back one major heading quickly. |
] | Next section | Move forward one major heading quickly. |
Frequently Asked Questions
Is Rego actually Datalog? +
What is the difference between undefined and false in OPA policies? +
true, false, or undefined depending on how you model it. Undefined usually means the rule body could not be proven, which is why default allow := false is such an important baseline for authorization decisions.How do I combine RBAC and ABAC in policy-as-logic? +
viewer or editor from stored tuples, then layering checks such as tenant match, request method, environment, or resource sensitivity on top.When should I use partial evaluation for access control? +
Get Engineering Deep-Dives in Your Inbox
Weekly breakdowns of architecture, security, and developer tooling — no fluff.