diff --git a/docs/networking/network.md b/docs/networking/network.md index 75a723f3..74ff49a9 100644 --- a/docs/networking/network.md +++ b/docs/networking/network.md @@ -260,7 +260,7 @@ host. It never decrypts TLS, so it holds no signing key. It caches nothing. - **Denied before any allow.** Destinations that resolve to loopback, link-local, the bridge subnet, a private range (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fc00::/7`), shared address space - (`100.64.0.0/10`), `0.0.0.0/8`, multicast or reserved space, and IP-literal + (`100.64.0.0/10`), `0.0.0.0/8`, `::`, multicast or reserved space, and IP-literal hosts, for every user including the unrestricted ones and for listed names. Squid resolves names on the host and connects from it, so without these a name pointing at `127.0.0.1`, at the host's own LAN address or at a private diff --git a/nix/host-modules/hive-squid.nix b/nix/host-modules/hive-squid.nix index fa53d1bb..7f1e107e 100644 --- a/nix/host-modules/hive-squid.nix +++ b/nix/host-modules/hive-squid.nix @@ -35,6 +35,10 @@ let # bridge, and the private, shared (CGNAT), unspecified, multicast and # reserved ranges. Squid checks the address it connects to, so a name an # agent controls cannot point past them. + # `::` is listed because a connect to it lands on loopback; squid's + # built-in `to_localhost` has `0.0.0.0/32` and `::1` but not `::`. NAT64 + # (`64:ff9b::/96`) and 6to4 (`2002::/16`) embed IPv4 addresses and are left + # out: they matter only on a host with such a route. privateRanges = [ "10.0.0.0/8" "172.16.0.0/12" @@ -45,6 +49,7 @@ let "240.0.0.0/4" "fc00::/7" "ff00::/8" + "::/128" ]; # Evaluated top to bottom, first match wins. The destination denies sit @@ -206,7 +211,7 @@ in for an unrestricted user. The self-check canary is allowed on top of this list. A name must resolve, on this host's resolver, to a public address: one in a loopback, link-local, private, shared (100.64.0.0/10), - multicast or reserved range, or in the bridge subnet, gets a 403 for + multicast or reserved range (or is `::`), or in the bridge subnet, gets a 403 for every user. ''; }; diff --git a/nix/module-eval/hive-otel.nix b/nix/module-eval/hive-otel.nix index 0e94ae03..94294594 100644 --- a/nix/module-eval/hive-otel.nix +++ b/nix/module-eval/hive-otel.nix @@ -550,6 +550,7 @@ let "240.0.0.0/4" "fc00::/7" "ff00::/8" + "::/128" ]; } { diff --git a/nix/vm-tests/hive-squid.nix b/nix/vm-tests/hive-squid.nix index 5e1fcb03..36f0283e 100644 --- a/nix/vm-tests/hive-squid.nix +++ b/nix/vm-tests/hive-squid.nix @@ -120,9 +120,10 @@ pkgs.testers.runNixOSTest { imports = [ hiveStubs ../host-modules/hive-squid.nix - # Listeners a leaked destination would reach: loopback and the bridge. + # Listeners a leaked destination would reach: loopback (`::` lands on `::1`) and the bridge. (listen443 [ "127.0.0.1" + "[::1]" bridgeIp ]) ]; @@ -157,6 +158,7 @@ pkgs.testers.runNixOSTest { ${bridgeIp} = [ "bridge.t.local" ]; "169.254.169.254" = [ "meta.t.local" ]; "fd00:99::2" = [ "ula.t.local" ]; + "::" = [ "any6.t.local" ]; } // lib.listToAttrs ( map (p: { @@ -173,6 +175,7 @@ pkgs.testers.runNixOSTest { "bridge.t.local" "meta.t.local" "ula.t.local" + "any6.t.local" ] ++ map (p: p.name) privateNets; }; @@ -242,7 +245,7 @@ pkgs.testers.runNixOSTest { web.wait_for_unit("nginx.service") proxy.wait_for_open_port(3128, "${bridgeIp}") - PRIVATE = ["priv10.t.local", "priv172.t.local", "priv192.t.local", "cgnat.t.local", "ula.t.local"] + PRIVATE = ["priv10.t.local", "priv172.t.local", "priv192.t.local", "cgnat.t.local", "ula.t.local", "any6.t.local"] def connect(host, cred=None): # The status squid answered the CONNECT with; 000 when none came back.