store: seal secret columns at rest !495

merged merged by cmc on 2026-09-28 22:37 UTC · krz/gitbay:secrets-at-rest into main

Discussion

cmc

Secret columns are stored sealed with AES-256-GCM.

  • internal/seal: a keyring read from server.secret_key_file (outside server.root, mode 0600). Every value is sealed with associated data <table>.<column>:<row key>, so a value copied to another row or column does not open. The key file is re-read when it changes; a bad file fails closed.
  • The store seals CI secrets, webhook secrets, mirror tokens and push device tokens. Push devices are looked up by a SHA-256 of the token (migration 0066 push_token_hash).
  • gitbayd serve refuses to start without the key file. It seals any value still in clear, or under a retired key, before it listens, and refuses to start if that fails.
  • gitbayd admin secrets init|rotate|check. init refuses an existing file; run as root, it gives the file to the owner of server.root. rotate reseals every value under a new key in one transaction and drops the old keys only after confirming. check exits 1 if any value does not open.
  • The store sets synchronous(FULL) explicitly; rotation relies on the reseal commit being durable.
  • The e2e harness gives each instance a key file. The backup test checks that the archive holds the value sealed and does not hold the key.
  • deploy/install.sh creates the key on first install. Admin and Architecture pages; #273 leaves Known-Gaps; CHANGELOG with an upgrade note.

Before deploying to bay1: gitbayd admin secrets init as root, then copy /etc/gitbay/secret.key off the host. Downgrading after values are sealed is not supported.

Stacked on !489 (build-failure-report), which is stacked on the web UX MRs.

Closes #273