Keyring

Propagation

How a change on the control plane reaches every node, and how to know that it has.

Every project has a policy_version. Every change to any key in it, and to the project's own defaults, bumps the version and appends a change row. The SDK's poller asks, every 5 seconds, for the delta since the version it holds.

The feed

  1. Snapshot. A node starts by fetching the project's complete key set, paged at 10,000 keys, and records the version it was served.
  2. Delta. Every poll asks for changes since that version and applies them: upserts for minted, edited and rotated keys, tombstones for revoked ones. A 304 means nothing changed and costs nothing.
  3. Fallback. A delta the control plane can no longer answer completely, because the node is too far behind or the change log was pruned past its version, is answered with a full snapshot rather than a partial delta. A delta is never partial: a truncated one would advance the node past changes it never received.
  4. Acknowledgement. Each poll records the node and the version it holds, which is what revoke?wait=true reads.

A project default change, such as a new default rate limit, raises the change-log floor so every node re-seeds from a snapshot on its next poll, because the alternative is one change row per key.

Disk snapshot

The store is persisted to disk on every change, atomically, off the main thread, under os.tmpdir()/keyring-policy-cache/ by default (cacheDir). A restart that finds a recent file loads it and refuses an unknown key immediately, as a warm node would, until the file's age crosses maxStalenessMs. Both the read and the write validate the file, because the default directory is world-writable and a planted scopes: "*" must not reach an authorisation decision.

How fast

EventReaches a node
Mint a keyWithin one poll, at most 5 s. Expect a freshly minted key to take a few seconds to answer 200.
Revoke, edit, rotateWithin one poll. Measured revoke-to-401 between 4.4 and 4.9 s across five trials.
revoke?wait=trueThe response returns when every polling node has acknowledged, or at 10 s.
A node that is not pollingNever, until it polls again. wait=true counts only nodes that have.

policyRefreshMs changes the interval. Nodes that miss a poll back off, up to six intervals, and a route that is admitting unknown keys triggers an on-demand refresh at most once a second.

Tenant narrowing

A node that serves a subset of tenants passes tenantIds and receives only their keys. The filter is only ever widened, at runtime with admitTenants(), and a widening the control plane refuses is rolled back and retried on a cooldown rather than left to poison every subsequent poll. A narrowed store is incomplete by construction, which puts it in the fail-open table's last row for tenants outside the filter.

On this page