Watch
0
0
Fork
You've already forked hyperhive
0

gateway: per-vhost public flag, private listen addresses, and IPv6 listeners

This commit is contained in:
atlas 2026-11-09 21:38:36 +01:00
commit fbbdf26a19

View file

@ -22,7 +22,7 @@ You rarely switch it on yourself. `gateway.enable` defaults to off, and every mo
| `<queue domain>/metrics` | the queue exporter's `/metrics` on loopback, behind an authelia `auth_request`; every other path, `/` included, returns `403`, so `/varz` and `/connz` are never reachable | `swarm-nats.nix`, the queue's host |
| `grafana.<swarm>`, `metrics.<swarm>`, `logs.<swarm>`, `otel.<swarm>`, `bao.<swarm>` | the matching swarm service | that service's module → [`2-handbook/swarm/services.md`](../../2-handbook/swarm/services.md) |
A service module declares its vhost as `services.hyperhive.gateway.vhosts.<name>`: the locations, `forceSSL` and any `extraConfig` it needs. The gateway renders it into `services.nginx.virtualHosts.<name>` and adds the `listen` set, the certificate for that name and the [security headers](#security-headers) (ahead of the vhost's own `extraConfig`), and drops `addSSL` from a vhost that sets `forceSSL`. The eval fails for a vhost that sets `listen`, `addSSL`, `onlySSL`, `enableACME`, `useACMEHost`, `sslCertificate` or `sslCertificateKey` itself, and for any vhost declared while `gateway.enable` is false. A name with a certificate from outside the gateway's leaves (the queue's) sets `certificate = { cert; key; }` instead. `hive-gateway/vhosts.nix` declares the gateway's own catch-all (`_`) the same way, `hive-c0re/vhosts.nix` the dashboard and the hive domain, and `glue-matrix-hive-discovery.nix` adds the matrix redirect to the hive domain where the homeserver runs, and discovery there when the hive domain is the pinned server name. The catch-all sets `securityHeaders = false` because it sends no response, and the gateway renders a vhost's `default` at `mkDefault` priority.
A service module declares its vhost as `services.hyperhive.gateway.vhosts.<name>`: the locations, `forceSSL` and any `extraConfig` it needs. The gateway renders it into `services.nginx.virtualHosts.<name>` and adds the `listen` set ([listen addresses](#listen-addresses)), the certificate for that name and the [security headers](#security-headers) (ahead of the vhost's own `extraConfig`), and drops `addSSL` from a vhost that sets `forceSSL`. The eval fails for a vhost that sets `listen`, `addSSL`, `onlySSL`, `enableACME`, `useACMEHost`, `sslCertificate` or `sslCertificateKey` itself, and for any vhost declared while `gateway.enable` is false. A name with a certificate from outside the gateway's leaves (the queue's) sets `certificate = { cert; key; }` instead. `hive-gateway/vhosts.nix` declares the gateway's own catch-all (`_`) the same way, `hive-c0re/vhosts.nix` the dashboard and the hive domain, and `glue-matrix-hive-discovery.nix` adds the matrix redirect to the hive domain where the homeserver runs, and discovery there when the hive domain is the pinned server name. The catch-all sets `securityHeaders = false` because it sends no response, and the gateway renders a vhost's `default` at `mkDefault` priority.
**The hive's own vhosts**: the dashboard's, named `<name>.hives.<swarm>` (`deploy.hive-controller.dashboardDomain`), and one named for the hive domain. They exist only on a host that runs a hive (`deploy.hive-controller.enable`); a host without a hive has none of these rows:
@ -124,6 +124,23 @@ The name sits under the swarm domain so authelia's session cookie reaches it, wh
**Certificate:** in self-signed mode the name is a SAN on the hive's own leaf, issued through its own store role, so it needs nothing extra. With `tls.acme` it needs a public DNS record like any other vhost. With `tls.certDir` your certificate must name it; a `*.<swarm>` wildcard doesn't cover `<name>.hives.<swarm>`.
## Listen addresses
Every vhost listens on `port` and `httpsPort` (80 and 443 by default). On which addresses depends on the vhost's `public` flag:
| vhost | listens on |
|---|---|
| `public = true` (default) | `0.0.0.0`, `[::]` (unless `listenIPv6` is off), and each of `privateListenAddresses` |
| `public = false` | each of `privateListenAddresses` only |
`gateway.privateListenAddresses` holds addresses of this host, IPv4 or IPv6, written bare or bracketed (`fd00::1`, `[fd00::1]`). The list is empty by default, so every vhost is public, on `0.0.0.0` and `[::]`. The eval refuses a vhost with `public = false` while the list is empty, and refuses a wildcard (`0.0.0.0`, `::`) in the list, since that would open the private vhosts on the public address. Each address must exist on the host when nginx starts (a WireGuard interface's address, say), or nginx fails to bind.
`gateway.listenIPv6` adds the `[::]` listens. It defaults to `networking.enableIPv6`, so a host that has IPv6 turned off keeps listening on `0.0.0.0` only. nginx opens the `[::]` socket IPv6-only (its default since 1.3.4), so it sits next to `0.0.0.0` on the same port without a conflict, and the IPv4 listeners keep working as before. `openFirewall` opens the ports for both address families.
**Why public vhosts also list the private addresses.** nginx chooses the server set from the address a connection arrived on, before it looks at `Host`. Once any vhost names `10.100.0.1:443`, that address has a server set of its own, and a vhost that lists only the wildcard doesn't belong to it. The vhosts that stay public, and the catch-all (`_`, which carries `default_server`), therefore list the private addresses too. Without that, an unknown or public `Host` on a private address would land on an arbitrary private vhost.
**Why the public address never reaches a private vhost.** nginx matches a connection to the public address against the wildcard listens only, and no `public = false` vhost listens there. With a wildcard listen present, nginx opens just the wildcard sockets and picks the server set from the connection's local address, so you need no `bind` parameter. A request for a private name on the public address gets the catch-all's `444`, the same as any unknown `Host`.
## Firewall posture (host-level)
`services.hyperhive.gateway.openFirewall = true` opens `port` and `httpsPort` on the host firewall — both, since the gateway always serves TLS. It defaults to off; set it for any reach from outside the host.