Join our Newsletter — 33% off our NHI Course
Home› Guides› Policy-as-Code for Authorisation Guide: OPA, Cedar and Rego
Guide Authorisation

Policy-as-Code for Authorisation Guide: OPA, Cedar and Rego

PDF for newsletter subscribers · free to read online

← All guides
By Lalit Choda, NHI Mgmt Group Updated 24 October 2026 16 min read
On this page

When authorisation rules are scattered through application code, nobody can say with confidence who is allowed to do what, and every change is a code release with no independent review. Policy-as-code moves those rules into a dedicated policy language, evaluated by an engine the application calls, stored in version control and tested like software. This guide covers the two most widely used open source options, Open Policy Agent (OPA) with its Rego language and the Cedar policy language, and the Kubernetes admission tools built around them. It explains how each evaluates a decision, how to choose between them, how to build a tested and signed policy pipeline, how to run the engine safely and how to measure whether it is working. It is written for platform engineers, application security teams and identity architects. For the models the policies express (RBAC, ABAC, ReBAC), see the Authorisation Models Guide.

Key takeaways

  • Separate the decision from the enforcement. The application or proxy asks "is this allowed?" and enforces the answer; the policy engine decides. That split is what makes rules reviewable, testable and consistent across services.
  • Rego and Cedar make different trade-offs. Rego is a general-purpose query language over arbitrary JSON, used from Kubernetes admission to API gateways; Cedar is a narrower language built for application permissions, with deny-by-default, forbid-overrides-permit and schema validation built in.
  • Default deny has to be designed in. Cedar denies when nothing permits. In Rego, an undefined result is not "false" unless you declare default allow := false and the enforcement point treats anything else as deny.
  • The policy pipeline is a privileged path. Whoever can change a bundle or policy store can grant access, so require review, tests, signing and signature enforcement before policies reach production.
  • Decision logs are the audit trail. Logging every decision with its input, result and policy revision answers "why was this allowed?", but mask secrets and personal data before logs leave the engine.

What policy-as-code for authorisation means

Authorisation architectures describe two roles. A policy enforcement point (PEP) sits in the request path, such as an API gateway, service mesh proxy, Kubernetes API server or application middleware. A policy decision point (PDP) evaluates the request against policy and returns a decision. NIST SP 800-207 uses this vocabulary for zero trust, and it treats authentication and authorisation as discrete steps performed before a session with a resource is established. Policy-as-code makes the PDP's rules a versioned artefact.

OPA's documentation describes the benefit as decoupling: policy owners can read, write, analyse, version and distribute policy separately from the service, and the service offloads decisions by sending a query. In practice policy-as-code covers two different jobs, and mixing them causes confusion:

  • Runtime authorisation: per-request decisions such as "can this user approve this invoice" or "can this workload call this endpoint". Latency, availability and data freshness matter.
  • Configuration guardrails: decisions about whether a Kubernetes object, Terraform plan or cloud resource is acceptable. These run at admission time or in CI. See the Infrastructure as Code Security Guide and the Kubernetes Admission Control Guide.

The same engine can do both, but the policies, owners, data sources and failure modes differ. This guide focuses on authorisation and touches guardrails where the tooling overlaps.

OPA and Rego

OPA is a general-purpose policy engine and a Cloud Native Computing Foundation Graduated project. It can run as a sidecar, a host-level daemon or a library embedded in the service.

  • Input and data: the caller sends a JSON query document, available to policies as input. Reference data loaded in advance (role mappings, resource owners) sits under data and is cached in memory, which is why evaluation is fast and why stale data is a risk.
  • Decisions are structured data: a decision can be a boolean, or an object carrying reasons, obligations or headers. The OPA-Envoy plugin, for example, accepts either a boolean or an object with a required allowed field plus optional headers, body and HTTP status, and defaults to 403 on denial.
  • Rules combine as OR: several rules with the same name are unioned, so adding an allow rule can only widen access. A complete rule that produces two different values for the same input is a runtime conflict error.
  • Undefined is not false: if no rule body matches, the rule is undefined. The default keyword supplies the fallback value.
  • OPA 1.0 syntax: Rego v1 makes the if and contains keywords mandatory in rule heads, reserves input and data as names, and removes deprecated built-ins such as re_match and net.cidr_overlap. Use opa check --v0-v1 and opa fmt --write --v0-v1 to migrate older policy, and the Regal linter in CI.

