Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Policy as Code with Open Policy Agent (OPA)

By Kokil Thapa | Last reviewed: September 2026

Scattered if statements in controllers, Terraform modules, and CI scripts are where authorization and compliance rules go to die. Policy as Code with Open Policy Agent (OPA) moves those rules into version-controlled Rego policies that any service can query with a single HTTP call. If you ship REST APIs, Kubernetes workloads, or multi-team platforms, OPA gives you one engine for "allow or deny" decisions instead of duplicating logic in PHP, YAML, and shell. This guide covers how OPA works, how to write real policies, and where it fits next to framework-level tools like Laravel policies and gates.

What is Policy as Code with Open Policy Agent (OPA)?

OPA is a CNCF-graduated policy engine. You describe rules in Rego. OPA evaluates those rules against structured input and returns a decision. The engine is language-agnostic. Your Laravel app, a Go microservice, and a Kubernetes admission webhook can all ask the same OPA instance the same question: "Is this action permitted?"

Policy as Code is the practice of treating those rules like application code. Policies live in Git. They pass through pull requests, code review, and CI. Changes are auditable. Rollbacks are a revert, not a frantic hotfix across five repositories.

On production systems I maintain, authorization often starts inside the framework. That works until multiple services, external gateways, and infrastructure pipelines need the same rule. OPA sits at that boundary. It does not replace every @can directive in Blade. It centralizes cross-cutting rules that must hold everywhere.

Policy as Code with OPA — Core ArchitectureGit RepositoryRego policy bundlesOPA EngineEvaluate input JSONDecisionallow / deny + reasonREST APILaravel / GoKubernetesAdmission controlCI PipelinePlan validationShared Policy BundleOne source of truth in Git
Policy as Code with Open Policy Agent (OPA): Rego policies in Git feed a central engine that serves APIs, Kubernetes, and CI pipelines.

The three building blocks

Every OPA integration uses the same pieces:

  • Policy — Rego files that define rules and helper functions.
  • Input — JSON describing the request, resource, or manifest under review.
  • Query — The decision path, commonly data.authz.allow for authorization.

OPA returns JSON. Your caller interprets the result. That contract stays stable even when policy logic changes.

How do you write Rego policies for OPA?

Rego is a declarative language built for policy, not general programming. You define rules that are true when conditions match. There is no mutable state. You think in terms of sets, objects, and partial definitions.

Start with a minimal authorization policy. The official Rego policy language documentation is the authoritative reference for syntax and built-ins.

Example: role-based API access

Save this as policies/authz.rego:

package authz

import future.keywords.if

default allow := false

allow if {
    input.method == "GET"
    input.path == "/health"
}

allow if {
    input.user.role == "admin"
}

allow if {
    input.user.role == "editor"
    input.method == "POST"
    startswith(input.path, "/documents/")
}

deny_reason := "role lacks permission" if {
    not allow
}

Test input JSON:

{
  "method": "POST",
  "path": "/documents/42/share",
  "user": {
    "id": 17,
    "role": "editor"
  }
}

Run OPA locally with the CLI:

opa eval --data policies/authz.rego \
  --input request.json \
  "data.authz.allow"

Expected output when allowed:

{
  "result": [
    {
      "expressions": [
        {
          "value": true,
          "text": "data.authz.allow"
        }
      ]
    }
  ]
}

Write tests before you deploy

Rego supports unit tests in _test.rego files. Treat them like any other test suite. Gate merges on passing OPA tests, similar to how teams use code coverage gates in CI.

package authz

test_editor_can_post_documents if {
    allow with input as {
        "method": "POST",
        "path": "/documents/1",
        "user": {"role": "editor"}
    }
}

test_viewer_cannot_post_documents if {
    not allow with input as {
        "method": "POST",
        "path": "/documents/1",
        "user": {"role": "viewer"}
    }
}

Run tests:

opa test policies/ -v

Validate JSON fixtures with a JSON formatter and linter before they enter your test suite. Malformed input causes false negatives that are painful to debug under load.

