squid intercepts restricted and log agents' web traffic, named from the bound source address
This commit is contained in:
parent
03b9c39498
commit
712dcf2b25
1 changed files with 1005 additions and 154 deletions
|
|
@ -5,13 +5,18 @@
|
|||
# to hold on every host lives here, in tables no firewall backend writes. Only
|
||||
# `networking.nftables.flushRuleset` removes them; the unit re-applies them then.
|
||||
#
|
||||
# The `inet` table holds two chains. `input`: traffic from the agent bridge to host
|
||||
# The `inet` table holds three chains. `input`: traffic from the agent bridge to host
|
||||
# loopback is dropped. The kernel does the same by default (a packet for
|
||||
# 127.0.0.0/8 arriving on a non-loopback interface is a martian while
|
||||
# `route_localnet` is 0); this rule is the second layer, and the assertion
|
||||
# below keeps the first one on. `forward` (`egress.forward = "log"`): traffic
|
||||
# the host routes from the agent bridge to anywhere else is logged and
|
||||
# counted, never blocked.
|
||||
# counted, never blocked. `intercept` (`egress.intercept.enable`, set by
|
||||
# hive-squid.nix): the web traffic of the bridge's agents is redirected into
|
||||
# squid's intercept ports. The traffic of the ports in the set `direct_mac` is
|
||||
# left alone: the service containers and the unrestricted agents. `hive-priv
|
||||
# egress-pin` and `egress-load` fill that set from the bindings the bridge
|
||||
# table uses, so a MAC in no binding is intercepted, and squid refuses the flow.
|
||||
#
|
||||
# The `bridge` table checks the source address of every frame a port of the
|
||||
# agent bridge sends (`egress.antiSpoof`). It is a `bridge`-family table because
|
||||
|
|
@ -48,7 +53,12 @@ let
|
|||
pkgs.iproute2
|
||||
pkgs.util-linux
|
||||
];
|
||||
pinEnvironment.HIVE_EGRESS_BRIDGE = br;
|
||||
pinEnvironment = {
|
||||
HIVE_EGRESS_BRIDGE = br;
|
||||
}
|
||||
// lib.optionalAttrs (cfg.egress.intercept.unrestrictedFile != null) {
|
||||
HIVE_EGRESS_UNRESTRICTED = toString cfg.egress.intercept.unrestrictedFile;
|
||||
};
|
||||
leaseScript = pkgs.writeShellScript "hive-egress-lease" ''
|
||||
export PATH=${lib.makeBinPath [ pkgs.nftables ]}
|
||||
exec ${hivePriv} egress-lease "$@"
|
||||
|
|
@ -71,6 +81,24 @@ let
|
|||
family: addr:
|
||||
''${fromBridge} ct state new update @log_${family} { ${addr} saddr limit rate ${logRate} } log prefix "hive-egress-would-drop " accept'';
|
||||
|
||||
# IPv4 TCP from the agent bridge to ports 80 and 443. `fib daddr oifname` is
|
||||
# the interface the route to the destination leaves by: the bridge (another
|
||||
# container), `lo` (an address of the host: `fib daddr type local`) and the
|
||||
# mesh interface are not egress. The redirect rewrites the destination to
|
||||
# the bridge address and the port of squid's intercept listener, which reads
|
||||
# the original destination from conntrack. IPv6 is left alone: the agent
|
||||
# bridge has no IPv6 addressing.
|
||||
interceptChain = ''
|
||||
chain intercept {
|
||||
type nat hook prerouting priority dstnat; policy accept;
|
||||
iifname "${br}" meta nfproto ipv4 ether saddr @direct_mac return
|
||||
iifname "${br}" meta nfproto ipv4 fib daddr type local return
|
||||
iifname "${br}" meta nfproto ipv4 fib daddr oifname { "${br}", "${meshInterface}" } return
|
||||
iifname "${br}" meta nfproto ipv4 tcp dport 80 counter redirect to :${toString cfg.egress.intercept.httpPort}
|
||||
iifname "${br}" meta nfproto ipv4 tcp dport 443 counter redirect to :${toString cfg.egress.intercept.httpsPort}
|
||||
}
|
||||
'';
|
||||
|
||||
# Priority `filter - 10` runs before the NixOS firewall's forward handling,
|
||||
# which sits at `filter` on both backends (`FORWARD` of the iptables backend,
|
||||
# the `forward` chain of `nixos-fw`). An `accept` here ends this chain only,
|
||||
|
|
@ -179,6 +207,8 @@ let
|
|||
iifname "${br}" ip daddr 127.0.0.0/8 counter drop
|
||||
iifname "${br}" ip6 daddr ::1 counter drop
|
||||
}
|
||||
set direct_mac { type ether_addr; }
|
||||
${lib.optionalString cfg.egress.intercept.enable interceptChain}
|
||||
${lib.optionalString (cfg.egress.forward == "log") forwardChain}
|
||||
}
|
||||
|
||||
|
|
@ -243,9 +273,48 @@ in
|
|||
address is never affected, so nothing legitimate breaks.
|
||||
- `log`: the same lines and counters, and the frame goes through. For
|
||||
finding out why a legitimate container is refused on a live hive.
|
||||
|
||||
With `log`, the identity squid takes from the source of an intercepted
|
||||
flow (`egress.intercept`) can be spoofed: use it to diagnose, not to run.
|
||||
'';
|
||||
};
|
||||
|
||||
options.services.hyperhive.network.egress.intercept = {
|
||||
enable = lib.mkEnableOption "the redirect of the agents' web traffic into squid's intercept ports";
|
||||
|
||||
httpPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 3129;
|
||||
description = ''
|
||||
The port on the bridge address where squid listens for the redirected
|
||||
TCP port 80 traffic.
|
||||
'';
|
||||
};
|
||||
|
||||
httpsPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 3130;
|
||||
description = ''
|
||||
The port on the bridge address where squid listens for the redirected
|
||||
TCP port 443 traffic.
|
||||
'';
|
||||
};
|
||||
|
||||
unrestrictedFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
The file hive-c0re renders with the names of the agents that hold the
|
||||
`unrestricted_internet` flag, one per line. The `direct_mac` set of the
|
||||
`inet hive_egress` table holds the MACs of the ports of the agents it
|
||||
lists and of every container that is not an agent, and the redirect
|
||||
skips them. `null` or an unreadable file lists no agent, so every agent's
|
||||
web traffic is redirected. The file is read when a port is bound, on
|
||||
each pass of the 30 second timer and when the file changes.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
options.services.hyperhive.network.egress.leaseScript = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
internal = true;
|
||||
|
|
@ -274,8 +343,9 @@ in
|
|||
assertion = cfg.egress.forward != "drop";
|
||||
message = ''
|
||||
services.hyperhive.network.egress.forward = "drop" is not available yet:
|
||||
the interception that lets an agent's web traffic through squid has not
|
||||
landed, so a drop would cut every direct connection with no replacement.
|
||||
the traffic squid does not handle (the unrestricted agents, the service
|
||||
containers, every port but 80 and 443) has no rule that lets it through, so a
|
||||
drop would cut it.
|
||||
Use "log" or "accept".
|
||||
'';
|
||||
}
|
||||
|
|
@ -361,6 +431,17 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
# A pass also refills `direct_mac`, so a change of the unrestricted list
|
||||
# takes effect at once instead of at the next tick of the timer.
|
||||
systemd.paths.hive-egress-direct = lib.mkIf (cfg.egress.intercept.unrestrictedFile != null) {
|
||||
description = "Watch the unrestricted agents list for the egress intercept exemptions";
|
||||
wantedBy = [ "paths.target" ];
|
||||
pathConfig = {
|
||||
PathChanged = toString cfg.egress.intercept.unrestrictedFile;
|
||||
Unit = "hive-egress-pin.service";
|
||||
};
|
||||
};
|
||||
|
||||
# nspawn names the host end of a container's veth `vb-<machine>` when the
|
||||
# container joins a bridge.
|
||||
services.udev.extraRules = ''
|
||||
|
|
|
|||
Loading…
Reference in a new issue