Custom policy server
Implement the HTTPS approval contract used by an io.sign Virtual Signer
A custom policy server lets your organisation decide when a Virtual Signer participates in signing, key generation, or resharing. The Broker sends an OPA-compatible JSON envelope to your HTTPS service and applies the returned decision.
Configure the Broker
IOSIGN_SIGNER_BROKER_APPROVAL_MODE=api-approval
IOSIGN_SIGNER_BROKER_APPROVAL_EXTERNAL_POLICY_BASE_URL=https://policy.example.internal/v2/approve
IOSIGN_SIGNER_BROKER_APPROVAL_EXTERNAL_POLICY_TIMEOUT=5The base URL must use HTTPS. It can include a path prefix. The Broker appends the operation:
| Operation | Request URL |
|---|---|
| Batch signing | POST <base>/sign |
| Key generation | POST <base>/keygen |
| Resharing | POST <base>/reshare |
For a private certificate authority, add its PEM certificate to the system trust pool:
IOSIGN_SIGNER_BROKER_APPROVAL_POLICY_CA_CERT_FILE=/run/secrets/policy-ca.pemTo require mutual TLS, configure both client files. Setting only one causes startup validation to fail.
IOSIGN_SIGNER_BROKER_APPROVAL_TLS_CLIENT_CERT_FILE=/run/secrets/policy-client.pem
IOSIGN_SIGNER_BROKER_APPROVAL_TLS_CLIENT_KEY_FILE=/run/secrets/policy-client-key.pemRequest contract
The Broker sends Content-Type: application/json. It does not add legacy nonce or HTTP Message Signature headers.
Every body uses an input envelope:
{
"input": {
"id": "request-123",
"type": "sign",
"vaultId": "vault-456",
"threshold": 4,
"curves": [],
"signers": [],
"signatures": []
}
}input.type is sign, keygen, or reshare. See Sample approval requests for operation-specific fields.
Response contract
Return an HTTP 2xx response containing JSON:
{
"result": {
"decision": "approve"
}
}The decision must be exactly one of:
approve— participate and contribute this signer's weight.reject— do not participate.abstain— do not participate without expressing approval or rejection.
Missing result, invalid JSON, and unknown decision values are treated as rejection.
Failure behavior
The Broker fails closed:
| Failure | Attempts | Final decision |
|---|---|---|
Network error, timeout, or HTTP 5xx | Up to 4 total, with exponential delays starting at 4 seconds | Reject |
HTTP 4xx | 1 | Reject |
Invalid JSON, missing result, or unknown decision | 1 | Reject |
Valid reject or abstain | 1 | Returned decision |
IOSIGN_SIGNER_BROKER_APPROVAL_EXTERNAL_POLICY_TIMEOUT is a per-attempt timeout in seconds. 0 disables the Broker's per-attempt deadline; use a finite production value so an unavailable policy service cannot hold requests indefinitely.
Minimal Node.js server
This dependency-free example rejects by default and approves only signing requests for an explicitly allowed vault. Replace the example condition with your reviewed policy logic.
import { createServer } from 'node:http';
const allowedVaults = new Set(['vault-456']);
const operations = new Set(['/sign', '/keygen', '/reshare']);
createServer((request, response) => {
if (request.method !== 'POST' || !operations.has(request.url)) {
response.writeHead(404).end();
return;
}
let body = '';
request.setEncoding('utf8');
request.on('data', (chunk) => {
body += chunk;
if (body.length > 1_000_000) request.destroy();
});
request.on('end', () => {
let decision = 'reject';
try {
const { input } = JSON.parse(body);
if (input?.type === 'sign' && allowedVaults.has(input.vaultId)) {
decision = 'approve';
}
} catch {
// Invalid input remains rejected.
}
response.writeHead(200, { 'Content-Type': 'application/json' });
response.end(JSON.stringify({ result: { decision } }));
});
}).listen(8080, '127.0.0.1');Terminate TLS in front of the process or in the application server itself. The Broker refuses an http:// policy URL.
Test the response shape
curl --request POST http://127.0.0.1:8080/sign \
--header 'Content-Type: application/json' \
--data '{"input":{"id":"request-123","type":"sign","vaultId":"vault-456","threshold":4,"curves":[],"signers":[],"signatures":[]}}'The example returns:
{"result":{"decision":"approve"}}Before production, test valid requests, malformed input, policy-service outages, timeouts, and every supported operation. Protect the service with network isolation and TLS or mTLS, and deploy policy changes through review and version control.
Updated about 2 months ago