Recipe: deny-closed policies (Cedar / OPA)
Edition: authoring custom Cedar policies, including permits and forbids, requires Business. Community retains enforcement of stored policies, custom roles and scoped grants; they remain readable and revocable. DELETE /v1/m/governance/pdp/active?engine=cedar disables only the authored Cedar surface and preserves revision history, managed grants and adopted policies. Fresh Community installations retain built-in roles, native deny-overlay rules and the kill switch. This recipe’s external restrict-only PDP is distinct from authored Cedar permits, which can grant access within their scope. Selecting that external engine (OLIVARES_PDP_ENGINE, a Cedar file or OPA) requires Business: Community refuses to start with an engine selected.
Goal: add attribute-based restrictions on top of deny-by-default RBAC —
for example, “no one touches resources tagged secret, whatever their role
says.”
The one invariant to keep in your head: the PDP only restricts. The decision composes as RBAC ∩ native ABAC ∩ external PDP — a policy can never grant what the role model denies (the model).
Cedar (embedded, primary)
Section titled “Cedar (embedded, primary)”Select the engine and point it at your policy file, then restart:
OLIVARES_PDP_ENGINE=cedarOLIVARES_PDP_CEDAR_FILE=/etc/olivares/policy.cedarA Cedar policy is a forbid overlay — the base permit stands in for “RBAC
already decided”, and your forbid rules subtract:
permit(principal, action, resource);
forbid(principal, action, resource) when { resource.kind == "credential" && resource.sensitivity == "secret" };Two authoring facts, verified against the adapter: resource.kind and
resource.sensitivity are always present on the decision input
(unconditional to reference); any other attribute must be guarded with
has() or the rule cannot match. A permit you write can never widen the
decision.
OPA (over HTTP)
Section titled “OPA (over HTTP)”OLIVARES_PDP_ENGINE=opaOLIVARES_PDP_OPA_URL=http://opa.internal:8181OLIVARES_PDP_OPA_PATH=/v1/data/olivares/decisionOLIVARES_PDP_OPA_TOKEN=<bearer-reference> # optionalAuthor the Rego permit-by-default:
package olivares
default allow := true
allow := false if { input.resource.sensitivity == "secret" endswith(input.permission, ":read")}true = no restriction. false, a missing result, or any transport or
non-2xx error fails closed — the request is denied, never silently
ungoverned.
Validate, dry-run, publish
Section titled “Validate, dry-run, publish”The governance module exposes a policy lifecycle so a bad policy never lands blind:
# policy.cedar is the Cedar policy above; jq 1.6 or newer builds the JSON bodies.# Compile-check the source:curl -ks -X POST "$BASE/v1/m/governance/pdp/validate" \ -H "Authorization: Bearer $TOKEN" -H "X-Olivares-Tenant: $TENANT" \ -d "$(jq -n --rawfile source policy.cedar '{engine:"cedar",$source}')"
# Pre-flight a decision WITHOUT audit side effects:curl -ks -X POST "$BASE/v1/m/governance/pdp/dry-run" \ -H "Authorization: Bearer $TOKEN" -H "X-Olivares-Tenant: $TENANT" \ -d "$(jq -n --rawfile source policy.cedar --argjson request '{"principal":{"kind":"user","id":"u1"},"permission":"models:keys:read","resource":{"kind":"credential","sensitivity":"secret"}}' \ '{engine:"cedar",$source,$request}')"
# Then publish (policy-admin permission):curl -ks -X POST "$BASE/v1/m/governance/pdp/publish" …GET /v1/m/governance/pdp/versions lists what is deployed;
POST /v1/m/governance/pdp/explain explains a decision.
Verify the safety properties
Section titled “Verify the safety properties”- Restart with an invalid policy file: the engine disables only the external PDP and logs it — RBAC and native ABAC keep governing; the control plane does not go down.
- Every restriction the PDP applies is audited — check the ledger after a denied request.
- Policies are versioned and published, not hot-edited files in production — treat the publish as a reviewed change.
- For approval-gated (rather than denied) actions, see HITL approvals.