hive-forge: read the webhook host from the option the target is built from

`swarm.domain` is not the value swarm-controller addresses this hive with —
`swarm-controller.nix` builds `SWARM_CONTROLLER_PUBLIC_URL` from
`swarm.ui.domain`, which merely defaults to it and is documented as
overridable. Allowing the one and being delivered the other would work until
someone takes that option up, then fail as a refused delivery with nothing
naming the cause.

Also states why the entry is NOT gated on `deploy.swarm-ui.enable`, which is
what makes the controller register the hooks at all: in a spread deployment the
forge and the swarm UI are different hosts, so that per-host flag is false
exactly where the allow-list entry is needed.

The comment-block lint caught the prose at 35 lines; trimmed to the parts a
reader cannot re-derive.
This commit is contained in:
atlas 2026-08-31 18:19:39 +02:00
commit b9919164e4

View file

@ -9,6 +9,13 @@ let
gatewayCfg = config.services.hyperhive.gateway;
hyperhiveDomain = config.services.hyperhive.domain;
swarmDomain = config.services.hyperhive.swarm.domain;
# The host the swarm-controller's webhooks actually target. NOT
# `swarm.domain`: `swarm-controller.nix` builds `SWARM_CONTROLLER_PUBLIC_URL`
# from `swarm.ui.domain`, which merely *defaults* to the swarm domain and is
# documented as overridable. Reading the other one would be right until
# someone takes that option up, and then wrong in a way whose only symptom is
# a refused delivery.
swarmUiDomain = config.services.hyperhive.swarm.ui.domain;
tlsCfg = config.services.hyperhive.deploy.hive-controller.tls;
networkCfg = config.services.hyperhive.network;
@ -807,41 +814,38 @@ in
# forge.darkest.space, etc.) were failing every sync
# attempt without this.
migrations.ALLOWED_DOMAINS = "*";
# `ALLOWED_HOST_LIST` is forgejo's webhook SSRF allow-list, and
# it's a STRICT whitelist (only listed hosts deliver). Its
# default is the `external` builtin: all public unicast IPs are
# allowed, private/loopback denied. We must KEEP `external` so
# user-repo webhooks to public hosts (github, slack, …) keep
# working, and ADD the hive gateway host on top: the config-PR +
# knowledge webhooks target `https://<hyperhive domain>/webhook/*`,
# which resolves to a private (RFC-1918) gateway IP that
# `external` alone would deny (so they'd only ever be caught by
# the 5-min poll fallback). Naming the single gateway host is
# `ALLOWED_HOST_LIST` is forgejo's webhook SSRF allow-list: a
# STRICT whitelist whose `external` builtin allows public unicast
# IPs and denies private/loopback. `external` STAYS so user-repo
# webhooks to github/slack keep working; the hyperhive hosts are
# added on top, because their webhooks target a private (RFC-1918)
# gateway IP `external` alone denies — naming the two hosts is
# tighter than the broad `private` builtin.
#
# ⚠️ The SWARM domain is on this list too, and leaving it off is
# what made every swarm-controller delivery fail. Those webhooks
# target `https://<swarm domain>/webhook/forge/*` — a different
# host from the hive's, resolving to the same private gateway IP,
# so `external` denies it and the hive entry does not cover it.
# Measured from the sender's own log, which names both the host
# and the rule:
# TWO hosts, not one. The hive's carries the config-PR + knowledge
# hooks; the swarm's carries swarm-controller's, and leaving it
# off denied every one of those:
# deny '<swarm host>(10.42.0.1:443)' — webhook can only call
# allowed HTTP servers
#
# deny 'constellation.darkest.space(10.42.0.1:443)'
# webhook can only call allowed HTTP servers
# (check your webhook.ALLOWED_HOST_LIST setting)
# ⚠️ `swarm.ui.domain`, NOT `swarm.domain`: swarm-controller.nix
# builds its `SWARM_CONTROLLER_PUBLIC_URL` from the former, which
# only *defaults* to the latter and is documented as overridable.
#
# `optional` because a hive with no swarm has no such domain, and
# `unique` because an all-local deployment can legitimately set
# both to the same string — a duplicate entry is harmless to
# forgejo but reads as a mistake.
# Not gated on `deploy.swarm-ui.enable`, even though that is what
# makes the controller register the hooks: in a spread deployment
# the forge and the UI are different hosts, so that per-host flag
# is false exactly where this entry is needed.
#
# `unique` because an all-local swarm legitimately serves both
# from one name.
webhook.ALLOWED_HOST_LIST = lib.concatStringsSep "," (
lib.unique (
[
"external"
hyperhiveDomain
]
++ lib.optional (swarmDomain != null) swarmDomain
++ lib.optional (swarmDomain != null) swarmUiDomain
)
);
log.LEVEL = "Warn";