Watch
0
0
Fork
You've already forked hyperhive
0

log the agent bridge's forwarded flows in the hive_egress table

A forward chain in the hive-owned nft table logs each new flow from the agent bridge to the uplink (v4 and v6) and counts every packet. It never blocks. The mesh interface is skipped. Logging is rate limited per source address; a flow that limit cannot log gets one line from a shared limit with its own prefix. services.hyperhive.network.egress.forward selects accept, log (default) or drop; drop is refused at evaluation until the interception it needs exists.
This commit is contained in:
atlas 2026-11-12 08:56:44 +01:00
commit bd79b58e78

View file

@ -231,7 +231,8 @@ only. An agent sends its outbound HTTP and HTTPS through it once its hive says
the proxy is ready and it has fetched its proxy credential (see _Agents and the
proxy_ below). Any other agent goes straight out through the NAT, as before,
and so does any program that ignores the proxy variables: nothing blocks direct
egress yet.
egress yet. The host logs those direct connections (see _Agent bridge egress log_
below).
Squid filters on the destination hostname: a `CONNECT` target or a plain-HTTP
host. It never decrypts TLS, so it holds no signing key. It caches nothing.
@ -517,6 +518,7 @@ the nix side sets up unconditionally:
| IP forwarding | `boot.kernel.sysctl."net.ipv4.ip_forward" = 1` |
| Internet NAT | `networking.nat { enable = true; internalInterfaces = [ bridgeName ]; }` — MASQUERADE on packets leaving via any external NIC |
| Loopback DROP | `nix/host-modules/hive-egress.nix` — own nftables table `inet hive_egress` (whichever firewall the host runs): drops bridge → `127.0.0.0/8` and `::1` traffic; the kernel default `route_localnet=0` is the first layer |
| Forward log | `nix/host-modules/hive-egress.nix` — the same table's `forward` chain logs and counts what the host routes from the bridge to the uplink; it blocks nothing (see _Agent bridge egress log_ below) |
| Gateway access | `networking.firewall.interfaces.<bridge>.allowedTCPPorts = [ 80 443 ]` — lets isolated agents (private netns, veth on bridge) reach nginx on the host |
| c0re signal | `HIVE_NETWORK_BRIDGE`, `HIVE_NETWORK_SUBNET` in `systemd.services.hive-c0re.environment` — both **required**; `hive-c0re` refuses to start without them |
@ -580,6 +582,52 @@ defence-in-depth against a compromised agent reaching the c0re dashboard HTTP at
`127.0.0.1`). Agents have no legitimate reason to reach the dashboard over
loopback — the hive-c0re admin socket is a UDS, not TCP.
### Agent bridge egress log
`services.hyperhive.network.egress.forward` sets what the `forward` chain of
the `inet hive_egress` table does with traffic the host routes from the agent
bridge to anywhere but the bridge and the `wg-hive` mesh interface. It takes
`accept`, `log` (the default) or `drop`. The evaluation refuses `drop` for now:
the interception that would carry an agent's web traffic through squid instead
hasn't landed. In `log` mode the chain blocks nothing. It logs each new flow
and counts every packet.
- **Which traffic.** IPv4 and IPv6 alike (the table is `inet`). The chain never
sees traffic to the host's own addresses, because the host delivers it
locally: a connection to squid on `<bridgeIp>:3128`, to dnsmasq or to the
gateway leaves no line. Neither does anything squid or the host opens itself.
- **The log line.** The kernel log carries one line per new flow, with the
prefix `hive-egress-would-drop ` followed by the usual netfilter fields
(`IN`, `OUT`, `SRC`, `DST`, `PROTO` and the ports). Read them with
`journalctl -k -g hive-egress-would-drop`.
- **Rate limit.** Each source address gets a burst of 20 lines, then one line a
second, and the chain forgets it after a minute without a new flow. A flood
from one agent can't hide another's flows, and it adds about 250 bytes of
journal a second per source address at most. A flow the per-source limit
can't log (the address is over its rate, or the table of addresses is full)
gets one line with the prefix `hive-egress-would-drop-unmetered ` instead,
from a limit shared by all addresses: 5 lines a second after a burst of 50,
about 100 MB of journal a day at most. A flow never gets both lines. The
counter in `nft list chain inet hive_egress forward` counts every packet,
logged or not.
- **Which agent.** `SRC` is the agent's address on the bridge. Containers get
theirs from the hive's dnsmasq, which records each lease in
`/var/lib/dnsmasq/dnsmasq.leases`, one line per lease:
`<expiry> <mac> <ip> <hostname> <client-id>`. Look the `SRC` address up in the
third column; the hostname names the container. A lease lasts one hour, so
look an address up soon after the line appears.
- **A hint, not evidence.** Until anti-spoofing lands, a container that forges
its source address can mislead the log. It can file its flows under another
agent's address, use up that agent's burst, or fill the table of addresses so
that its own flows only reach the shared limit. It can also influence the
lease behind the address. Use the log to find out what agents send directly
now. Don't treat it as proof of which agent sent a flow, or as proof that a
flow is absent. Anti-spoofing has to land before the log serves as the soak
exit for enforcement.
Enforcement comes with later changes in the same series: the log only shows
what such a change would cut.
## Cross-references
- `docs/4-implementation/networking/gateway.md` — vhost map + the gateway's other duties