Open Policy Agent
Evaluate Virtual Signer approval requests with tested Rego policy
The Virtual Signer directly supports Open Policy Agent (OPA) through its external-policy contract. Requests already use OPA's {"input": ...} envelope and OPA returns the required {"result": ...} response through its Data API.
The examples on this page were executed with OPA 1.17.0. Pin the OPA version approved for your io.sign release rather than using a floating image tag.
OPA's Data API uses a different URL shape, so place a TLS adapter in front of OPA:
Virtual Signer Broker
POST /v2/approve/sign
|
v
TLS adapter and path rewrite
/v2/approve/{operation} -> /v1/data/policy/approve/{operation}
|
v
OPA Data API
data.policy.approve.signConfigure the Broker
IOSIGN_SIGNER_BROKER_APPROVAL_MODE=api-approval
IOSIGN_SIGNER_BROKER_APPROVAL_EXTERNAL_POLICY_BASE_URL=https://opa-policy.example.internal/v2/approve
IOSIGN_SIGNER_BROKER_APPROVAL_EXTERNAL_POLICY_TIMEOUT=5
IOSIGN_SIGNER_BROKER_APPROVAL_POLICY_CA_CERT_FILE=/run/secrets/policy-ca.pemThe Broker appends /sign, /keygen, or /reshare. The adapter rewrites that path to the corresponding rule under /v1/data/policy/approve/ and forwards the body unchanged.
Start with a fail-closed policy
Save this as approve.rego. It rejects every operation by default. The example approves a batch signing request only when it targets the allowed vault and every signature is a Polygon transfer no greater than the configured amount.
package policy.approve
import rego.v1
default sign := {"decision": "reject"}
default keygen := {"decision": "reject"}
default reshare := {"decision": "reject"}
allowed_vault := "vault-456"
polygon_chain_id := "137"
max_polygon_wei := 4000000000000000
sign := {"decision": "approve"} if {
input.type == "sign"
input.vaultId == allowed_vault
count(input.signatures) > 0
every signature in input.signatures {
signature.metadata.contextType == "application/evm-rlp-v1+hex"
signature.metadata.decoded.parsed.ChainID == polygon_chain_id
to_number(signature.metadata.decoded.parsed.Amount) <= max_polygon_wei
}
}The every check is important because one approval request can contain multiple signatures. Missing metadata, an unexpected chain, an excessive amount, or an empty batch leaves the default rejection in place.
Test the policy
Save this as approve_test.rego beside the policy:
package policy.approve_test
import rego.v1
import data.policy.approve
approved_input := {
"type": "sign",
"vaultId": "vault-456",
"signatures": [{
"metadata": {
"contextType": "application/evm-rlp-v1+hex",
"decoded": {"parsed": {"ChainID": "137", "Amount": "1000"}}
}
}]
}
test_approved_transfer if {
result := approve.sign with input as approved_input
result.decision == "approve"
}
test_rejects_excessive_transfer if {
excessive := object.union(
approved_input,
{"signatures": [{
"metadata": {
"contextType": "application/evm-rlp-v1+hex",
"decoded": {"parsed": {"ChainID": "137", "Amount": "5000000000000000"}}
}
}]},
)
result := approve.sign with input as excessive
result.decision == "reject"
}
test_rejects_keygen_by_default if {
result := approve.keygen with input as {"type": "keygen", "vaultId": "vault-456"}
result.decision == "reject"
}Run all discovered tests and fail if none are found. See OPA's policy-testing reference for test discovery and additional options.
opa test --fail-on-empty approve.rego approve_test.regoConfigure a TLS path adapter
This Caddy route illustrates the required rewrite. Supply production-managed certificates and restrict OPA's port to the adapter.
policy.example.internal:8443 {
tls /certs/server.pem /certs/server-key.pem
handle_path /v2/approve/* {
rewrite * /v1/data/policy/approve{uri}
reverse_proxy opa:8181
}
}OPA evaluates data.policy.approve.sign, keygen, or reshare and wraps the rule value inside result, which matches the Broker response contract.
Production guidance
- Pin an approved OPA image version; do not deploy an unpinned
latesttag. - Keep OPA's policy-management and Data API ports private to the adapter and administration plane.
- Manage Rego and its tests in version control with review required for policy changes.
- Do not enable raw decision logs in production without redaction. Inputs can contain transaction details and public-key material.
- Monitor evaluation latency and errors below the Broker timeout.
- Add explicit key-generation and resharing rules only after defining which vault membership and threshold changes are allowed.
See Custom policy server for the common wire contract and failure behavior.
Updated about 2 months ago