A minimal deny-by-default rule in current syntax, adapted from the shape used in the OPA documentation:

package app.invoices

default allow := false

allow if {
    input.action == "approve"
    input.user.department == input.resource.department
    input.user.id != input.resource.created_by
}

The third condition expresses separation of duties: nobody approves an invoice they created. Writing that once in policy, rather than in each service that handles invoices, is the core value of the approach.

Cedar

Cedar is an open source language designed specifically for application authorisation. Amazon Verified Permissions is a managed service that evaluates Cedar policies (the AWS documentation currently cites Cedar 4.7), but Cedar itself runs anywhere its libraries do.

  • Structure: each policy has an effect (permit or forbid), a scope over principal, action and resource (matching anything, one entity with ==, a hierarchy with in, or a type with is), and optional when and unless conditions over attributes and request context.
  • Evaluation: if any forbid policy is satisfied, the answer is Deny; otherwise if any permit is satisfied, Allow; otherwise Deny. Forbid policies therefore act as guardrails that later permit policies cannot cross.
  • Skip on error: a policy that errors during evaluation is ignored rather than denying everything. That is safe for permits, but a forbid that errors (for example by reading a missing attribute) silently stops guarding. Validation and tests must catch this.
  • Diagnostics: every response lists the determining policies (the satisfied permits on Allow, the satisfied forbids on an explicit Deny) and any policy errors, which makes decisions explainable.
  • Schema and validation: a schema declares entity types, parent types, attributes (required unless marked optional) and, for each action, the principal and resource types it applies to and its context shape. Validating policies against it catches misspelt attributes and wrong entity references before deployment. A schema change can invalidate existing policies, so validate on every schema change too.
  • Templates: templates use ?principal and ?resource slots, only in the scope, to create linked policies such as "share this album with this group". Changing a template changes every linked policy, and linked policies tie the policy store to user lifecycle: offboarding means deleting or archiving them.
@id("finance-approve")
permit (
    principal in Role::"FinanceApprover",
    action == Action::"approve",
    resource is Invoice
)
when { resource.department == principal.department };

@id("no-self-approval")
forbid (principal, action == Action::"approve", resource is Invoice)
when { resource.created_by == principal };

The same separation-of-duties rule becomes an explicit forbid, which no future permit can override.

Comparing the engines and languages

AspectOPA with RegoCedarGatekeeper (OPA for Kubernetes)Kubernetes ValidatingAdmissionPolicy
Primary useAny JSON decision: APIs, microservices, admission, CI checksApplication permissions: principal, action, resourceKubernetes admission and audit of existing objectsKubernetes admission, in-process in the API server
Default outcomeUndefined unless a default is setDeny unless permittedAllowed unless a constraint reports a violationAllowed unless a validation fails
Conflict handlingRules OR together; you write the combining logicForbid overrides permitAny violation denies (per enforcement action)A failed CEL validation rejects the request
Type checkingOptional JSON schema annotationsSchema validation built inParameter schema in the ConstraintTemplateCEL type checking
Safe rollout modeDecision logs; OPA-Envoy dry-runRun in shadow beside existing checksenforcementAction: dryrun or warn (deny is the default)Test in a non-production cluster first
Where it runsSidecar, daemon, library, WasmEmbedded library or managed serviceValidating admission webhookKubernetes API server (stable since v1.30)
Learning curveSteeper: Datalog-style queries, iteration with some and everyLower: deliberately restricted languageRego plus Kubernetes object modelCEL expressions

Choosing an approach

If you needLean towardsWatch for
Fine-grained permissions inside a product (documents, tenants, sharing)Cedar, self-hosted or managedEntity data must be supplied with each request or loaded; template sprawl
One engine for gateway, mesh, Kubernetes and CIOPA with RegoRego skills concentrated in a few people; write review guidelines
Simple Kubernetes guardrails without running a webhookValidatingAdmissionPolicy with CELFewer reuse and audit features than Gatekeeper for large libraries
Kubernetes policy library with audit of existing resourcesGatekeeperWebhook availability and its failure behaviour
Relationship-heavy access (who shares what with whom at scale)A relationship-based (ReBAC) service, possibly alongside Cedar or OPAPolicy engines evaluate data they are given; they are not graph stores

Small organisations usually gain most by externalising one high-risk decision (admin actions, payments, data export) rather than building a platform. Large organisations need a central policy team that owns libraries, tooling and the pipeline, with application teams owning their own rules. For product evaluation, see the Externalised Authorisation Buyer's Guide.