Rego Policy Development WorkflowWrite Regopolicy rulesUnit Testopa testBundletar.gz artifactDeploysidecar or svcRuntime Evaluation LoopService sends inputOPA evaluatesAllow or denyLog denials with reason strings for audit trails
OPA Rego workflow: write policies, run opa test in CI, bundle artifacts, then evaluate at runtime with structured JSON input.

Where should you integrate OPA in a Laravel or API application?

Laravel 13 ships authorization through gates and policies. That layer belongs close to your domain models. OPA earns its place when rules span services or must match infrastructure policy.

On a legal-tech client portal, document sharing rules might depend on case status, user role, and jurisdiction. PHP policies handle request-level checks. OPA can enforce the same rule when a separate export service, a webhook worker, and an admin API all touch documents.

Sidecar versus embedded OPA

You have two common patterns:

  1. OPA sidecar — A container or local daemon on port 8181. Your app POSTs to /v1/data/authz/allow. Best when many services share one policy bundle.
  2. Embedded OPA — The open-policy-agent/opa Go library inside a service. Lower latency, tighter coupling. Use when one high-throughput service owns the decision.

For PHP/Laravel stacks, the sidecar pattern is usually simpler. PHP calls OPA over HTTP with Guzzle or Laravel's HTTP client. No native Rego interpreter exists in PHP.

Laravel middleware calling OPA

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class OpaAuthorize
{
    public function handle(Request $request, Closure $next)
    {
        $response = Http::timeout(2)->post('http://127.0.0.1:8181/v1/data/authz/allow', [
            'input' => [
                'method' => $request->method(),
                'path'   => $request->path(),
                'user'   => [
                    'id'   => $request->user()?->id,
                    'role' => $request->user()?->getRoleNames()->first(),
                ],
            ],
        ]);

        if (! data_get($response->json(), 'result')) {
            abort(403, 'Policy denied this action.');
        }

        return $next($request);
    }
}

Keep middleware thin. Map the request to JSON. Let Rego hold the rules. Fail closed when OPA is unreachable unless your risk model explicitly allows a degraded mode.

Projects that need deeper custom authorization logic often start with custom software development where domain rules are modeled first, then extracted into shared policy bundles as the platform grows.

OPA on Kubernetes and in CI

OPA Gatekeeper applies Rego to cluster resources at admission time. That pairs with Kubernetes pod security and network policies for defense in depth. In CI, OPA can scan Terraform plans before apply, complementing Infrastructure as Code with Terraform workflows and tools like Sentinel for Terraform policy.

Laravel Policies vs OPA — Decision TreeNew auth rule needed?Single Laravel appUse gates / policiesMulti-service ruleExtract to OPA RegoK8s or IaC scopeGatekeeper / conftestPractical Rule of ThumbFramework auth for domain logic close to modelsOPA for shared rules across apps, clusters, and pipelinesNever duplicate — sync Rego with PHP policy tests
When to use Laravel policies versus Policy as Code with Open Policy Agent (OPA): single-app domain rules stay in PHP; cross-cutting rules belong in Rego.

How does OPA compare to Kyverno, Gatekeeper, and Sentinel?

Teams evaluating policy engines usually compare OPA with Kubernetes-native tools and cloud-vendor options. The right choice depends on where policies must run, not brand preference.

EnginePolicy languageBest fitTrade-off
OPA (standalone)RegoAPIs, microservices, custom apps, multi-cloudRego learning curve; you operate the engine
OPA GatekeeperRego via ConstraintTemplatesKubernetes admission controlK8s-only; CRD overhead
KyvernoYAML policies (no Rego)Kubernetes mutate/validate/generateLess portable outside K8s
HashiCorp SentinelSentinel HCL-likeTerraform Cloud/Enterprise plansCommercial; Terraform-centric
Laravel PoliciesPHPSingle-app Eloquent authorizationNot shared across services or infra

