relay: copy X-Gitea-Event into X-GitHub-Event, fix deploy ordering
The relay was forwarding X-Gitea-Event and re-signing the body into the
deprecated generic-V1 X-Webhook-Signature header. Neither is something
Hermes acts on, which left the PR's core premise — "Hermes owns event
selection" — impossible to reach:
- Hermes reads the event name only from X-GitHub-Event/X-GitLab-Event,
then payload event_type/type, then falls back to the literal string
"unknown" (gateway/platforms/webhook.py). Gitea sends X-Gitea-Event and
no such payload key, so every delivery arrived as "unknown" and
`hermes webhook subscribe --events ...` could never select anything.
- Gitea's addDefaultHeaders() already signs every webhook type with
X-Hub-Signature-256 in GitHub's exact format, and Hermes accepts that
header on any route with no per-route provider gating. Re-signing into
V1 was both redundant and on a deprecated path.
So the relay now verifies the signature (accepting either X-Hub-Signature-256
or X-Gitea-Signature), forwards body and signature byte-for-byte, and copies
the one header Hermes actually needs. Authentication alone never justified
this service; that header copy does, and the module comment now says so.
Also fixed:
- gitea-hermes-webhook-provision had no API readiness wait, unlike both
sibling units in the same file. After=gitea.service does not mean gitea
is serving HTTP, so under `set -e` a Type=oneshot with no Restart= would
fail on first boot and stay failed, leaving the webhook unregistered.
- podman-hermes-agent added to the secret's restartUnits. The secret
reaches the container only via sops.templates, whose rendered path never
changes, so systemd would not restart the container when the secret was
first added — hermes-agent-webhook-route then read an empty value back
out of it and subscribed with an empty secret.
- Webhook provisioning passes the request body to curl on stdin rather
than in argv, keeping the shared secret out of /proc/<pid>/cmdline.
- Missing Content-Length now returns 411 rather than 413; dropped the
unreachable non-2xx branch (urlopen raises on non-2xx); env-var secret
fallback is stripped to match the credential-file path.
Adds gitea-hermes-webhook-relay-test.py, which drives the real relay over
real HTTP against a stub Hermes and covers the header copy as a regression
test. Both nixosConfigurations still evaluate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S94o42aQ8VkBmEWvDem5xa
This commit is contained in:
@@ -40,16 +40,31 @@ run. Each service module opens its own firewall ports.
|
||||
## Gitea event relay
|
||||
|
||||
Mars includes a small HMAC-validating relay for Gitea webhooks. It forwards the
|
||||
authenticated request body unchanged, along with Gitea event and delivery
|
||||
headers, to Hermes over localhost. The relay has no event, repository, action,
|
||||
payload, or prompt policy; Hermes owns interpretation and response behavior.
|
||||
Jupiter's Gitea provisioning service registers the webhook idempotently at
|
||||
`http://mars.orbit.sol:8645/gitea`.
|
||||
authenticated request body and Gitea's own signature to Hermes over localhost
|
||||
completely unchanged, and copies `X-Gitea-Event` into `X-GitHub-Event`. It has
|
||||
no event, repository, action, payload, or prompt policy; Hermes owns
|
||||
interpretation and response behavior. Jupiter's Gitea provisioning service
|
||||
registers the webhook idempotently at `http://mars.orbit.sol:8645/gitea`.
|
||||
|
||||
That one header copy is the entire reason the relay exists. Gitea signs every
|
||||
webhook with `X-Hub-Signature-256` in GitHub's exact format, which Hermes
|
||||
already accepts on any route — so authentication would work pointing Gitea
|
||||
straight at Hermes on 8644. But Hermes reads the event name only from
|
||||
`X-GitHub-Event`/`X-GitLab-Event` (then `event_type`/`type` in the payload,
|
||||
then the literal `"unknown"`), and Gitea sends none of those. Without the copy
|
||||
every delivery arrives as `unknown` and `hermes webhook subscribe --events ...`
|
||||
can never match anything.
|
||||
|
||||
Run `python3 services/dev/gitea-hermes-webhook-relay-test.py` to exercise the
|
||||
relay end to end (signature acceptance and rejection, byte-identical body
|
||||
forwarding, and the event-header copy).
|
||||
|
||||
Before deploying either host, add the same random
|
||||
`gitea_hermes_webhook_secret` value to both `secrets/mars.yaml` and
|
||||
`secrets/jupiter.yaml` with `sops --set`. The value is intentionally not
|
||||
included in the repository.
|
||||
`secrets/jupiter.yaml` using `scripts/edit_secrets`, with no trailing newline
|
||||
— the value reaches Hermes through an env-file template, where a newline both
|
||||
corrupts the file and changes the key the HMAC is computed with. The value is
|
||||
intentionally not included in the repository.
|
||||
|
||||
## Test in VirtualBox (no hardware needed)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user