200 lines
7.4 KiB
Markdown
200 lines
7.4 KiB
Markdown
# OPA Policy Checker Plugin
|
|
|
|
Validates incoming Beckn messages against network-defined business rules using [Open Policy Agent (OPA)](https://www.openpolicyagent.org/) 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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```yaml
|
|
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 `bool` → `false` = 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
|
|
|
|
```yaml
|
|
checkPolicy:
|
|
id: opapolicychecker
|
|
config:
|
|
type: file
|
|
location: ./pkg/plugin/implementation/opapolicychecker/testdata/example.rego
|
|
query: "data.policy.result"
|
|
```
|
|
|
|
### Remote URL
|
|
|
|
```yaml
|
|
checkPolicy:
|
|
id: opapolicychecker
|
|
config:
|
|
type: url
|
|
location: https://policies.example.com/compliance.rego
|
|
query: "data.policy.result"
|
|
fetchTimeoutSeconds: "10"
|
|
```
|
|
|
|
### Local Directory (multiple `.rego` files)
|
|
|
|
```yaml
|
|
checkPolicy:
|
|
id: opapolicychecker
|
|
config:
|
|
type: dir
|
|
location: ./policies
|
|
query: "data.policy.result"
|
|
```
|
|
|
|
### OPA Bundle (`.tar.gz`)
|
|
|
|
```yaml
|
|
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](https://www.openpolicyagent.org/docs/latest/policy-language/). The plugin passes the full beckn message body as `input` and any adapter config values as `data.config`:
|
|
|
|
```rego
|
|
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`](./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.
|