For a deeper Kubernetes-focused comparison, read Kyverno vs OPA Gatekeeper for policy. For org-wide governance spanning clouds and repos, see multi-cloud governance and policy as code.

OPA wins when one team must enforce the same logical rule in PHP APIs, Node workers, and cluster manifests. Kyverno wins when your team lives entirely inside Kubernetes and prefers YAML. Sentinel wins when you are already on HashiCorp Cloud Platform for Terraform.

What are common OPA deployment patterns in production?

Production OPA is more than a Rego file on disk. You need bundle delivery, health checks, observability, and a rollback path. Patterns I have seen work on enterprise application platforms and shared hosting stacks follow the same shape.

Policy bundles and hot reload

Build bundles with opa build or your CI pipeline. Serve them from an HTTP bundle server or object storage. OPA polls for updates. That decouples policy releases from application deploys.

opa run --server \
  --set bundles.authz.service=local \
  --set bundles.authz.resource=/bundles/authz.tar.gz \
  --set bundles.authz.polling.min_delay_seconds=10 \
  --set bundles.authz.polling.max_delay_seconds=20

Version bundles with Git tags. Pin each environment to a known tag. Staging can track main. Production should not.

Observability and failure modes

Log every deny with the policy path and input hash. Redact PII from logged input. Track p99 latency for OPA calls separately from app latency. A 50 ms policy check that runs on every request adds up fast.

Common failure modes:

  • Timeout cascades — OPA slow or down; app threads block. Set aggressive HTTP timeouts and circuit breakers.
  • Policy drift — PHP allows an action Rego denies. Align with contract tests that hit both layers.
  • Over-broad input — Sending entire request bodies bloats evaluation. Send only fields Rego needs.
  • Missing default deny — Without default allow := false, undefined rules can surprise you.

Pair OPA with quality gates in CI. Tools like SonarQube for code quality and security gates catch application bugs. OPA catches policy violations before they reach production.

Production OPA Deployment PatternGitLab CIopa test + buildBundle StoreS3 or nginxOPA Sidecarpolls bundleAppPHP APIAudit and Compliance LayerDeny logs + reasonPolicy version tagSIEM exportRequired for legal-tech portals with document access audit trailsExample: client portals like Mijar Law Associates workflows
Production Policy as Code with Open Policy Agent (OPA): CI builds signed bundles, sidecars poll for updates, and deny decisions feed audit logs.

Security hardening checklist

  1. Run OPA on localhost or a private network segment, not the public internet.
  2. Sign bundles and verify signatures at load time.
  3. Limit Rego to approved built-ins; deny http.send in prod unless required.
  4. Run opa check --strict in CI on every pull request.
  5. Document who owns policy changes — same RACI as application code.

For platforms that also expose AI endpoints, policy boundaries overlap with guardrails for autonomous AI agents. OPA can gate tool calls and data scopes before an LLM acts.

On client portals with document sharing, like work shipped for Mijar Law Associates, audit trails matter as much as the allow/deny bit. OPA's structured deny reasons make that logging straightforward.

Server-side enforcement still needs transport security. Combine OPA with headers defined in Content Security Policy for Laravel apps and standard TLS termination on your Linux production servers.

Key Takeaways

  • Policy as Code with Open Policy Agent (OPA) centralizes allow/deny rules in version-controlled Rego instead of scattered conditionals.
  • Start with opa test and default-deny policies before wiring any production sidecar.
  • Keep Laravel policies for domain logic; use OPA when the same rule must hold across APIs, workers, Kubernetes, or CI.
  • Deploy OPA as a sidecar for PHP stacks, poll signed bundles, and fail closed on timeouts.
  • Log deny reasons with policy version tags for audit compliance on sensitive portals.
  • Compare OPA with Kyverno and Sentinel based on deployment surface, not hype — one engine rarely covers every layer.

People Also Ask

Is Open Policy Agent only for Kubernetes?