Building the policy pipeline

A policy-as-code gate is only as strong as the path policies take to production. Treat it like any production deployment pipeline, with one difference: a merged policy can grant access directly.

  • Repository and ownership: one repository or directory per policy domain, CODEOWNERS for review, and branch protection that requires approval from the owning team plus security for changes to forbid or admin rules.
  • Tests: opa test runs rules prefixed test_, can mock input and data with the with keyword, and supports --coverage, --format=json and --fail-on-empty (which catches a test suite that silently ran nothing). For Cedar, validate every policy against the schema and run request-and-expected-decision suites through the Cedar CLI or SDK.
  • Test the negative cases: for every permit, write tests for the near misses: wrong tenant, wrong department, missing attribute, the creator approving their own record.
  • Bundles and signing: opa build -b produces a bundle with a .manifest carrying a revision and the roots it owns, and --signing-key adds a .signatures.json JWT with per-file hashes. OPA only enforces signatures when a signing block is configured; top-level keys alone still accepts unsigned bundles. Delta bundles carry no signatures.
  • Distribution: serve bundles over authenticated HTTPS, use non-overlapping roots when several sources contribute, and protect the persistence directory, because OPA loads persisted bundles at start-up.
  • Policy stores: for a managed Cedar service, change the store through infrastructure as code (the AWS documentation lists CloudFormation, CDK, the CLI and APIs), not through console edits.

See the CI/CD Security Guide for protecting the pipeline itself.

Securing the policy engine

An OPA instance holds policy and often sensitive reference data, and its API can change both. The OPA security documentation is explicit about defaults.

  • Authentication and authorisation are off by default: --authentication=off and --authorization=off. Set --authentication=token or tls and --authorization=basic, with a data.system.authz.allow policy that denies by default, for example allowing only POST / for decision queries.
  • Bind locally: since OPA 1.0, opa run --server binds to localhost by default. Containers often override this with --addr 0.0.0.0:8181; only do so with TLS and API authorisation in place.
  • TLS: configure --tls-cert-file and --tls-private-key-file, and --tls-ca-cert-file for client certificates. Serve health and metrics on a separate --diagnostic-addr.
  • Least privilege for the process: run as non-root, keep credentials out of command-line arguments and restrict token files.
  • Fail closed at the enforcement point: decide explicitly what the PEP does when the PDP is unreachable or returns an error, and default to deny for anything other than low-risk reads.

Where identity fits: non-human identities and AI agents

Policy-as-code is increasingly the control point for machine identities. Workload identities from mutual TLS or SPIFFE IDs, OAuth client credentials and agent tokens all become the principal in a decision.

  • Use verified identity, not claims in the body: derive the principal from the authenticated token or certificate at the PEP, never from a field the caller controls. OWASP ASVS 5.0 V8.3.1 requires authorisation to be enforced at a trusted service layer.
  • Agents need tighter context: include the delegating user, the task and the tool in context, and forbid high-impact actions without a human approval attribute. See the AI Agent Authorisation Guide.
  • Service-to-service: mesh proxies calling OPA through Envoy external authorisation give per-call decisions for workloads. See the Service Mesh Security Guide.

Rolling out policy-as-code

  1. Pick one decision: choose a high-risk, well-understood rule (admin actions, data export, cross-tenant access) and write its current behaviour down as test cases before writing policy.
  2. Model the inputs: define the request shape or Cedar schema: principal attributes, resource attributes, context. Decide where each attribute comes from and how fresh it must be.
  3. Write policy and tests together: deny by default, then permits, then forbids for invariants. Gate merges on tests, coverage reporting and schema validation.
  4. Run in shadow: call the engine alongside the existing in-code check and log both answers, or use OPA-Envoy dry-run or Gatekeeper dryrun. Investigate every disagreement.
  5. Enforce in rings: switch one service, tenant or namespace at a time; Gatekeeper's warn action is a useful middle step for admission policy.
  6. Remove the old check: leaving duplicate in-code logic means two sources of truth that drift.
  7. Roll back by revision: keep the previous signed bundle or policy store version deployable, and record the bundle revision in every decision log so you know which version made which decision.

Detection and measurement

OPA decision log events record the policy path, input, result, bundle revision, a decision ID and a timestamp. They can be printed to the console or uploaded in compressed batches to a remote service. Before logs leave the engine, a data.system.log.mask rule can erase or rewrite fields under /input or /result; use it for tokens and personal data. Note that max_decisions_per_second drops events above the limit, which matters if logs are your audit evidence. Cedar responses carry determining policies and errors that the application should log alongside its own request ID.

Monitor for:

  • Bundle activation failures and signature verification errors (OPA Status API).
  • Policy evaluation errors, especially Cedar policies skipped on error.
  • Sudden shifts in the allow-to-deny ratio for a policy after a deployment.
  • Decisions served by an unexpected bundle revision, which indicates a stale or tampered instance.
  • Changes to policy stores or bundle sources outside the pipeline.

Metrics that show it is working:

  • Percentage of high-risk authorisation decisions served by the policy engine rather than in-code checks.
  • Policy line coverage from opa test --coverage, and number of negative test cases per permit rule.
  • Shadow-mode disagreement rate, trending to zero before enforcement.
  • Percentage of instances running the current signed revision.
  • p99 decision latency at the PEP.
  • Mean time from approved policy change to enforcement in production.

Framework mapping

FrameworkReferenceHow policy-as-code supports it
NIST SP 800-53 Rev. 5AC-3 Access Enforcement; AC-24 Access Control Decisions; AC-6 Least PrivilegeEngine makes decisions; PEP enforces; policies encode least privilege
NIST SP 800-53 Rev. 5CM-3 Configuration Change Control; SA-11 Developer Testing and Evaluation; AU-2 Event LoggingReviewed, tested policy changes; decision logs
NIST CSF 2.0PR.AA-05 access permissions defined in policy, managed, enforced and reviewed; PR.PS-01 configuration managementPolicies as reviewed, versioned artefacts
NIST SP 800-207Policy decision and enforcement points in zero trust architectureOPA or Cedar as the PDP
OWASP ASVS 5.0V8.1.1 documented authorisation rules; V8.2.1 function-level access needs explicit permission; V8.3.1 enforcement at a trusted service layerPolicies are the documented rules; tests prove them
OWASP API Security Top 10 2023API1 Broken Object Level Authorization; API5 Broken Function Level AuthorizationObject and function checks centralised and tested

Common failure patterns

  • Undefined treated as allow: a Rego policy without default allow := false, called by a PEP that only denies on an explicit false, lets every unmatched request through.
  • Unsigned bundles accepted: verification keys configured but no signing block, so a tampered or unsigned bundle activates. Anyone with write access to the bundle server can then grant access.
  • Exposed OPA API: the server bound to all interfaces with authentication off lets anyone on the network read reference data or push new policy through PUT /v1/policies or PUT /v1/data.
  • Guardrail forbids that error out: a Cedar forbid reading an optional attribute without a has check is skipped on error and stops protecting. Schema validation and a test per forbid prevent it.
  • Policy that only checks infrastructure, not permissions granted: broad cloud roles pass every pipeline check because no policy inspects what the role can do. The Capital One Breach 2019 shows how far an over-permissioned role reaches once its credentials are taken; a policy gate on IAM permissions in infrastructure code is one of the controls that addresses it.
  • Stale data: role or ownership data in data refreshed hourly means leavers and moved users keep access until the next sync. Pass time-sensitive attributes in the request or shorten the refresh.

Practitioner checklist

  • Inventory where authorisation decisions are made today and pick the first high-risk decision to externalise.
  • Choose Rego or Cedar per use case, and document why.
  • Enforce deny by default at both the policy and the enforcement point, including on PDP errors and timeouts.
  • Express invariants such as separation of duties and tenant isolation as forbid rules or dedicated deny rules.
  • Require code review, opa test or Cedar validation with negative cases, and coverage reporting before merge.
  • Sign bundles and configure signature enforcement; manage policy stores only through infrastructure as code.
  • Turn on OPA API authentication and authorisation, TLS and localhost binding.
  • Run new policies in dry-run or shadow mode and resolve every disagreement before enforcing.
  • Ship decision logs with policy revision to your SIEM, masking secrets and personal data.
  • Derive principals from authenticated tokens or certificates, including for workloads and AI agents.

Standards and references

Related NHI Mgmt Group resources: Authorisation Models Guide · AI Agent Authorisation Guide · Broken Access Control Prevention Guide · Zero Trust Identity Guide · Compliance Automation Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Written and reviewed by Lalit Choda, NHI Mgmt Group. Last updated 24 October 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.