deny private and reserved destinations for every squid user
This commit is contained in:
parent
501445ebc3
commit
3d9561a9a6
1 changed files with 167 additions and 37 deletions
|
|
@ -66,6 +66,38 @@ let
|
|||
config.services.hyperhive.network.exposeHostPorts;
|
||||
};
|
||||
|
||||
# The uplink is a public range. The private ranges squid must refuse are
|
||||
# extra subnets on the same link, so the proxy can reach a listener in each:
|
||||
# a name that resolves there is refused only by squid's own rules.
|
||||
privateNets = [
|
||||
{
|
||||
name = "priv10.t.local";
|
||||
net = "10.99.0";
|
||||
}
|
||||
{
|
||||
name = "priv172.t.local";
|
||||
net = "172.16.99";
|
||||
}
|
||||
{
|
||||
name = "priv192.t.local";
|
||||
net = "192.168.99";
|
||||
}
|
||||
{
|
||||
name = "cgnat.t.local";
|
||||
net = "100.64.99";
|
||||
}
|
||||
];
|
||||
privateAddrs =
|
||||
host:
|
||||
map (p: {
|
||||
address = "${p.net}.${toString host}";
|
||||
prefixLength = 24;
|
||||
}) privateNets;
|
||||
ulaAddr = host: {
|
||||
address = "fd00:99::${toString host}";
|
||||
prefixLength = 64;
|
||||
};
|
||||
|
||||
# Plain TCP on 443: a CONNECT is answered before any TLS, which is all the
|
||||
# test reads.
|
||||
listen443 = addrs: {
|
||||
|
|
@ -104,23 +136,34 @@ pkgs.testers.runNixOSTest {
|
|||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
networking.interfaces.eth2.ipv4.addresses = lib.mkForce [
|
||||
{
|
||||
address = "192.168.2.1";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
networking.interfaces.eth2.ipv4.addresses = lib.mkForce (
|
||||
[
|
||||
{
|
||||
address = "203.0.113.1";
|
||||
prefixLength = 24;
|
||||
}
|
||||
]
|
||||
++ privateAddrs 1
|
||||
);
|
||||
networking.interfaces.eth2.ipv6.addresses = [ (ulaAddr 1) ];
|
||||
# Names the proxy resolves, each a different destination class.
|
||||
networking.hosts = {
|
||||
# `other.t.local` resolves, is reachable and is on no allow-list.
|
||||
"192.168.2.2" = [
|
||||
"203.0.113.2" = [
|
||||
"forge.t.local"
|
||||
"other.t.local"
|
||||
];
|
||||
"127.0.0.1" = [ "loop.t.local" ];
|
||||
${bridgeIp} = [ "bridge.t.local" ];
|
||||
"169.254.169.254" = [ "meta.t.local" ];
|
||||
};
|
||||
"fd00:99::2" = [ "ula.t.local" ];
|
||||
}
|
||||
// lib.listToAttrs (
|
||||
map (p: {
|
||||
name = "${p.net}.2";
|
||||
value = [ p.name ];
|
||||
}) privateNets
|
||||
);
|
||||
# Renders the users file the way hive-c0re does.
|
||||
environment.systemPackages = [ pkgs.mkpasswd ];
|
||||
services.hyperhive.squid.selfcheck.unrestrictedCanary = "other.t.local";
|
||||
|
|
@ -129,20 +172,31 @@ pkgs.testers.runNixOSTest {
|
|||
"loop.t.local"
|
||||
"bridge.t.local"
|
||||
"meta.t.local"
|
||||
];
|
||||
"ula.t.local"
|
||||
]
|
||||
++ map (p: p.name) privateNets;
|
||||
};
|
||||
|
||||
web =
|
||||
{ lib, ... }:
|
||||
{
|
||||
imports = [ (listen443 [ "0.0.0.0" ]) ];
|
||||
virtualisation.vlans = [ 2 ];
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
|
||||
{
|
||||
address = "192.168.2.2";
|
||||
prefixLength = 24;
|
||||
}
|
||||
imports = [
|
||||
(listen443 [
|
||||
"0.0.0.0"
|
||||
"[::]"
|
||||
])
|
||||
];
|
||||
virtualisation.vlans = [ 2 ];
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce (
|
||||
[
|
||||
{
|
||||
address = "203.0.113.2";
|
||||
prefixLength = 24;
|
||||
}
|
||||
]
|
||||
++ privateAddrs 2
|
||||
);
|
||||
networking.interfaces.eth1.ipv6.addresses = [ (ulaAddr 2) ];
|
||||
networking.firewall.allowedTCPPorts = [ 443 ];
|
||||
};
|
||||
|
||||
|
|
@ -188,6 +242,8 @@ 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"]
|
||||
|
||||
def connect(host, cred=None):
|
||||
# The status squid answered the CONNECT with; 000 when none came back.
|
||||
auth = f"-U '{cred}' " if cred else ""
|
||||
|
|
@ -228,6 +284,11 @@ pkgs.testers.runNixOSTest {
|
|||
time.sleep(1)
|
||||
raise AssertionError(f"{host} as {cred.split(':')[0]}: wanted {want}, last got {got}")
|
||||
|
||||
with subtest("the private listeners answer the proxy itself, so a refusal below is squid's"):
|
||||
for addr in ["10.99.0.2", "172.16.99.2", "192.168.99.2", "100.64.99.2", "fd00:99::2"]:
|
||||
proxy.wait_until_succeeds(f"timeout 3 bash -c 'echo > /dev/tcp/{addr}/443'")
|
||||
assert connect("forge.t.local", None) == "407", "control: the public name is challenged, not refused"
|
||||
|
||||
with subtest("squid starts on the empty users file and list and refuses every login"):
|
||||
assert proxy.succeed(f"stat -c '%U:%G %a' {USERS}").strip() == "hive-core:squid 640"
|
||||
assert proxy.succeed(f"stat -c '%U:%G %a' {LIST}").strip() == "hive-core:squid 640"
|
||||
|
|
@ -270,14 +331,16 @@ pkgs.testers.runNixOSTest {
|
|||
assert connect("other.t.local", "uma:" + secrets.token_urlsafe(24)) == "407", "wrong password"
|
||||
assert connect("deny-canary.invalid", ATLAS) == "403", "restricted, a name that does not resolve"
|
||||
|
||||
with subtest("neither kind of user reaches a host-local destination or another port"):
|
||||
with subtest("neither kind of user reaches a host-local or private destination, or another port"):
|
||||
for cred in (ATLAS, UMA):
|
||||
who = cred.split(":")[0]
|
||||
assert connect("loop.t.local", cred) == "403", f"{who}: loopback"
|
||||
assert connect("bridge.t.local", cred) == "403", f"{who}: bridge address"
|
||||
assert connect("meta.t.local", cred) == "403", f"{who}: link-local"
|
||||
assert connect("127.0.0.1", cred) == "403", f"{who}: loopback literal"
|
||||
assert connect("192.168.2.2", cred) == "403", f"{who}: IP literal"
|
||||
assert connect("203.0.113.2", cred) == "403", f"{who}: IP literal"
|
||||
for name in PRIVATE:
|
||||
assert connect(name, cred) == "403", f"{who}: {name}, a listed name that resolves to a private address"
|
||||
assert connect("other.t.local:22", cred) == "403", f"{who}: port 22"
|
||||
assert connect("other.t.local:80", cred) == "403", f"{who}: CONNECT to port 80"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue