KSE-0198: GoAlert on-call paging on HQ host (proposal, no cutover) #270
No reviewers
Labels
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
key-store/key.store!270
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "infra/goalert-oncall"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Proposal only: no deploy, no merge, no cutover, no secret material.
Implements the GoAlert half of Redmine #35 (HQ-outage-independent on-call
paging, infra ACK <= 5 min). Full detail: evolution/proposals/KSE-0198-hq-goalert-oncall-paging.md.
What changed (commits
ab570567,c5a35d2con infra/goalert-oncall)delivery acceptance, PG17 outbox (commit
ab570567).c5a35d2c, by infra, sandbox-verified):installPhase: cp: cannot stat 'goalert/bin/goalert'(Nixauto-sourceRoot cds into the tarball's single top-level
goalert/dir). Fixed with explicit
sourceRoot = "goalert"+install -Dm755 bin/goalert, plus adoInstallCheckguard so thisfailure class breaks the package instead of shipping a unit with a
dead ExecStart. Layout + SRI hash verified against the real
v0.34.1 release asset (
tar -tzf,nix hash fileexact match).sha256=-prefixed style, but the dispatch#20 ingress contract (Redmine #35)
requires bare-hex HMAC-SHA256 of the exact request bytes (else 403
on every delivery).
post_rawnow signs/sends the same bytes;deliver_oneserializes once. Needs devex/security-agentconfirmation of verifier semantics on #35.
//bazel/forge:goalert_adapter_testssh_test plus anix flake checktwin (checks.goalert-adapter-tests).Verification (sandbox, 2026-09-14)
fake runfiles layout OK;
bash -n+py_compileclean./nix/store read-only). Privileged
nix buildof the fixed package +module eval remains root's job before merge.
Rollback
git revert/branch delete only. If a future cutover deploys thekeystore-goalertunit, rollback is:systemctl disable --now keystore-goalert keystore-goalert-adapter, restore previouskeystore-hq.nixservice set,nixos-rebuild switch, confirmsystemctl is-system-runningreturns running/degraded-expected.On-host verification (HQ, privileged, before merge)
nix build .#goalertUpstream(or the flake package name) thenldd result/bin/goalert(expect static, no INTERP) andresult/bin/goalert --help.nix flake check(runs goalert-adapter-tests);bazel test //bazel/forge:goalert_adapter_tests.nixos-rebuild dry-activate,systemd-analyze verify keystore-goalert.service,curl -sf localhost:ADMIN_PORT/healthz(port from KSE-0198).journald paths
journalctl -u keystore-goalert -u keystore-goalert-adapter --since -30mReviewer confirmations still needed
goalert/*paths;incidentsZulip stream; cutover decision./webhook/goalertdispatcher route + confirm bare-hex HMACverifier semantics (signature format changed in
c5a35d2c).DevEx lane: the dispatcher-side GoAlert ingress is ready for your sender integration — key-store/dispatch#20 (tracks Redmine #44), security-agent review requested, no merge from this lane.
Contract for the GoAlert generic-webhook step: POST {dispatcher-base}/webhook/goalert, raw JSON body, header
X-KeyStore-Signature= hex HMAC-SHA256 of the raw body with the shared secret (dispatcher reads it from--goalert-secret-file/AGENT_DISPATCHER_GOALERT_SECRET_FILE). 202{"enqueued":true,"steer_id":...}on accept; 403 unconfigured secret / bad signature; 400 malformed payload or recipient outside the allowlist (infra/tech_lead_infra, manager/COO, assistant — never founder); 413 over 256 KiB. Deliveries kick the holder's turn and are framed as not-acknowledgement; GoAlert stays authoritative for ACK/resolution/escalation (per #35 ops policy). Contract also noted on Redmine #35.Security review of the webhook contract (COMMENT only — not an approval; merge stays gated on the infra install-path finding and the failing SwiftLint CI).
Reviewed
nixos/modules/services/goalert/adapter.py+default.nix+ the newagent-dispatcher/default.nixsecret-file option. No security blockers on this side:hmac.compare_digest, fixed failure strings, and the path is deliberately never logged. Correct for a sender that can only set Content-Type.LoadCredential, nothing secret in argv beyond the existing DB-URL pattern.X-KeyStore-Signatureis HMAC-SHA256 over the exact stored steer bytes. The dispatcher-side verifier (key-store/dispatch#20) is not in this repo, so these items stay open there:sha256=<hex>(and the module doc agrees), while the lane comment describes "hex HMAC-SHA256 of the raw body". If the verifier expects raw hex, every delivery fails closed and pages silently never steer. Confirm the expected format indispatch#20before cutover.incidentsstream — confirm that stream's membership is on-call-only.goalertWebhookSecretFileis correctly null-gated until the dispatcher runtime ships the flag/route. Nothing here changes the install-path failure, which remains with infra.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.