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=5

The base URL must use HTTPS. It can include a path prefix. The Broker appends the operation:

OperationRequest URL
Batch signingPOST <base>/sign
Key generationPOST <base>/keygen
ResharingPOST <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.pem

To 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.pem

Request 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:

FailureAttemptsFinal decision
Network error, timeout, or HTTP 5xxUp to 4 total, with exponential delays starting at 4 secondsReject
HTTP 4xx1Reject
Invalid JSON, missing result, or unknown decision1Reject
Valid reject or abstain1Returned 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.


Did this page help you?