No. Kubernetes admission control is a popular OPA use case through Gatekeeper, but OPA runs anywhere. Teams embed it in microservices, place it as a sidecar beside APIs, and run it in CI with tools like Conftest. The engine is portable by design.

Is Rego hard to learn for PHP or Laravel developers?

Rego feels unfamiliar at first because it is declarative, not imperative. Most Laravel developers become productive within a few days of writing tests alongside policies. Treat Rego like SQL or regex — specialized, but stable once you learn the evaluation model.

Can OPA replace Laravel authorization entirely?

Not usually. Laravel policies express Eloquent-centric rules close to models. OPA excels at shared, cross-service, and infrastructure rules. The practical approach is both — PHP for domain auth, Rego for platform-wide policy that must stay consistent everywhere.

How do you debug a deny decision in OPA?

Use opa eval --explain full locally with the same input JSON production sent. Add trace rules or structured deny_reason messages in Rego. Never debug production denials by guessing — reproduce the exact input in a test case and fix the policy with opa test.

Ship policy with the same discipline as application code

Policy as Code with Open Policy Agent (OPA) turns "who can do what" from tribal knowledge into tested, reviewable Rego. You gain one decision engine for APIs, clusters, and pipelines. The cost is operational — bundles, sidecars, and Rego fluency — but that cost beats untracked if statements spread across repos.

Start small. Pick one high-risk rule — document export, admin mutation, or Terraform resource type — write Rego tests, and wire a sidecar on staging. Expand only after deny logs prove the policy matches business intent.

If you want help designing authorization for a multi-service platform or a compliance-sensitive portal, contact us to talk through architecture. For ongoing enforcement after launch, testing and optimization services and support and maintenance keep policy bundles aligned with application releases.

Further reading: the official Open Policy Agent documentation and the CNCF OPA project page. For background on the author’s production work, see about me and the wider blog archive.

Frequently Asked Questions

Authorization and compliance rules written in Rego, stored in Git, evaluated by a central OPA engine that returns allow or deny for any input JSON.

Every OPA integration uses the same three pieces. Policy is Rego files defining rules and helper functions. Input is JSON describing the request, resource, or manifest under review. Query is the decision path, commonly data.authz.allow for authorization. OPA returns JSON and your caller interprets the result. That contract stays stable even when policy logic changes, which is why teams can update Rego without redeploying every consuming service at once.

Rego is declarative with no mutable state. You define rules true when conditions match, thinking in sets, objects, and partial definitions. Start with default allow := false for fail-closed behavior. A minimal RBAC policy might allow GET /health for anyone, allow all actions for admin role, and allow editors to POST under /documents/. Add structured deny_reason messages when access fails. The official Rego policy language documentation is the authoritative syntax reference. Validate input JSON fixtures before testing because malformed input causes false negatives that are painful to debug under load.

Rego supports unit tests in _test.rego files alongside your policies. Write cases like test_editor_can_post_documents and test_viewer_cannot_post_documents using the allow rule with input as fixtures. Run opa test policies/ -v locally and gate merges on passing OPA tests, similar to code coverage gates in CI. Also run opa check --strict on every pull request. Treat policy tests with the same discipline as application tests. Fix failing cases before wiring any production sidecar or bundle server.

Laravel 13 authorization through gates and policies belongs close to domain models. OPA earns its place when rules span services or must match infrastructure policy. On a legal-tech client portal, document sharing might depend on case status, user role, and jurisdiction. PHP policies handle request-level checks while OPA enforces the same rule when an export service, webhook worker, and admin API all touch documents. For PHP stacks, keep middleware thin: map the request to JSON, POST to OPA, and let Rego hold the rules.

An OPA sidecar is a container or local daemon on port 8181. Your app POSTs to /v1/data/authz/allow. This pattern works best when many services share one policy bundle. Embedded OPA uses the open-policy-agent/opa Go library inside a service for lower latency but tighter coupling. Use embedded when one high-throughput service owns the decision. For PHP and Laravel stacks, the sidecar pattern is usually simpler because no native Rego interpreter exists in PHP. Call OPA over HTTP with Guzzle or Laravel's HTTP client.

Not usually. Laravel policies handle domain rules; OPA centralizes cross-cutting rules that must stay consistent across services, workers, and infrastructure.

OPA standalone uses Rego and fits APIs, microservices, custom apps, and multi-cloud, but carries a Rego learning curve and you operate the engine. OPA Gatekeeper applies Rego via ConstraintTemplates for Kubernetes admission only, with CRD overhead. Kyverno uses YAML policies without Rego for Kubernetes mutate, validate, and generate, but is less portable outside K8s. HashiCorp Sentinel targets Terraform Cloud and Enterprise plans with commercial, Terraform-centric scope. Laravel policies stay in PHP for single-app Eloquent authorization but do not share across services or infrastructure. OPA wins when one team must enforce the same logical rule in PHP APIs, Node workers, and cluster manifests.

No. Kubernetes Gatekeeper is one use case, but OPA also runs in microservices, API sidecars, and CI tools like Conftest. The engine is portable.

OPA Gatekeeper applies Rego to cluster resources at Kubernetes admission time, pairing with pod security and network policies for defense in depth. In CI, OPA can scan Terraform plans before apply, complementing Infrastructure as Code workflows and tools like Sentinel for Terraform policy. Conftest is another common CI integration mentioned for policy checks outside the cluster. The same Rego bundle can theoretically govern API requests, admission webhooks, and infrastructure changes, which is why platform teams adopt OPA when governance must span repos and environments rather than staying inside one cluster.

Production OPA needs bundle delivery, health checks, observability, and a rollback path. Build bundles with opa build or your CI pipeline, serve them from an HTTP bundle server or object storage, and let OPA poll for updates. That decouples policy releases from application deploys. Version bundles with Git tags and pin each environment to a known tag. Staging can track main; production should not. Log every deny with the policy path and input hash, redact PII, and track p99 latency for OPA calls separately from app latency because a 50 ms check on every request adds up fast.

Timeout cascades happen when OPA is slow or down and app threads block on HTTP calls. Set aggressive timeouts and circuit breakers, and fail closed unless your risk model explicitly allows degraded mode. Policy drift occurs when PHP allows an action Rego denies. Align layers with contract tests hitting both. Over-broad input bloats evaluation when you send entire request bodies instead of only fields Rego needs. Missing default deny is dangerous because without default allow := false, undefined rules can surprise you. Pair OPA with quality gates in CI so policy violations are caught before production.

Run OPA on localhost or a private network segment, not the public internet. Sign bundles and verify signatures at load time. Limit Rego to approved built-ins and deny http.send in production unless required. Run opa check --strict in CI on every pull request. Document who owns policy changes with the same RACI as application code. For platforms exposing AI endpoints, OPA can gate tool calls and data scopes before an LLM acts. Combine OPA with transport security through standard TLS termination on Linux production servers and headers defined in Content Security Policy for Laravel apps.

Rego feels unfamiliar at first because it is declarative, not imperative like PHP. Most Laravel developers become productive within a few days of writing tests alongside policies. Treat Rego like SQL or regex: specialized, but stable once you learn the evaluation model. Start with a minimal authz.rego file, run opa eval locally with request JSON, then add _test.rego cases before touching production middleware. The learning investment pays off when the same policy must govern Laravel middleware, background workers, and Kubernetes admission without copying if statements across repositories.

Use opa eval --explain full locally with the same input JSON production sent. Add trace rules or structured deny_reason messages in Rego so denials return human-readable causes instead of a bare false. Never debug production denials by guessing. Reproduce the exact input in a test case, fix the policy, and confirm with opa test policies/ -v before redeploying the bundle. Log deny reasons with policy version tags for audit compliance on sensitive portals such as client document-sharing systems. Structured deny output makes audit trails as useful as the allow or deny bit itself.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: