Policy evaluation agent for Zentinel reverse proxy. Supports multiple policy languages including Rego (OPA) and Cedar.
- Multi-Engine Support - Rego (OPA) and Cedar policy languages
- Request Context Evaluation - Full access to request metadata, headers, path, method
- Flexible Policy Loading - File-based, inline, or remote policy bundles
- Audit Logging - Detailed decision audit trail with matched rules
- Caching - Optional decision caching for performance
- Written in Haskell - Delegates to
cedarandopaCLI tools for evaluation (must be installed separately)
cabal install zentinel-agent-policygit clone https://github.com/zentinelproxy/zentinel-agent-policy
cd zentinel-agent-policy
cabal buildzentinel-policy-agent --socket /var/run/zentinel/policy.sock --config policy.yaml| Option | Environment Variable | Description | Default |
|---|---|---|---|
--socket |
AGENT_SOCKET |
Unix socket path | /tmp/zentinel-policy.sock |
--config |
POLICY_CONFIG |
Configuration file path | - |
--engine |
POLICY_ENGINE |
Policy engine (cedar, rego, auto) |
auto |
--policy-dir |
POLICY_DIR |
Directory containing policy files | - |
--log-level |
LOG_LEVEL |
Log level (debug, info, warn, error) |
info |
This agent requires external CLI tools for policy evaluation:
- For Cedar policies: Install the cedar CLI (
cargo install cedar-policy-cli) - For Rego policies: Install the opa CLI
The agent writes temporary files and invokes these tools per evaluation (with caching to reduce overhead).
# Policy engine configuration
engine: cedar # or "rego" or "auto"
# Policy sources
policies:
# File-based policies
- type: file
path: /etc/zentinel/policies/authz.cedar
# Inline policy
- type: inline
content: |
permit(
principal,
action == Action::"read",
resource
) when {
principal.role == "viewer"
};
# Remote bundle (OPA-style)
- type: bundle
url: https://policy-server.example.com/v1/policies
refresh_interval: 60s
# Input mapping - how to build policy input from request
input_mapping:
principal:
type: header
name: X-User-ID
resource:
type: path
pattern: "/api/{resource_type}/{resource_id}"
action:
type: method_mapping
GET: read
POST: create
PUT: update
DELETE: delete
# Default decision when no policy matches
default_decision: deny
# Caching
cache:
enabled: true
ttl: 60s
max_entries: 10000
# Audit logging
audit:
enabled: true
include_input: true
include_policies: false// Allow authenticated users to read public resources
permit(
principal,
action == Action::"read",
resource
) when {
resource.visibility == "public"
};
// Allow resource owners full access
permit(
principal,
action,
resource
) when {
principal == resource.owner
};
// Deny access to admin endpoints unless admin role
forbid(
principal,
action,
resource
) when {
resource.path.hasPrefix("/admin") &&
principal.role != "admin"
};
package zentinel.authz
default allow := false
# Allow authenticated users to read public resources
allow {
input.action == "read"
input.resource.visibility == "public"
}
# Allow resource owners full access
allow {
input.principal.id == input.resource.owner_id
}
# Deny access to admin endpoints unless admin role
deny {
startswith(input.resource.path, "/admin")
input.principal.role != "admin"
}
# Final decision
decision := "allow" {
allow
not deny
}
decision := "deny" {
not allow
}
decision := "deny" {
deny
}Add to your Zentinel config.kdl:
agents {
agent "policy" {
type "custom"
unix-socket path="/var/run/zentinel/policy.sock"
events "request_headers"
timeout-ms 100
failure-mode "closed" # Deny on policy engine failure
}
}
routes {
route "api" {
matches { path-prefix "/api" }
upstream "backend"
agents ["policy"]
}
}┌─────────────────────────────────────────────────────────────────┐
│ zentinel-agent-policy │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Request Handler │ │
│ │ • Extract principal, action, resource from request │ │
│ │ • Build policy input context │ │
│ └─────────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────▼─────────────────────────────────┐ │
│ │ Policy Evaluator │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Cedar │ │ Rego │ │ │
│ │ │ Evaluator │ │ Evaluator │ │ │
│ │ │ (via CLI) │ │ (via OPA) │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────▼─────────────────────────────────┐ │
│ │ Decision Cache │ │
│ └───────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
│ Unix Socket (v2 Protocol)
▼
┌─────────────────────┐
│ Zentinel Proxy │
└─────────────────────┘
The agent exposes Prometheus metrics:
| Metric | Type | Description |
|---|---|---|
policy_evaluations_total |
Counter | Total policy evaluations |
policy_decisions_total |
Counter | Decisions by result (allow/deny) |
policy_evaluation_duration_seconds |
Histogram | Evaluation latency |
policy_cache_hits_total |
Counter | Cache hits |
policy_cache_misses_total |
Counter | Cache misses |
policy_errors_total |
Counter | Policy evaluation errors |
- GHC 9.8+
- Cabal 3.10+
# Build
cabal build
# Run tests
cabal test
# Run with debug logging
cabal run zentinel-policy-agent -- --socket /tmp/policy.sock --log-level debugzentinel-agent-policy/
├── app/
│ └── Main.hs # CLI entry point
├── src/
│ └── Zentinel/
│ └── Agent/
│ └── Policy/
│ ├── Config.hs # Configuration types
│ ├── Engine.hs # Policy engine interface
│ ├── Cedar.hs # Cedar evaluator
│ ├── Rego.hs # Rego/OPA evaluator
│ ├── Input.hs # Input mapping
│ ├── Cache.hs # Decision caching
│ └── Handler.hs # Agent handler
├── test/
│ └── ...
├── policies/ # Example policies
│ ├── example.cedar
│ └── example.rego
├── zentinel-agent-policy.cabal
├── cabal.project
└── README.md
Apache-2.0
Contributions welcome! Please see the Zentinel contributing guide.