opapolicychecker

package
v1.6.0 Latest Latest
Warning

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

Go to latest
Published: Apr 30, 2026 License: MIT Imports: 29 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: single policy file, local policy directory, or OPA bundle (.tar.gz)
  • Supports manifest-backed policy resolution through the manifestloader plugin
  • Signature verification for single-file policies and OPA bundles
  • 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

This plugin now requires networkPolicyConfig. Older top-level policy keys such as type, location, query, actions, and refreshIntervalSeconds are no longer supported.

manifestLoader:
  id: manifestloader
  config:
    cacheTTL: 24h
    fetchTimeoutSeconds: "30"

checkPolicy:
  id: opapolicychecker
  config:
    networkPolicyConfig: ./config/opa-network-policies.yaml
    refreshInterval: "5m"
steps:
  - checkPolicy
  - addRoute
Configuration Parameters
Parameter Type Required Default Description
networkPolicyConfig string Yes - Path to a YAML file containing networkPolicies keyed by network_id
enabled string No "true" Enable or disable the plugin
debugLogging string No "false" Enable verbose OPA evaluation logging
refreshInterval string No - Reload all configured policies on a Go duration such as 30s, 20m, or 24h
any other key string No - Forwarded to Rego as data.config.<key>
Network Policy Config File

The plugin loads all configured policies at startup and selects the correct one at request time using context.networkId or context.network_id.

Top-level plugin config:

checkPolicy:
  id: opapolicychecker
  config:
    networkPolicyConfig: ./config/opa-network-policies.yaml
    refreshInterval: "5m"

Structured config file:

networkPolicies:
  nfh.global/testnet:
    type: manifest

  nfo.example.org/mobility-network:
    type: file
    location: https://nfo.example.org/policies/mobility.rego
    query: "data.mobility.policy.result"

  nfo.example.org/logistics-network:
    type: bundle
    location: https://nfo.example.org/policies/logistics.tar.gz
    query: "data.logistics.policy.result"

  default:
    type: file
    location: ./policies/default.rego
    query: "data.default.policy.result"

Behavior:

  • all configured policies are loaded at startup
  • request-time selection uses exact match on context.networkId and falls back to context.network_id
  • if an exact network-specific policy matches, it is selected even when enabled: false
  • an exact network match with enabled: false intentionally overrides default
  • if no network-specific policy matches, default is used when configured
  • if neither a network-specific policy nor default matches, OPA evaluation is skipped by design
  • policy enforcement is opt-in by network_id; unmatched networks are skipped unless default is defined
  • if you want one global policy only, define just default

Each entry under networkPolicies supports:

  • type: file, dir, bundle, or manifest
  • location and query for file, dir, and bundle
  • optional actions
  • optional enabled
  • optional fetchTimeoutSeconds
  • optional verification for file and bundle
Manifest-backed Policies

Use type: manifest when the network policy should be resolved indirectly through a verified network manifest fetched by the manifestloader plugin.

Guides for NFOs creating and publishing network policies can be found here:

manifestLoader:
  id: manifestloader
  config:
    cacheTTL: 24h
    fetchTimeoutSeconds: "30"
    forceRefreshOnStartup: false
    disableCache: false

checkPolicy:
  id: opapolicychecker
  config:
    networkPolicyConfig: ./config/opa-network-policies.yaml
    refreshInterval: "5m"
networkPolicies:
  nfh.global/testnet:
    type: manifest

Rules for type: manifest:

  • manifestLoader must be configured in the same handler/module as checkPolicy
  • location, query, and verification must not be set on the type: manifest entry
  • the manifest is fetched by network ID using the network policy key
  • the manifest uses fields such as manifest_version, manifest_type, network_id, policy_query_path, and signature_url
  • the manifest must contain:
    • manifest_type: "network-manifest"
    • network_id matching the configured network policy key exactly
    • policies.type: "rego"
    • policies.source: "file" or "bundle"
    • valid governance.effective_from
  • if governance.effective_until is present, it must be later than effective_from and not expired
  • resolved manifest policy sources are then loaded through the normal file or bundle OPA code paths
Signature Verification

Verification is optional and configured per policy entry.

Single-file policy with detached signature:

networkPolicies:
  retail.network/production:
    type: file
    location: ./policies/retail.rego
    query: data.policy.result
    verification:
      enabled: true
      publicKeyLookupUrl: https://api.dedi.global/dedi/lookup/example-nfo.com/public_key_test/retail-key
      signatureLocation: ./policies/retail.rego.sig

Signed bundle:

networkPolicies:
  retail.network/production:
    type: bundle
    location: ./policies/retail-bundle.tar.gz
    query: data.retail.validation.result
    verification:
      enabled: true
      publicKeyLookupUrl: https://api.dedi.global/dedi/lookup/example-nfo.com/public_key_test/retail-key

