squid intercepts restricted and log agents' web traffic, named from the bound source address
This commit is contained in:
parent
03b9c39498
commit
712dcf2b25
2 changed files with 1005 additions and 154 deletions
|
|
@ -1686,6 +1686,104 @@ let
|
|||
in
|
||||
refused egressForwardDrop && !(refused bare) && !(refused egressForwardAccept);
|
||||
}
|
||||
{
|
||||
# The redirect of the agents' web traffic: IPv4 TCP 80 and 443 from the
|
||||
# bridge go to squid's two intercept ports, after the rules that leave
|
||||
# alone the exempt MACs (service containers, unrestricted agents), the
|
||||
# host's own addresses, the bridge and the mesh. The chain is read as the
|
||||
# lines between its header and the log sets that follow it. The host
|
||||
# without a controller (`egressForwardAccept`) has the set but no chain.
|
||||
name = "the egress table redirects the bridge's web traffic into squid after its exemptions";
|
||||
ok =
|
||||
let
|
||||
inet = lib.head (
|
||||
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))
|
||||
);
|
||||
rules = lib.filter (l: l != "" && l != "}") (map lib.trim (lib.splitString "\n" chain));
|
||||
idx = pat: lib.lists.findFirstIndex (lib.hasInfix pat) 1000 rules;
|
||||
redirects = lib.filter (lib.hasInfix " redirect to ") rules;
|
||||
without = egressForwardAccept.services.hyperhive.network.egress.ruleset;
|
||||
in
|
||||
lib.hasInfix "type nat hook prerouting priority dstnat; policy accept;" chain
|
||||
&& lib.hasInfix "set direct_mac { type ether_addr; }" inet
|
||||
&& idx "ether saddr @direct_mac return" < idx "tcp dport 80"
|
||||
&& idx "fib daddr type local return" < idx "tcp dport 80"
|
||||
&& 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)
|
||||
# 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);
|
||||
}
|
||||
{
|
||||
# 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
|
||||
# it cannot name before any other rule, lets the CONNECT it builds
|
||||
# before the ClientHello through only after the address denies, and
|
||||
# leaves the proxy credential rules to the proxy port.
|
||||
name = "squid's intercept ports splice, name the agent from the bindings and keep the credential rules for the proxy port";
|
||||
ok =
|
||||
let
|
||||
conf = bare.services.squid.configText;
|
||||
lines = map lib.trim (lib.splitString "\n" conf);
|
||||
idx = line: lib.lists.findFirstIndex (l: l == line) 1000 lines;
|
||||
squidCfg = bare.services.hyperhive.deploy.hive-controller.squid;
|
||||
in
|
||||
lib.hasInfix "http_port 10.42.0.1:3129 intercept name=icpt_http" conf
|
||||
&& lib.hasInfix "https_port 10.42.0.1:3130 intercept ssl-bump name=icpt_tls " conf
|
||||
&& lib.hasInfix "generate-host-certificates=off" conf
|
||||
&& lib.elem "ssl_bump peek first_hop" lines
|
||||
&& lib.elem "ssl_bump splice all" lines
|
||||
&& !(lib.hasInfix "ssl_bump bump" conf)
|
||||
&& !(lib.hasInfix "ssl_bump stare" conf)
|
||||
&& !(lib.hasInfix "sslcrtd_program" conf)
|
||||
&& lib.elem "host_verify_strict on" lines
|
||||
&& lib.hasInfix "%SRC /run/hive-rootfs-check egress-whois" conf
|
||||
&& idx "http_access deny icpt !icpt_agent" < idx "http_access deny ip_v4_host"
|
||||
&& idx "http_access deny icpt !icpt_agent" < idx "include ${squidCfg.denyFile}"
|
||||
&&
|
||||
idx "http_access deny icpt !icpt_agent"
|
||||
< idx "http_access allow icpt first_hop CONNECT !to_localhost !to_linklocal !to_bridge !to_private"
|
||||
&&
|
||||
idx "http_access allow icpt first_hop CONNECT !to_localhost !to_linklocal !to_bridge !to_private"
|
||||
< idx "http_access deny ip_v4_host"
|
||||
&& lib.elem "http_access deny !icpt !authed" lines
|
||||
&& !(lib.elem "http_access deny !authed" lines)
|
||||
&& lib.elem "http_access allow !icpt unrestricted" lines
|
||||
&& !(lib.elem "http_access allow unrestricted" lines)
|
||||
&& lib.all (p: lib.elem p bare.services.hyperhive.network.exposeHostPorts) [
|
||||
3128
|
||||
3129
|
||||
3130
|
||||
];
|
||||
}
|
||||
{
|
||||
# `hive-priv` fills `direct_mac` from the same bindings as the bridge
|
||||
# table's, so the units that run it are told the list of unrestricted
|
||||
# agents, and a change of the list runs a pass. A host without squid has
|
||||
# neither.
|
||||
name = "the units that fill the egress exemptions read squid's unrestricted list and a change of it runs a pass";
|
||||
ok =
|
||||
let
|
||||
list = bare.services.hyperhive.deploy.hive-controller.squid.unrestrictedFile;
|
||||
envOf = m: name: m.systemd.services.${name}.environment;
|
||||
in
|
||||
list == "/var/lib/hive-squid/unrestricted"
|
||||
&& lib.all (name: ((envOf bare name).HIVE_EGRESS_UNRESTRICTED or null) == list) [
|
||||
"hive-egress"
|
||||
"hive-egress-pin@"
|
||||
"hive-egress-pin"
|
||||
]
|
||||
&& bare.systemd.paths.hive-egress-direct.pathConfig.PathChanged == list
|
||||
&& bare.systemd.paths.hive-egress-direct.pathConfig.Unit == "hive-egress-pin.service"
|
||||
&& !((envOf egressForwardAccept "hive-egress") ? HIVE_EGRESS_UNRESTRICTED)
|
||||
&& !(egressForwardAccept.systemd.paths ? hive-egress-direct);
|
||||
}
|
||||
{
|
||||
# The host that turns `route_localnet` on is refused by an assertion
|
||||
# naming it; the ordinary host, which leaves it alone, is not.
|
||||
|
|
@ -2189,7 +2287,7 @@ let
|
|||
"http_access deny to_linklocal"
|
||||
"http_access deny to_bridge"
|
||||
"http_access deny to_private"
|
||||
"http_access deny !authed"
|
||||
"http_access deny !icpt !authed"
|
||||
];
|
||||
in
|
||||
e.HIVE_SQUID_PROXY == "${withIdentity.services.hyperhive.network.bridgeIp}:3128"
|
||||
|
|
|
|||
|
|
@ -513,17 +513,24 @@ let
|
|||
}
|
||||
{
|
||||
# Control for the absence checks: the bridge listener is there, so the
|
||||
# absences below are not a misspelled lookup.
|
||||
name = "squid listens on the bridge IP only, without a second listener or ssl_bump";
|
||||
# absences below are not a misspelled lookup. The two intercept ports are
|
||||
# on the bridge IP too, and the only TLS directives are the ones that
|
||||
# peek and splice.
|
||||
name = "squid listens on the bridge IP only, with one proxy port, two intercept ports and no decryption";
|
||||
ok =
|
||||
let
|
||||
conf = hiveOtel.services.squid.configText;
|
||||
lines = lib.splitString "\n" conf;
|
||||
ip = hiveOtel.services.hyperhive.network.bridgeIp;
|
||||
listeners = lib.filter (l: lib.hasPrefix "http_port " l || lib.hasPrefix "https_port " l) lines;
|
||||
in
|
||||
builtins.elem "http_port ${hiveOtel.services.hyperhive.network.bridgeIp}:3128" lines
|
||||
&& lib.length (lib.filter (l: lib.hasPrefix "http_port " l) lines) == 1
|
||||
&& !(lib.hasInfix "ssl_bump" conf)
|
||||
&& !(lib.hasInfix "ssl-bump" conf)
|
||||
builtins.elem "http_port ${ip}:3128" lines
|
||||
&& lib.length listeners == 3
|
||||
&& lib.all (l: lib.hasPrefix "${lib.head (lib.splitString " " l)} ${ip}:" l) listeners
|
||||
&& lib.length (lib.filter (lib.hasInfix " intercept ") listeners) == 2
|
||||
&& lib.elem "ssl_bump peek first_hop" lines
|
||||
&& lib.elem "ssl_bump splice all" lines
|
||||
&& lib.length (lib.filter (lib.hasPrefix "ssl_bump ") lines) == 2
|
||||
&& !(lib.hasInfix "localnet" conf);
|
||||
}
|
||||
{
|
||||
|
|
@ -540,8 +547,8 @@ let
|
|||
at = l: lib.lists.findFirstIndex (x: x == l) null lines;
|
||||
allow = at "http_access allow allowed_dst";
|
||||
base = at "http_access allow base_dst";
|
||||
unrestricted = at "http_access allow unrestricted";
|
||||
authed = at "http_access deny !authed";
|
||||
unrestricted = at "http_access allow !icpt unrestricted";
|
||||
authed = at "http_access deny !icpt !authed";
|
||||
# The generated per-agent `http_access allow` rules sit in this file.
|
||||
agentRules = at "include ${hiveOtel.services.hyperhive.deploy.hive-controller.squid.agentAllowFile}";
|
||||
firstAllow = lib.min agentRules (lib.min base (lib.min unrestricted allow));
|
||||
|
|
@ -631,11 +638,11 @@ let
|
|||
at = l: lib.lists.findFirstIndex (x: x == l) null lines;
|
||||
list = hiveOtel.services.hyperhive.deploy.hive-controller.squid.unrestrictedFile;
|
||||
in
|
||||
at "http_access allow unrestricted" != null
|
||||
at "http_access allow !icpt unrestricted" != null
|
||||
&& at "http_access deny !allowed_dst" != null
|
||||
&& at "http_access deny !authed" < at "http_access allow base_dst"
|
||||
&& at "http_access allow base_dst" < at "http_access allow unrestricted"
|
||||
&& at "http_access allow unrestricted" < at "http_access deny !allowed_dst"
|
||||
&& at "http_access deny !icpt !authed" < at "http_access allow base_dst"
|
||||
&& at "http_access allow base_dst" < at "http_access allow !icpt unrestricted"
|
||||
&& at "http_access allow !icpt unrestricted" < at "http_access deny !allowed_dst"
|
||||
&& at "http_access deny !allowed_dst" < at "http_access allow allowed_dst"
|
||||
&& builtins.elem ''acl unrestricted proxy_auth "${list}"'' lines
|
||||
&& list == "/var/lib/hive-squid/unrestricted";
|
||||
|
|
@ -655,7 +662,7 @@ let
|
|||
allows = map at [
|
||||
"http_access allow base_dst"
|
||||
"include ${sq.agentAllowFile}"
|
||||
"http_access allow unrestricted"
|
||||
"http_access allow !icpt unrestricted"
|
||||
"http_access allow allowed_dst"
|
||||
];
|
||||
in
|
||||
|
|
@ -665,7 +672,7 @@ let
|
|||
&& lib.all (a: deny < a) allows
|
||||
&& at "http_access deny to_private" < deny
|
||||
&& at "http_access deny ip_v4_host" < deny
|
||||
&& deny < at "http_access deny !authed"
|
||||
&& deny < at "http_access deny !icpt !authed"
|
||||
# Two includes: this one and the per-agent allow rules.
|
||||
&& lib.length (lib.filter (l: lib.hasPrefix "include " l) lines) == 2;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue