opapolicychecker

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 25, 2026 License: MIT Imports: 20 Imported by: 0

README

OPA Policy Checker Plugin

Validates incoming Beckn messages against network-defined business rules using Open Policy Agent (OPA) and the Rego policy language. Non-compliant messages are rejected with a BadRequest error code.

Features

  • Evaluates business rules defined in Rego policies
  • Supports multiple policy sources: remote URL, local file, directory, or OPA bundle (.tar.gz)
  • Structured result format: {"valid": bool, "violations": []string}
  • Fail-closed on empty/undefined query results — misconfigured policies are treated as violations
  • Runtime config forwarding: adapter config values are accessible in Rego as data.config.<key>
  • Action-based enforcement: apply policies only to specific beckn actions (e.g., confirm, search)
  • Configurable fetch timeout for remote policy and bundle sources
  • Warns at startup when policy enforcement is explicitly disabled

Configuration

checkPolicy:
  id: opapolicychecker
  config:
    type: file
    location: ./pkg/plugin/implementation/opapolicychecker/testdata/example.rego
    query: "data.policy.result"
    actions: "confirm,search"
steps:
  - checkPolicy
  - addRoute
Configuration Parameters
Parameter Type Required Default Description
type string Yes - Policy source type: url, file, dir, or bundle
location string Yes - Path or URL to the policy source (.tar.gz for bundles)
query string Yes - Rego query path to evaluate (e.g., data.policy.result)
actions string No (all) Comma-separated beckn actions to enforce
enabled string No "true" Enable or disable the plugin
debugLogging string No "false" Enable verbose OPA evaluation logging
fetchTimeoutSeconds string No "30" Timeout in seconds for fetching remote .rego files or bundles
refreshIntervalSeconds string No - Reload policies every N seconds (0 or omit = disabled)
any other key string No - Forwarded to Rego as data.config.<key>

Policy Hot-Reload

When refreshIntervalSeconds is set, a background goroutine periodically re-fetches and recompiles the policy source without restarting the adapter:

  • Atomic swap: the old evaluator stays fully active until the new one is compiled — no gap in enforcement
  • Non-fatal errors: if the reload fails (e.g., file temporarily unreachable or parse error), the error is logged and the previous policy stays active
  • Goroutine lifecycle: the reload loop stops when the adapter context is cancelled or when plugin Close() is invoked during shutdown
config:
  type: file
  location: ./policies/compliance.rego
  query: "data.policy.result"
  refreshIntervalSeconds: "300"  # reload every 5 minutes

How It Works

Initialization (Load Time)
  1. Load Policy Source: Fetches .rego files from the configured location — URL, file, directory, or OPA bundle
  2. Compile Policies: Compiles all Rego modules into a single optimized PreparedEvalQuery
  3. Set Query: Prepares the OPA query from the configured query path (e.g., data.policy.result)
Request Evaluation (Runtime)
  1. Check Action Match: If actions is configured, skip evaluation for non-matching actions. The plugin assumes standard adapter routes look like /{participant}/{direction}/{action} such as /bpp/caller/confirm; non-standard paths fall back to context.action from the JSON body.
  2. Evaluate OPA Query: Run the prepared query with the full beckn message as input
  3. Handle Result:
    • If the query returns no result (undefined) → violation (fail-closed)
    • If result is {"valid": bool, "violations": []string} → use structured format
    • If result is a set or []string → each string is a violation
    • If result is a boolfalse = violation
    • If result is a string → non-empty = violation
  4. Reject or Allow: If violations are found, NACK the request with all violation messages
Supported Query Output Formats
Rego Output Behavior
{"valid": bool, "violations": ["string"]} Structured result format (recommended)
set() / []string Each string is a violation message
bool (true/false) false = denied, true = allowed
string Non-empty = violation
Empty/undefined Violation (fail-closed) — indicates misconfigured query path

Example Usage

Local File
checkPolicy:
  id: opapolicychecker
  config:
    type: file
    location: ./pkg/plugin/implementation/opapolicychecker/testdata/example.rego
    query: "data.policy.result"
Remote URL
checkPolicy:
  id: opapolicychecker
  config:
    type: url
    location: https://policies.example.com/compliance.rego
    query: "data.policy.result"
    fetchTimeoutSeconds: "10"
Local Directory (multiple .rego files)
checkPolicy:
  id: opapolicychecker
  config:
    type: dir
    location: ./policies
    query: "data.policy.result"
OPA Bundle (.tar.gz)
checkPolicy:
  id: opapolicychecker
  config:
    type: bundle
    location: https://nfo.example.org/policies/bundle.tar.gz
    query: "data.retail.validation.result"

Writing Policies

Policies are written in Rego. The plugin passes the full beckn message body as input and any adapter config values as data.config:

package policy

import rego.v1

# Default result: valid with no violations.
default result := {
  "valid": true,
  "violations": []
}

# Compute the result from collected violations.
result := {
  "valid": count(violations) == 0,
  "violations": violations
}

# Require provider on confirm
violations contains "confirm: missing provider" if {
    input.context.action == "confirm"
    not input.message.order.provider
}

# Configurable threshold from adapter config
violations contains "delivery lead time too short" if {
    input.context.action == "confirm"
    lead := input.message.order.fulfillments[_].start.time.duration
    to_number(lead) < to_number(data.config.minDeliveryLeadHours)
}

See testdata/example.rego for a full working example.

Relationship with Schema Validator

opapolicychecker and schemav2validator serve different purposes:

  • Schemav2Validator: Validates message structure against OpenAPI/JSON Schema specs
  • OPA Policy Checker: Evaluates business rules via OPA/Rego policies

Configure them side-by-side in your adapter steps as needed.

Plugin ID vs Step Name

  • Plugin ID (used in id:): opapolicychecker (lowercase, implementation-specific)
  • Step name (used in steps: list and YAML key): checkPolicy (camelCase verb)

Dependencies

  • github.com/open-policy-agent/opa — OPA Go SDK for policy evaluation and bundle loading

Known Limitations

  • No bundle signature verification: When using type: bundle, bundle signature verification is skipped. This is planned for a future enhancement.
  • Network-level scoping: Policies apply to all messages handled by the adapter instance. Per-network policy mapping (by networkId) is tracked for follow-up.
  • Non-standard route shapes: URL-based action extraction assumes the standard Beckn adapter route shape /{participant}/{direction}/{action} and falls back to context.action for other path layouts.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	Type            string
	Location        string
	PolicyPaths     []string
	Query           string
	Actions         []string
	Enabled         bool
	DebugLogging    bool
	FetchTimeout    time.Duration
	IsBundle        bool
	RefreshInterval time.Duration // 0 = disabled
	RuntimeConfig   map[string]string
}

Config holds the configuration for the OPA Policy Checker plugin.

func DefaultConfig

func DefaultConfig() *Config

func ParseConfig

func ParseConfig(cfg map[string]string) (*Config, error)

ParseConfig parses the plugin configuration map into a Config struct. Uses type + location pattern (matches schemav2validator).

func (*Config) IsActionEnabled

func (c *Config) IsActionEnabled(action string) bool

type Evaluator

type Evaluator struct {
	// contains filtered or unexported fields
}

Evaluator wraps the OPA engine: loads and compiles .rego files at startup, then evaluates messages against the compiled policy set.

func NewEvaluator

func NewEvaluator(policyPaths []string, query string, runtimeConfig map[string]string, isBundle bool, fetchTimeout time.Duration) (*Evaluator, error)

NewEvaluator creates an Evaluator by loading .rego files from local paths and/or URLs, then compiling them. runtimeConfig is passed to Rego as data.config. When isBundle is true, the first policyPath is treated as a URL to an OPA bundle (.tar.gz).

func (*Evaluator) Evaluate

func (e *Evaluator) Evaluate(ctx context.Context, body []byte) ([]string, error)

Evaluate runs the compiled policy against a JSON message body. Returns a list of violation strings (empty = compliant).

func (*Evaluator) ModuleNames

func (e *Evaluator) ModuleNames() []string

ModuleNames returns the names of the loaded .rego policy modules.

type PolicyEnforcer

type PolicyEnforcer struct {
	// contains filtered or unexported fields
}

PolicyEnforcer evaluates beckn messages against OPA policies and NACKs non-compliant messages.

func New

func New(ctx context.Context, cfg map[string]string) (*PolicyEnforcer, error)

func (*PolicyEnforcer) CheckPolicy

func (e *PolicyEnforcer) CheckPolicy(ctx *model.StepContext) error

CheckPolicy evaluates the message body against loaded OPA policies. Returns a BadReqErr (causing NACK) if violations are found. Returns an error on evaluation failure (fail closed).

func (*PolicyEnforcer) Close

func (e *PolicyEnforcer) Close()

Directories

Path Synopsis
Package main provides the plugin entry point for the OPA Policy Checker plugin.
Package main provides the plugin entry point for the OPA Policy Checker plugin.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL