Keystore

Keystore #

The keystore is a node-local file of secrets referenced from pizza.yml — API keys and tokens never have to live in the config file itself. Entries resolve through ${keystore:NAME} references:

node:
  embedding:
    endpoints:
      - name: openai
        url: https://api.openai.com/v1/embeddings
        api_key: ${keystore:openai}

The file is <config dir>/keystore.json — next to the config file it serves, so one instance per config (multi-instance hosts keep separate keystores) and it never enters the data directory’s lifecycle. It is written 0600 and replaced atomically; entries never enter cluster metadata, raft logs, or API responses — views answer with a masked hint only (sk-…4f2a).

Resolution order for a config value: -E CLI overrides (not substituted) > environment references (${VAR}) > keystore references

the literal value in the file. References fail fast at startup when the entry or variable is missing — see the configuration reference for the full syntax.

CLI #

Managed through subcommands — no config load, no server required; -c/--config selects which keystore:

pizza keystore add <name>      # hidden prompt, enter + confirm
pizza keystore add <name> -    # one line from stdin (CI, scripts)
pizza keystore add-file <name> <file>   # contents become the value
pizza keystore list            # names + masked hints + timestamps
pizza keystore show <name>     # print the value in the clear
pizza keystore remove <name>
pizza keystore path            # which file this node would use

Examples:

# interactive — the value never touches shell history or the terminal
pizza keystore add openai

# from a pipe
echo "$OPENAI_API_KEY" | pizza keystore add openai -

# a certificate / key file
pizza keystore add-file snapshot_crt /etc/pizza/certs/tls.crt
  • add refuses a literal value argument on purpose — arguments land in shell history; use the prompt or stdin.
  • Re-adding an existing name replaces the entry (rotation = re-add + reload).
  • Keystore edits re-resolve into the running config on the next reload (the console triggers one automatically); only pizza.yml itself keeps read-once, restart-to-apply semantics. The embedding registry (see AI Services) keeps its own dynamic layer over the yml/keystore baseline without a restart.

Keystore API #

A masked view of the keystore file as it is on disk now — what the ${keystore:...} references resolve against (on boot, and again on every reload). Values are write-only: responses carry a masked hint, never the secret.

Get Keystore #

GET /_node/_local/keystore

On a peer node through the unified forward:

GET /_node/<node_id>/keystore

Response:

{
  "path": "/etc/pizza/keystore.json",
  "read_only": false,
  "entries": [
    {
      "name": "openai",
      "hint": "sk-…4f2a",
      "created_at": "2026-09-27T14:00:00.123456+08:00"
    }
  ]
}

Put Keystore Entry #

Add or replace one entry. On a read-only keystore the call fails with 400.

PUT /_node/_local/keystore/entries/<name>
Content-Type: application/json

{"value": "sk-..."}

The value is write-only — the response is the updated masked view (same shape as the GET above), never the stored value. On a peer node:

PUT /_node/<node_id>/keystore/entries/<name>

Delete Keystore Entry #

Remove one entry; 404 when the name does not exist. The response is the updated masked view.

DELETE /_node/_local/keystore/entries/<name>

The console entry points (PUT/DELETE) re-resolve the running config’s keystore references immediately on success — a rotated embedding key applies to the next inference call, a rotated join token to the next join attempt. A reference whose entry was just removed keeps its running value (the reload logs and reports it) until the entry returns or the node restarts. The CLI remains the writer of record for scripted setups; the HTTP surface exists for the console (below) and is subject to the API port’s auth middleware like every other endpoint.

Reload Keystore #

Re-reads keystore.json from disk and re-resolves the running config’s ${keystore:...} references — the pickup path for out-of-band changes: CLI edits, or a mounted Secret replaced at its source. Works on a read-only keystore (that is exactly the mounted-secret case).

POST /_node/_local/keystore/_reload

On a peer node:

POST /_node/<node_id>/keystore/_reload

Response — key names only, never values:

{
  "entries": 3,
  "read_only": false,
  "references": 2,
  "changed": ["node.embedding.endpoints", "catalog.join_token"],
  "unchanged": 0,
  "failed": []
}
  • references — config keys carrying ${keystore:...} references;
  • changed — keys whose re-resolved value differs from the running one;
  • failed — references that could not be re-resolved (their entry is gone); each keeps the running value until the entry returns.

Only values that referenced the keystore hot-apply: pizza.yml itself still changes at boot, environment references stay boot-frozen, and -E CLI overrides are never re-resolved.

Console #

The keystore is node-local, so the web console manages it on the node it belongs to: open Nodes → (a node) → Keystore — a tab next to Configuration — for the same visual management as the CLI:

  • the tab edits that node’s own keystore.json in place; remote nodes are reached through the unified forward, so a multi-node cluster edits the right file by visiting each node’s page;
  • the entries table shows names, masked hints, and creation times — values are never displayed;
  • Add entry / Replace prompt for the value twice (the confirm guards typos) and show the ${keystore:NAME} reference to paste into pizza.yml;
  • delete removes an entry the same way pizza keystore remove would;
  • Reload re-reads the file on the node and re-resolves the running config’s references — the pickup path for CLI edits and replaced mounted Secrets, and the only control that also works on a read-only keystore;
  • a read-only keystore (a mounted Kubernetes Secret, say) renders the editing controls read-only instead of failing edits one by one.

Edits through either surface land in the same 0600 file; console edits re-resolve into the running config immediately, other changes on the next Reload — only pizza.yml itself still needs a restart.

Read-only mounts #

When keystore.json is not writable (a CSI-mounted Kubernetes Secret, for example) the node starts normally and logs that the keystore is read-only; keystore add/remove fail with a pointer to update the file at its source. This makes “mount the Secret as the keystore” a zero-integration way to feed secrets from an external secret manager — and when the mount is replaced, Reload picks the new file up without touching the node.

Calendar September 30, 2026
Edit Edit this page