Policy

Official Beta

Multi-language policy evaluation agent supporting Cedar and Rego/OPA for fine-grained authorization decisions.

Version: 0.1.0 Author: Sentinel Core Team License: Apache-2.0 Protocol: vv2 View Source

Overview

The Policy agent provides flexible authorization decisions using multiple policy languages. It supports both Cedar (AWS’s policy language) and Rego (Open Policy Agent’s language), allowing you to choose the policy engine that best fits your needs or use both together.

Written in Haskell for correctness and reliability, this agent evaluates policies by shelling out to the cedar and opa CLI tools, making it easy to use existing policies and benefit from the official policy engine implementations.

Features

Policy Languages

  • Cedar: AWS’s policy language for fine-grained access control

    • Intuitive permit/forbid syntax
    • Entity-based authorization model
    • Principal, action, resource, and context evaluation
  • Rego/OPA: Open Policy Agent’s declarative policy language

    • General-purpose policy language
    • Rich built-in functions
    • Package-based policy organization

Core Features

  • Multi-Engine Support: Use Cedar, Rego, or both simultaneously
  • Auto-Detection: Automatically detect policy language from file extension
  • Decision Caching: LRU cache with configurable TTL for fast repeated decisions
  • Input Mapping: Flexible mapping from HTTP requests to policy inputs
  • Audit Logging: Track all authorization decisions

Protocol v2

  • Unix Domain Socket: High-performance UDS transport
  • Health Reporting: Automatic health status reporting to proxy
  • Metrics Export: Built-in metrics (evaluations, allows, denies, cache hits)
  • Graceful Lifecycle: Proper startup and shutdown handling

Requirements

This agent requires external CLI tools for policy evaluation:

# Install Cedar CLI (via cargo)
cargo install cedar-policy-cli

# Install OPA CLI (via homebrew on macOS)
brew install opa

# Or download OPA binary directly
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static
chmod +x opa && sudo mv opa /usr/local/bin/

Installation

From Source

git clone https://github.com/raskell-io/sentinel-agent-policy
cd sentinel-agent-policy
cabal build
cabal install

Using Cabal

cabal install sentinel-agent-policy

Quick Start

Command Line

# Start with Cedar policy
sentinel-policy-agent \
  --socket /var/run/sentinel/policy.sock \
  --engine cedar \
  --policy-file /etc/sentinel/policies/authz.cedar

# Start with Rego policy
sentinel-policy-agent \
  --socket /var/run/sentinel/policy.sock \
  --engine rego \
  --policy-file /etc/sentinel/policies/authz.rego

# Auto-detect engine from file extension
sentinel-policy-agent \
  --socket /var/run/sentinel/policy.sock \
  --engine auto \
  --policy-file /etc/sentinel/policies/authz.cedar

Configuration File

Create a policy.yaml configuration file:

engine: cedar  # or "rego" or "auto"
policies:
  - type: file
    path: /etc/sentinel/policies/authz.cedar
default_decision: deny
cache:
  enabled: true
  ttl_seconds: 60
  max_entries: 10000
socket_path: /var/run/sentinel/policy.sock
log_level: info

Then run:

sentinel-policy-agent --config policy.yaml

Configuration

Sentinel Proxy Configuration

agents {
    agent "policy" {
        type "custom"
        transport "unix_socket" {
            path "/var/run/sentinel/policy.sock"
        }
        events ["request_headers"]
        timeout-ms 100
        failure-mode "closed"
        protocol-version 2
    }
}

routes {
    route "api" {
        matches { path-prefix "/api" }
        upstream "backend"
        agents ["policy"]
    }
}

Command Line Options

OptionEnvironmentDescriptionDefault
--socketAGENT_SOCKETUnix socket path/tmp/sentinel-policy.sock
--configCONFIG_FILEPath to YAML config file-
--enginePOLICY_ENGINEEngine: cedar, rego, autoauto
--policy-filePOLICY_FILEPath to policy file-
--default-decisionDEFAULT_DECISIONDefault: allow or denydeny
--cache-enabledCACHE_ENABLEDEnable decision cachingtrue
--cache-ttlCACHE_TTLCache TTL in seconds60
--log-levelLOG_LEVELLog level: debug, info, warn, errorinfo

Configuration File Options

# Policy engine: cedar, rego, or auto (detect from file extension)
engine: auto

# Socket path for Sentinel proxy connection
socket_path: /var/run/sentinel/policy.sock

# Policy sources
policies:
  - type: file
    path: /etc/sentinel/policies/authz.cedar
  - type: file
    path: /etc/sentinel/policies/rbac.rego
  - type: inline
    content: |
      permit(principal, action, resource)
      when { resource.path like "/public/*" };

# Default decision when no policy matches or on error
default_decision: deny

# Decision caching
cache:
  enabled: true
  ttl_seconds: 60
  max_entries: 10000

# Input mapping (how to extract principal/action/resource from requests)
input_mapping:
  principal:
    source: header
    name: X-User-Id
  action:
    get_maps_to: read
    post_maps_to: create
    put_maps_to: update
    patch_maps_to: update
    delete_maps_to: delete
    default: read
  resource:
    pattern: "/api/{resource_type}/{resource_id}"

# Logging
log_level: info

