Compare commits

...
Author SHA1 Message Date
atlas
bb21ae6d7e 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.
2026-08-17 17:05:18 +02:00
atlas
2b21bedaa3 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.
2026-08-17 16:33:22 +02:00
2 changed files with 46 additions and 2 deletions

View file

@ -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

View file

@ -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"
];
};