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