Watch
0
0
Fork
You've already forked hyperhive
0

refuse tls hellos that carry encrypted client hello before squid sees them

This commit is contained in:
atlas 2026-11-13 06:23:42 +01:00 • committed by mara
commit dff662c6b3

View file

@ -88,7 +88,10 @@ let
# Every hive defaults the agents' queue address to the queue's name, so the
# only hive without one is a hive told to have none. Also the host still
# setting the four removed host options; no case on its queue reads them.
# It is also the one controller host with `egress.intercept.refuseEch` off,
# which the egress cases read.
noAgentQueue = hiveWithController {
network.egress.intercept.refuseEch = false;
deploy.hive-controller.queue.agentNatsUrl = null;
c0re.operatorPronouns = "they/them";
c0re.contextWindowTokens.sonnet = 900000;
@ -1700,7 +1703,9 @@ let
lib.splitString "table bridge hive_egress {" bare.services.hyperhive.network.egress.ruleset
);
chain = lib.head (
lib.splitString "set log_v4" (lib.last (lib.splitString "chain intercept {" inet))
lib.splitString "chain intercept_relay {" (
lib.head (lib.splitString "set log_v4" (lib.last (lib.splitString "chain intercept {" inet)))
)
);
rules = lib.filter (l: l != "" && l != "}") (map lib.trim (lib.splitString "\n" chain));
idx = pat: lib.lists.findFirstIndex (lib.hasInfix pat) 1000 rules;
@ -1714,12 +1719,72 @@ let
&& idx ''fib daddr oifname { "hive-br0", "wg-hive" } return'' < idx "tcp dport 80"
&& lib.length redirects == 2
&& lib.hasInfix "tcp dport 80 counter redirect to :3129" (lib.head redirects)
&& lib.hasInfix "tcp dport 443 counter redirect to :3130" (lib.last redirects)
# Port 443 goes to the ClientHello relay, not to squid.
&& lib.hasInfix "tcp dport 443 counter redirect to :3131" (lib.last redirects)
# Only IPv4 that comes in on the bridge is looked at.
&& lib.all (l: lib.hasPrefix ''iifname "hive-br0" meta nfproto ipv4 '' l) (lib.drop 1 rules)
&& lib.hasInfix "set direct_mac { type ether_addr; }" without
&& !(lib.hasInfix "chain intercept" without);
}
{
# With the relay on, port 443 reaches squid through `hive-priv
# egress-ech-relay`: its connections (from the pool, carrying the mark)
# are DNATed to squid's HTTPS port, the bridge may not send from the
# pool, and the unit routes the pool to `lo`. `noAgentQueue` has the
# relay off: the redirect then goes straight to squid and none of the
# rest is rendered. Its intercept chain is the presence control.
name = "the ClientHello relay sits between the redirect and squid, and refuseEch off removes it";
ok =
let
ruleset = m: m.services.hyperhive.network.egress.ruleset;
unit = bare.systemd.services.hive-egress-ech-relay;
off = noAgentQueue;
in
lib.hasInfix "chain intercept_relay {" (ruleset bare)
&& lib.hasInfix "type nat hook output priority dstnat; policy accept;" (ruleset bare)
&& lib.hasInfix "meta nfproto ipv4 ip saddr 198.18.0.0/15 meta mark 0x45434800 tcp dport 443 counter dnat ip to 10.42.0.1:3130" (
ruleset bare
)
&& lib.hasInfix ''iifname "hive-br0" ip saddr 198.18.0.0/15 counter drop'' (ruleset bare)
&& lib.hasInfix "egress-ech-relay 10.42.0.1:3131 0x45434800" unit.serviceConfig.ExecStart
&& lib.hasInfix "ip route replace local 198.18.0.0/15 dev lo" unit.serviceConfig.ExecStartPre
&& lib.elem "multi-user.target" unit.wantedBy
# The relay reads the include in which squid finds the `log` agents.
&&
(unit.environment.HIVE_EGRESS_AGENT_ALLOW or null)
== bare.services.hyperhive.deploy.hive-controller.squid.agentAllowFile
&& unit.serviceConfig.SupplementaryGroups == [ "squid" ]
&& lib.elem 3131 bare.services.hyperhive.network.exposeHostPorts
&& lib.hasInfix "chain intercept {" (ruleset off)
&& lib.hasInfix "tcp dport 443 counter redirect to :3130" (ruleset off)
&& lib.elem 3130 off.services.hyperhive.network.exposeHostPorts
&& !(lib.hasInfix "intercept_relay" (ruleset off))
&& !(lib.hasInfix "198.18.0.0/15" (ruleset off))
&& !(off.systemd.services ? hive-egress-ech-relay)
&& !(lib.elem 3131 off.services.hyperhive.network.exposeHostPorts);
}
{
# The relay names a flow's source with the low 17 bits of the agent's
# address, which is unique only in a subnet of /15 or smaller. The
# boundary is read at /15 (accepted) and /14 (refused); `bare`, a /24, is
# the control that the lookup finds nothing otherwise.
name = "the ClientHello relay is refused on a bridge subnet larger than a /15";
ok =
let
refused =
m:
lib.any (
a: !a.assertion && lib.hasInfix "bridgePrefixLength must be 15 or more" a.message
) m.assertions;
in
refused (hiveWithController {
network.bridgePrefixLength = 14;
})
&& !(refused (hiveWithController {
network.bridgePrefixLength = 15;
}))
&& !(refused bare);
}
{
# Squid listens on the two intercept ports, peeks and splices and never
# decrypts, names the agent of a flow with the helper, refuses a source