Rules:

  • type: file supports local files and remote URLs
  • type: bundle supports local .tar.gz files and remote bundle URLs
  • type: dir is not recommended for production use and should be used only for testing or local development
  • type: dir does not support signature verification; package directories as signed bundles instead
  • verification.publicKeyLookupUrl should point to a DeDi public-key record lookup endpoint
  • public-key lookup JSON is read only from supported fields: data.details.publicKey, data.details.signing_public_key, data.details.public_key, or legacy top-level publicKey, signing_public_key, and public_key
  • DeDi public-key lookup supports keyFormat: base64 using standard padded base64
  • as a fallback, raw PEM public keys, PEM certificates, and parseable DER key material are also accepted
  • detached signature locations may return raw signature bytes, base64-encoded signature text, or JSON with a top-level signature field
  • formats such as base58, hex, and JWK are not supported by the current plugin implementation
  • when verification.enabled: true for type: file, verification.signatureLocation and verification.publicKeyLookupUrl are required
  • when verification.enabled: true for type: bundle, verification.publicKeyLookupUrl is required
  • verification.algorithm is optional for type: bundle and defaults to ES256
  • supported verification.algorithm values for bundle verification are:
    • ES256, ES384, ES512
    • RS256, RS384, RS512
    • PS256, PS384, PS512
  • EdDSA is not supported by the current plugin implementation for bundle verification

Single-file detached signature verification does not use a separate algorithm config field. The plugin chooses the verification path automatically from the key type returned by DeDi:

  • RSA key type:
    • verifies detached signatures using RSA PKCS#1 v1.5 with SHA-256
  • ECDSA key type:
    • verifies detached signatures using ECDSA with SHA-256
  • Ed25519 key type:
    • verifies detached signatures using Ed25519

Policy Hot-Reload

When refreshInterval is set, a background goroutine periodically reloads and recompiles all configured policy sources 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
  • Manifest cache boundary: for type: manifest, each refresh asks manifestloader for the manifest again, but manifest freshness is still controlled by manifestloader.cacheTTL, forceRefreshOnStartup, and disableCache. A short OPA refreshInterval does not force a network manifest re-fetch while the manifest cache entry is still valid.
  • Goroutine lifecycle: the reload loop stops when the adapter context is cancelled or when plugin Close() is invoked during shutdown
config:
  networkPolicyConfig: ./config/opa-network-policies.yaml
  refreshInterval: "5m"

If operators expect manifest changes to become eligible on every OPA refresh, set the manifest loader cacheTTL less than or equal to the OPA refreshInterval, or use disableCache during debugging.

How It Works

Initialization (Load Time)
  1. Load Policy Config: Reads the structured networkPolicyConfig file
  2. Resolve Manifest-backed Entries: For type: manifest, fetches the verified manifest through manifestloader, validates it, and resolves it into a concrete file or bundle policy source
  3. Load Policy Sources: Fetches .rego files or bundles for each configured network policy entry
  4. Verify Signatures: When enabled, verifies detached signatures for single-file policies or embedded signatures for signed bundles
  5. Compile Policies: Compiles one evaluator per configured network_id plus optional default
Request Evaluation (Runtime)
  1. Select Policy: Match context.networkId exactly, fall back to context.network_id, then default
  2. Check Action Match: If actions is configured on the selected policy, 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.
  3. Evaluate OPA Query: Run the selected policy with the full beckn message as input
  4. 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
  5. 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

Default-only Policy
checkPolicy:
  id: opapolicychecker
  config:
    networkPolicyConfig: ./config/opa-network-policies.yaml
networkPolicies:
  default:
    type: file
    location: ./pkg/plugin/implementation/opapolicychecker/testdata/example.rego
    query: "data.policy.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

  • Signed directories are not supported: If you want signature verification for multiple Rego files, package them as a signed OPA bundle instead of using type: dir.
  • 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 ArtifactVerificationConfig added in v1.6.0

type ArtifactVerificationConfig struct {
	Enabled            bool
	PublicKeyLookupURL string
	SignatureLocation  string
	Algorithm          string
}

type Config

type Config struct {
	NetworkPolicyConfig string
	RefreshInterval     time.Duration // 0 = disabled
	Enabled             bool
	DebugLogging        bool
	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 top-level plugin configuration map into a Config struct.

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, verification *ArtifactVerificationConfig) (*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 local path or 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 PolicyConfig added in v1.6.0

type PolicyConfig struct {
	Type          string
	Location      string
	PolicyPaths   []string
	Query         string
	Actions       []string
	Enabled       bool
	FetchTimeout  time.Duration
	IsBundle      bool
	Verification  *ArtifactVerificationConfig
	RuntimeConfig map[string]string
}

func (*PolicyConfig) IsActionEnabled added in v1.6.0

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

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 NewWithManifestLoader added in v1.6.0

func NewWithManifestLoader(ctx context.Context, manifestLoader definition.ManifestLoader, 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