Ship Policies Like You Ship Code

Your team already has a way to version code, run tests, publish artifacts, and monitor production. Authorization policies should flow through the same process.

This guide shows a policy pipeline built entirely from standard tools: Git for versioning, CI for testing and signing, HTTP for distribution, Prometheus for metrics, and structured logging for audit trails. The only new component is the SAPL CLI and runtime. Everything else is already in your infrastructure.

The problem

Authorization logic changes as regulations tighten, roles evolve, and new resources appear. Most teams handle these changes outside their established development workflow:

Every proprietary tool added for authorization is another system your team has to learn, operate, secure, and pay for. The alternative is to treat policies like any other artifact in your software delivery process: write them in your editor, version them in your repository, test them in your CI, monitor them in your observability stack.

How it works

Policy pipeline: git push to CI pipeline to GitHub Release to SAPL Node, with testing, signing, and observability at each stage

Author. Write policies and tests in your existing editor. The SAPL Language Server provides syntax highlighting, code completion, inline validation, and quick fixes for VS Code, IntelliJ, Neovim, and any other LSP-capable editor. Policy authors get the same development experience they have for application code.

Version. Policies and tests live in Git. Every change is a commit with an author, timestamp, and diff. Pull requests gate changes through review. Any git forge works: GitHub, GitLab, Gitea, Bitbucket.

Test. The SAPL CLI discovers all .sapl policies and .sapltest test files, runs every scenario, and enforces coverage quality gates. The testing DSL supports mocking of attributes and functions, streaming scenarios with multiple decision steps, and call count verification. If any test fails or coverage is below threshold, the pipeline stops and no bundle is published.

Sign. The CLI packages policies into a .saplbundle archive and signs it with an Ed25519 key. The private key is a CI secret. The public key is committed to the repository. The bundle is verified before publishing.

Distribute. The signed bundle is uploaded to a GitHub Release. Any HTTP server works (S3, Artifactory, Nginx, GitLab Releases). SAPL Node loads bundles from the URL and uses HTTP ETag for efficient change detection. Two modes are supported: interval-based polling for static servers, and long-poll for servers that hold the connection until a new bundle is available.

Run. SAPL Node serves authorization decisions via HTTP API. When it detects a new bundle, it verifies the signature and hot-reloads the policies. No restart, no manual intervention. For multi-tenant deployments, a single repository can publish multiple bundles (one per tenant), each with its own pdpId, combining algorithm, and fetch interval.

Observe. Every authorization decision is logged as structured output to stdout: which policies matched, what the combining algorithm produced, and why. Route these logs to your existing aggregator (ELK, Loki, Splunk, CloudWatch, Datadog) with no proprietary integration.

The node exposes standard monitoring endpoints:

No proprietary dashboard. No separate monitoring tool. The same Grafana instance that monitors your application monitors your authorization.

The guide in action

Testing catches mistakes

The test suite includes an integration test that loads all policies together with pdp.json through the combining algorithm. This catches configuration errors that unit tests miss:

requirement "combined policy evaluation with pdp.json" {
    given
        - configuration "."

    scenario "unmatched action denied by default"
        given
            - attribute "nowMock" <time.now> emits "t1"
            - function time.secondOf(any) maps to 5
        when "user" attempts { "java": { "name": "deleteEverything" } } on "resource"
        expect deny;
}

A broken pdp.json (wrong combining algorithm, invalid field) fails this test before the bundle is ever built.

Coverage gates prevent blind spots

The pipeline enforces quality gates:

If coverage drops below these thresholds, the pipeline fails with exit code 3 and no bundle is published.

Signed bundles prevent tampering

Every bundle is signed with an Ed25519 key. The SAPL Node verifies the signature before loading. If the bundle is modified in transit or on the server, the node rejects it.

sapl bundle verify -b default.saplbundle -k signing.pub

Decisions are observable

With print-text-report enabled, the node logs a human-readable evaluation report for every decision:

--- PDP Decision ---
Subscription   : {"subject"="user", "action"={"http"={"contextPath"="/string"}}, "resource"="resource"}
Decision       : PERMIT
Obligations    : [{"suffix"="HELLO MODIFICATION"}]
Documents:
  modify string arguments -> PERMIT
  patients -> NOT_APPLICABLE
  demo set -> NOT_APPLICABLE
  classified documents -> NOT_APPLICABLE

These logs go to stdout. Route them to your existing log aggregator with no proprietary integration. Prometheus metrics are available at /actuator/prometheus for alerting on decision rates, error rates, and bundle reload events.

Live policy updates via streaming

A running SAPL Node loads bundles from the configured URL using HTTP ETag for change detection. In polling mode, it checks at a configurable interval. In long-poll mode, the server holds the connection until a new bundle is available, delivering near-instant updates. When the pipeline publishes a new bundle, the node downloads it, verifies its signature, and hot-reloads the policies. Active streaming subscriptions re-evaluate and emit updated decisions automatically.

For example, changing the obligation suffix in a policy from "HELLO MODIFICATION" to "UPDATED VIA GITOPS", pushing to main, and waiting for the pipeline to publish the new bundle results in the streaming output changing from:

{"decision":"PERMIT","obligations":[{"suffix":"HELLO MODIFICATION"}]}

to:

{"decision":"PERMIT","obligations":[{"suffix":"UPDATED VIA GITOPS"}]}

No restart, no redeployment. The configurationId in each bundle includes the Git commit hash for traceability.

Run the demo

Fork the demo repository and follow the setup instructions:

sapl-gitops-demo on GitHub

The repository includes four policy sets with streaming tests, a GitHub Actions pipeline with coverage gates and Ed25519 signing, and instructions for running a local SAPL Node that loads the published bundle.