# Audit logging
audit:
  enabled: true
  log_allows: false
  log_denies: true

Policy Examples

Cedar Policy

// Allow authenticated users to read any resource
permit(
    principal,
    action == Action::"read",
    resource
);

// Allow users to modify their own resources
permit(
    principal,
    action,
    resource
) when {
    resource.owner == principal
};

// Admins can do anything
permit(
    principal,
    action,
    resource
) when {
    principal.role == "admin"
};

// Deny access to admin paths for non-admins
forbid(
    principal,
    action,
    resource
) when {
    resource.path like "/admin/*" &&
    principal.role != "admin"
};

Rego Policy

package sentinel.authz

import future.keywords.if
import future.keywords.in

default allow := false

# Allow authenticated users to read
allow if {
    input.action == "read"
    input.principal.id != "anonymous"
}

# Allow users to modify their own resources
allow if {
    input.resource.owner == input.principal.id
}

# Admins can do anything
allow if {
    "admin" in input.principal.roles
}

# Deny access to admin paths for non-admins
deny if {
    startswith(input.resource.path, "/admin/")
    not "admin" in input.principal.roles
}

# Final decision
decision := "allow" if {
    allow
    not deny
}

decision := "deny" if {
    not allow
}

decision := "deny" if {
    deny
}

Input Format

The agent constructs a policy input from each HTTP request:

Cedar Request Format

{
  "principal": "User::\"user-123\"",
  "action": "Action::\"read\"",
  "resource": "Resource::\"/api/documents/456\"",
  "context": {
    "method": "GET",
    "path": "/api/documents/456",
    "headers": {
      "content-type": "application/json"
    }
  }
}

Rego Input Format

{
  "principal": {
    "id": "user-123",
    "type": "User",
    "attributes": {}
  },
  "action": "read",
  "method": "GET",
  "resource": {
    "id": "456",
    "type": "Document",
    "path": "/api/documents/456",
    "attributes": {}
  },
  "context": {
    "headers": {"content-type": "application/json"},
    "query": {}
  }
}

Decision Caching

The agent caches policy decisions to improve performance for repeated requests:

  • Cache Key: Hash of (principal, action, resource, relevant context)
  • TTL: Configurable expiration time (default 60 seconds)
  • LRU Eviction: Least-recently-used entries evicted when cache is full
  • Invalidation: Cache clears on policy reload

Caching is important for performance since each uncached evaluation spawns a subprocess. The default TTL of 60 seconds and 10,000 max entries provide good coverage for most workloads.

cache:
  enabled: true
  ttl_seconds: 60
  max_entries: 10000

Metrics

The agent reports the following metrics:

MetricTypeDescription
policy_evaluations_totalCounterTotal policy evaluations
policy_allow_totalCounterTotal allow decisions
policy_deny_totalCounterTotal deny decisions
policy_cache_hits_totalCounterCache hits
policy_cache_misses_totalCounterCache misses
policy_errors_totalCounterEvaluation errors
policy_in_flightGaugeCurrent in-flight evaluations
policy_cache_entriesGaugeCurrent cache size

Response Codes

StatusCondition
403Policy denied the request
(passthrough)Policy allowed the request

On denial, the agent returns:

403 Forbidden
Access denied by policy

Health Status

The agent reports health status including:

  • Current load (in-flight requests)
  • Average evaluation latency
  • Error rate
  • Cache statistics

Health degrades to “degraded” when:

  • Error rate exceeds 10% of total evaluations
  • In-flight requests exceed 80

Comparison with Auth Agent

FeaturePolicy AgentAuth Agent
FocusAuthorization onlyAuthN + AuthZ
Cedar SupportFullBasic (embedded)
Rego/OPA SupportFullNo
AuthenticationNoJWT, OIDC, SAML, mTLS, API keys
Session ManagementNoYes
Use CaseDedicated policy evaluationAll-in-one auth

Use Policy Agent when:

  • You need advanced Cedar or Rego policies
  • You have separate authentication (handled by Auth agent or upstream)
  • You want dedicated policy evaluation with caching
  • You’re migrating existing OPA/Cedar policies

Use Auth Agent when:

  • You need authentication AND authorization
  • Basic Cedar policies are sufficient
  • You want a single agent for all auth concerns

Combining with Auth Agent

For complex setups, use both agents:

agents {
    // Auth agent handles authentication
    agent "auth" {
        transport "unix_socket" {
            path "/var/run/sentinel/auth.sock"
        }
        events ["request_headers"]
        timeout-ms 100
        failure-mode "closed"
    }

    // Policy agent handles fine-grained authorization
    agent "policy" {
        transport "unix_socket" {
            path "/var/run/sentinel/policy.sock"
        }
        events ["request_headers"]
        timeout-ms 100
        failure-mode "closed"
    }
}

routes {
    route "api" {
        matches { path-prefix "/api" }
        upstream "backend"
        // Auth runs first (authenticates), then policy (authorizes)
        agents ["auth", "policy"]
    }
}

The Auth agent authenticates the user and adds headers like X-User-Id, which the Policy agent then uses for authorization decisions.

AgentIntegration
AuthAuthentication + basic authorization
WAFCombine policy with attack detection
DenylistBlock IPs before policy evaluation

Resources