depot/third_party/nixpkgs/nixos/tests/squid.nix

188 lines
6.1 KiB
Nix

# This is a distributed test of the Squid as a forward proxy
# - "external" -- i.e. the internet, where the proxy and server communicate
# - "internal" -- i.e. an office LAN, where the client and proxy communicat
import ./make-test-python.nix (
{
pkgs,
lib,
...
}:
# VLANS:
# 1 -- simulates the internal network
# 2 -- simulates the external network
let
commonConfig = {
# Disable eth0 autoconfiguration
networking.useDHCP = false;
environment.systemPackages = [
(pkgs.writeScriptBin "check-connection" ''
#!/usr/bin/env bash
set -e
if [[ "$2" == "" || "$1" == "--help" || "$1" == "-h" ]];
then
echo "check-connection <target-address> <[expect-success|expect-failure]>"
exit 1
fi
ADDRESS="$1"
function test_icmp() { timeout 3 ping -c 1 "$ADDRESS"; }
if [[ "$2" == "expect-success" ]];
then
test_icmp
else
! test_icmp
fi
'')
];
};
in
{
name = "squid";
meta = with pkgs.lib.maintainers; {
maintainers = [ cobalt ];
};
nodes = {
client =
{ ... }:
lib.mkMerge [
commonConfig
{
virtualisation.vlans = [ 1 ];
networking.firewall.enable = true;
# NOTE: the client doesn't need a HTTP server, this is here to allow a validation of the proxy acl
networking.firewall.allowedTCPPorts = [ 80 ];
services.nginx = {
enable = true;
virtualHosts."server" = {
root = "/etc";
locations."/".index = "hostname";
listen = [
{
addr = "0.0.0.0";
port = 80;
}
];
};
};
}
];
proxy =
{ config, nodes, ... }:
let
clientIp = (pkgs.lib.head nodes.client.networking.interfaces.eth1.ipv4.addresses).address;
serverIp = (pkgs.lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address;
in
lib.mkMerge [
commonConfig
{
nixpkgs.config.permittedInsecurePackages = [ "squid-7.0.1" ];
virtualisation.vlans = [
1
2
];
networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [ config.services.squid.proxyPort ];
services.squid = {
enable = true;
extraConfig = ''
acl client src ${clientIp}
acl server dst ${serverIp}
http_access allow client server
http_access deny all
'';
};
}
];
server =
{ ... }:
lib.mkMerge [
commonConfig
{
virtualisation.vlans = [ 2 ];
networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [ 80 ];
services.nginx = {
enable = true;
virtualHosts."server" = {
root = "/etc";
locations."/".index = "hostname";
listen = [
{
addr = "0.0.0.0";
port = 80;
}
];
};
};
}
];
};
testScript =
{ nodes, ... }:
let
clientIp = (pkgs.lib.head nodes.client.networking.interfaces.eth1.ipv4.addresses).address;
serverIp = (pkgs.lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address;
proxyExternalIp = (pkgs.lib.head nodes.proxy.networking.interfaces.eth2.ipv4.addresses).address;
proxyInternalIp = (pkgs.lib.head nodes.proxy.networking.interfaces.eth1.ipv4.addresses).address;
in
''
client.start()
proxy.start()
server.start()
proxy.wait_for_unit("network.target")
proxy.wait_for_unit("squid.service")
client.wait_for_unit("network.target")
server.wait_for_unit("network.target")
server.wait_for_unit("nginx.service")
# Topology checks.
with subtest("proxy connectivity"):
## The proxy should have direct access to the server and client
proxy.succeed("check-connection ${serverIp} expect-success")
proxy.succeed("check-connection ${clientIp} expect-success")
with subtest("server connectivity"):
## The server should have direct access to the proxy
server.succeed("check-connection ${proxyExternalIp} expect-success")
## ... and not have access to the client
server.succeed("check-connection ${clientIp} expect-failure")
with subtest("client connectivity"):
# The client should be also able to connect to the proxy
client.succeed("check-connection ${proxyInternalIp} expect-success")
# but not the client to the server
client.succeed("check-connection ${serverIp} expect-failure")
with subtest("HTTP"):
# the client cannot reach the server directly over HTTP
client.fail('[[ `timeout 3 curl --fail-with-body http://${serverIp}` ]]')
# ... but can with the proxy
client.succeed('[[ `timeout 3 curl --fail-with-body --proxy http://${proxyInternalIp}:3128 http://${serverIp}` == "server" ]]')
# and cannot from the server (with a 4xx error code) and ...
server.fail('[[ `timeout 3 curl --fail-with-body --proxy http://${proxyExternalIp}:3128 http://${clientIp}` == "client" ]]')
# .. not the client hostname
server.fail('[[ `timeout 3 curl --proxy http://${proxyExternalIp}:3128 http://${clientIp}` == "client" ]]')
# with an explicit deny message (no --fail because we want to parse the returned message)
server.succeed('[[ `timeout 3 curl --proxy http://${proxyExternalIp}:3128 http://${clientIp}` == *"ERR_ACCESS_DENIED"* ]]')
'';
}
)