feat(certs): serve leaf + device CA chain from Traefik for client CA pinning#197
Conversation
Review: serve leaf + device CA chain from TraefikReviewed at Verdict: the core idea is sound and the security design is clean — chain order is leaf-first, the appended CA is already a public artifact (no new exposure), and the Cockpit combined PEM still validates. But it can't merge as-is: CI is red, and one load-bearing comment is factually wrong about why the change works. The key correction from review: the feature does take effect, but via the package-upgrade stack restart, not the file-provider watcher the comment claims. postinst restarts Blocker1. Major (fix before merge)2. The new comment (lines 298-299) is false and contradicts the file's own cited reasoning 60 lines below.
Lines 363-364 (citing 3. Non-atomic 4. The new test can't catch the behavior it's named for.
Minor5. Reload-on-append (optional hardening). The upgrade restart covers the real deployment path, so this isn't a functional blocker. But triggering the dynamic-config 6. docs/CERTS.md drift. Line 86 now mis-states the Cockpit override as "leaf cert + leaf key" — it's leaf + device CA + leaf key after this change. And the deliberate served-chain behavior is undocumented in CERTS.md (the operator doc the units reference via Nit7. 8. The commit carries a non-standard What's good
Net: bump VERSION (blocker), fix the false comment + make the append atomic (major), broaden the test to the migration/rotation paths (major). The optional reload-on-append and the doc updates round it out. 🤖 Synthesized from a multi-agent review (Claude Code). |
b566e37 to
97c9898
Compare
|
On-device finding (testing SensESP CA-pinning end-to-end against a HALSER + a halosdev server): the file-provider watcher does not reload Traefik on an in-place CA append. After the script appended the CA to |
SensESP's Signal K CA-pinning captures the issuing CA from the TLS handshake to pin it instead of the rotating leaf. Traefik served the leaf only -- correct per RFC (a self-signed root may be omitted), but it leaves TOFU clients nothing to pin but the leaf. Append the device CA to the served certificate file (idempotently, only when it still holds the leaf alone) so Traefik presents leaf + CA. Clients that already trust the CA ignore the extra cert. Runs before the Cockpit install so :9090 serves the same chain. The chain is assembled via stage-and-mv so an interrupted write can never freeze a half-written second cert into the served file. Traefik's file-provider watcher does NOT reload on a cert-file change (verified on-device: the served chain stayed at the leaf alone until the dynamic config was touched), so a successful append explicitly pokes the dynamic config to force a reload -- the same mechanism the leaf-rotation path uses. Covered by tests for the migration, leaf-resign, CA-rotation, and custom-CA paths; documented in docs/CERTS.md. Refs #196 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97c9898 to
3c1badd
Compare
Review findings addressed (pushed
|
Summary
Makes Traefik serve leaf + device CA so TOFU clients (notably SensESP's Signal K CA-pinning) can capture and pin the stable CA instead of the rotating leaf. Closes #196.
Traefik served the leaf only — correct per RFC (a self-signed root may be omitted, and the HaLOS device CA is a self-signed root signing the leaf directly) but it leaves TOFU clients nothing to pin but the leaf.
What changed
halos-manage-certsappends the device CA (${CA_CRT}) to the served cert file (halos.crt) after signing, idempotently — only when the file still holds the leaf alone, so re-runs and already-migrated devices are untouched. Runs before the Cockpit install so :9090 serves the same chain. A comment marks the deliberate root inclusion so it isn't "cleaned up".Verification
bash -nclean; alltests/test-halos-manage-certs.shpass (40/40), including a newtest_traefik_serves_leaf_plus_ca_chainthat asserts the served file is leaf + CA, the 2nd cert is the device CA, and a rerun stays at 2 certs.openssl s_client -connect <host>:4430 -showcerts(expect 2 certs) and that Cockpit/browser trust is unaffected.Rollout
SignalK/SensESP#1028 can land first; until this ships, devices degrade to leaf-fingerprint pinning (no worse than today). Once deployed and verified, SensESP's verified-upgrade path upgrades pinned devices to CA pinning automatically on a handshake authenticated by their existing trusted leaf.
🤖 Generated with Claude Code