Transport bundle signing: key/trust/rotation decisions; pull + differential bundles (#45 follow-up) #110

Closed
opened 2026-11-09 17:44:18 -05:00 by dohertj2 · 2 comments
Owner

Follow-up from ScadaBridge#45. Bundle signing landed on main (detached ECDSA P-256 manifest.sig over manifest.json, off by default, RequireSignedBundles opt-in, format version unchanged; pre-signing bundles checked in as fixtures and proven to still import). Cluster-to-cluster pull and differential bundles are written up without code in docs/plans/2026-11-09-transport-pull-and-differential-addendum.md.

Decisions needed:

  1. Signing keys: one per environment or one per organisation?
  2. Trust distribution/rotation: keep hand-edited ScadaBridge:Transport:Signing:TrustedKeys, or a config-DB trust list editable in the Admin UI with expiry/revocation?
  3. Default RequireSignedBundles to on at the production boundary once every exporting cluster signs?
  4. Should the default key source read the Secrets store directly instead of via ${secret:} config expansion?
  5. Pull: is any cross-environment network path acceptable to change control, and which machine identity would the pull use? (Recommendation: no code until decided; CLI export→import already covers it.)
  6. Differential bundles: are deletions in scope? (Recommendation: stay deferred; import already skips identical artifacts.)
Follow-up from ScadaBridge#45. Bundle signing landed on main (detached ECDSA P-256 `manifest.sig` over `manifest.json`, off by default, `RequireSignedBundles` opt-in, format version unchanged; pre-signing bundles checked in as fixtures and proven to still import). Cluster-to-cluster pull and differential bundles are written up without code in `docs/plans/2026-11-09-transport-pull-and-differential-addendum.md`. Decisions needed: 1. Signing keys: one per environment or one per organisation? 2. Trust distribution/rotation: keep hand-edited `ScadaBridge:Transport:Signing:TrustedKeys`, or a config-DB trust list editable in the Admin UI with expiry/revocation? 3. Default `RequireSignedBundles` to on at the production boundary once every exporting cluster signs? 4. Should the default key source read the Secrets store directly instead of via `${secret:}` config expansion? 5. Pull: is any cross-environment network path acceptable to change control, and which machine identity would the pull use? (Recommendation: no code until decided; CLI export→import already covers it.) 6. Differential bundles: are deletions in scope? (Recommendation: stay deferred; import already skips identical artifacts.)
dohertj2 added the type:followuppriority:p3 labels 2026-11-09 17:44:18 -05:00
Author
Owner

Owner decision 2026-11-09: one signing key per environment, held in the Secrets store and read via ${secret:}; keep the hand-edited ScadaBridge:Transport:Signing:TrustedKeys (no Admin UI trust list yet); default RequireSignedBundles on at the production boundary once every exporting cluster signs; no cluster-to-cluster pull (CLI export→import covers it); differential deletions stay deferred.

**Owner decision 2026-11-09:** **one signing key per environment**, held in the Secrets store and read via ${secret:}; keep the hand-edited `ScadaBridge:Transport:Signing:TrustedKeys` (no Admin UI trust list yet); default `RequireSignedBundles` on at the production boundary once every exporting cluster signs; **no cluster-to-cluster pull** (CLI export→import covers it); differential deletions stay deferred.
Author
Owner

Implemented in 559e02f5 (main).

  • Decision 1/2: no code change; one key per environment, hand-edited TrustedKeys. Runbook: docs/operations/bundle-signing-runbook.md (openssl keygen, store as secret, distribute public key, rotation, RequireSignedBundles flip order).
  • Decision 3: new Signing:ProductionBoundary flag (default off). While true and RequireSignedBundles is false: startup WARNING + Degraded bundle-signing-policy /health/ready entry (HTTP 200). Code default of RequireSignedBundles unchanged.
  • Decision 4: ${secret:} PrivateKey verified end to end (TransportSigningSecretExpansionTests: expand -> bind -> validate -> sign -> Verified); validation messages and the signature entry never contain key material (tests).
  • Decisions 5/6: pull marked decided-not-to-build, differential deletions deferred, in the addendum and Component-Transport.md.

Evidence: build 0 warnings; Transport.Tests 228+, Transport.IntegrationTests 151, Host.Tests 668 pass (plus 3 new health-check tests); mutation-proved (advisory condition, warning log, key source, health status each fail their tests when broken).

Implemented in 559e02f5 (main). - Decision 1/2: no code change; one key per environment, hand-edited TrustedKeys. Runbook: docs/operations/bundle-signing-runbook.md (openssl keygen, store as secret, distribute public key, rotation, RequireSignedBundles flip order). - Decision 3: new `Signing:ProductionBoundary` flag (default off). While true and RequireSignedBundles is false: startup WARNING + Degraded `bundle-signing-policy` /health/ready entry (HTTP 200). Code default of RequireSignedBundles unchanged. - Decision 4: `${secret:}` PrivateKey verified end to end (TransportSigningSecretExpansionTests: expand -> bind -> validate -> sign -> Verified); validation messages and the signature entry never contain key material (tests). - Decisions 5/6: pull marked decided-not-to-build, differential deletions deferred, in the addendum and Component-Transport.md. Evidence: build 0 warnings; Transport.Tests 228+, Transport.IntegrationTests 151, Host.Tests 668 pass (plus 3 new health-check tests); mutation-proved (advisory condition, warning log, key source, health status each fail their tests when broken).
Sign in to join this conversation.