Commit Graph
3 Commits
Author SHA1 Message Date
Jeroen UbbinkandClaude Opus 4.8 c2a2d76eae fix(vault): harden the auto-generated master key file
Three fixes to how a node provisions its own master key when the operator
sets no ROUTSTR_SECRET_KEY:

- Publish the key atomically. It is written to a same-directory temp file,
  fsynced, then os.link-ed into place and the directory fsynced. os.link
  publishes the complete file in one step, so a crash mid-write can no longer
  strand an empty key at the final path that a later boot would read as corrupt
  and then fail to decrypt every secret under. os.link also refuses to clobber,
  so a racing worker that generated first keeps ownership and the loser adopts
  its key.
- Tighten loose permissions on read. A key file that is group/other-readable is
  repaired to 0600 rather than trusted, keeping an upgrading node booting.
- Stop printing the key value. The one-time notice names the file to back up and
  shouts the backup imperative, but no longer echoes the key itself, which would
  leak it into captured stdout / aggregated container logs; the durable 0600 file
  is the recovery path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:51:21 +02:00
Jeroen UbbinkandClaude Opus 4.8 7106dfe330 fix(vault): provision a master key on upgrade instead of refusing to boot
A node with a legacy plaintext nsec but no ROUTSTR_SECRET_KEY refused to
boot: bootstrap_secrets raised and vault.encrypt required the env key.
That turned encryption at rest into a hard breaking change on auto-upgrade.

Encryption stays mandatory — the nsec is never persisted in plaintext —
but key custody becomes flexible. When no ROUTSTR_SECRET_KEY is set,
encrypt() generates a Fernet key, writes it owner-only (0600) to a key
file, and prints a one-time back-it-up notice, so an upgrading node keeps
running. The read path stays strict: decrypt()/get_fernet() never mint a
key (a fresh key could not match existing ciphertext) and fail fast with
the generation command when none is configured. A malformed env key still
fails fast rather than silently self-provisioning a different key.

The key file defaults beside the SQLite database (ROUTSTR_SECRET_KEY_FILE
overrides), so it rides whatever volume already persists the data instead
of a working-directory path a container recreate would drop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:51:20 +02:00
Jeroen UbbinkandClaude Opus 4.8 86adee9b1b feat(vault): add Fernet/scrypt secret primitives
Encrypt/decrypt secrets at rest with Fernet keyed by ROUTSTR_SECRET_KEY,
and hash/verify admin passwords with scrypt. Self-contained helpers with
no consumers yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:50:51 +02:00