clusev/docker/release
boban 0943d56201 harden(update): verify the self-update commit signature before the root re-exec/build
The re-audit's top finding (HIGH): update.sh runs as root, pulled + re-exec'd its own new copy +
ran install.sh's `docker compose build` on the pulled tree with NO signature check, so a
compromised/redirected origin or a transport MITM could feed attacker commits into a root RCE. The
branch-pin (bc2f2b5) stops a local `git checkout` redirect but not a remote origin compromise —
only a signature does.

- update.sh: after the pull and BEFORE the self-update re-exec / install.sh build, verify the pulled
  HEAD with `git -c gpg.ssh.allowedSignersFile=<anchor> verify-commit HEAD`. Opt-in and safe by
  default — skipped with a warning when no anchor is configured (existing installs keep updating),
  but FAIL CLOSED once configured. The anchor is resolved from .env CLUSEV_ALLOWED_SIGNERS or the
  gitignored .clusev-allowed-signers; a SET-but-missing anchor dies (no silent downgrade), and a
  git-TRACKED anchor is refused (a pulled tree could otherwise swap in the attacker's own key).
- clusev-release.sh: opt-in SSH signing of the release commit + tag when CLUSEV_SIGNING_KEY is set,
  so the verify side has provenance to check. Unsigned (identical behaviour) when unset.
- .gitignore /.clusev-allowed-signers (the trust anchor must never be pull-overwritable);
  .env.example documents CLUSEV_ALLOWED_SIGNERS.

verify-commit + allowed-signers proven end-to-end in a scratch repo (signed→pass, unsigned→fail,
wrong-key→fail, tracked-anchor→refuse, set-but-missing→fail-closed). shellcheck clean. Activation
(provision the maintainer keypair + sign releases) is a documented maintainer step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:10:19 +02:00
..
README.md refactor(release): single stable channel — cut internal release candidates (-rc), remove all beta wording 2026-07-05 13:13:09 +02:00
clusev-release-request.path feat(release): systemd watcher units + dev run-mount + flag-gated install 2026-06-22 23:28:27 +02:00
clusev-release-request.service feat(release): systemd watcher units + dev run-mount + flag-gated install 2026-06-22 23:28:27 +02:00
clusev-release.sh harden(update): verify the self-update commit signature before the root re-exec/build 2026-07-05 17:10:19 +02:00
serve-request.test.sh refactor(release): single stable channel — cut internal release candidates (-rc), remove all beta wording 2026-07-05 13:13:09 +02:00

README.md

Release bridge (host watcher) — DEV ONLY

The dashboard "Release" page (Deploy to Staging) cuts a release candidate by asking the host to do the git work, so the container never holds git credentials — the same isolation as the WireGuard bridge.

  • clusev-release.sh serve-request — reads run/release-request.json (the dashboard wrote it), re-validates, bumps config/clusev.php, commits, tags vX.Y.Z-rcN, pushes to Gitea (token read host-side from /home/nexxo/.env.gitea), and writes run/release-result-<id>.json for the dashboard to poll. Roll back leaves no half-release if the push fails.
  • clusev-release-request.path / .service — a systemd .path watcher that runs the handler when a request file appears.

Enable on the dev box

These units are installed only when CLUSEV_RELEASE_CONTROLS=true is in the project .env (install.sh gates them). To enable manually:

cd /home/nexxo/clusev
sudo install -m 0644 docker/release/clusev-release-request.service /etc/systemd/system/
sudo install -m 0644 docker/release/clusev-release-request.path    /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now clusev-release-request.path

The dev docker-compose.yml must bind-mount ./run to the container's storage/app/restart-signal (shared with the WireGuard/restart bridges) so the dashboard's request lands where the watcher looks.

Verify (manual gate)

This is the end-to-end gate (like the WireGuard runbook): with the watcher enabled and the flag on, open /release, click a bump button, confirm, and check that vX.Y.Z-rcN lands on Gitea (and, via the push-mirror, on GitHub-private). The handler never logs the Gitea token.