Dynamic Key Management

Mint, rotate, and revoke virtual keys at runtime, hashed at rest, with instant revoke

Mint, list, rotate, and revoke a virtual key at runtime through the admin API

Overview

A virtual key is a live, governed resource, not a line of YAML. With the key_management: block enabled, you mint, revoke, and rotate inbound keys at runtime through an admin API. Each change takes effect on the next request without a reload, because every request resolves its key through a cache and then a store. Inbound keys are hashed at rest; upstream provider credentials can be encrypted at rest.

This is the runtime layer on top of the static credentials: block. The static block still works and lowers into the same store as config-sourced records.

The block

proxy:
  key_management:
    enabled: true
    store:
      backend: embedded            # embedded | redis | secrets_manager
      path: /var/lib/sbproxy/keystore.redb
    cache:
      ttl_secs: 60
      tier: none                   # none | redis | mesh
    crypto:
      pepper: env:SBPROXY_KEY_PEPPER       # HMAC key for inbound hashing
      master_key: env:SBPROXY_KEY_MASTER   # envelope key for upstream creds
    failure_mode_allow: false      # fail closed when the store is down

The security model

Two kinds of secret, two treatments. Inbound virtual keys are hashed, never stored in a form you can read back. The at-rest verifier is HMAC-SHA256(secret, pepper); the server pepper means a stolen store is useless without it. A minted token looks like sk-<key_id>-<secret>: the key_id is a public prefix, the secret is shown once and never stored. Verification is constant-time.

Upstream provider credentials are encrypted, because the proxy has to present them to the provider. Use a vault reference (vault://, awssm://, gcpsm://) resolved at use, or an AES-256-GCM envelope: a per-record data key encrypts the secret, then is wrapped under a key derived from the master_key, so you can rotate the master without re-encrypting every payload.

By default the plane fails closed. If the store cannot be reached, a request carrying a virtual key is denied.

The admin API

Mounted on the existing admin server, under the same bind and basic auth. The plaintext token comes back exactly once on create and rotate.

Method and pathEffect
POST /admin/keysMint a key (token shown once)
GET /admin/keysList keys (no secrets)
PATCH /admin/keys/{id}Update limits, budget, models, attribution
POST /admin/keys/{id}/revokeMark revoked (terminal, instant)
POST /admin/keys/{id}/rotateRotate with a grace window
POST /admin/credentialsCreate an upstream credential

Revoke is instant: the next request with that key is denied. Rotation mints a fresh secret for the same key_id and keeps the prior secret valid for a grace window, so a client fleet can pick up the new token before the old one stops working.

OIDC and JWT

If your callers authenticate with an OIDC or JWT identity instead of a bearer key, set oidc_claim_map.claim_field to the claim whose value names a key record. After the token is verified, the claim value resolves the record and its policy applies, so a bearer key and an OIDC identity converge on the same record and the same limits.

Across a fleet

For multiple replicas, set cache.tier: redis or cache.tier: mesh. The mesh tier keeps the key plane coherent across the cluster with no external Redis in the path; see Mesh Clustering.