From 2b21bedaa348c401aa6b937f93ddbf4dc2032af8 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 17 Aug 2026 16:33:22 +0200 Subject: [PATCH 1/2] fix(swarm-controller): permit the socket families the daemon opens The unit restricted RestrictAddressFamilies to AF_UNIX, which was correct while the daemon only served its unix socket. It has since grown three outbound clients -- authelia token minting and forge calls over HTTPS, and the queue over NATS -- and every socket(AF_INET, ...) was refused by seccomp. systemd surfaces that refusal as EAFNOSUPPORT, "Address family not supported by protocol", so the failure names the protocol and never the sandbox. The visible symptom was swarm agent creation failing while minting a bearer token, with a connect error that reads like a network fault. Permit AF_INET/AF_INET6 for those clients and AF_NETLINK, which glibc's getaddrinfo needs to enumerate local addresses before returning one. The rest of the unit's hardening is unchanged. --- nix/host-modules/swarm-controller.nix | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/nix/host-modules/swarm-controller.nix b/nix/host-modules/swarm-controller.nix index 90c2ca67..9c8b1fca 100644 --- a/nix/host-modules/swarm-controller.nix +++ b/nix/host-modules/swarm-controller.nix @@ -508,8 +508,7 @@ in StateDirectoryMode = "0750"; # Nothing here needs a writable filesystem, real privileges, or a - # view of the rest of the machine; the daemon reads its socket path - # from config and serves. + # view of the rest of the machine. PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; @@ -518,8 +517,27 @@ in ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; + + # `AF_UNIX` for the socket this daemon serves on, plus what its + # outbound clients need: it mints authelia tokens and calls the forge + # over HTTPS (`auth.rs`, `forge.rs`) and reaches the queue over NATS + # (`main.rs`, `status.rs`). `AF_NETLINK` because glibc's + # `getaddrinfo` opens a netlink socket to enumerate local addresses + # before it will return one. + # + # ⚠️ This list is a CLAIM ABOUT WHAT THE DAEMON DOES, so it goes stale + # the moment the daemon grows a client — and it goes stale in the + # worst available way: a blocked family makes `socket()` return + # EAFNOSUPPORT, i.e. "Address family not supported by protocol", so + # the error names the protocol and never the sandbox that refused it. + # This was `AF_UNIX`-only while the daemon merely served its socket; + # all three clients above arrived later, and the restriction was not + # revisited. Add the family when you add the client. RestrictAddressFamilies = [ "AF_UNIX" + "AF_INET" + "AF_INET6" + "AF_NETLINK" ]; }; From bb21ae6d7e7cfd01f049c4f03846ce3eb476f518 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 17 Aug 2026 17:05:18 +0200 Subject: [PATCH 2/2] docs(gotchas): record how a too-narrow RestrictAddressFamilies presents The error names the protocol and never the sandbox, so it reads like a network fault. Records the two easy-to-miss families (AF_INET6 alongside AF_INET, and AF_NETLINK for getaddrinfo) and the reason the directive rots: it is a claim about what the program does, and nothing re-checks it when a client is added. --- docs/gotchas.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/gotchas.md b/docs/gotchas.md index c50421be..4c106d3d 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -72,6 +72,32 @@ the path entry resolves to `/run/wrappers/bin/bin` instead. hive-c0re restarts. Without it, every restart wipes bind sources and existing containers can't be started. +### `RestrictAddressFamilies` fails as "Address family not supported by protocol" + +A unit whose `RestrictAddressFamilies` omits a family gets `EAFNOSUPPORT` +(errno 97) back from `socket()`. Clients surface that as *"tcp open error: +Address family not supported by protocol"* — the message names the +**protocol** and never the **sandbox**, so it reads like a dead network, a +missing route, or an IPv6 problem. + +⇒ On that error, read the unit before you touch the network. + +Two things to get right when a daemon needs outbound TCP: + +- list `AF_INET` **and** `AF_INET6` — omitting one leaves a client that + works until DNS hands back the other family; +- list `AF_NETLINK` too. glibc's `getaddrinfo` opens a netlink socket to + enumerate local addresses before it returns any, so name resolution + fails without it even when `AF_INET` is allowed. + +**The directive is a claim about what the program does, and nothing +re-checks it when the program changes.** A unit that only served a unix +socket when it was written is correct at `[ "AF_UNIX" ]` and silently wrong +the day someone adds an HTTP client. Check the unit in the same commit as +the client — and when narrowing it, prefer a test that derives the required +families from the code (which fails on the *next* client too) over one that +asserts today's list. + ### `register_agent` is idempotent Drops any prior socket task before rebinding. Required so a