1# This is a distributed test of the Squid as a forward proxy
2# - "external" -- i.e. the internet, where the proxy and server communicate
3# - "internal" -- i.e. an office LAN, where the client and proxy communicat
4
5import ./make-test-python.nix (
6 {
7 pkgs,
8 lib,
9 ...
10 }:
11 # VLANS:
12 # 1 -- simulates the internal network
13 # 2 -- simulates the external network
14 let
15 commonConfig = {
16 # Disable eth0 autoconfiguration
17 networking.useDHCP = false;
18
19 environment.systemPackages = [
20 (pkgs.writeScriptBin "check-connection" ''
21 #!/usr/bin/env bash
22
23 set -e
24
25 if [[ "$2" == "" || "$1" == "--help" || "$1" == "-h" ]];
26 then
27 echo "check-connection <target-address> <[expect-success|expect-failure]>"
28 exit 1
29 fi
30
31 ADDRESS="$1"
32
33 function test_icmp() { timeout 3 ping -c 1 "$ADDRESS"; }
34
35 if [[ "$2" == "expect-success" ]];
36 then
37 test_icmp
38 else
39 ! test_icmp
40 fi
41 '')
42 ];
43 };
44 in
45 {
46 name = "squid";
47 meta = with pkgs.lib.maintainers; {
48 maintainers = [ cobalt ];
49 };
50
51 nodes = {
52 client =
53 { ... }:
54 lib.mkMerge [
55 commonConfig
56 {
57 virtualisation.vlans = [ 1 ];
58 networking.firewall.enable = true;
59
60 # NOTE: the client doesn't need a HTTP server, this is here to allow a validation of the proxy acl
61 networking.firewall.allowedTCPPorts = [ 80 ];
62
63 services.nginx = {
64 enable = true;
65
66 virtualHosts."server" = {
67 root = "/etc";
68 locations."/".index = "hostname";
69 listen = [
70 {
71 addr = "0.0.0.0";
72 port = 80;
73 }
74 ];
75 };
76 };
77 }
78 ];
79
80 proxy =
81 { config, nodes, ... }:
82 let
83 clientIp = (pkgs.lib.head nodes.client.networking.interfaces.eth1.ipv4.addresses).address;
84 serverIp = (pkgs.lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address;
85 in
86 lib.mkMerge [
87 commonConfig
88 {
89 nixpkgs.config.permittedInsecurePackages = [ "squid-7.0.1" ];
90
91 virtualisation.vlans = [
92 1
93 2
94 ];
95 networking.firewall.enable = true;
96 networking.firewall.allowedTCPPorts = [ config.services.squid.proxyPort ];
97
98 services.squid = {
99 enable = true;
100
101 extraConfig = ''
102 acl client src ${clientIp}
103 acl server dst ${serverIp}
104 http_access allow client server
105 http_access deny all
106 '';
107 };
108 }
109 ];
110
111 server =
112 { ... }:
113 lib.mkMerge [
114 commonConfig
115 {
116 virtualisation.vlans = [ 2 ];
117 networking.firewall.enable = true;
118 networking.firewall.allowedTCPPorts = [ 80 ];
119
120 services.nginx = {
121 enable = true;
122
123 virtualHosts."server" = {
124 root = "/etc";
125 locations."/".index = "hostname";
126 listen = [
127 {
128 addr = "0.0.0.0";
129 port = 80;
130 }
131 ];
132 };
133 };
134 }
135 ];
136 };
137
138 testScript =
139 { nodes, ... }:
140 let
141 clientIp = (pkgs.lib.head nodes.client.networking.interfaces.eth1.ipv4.addresses).address;
142 serverIp = (pkgs.lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address;
143 proxyExternalIp = (pkgs.lib.head nodes.proxy.networking.interfaces.eth2.ipv4.addresses).address;
144 proxyInternalIp = (pkgs.lib.head nodes.proxy.networking.interfaces.eth1.ipv4.addresses).address;
145 in
146 ''
147 client.start()
148 proxy.start()
149 server.start()
150
151 proxy.wait_for_unit("network.target")
152 proxy.wait_for_unit("squid.service")
153 client.wait_for_unit("network.target")
154 server.wait_for_unit("network.target")
155 server.wait_for_unit("nginx.service")
156
157 # Topology checks.
158 with subtest("proxy connectivity"):
159 ## The proxy should have direct access to the server and client
160 proxy.succeed("check-connection ${serverIp} expect-success")
161 proxy.succeed("check-connection ${clientIp} expect-success")
162
163 with subtest("server connectivity"):
164 ## The server should have direct access to the proxy
165 server.succeed("check-connection ${proxyExternalIp} expect-success")
166 ## ... and not have access to the client
167 server.succeed("check-connection ${clientIp} expect-failure")
168
169 with subtest("client connectivity"):
170 # The client should be also able to connect to the proxy
171 client.succeed("check-connection ${proxyInternalIp} expect-success")
172 # but not the client to the server
173 client.succeed("check-connection ${serverIp} expect-failure")
174
175 with subtest("HTTP"):
176 # the client cannot reach the server directly over HTTP
177 client.fail('[[ `timeout 3 curl --fail-with-body http://${serverIp}` ]]')
178 # ... but can with the proxy
179 client.succeed('[[ `timeout 3 curl --fail-with-body --proxy http://${proxyInternalIp}:3128 http://${serverIp}` == "server" ]]')
180 # and cannot from the server (with a 4xx error code) and ...
181 server.fail('[[ `timeout 3 curl --fail-with-body --proxy http://${proxyExternalIp}:3128 http://${clientIp}` == "client" ]]')
182 # .. not the client hostname
183 server.fail('[[ `timeout 3 curl --proxy http://${proxyExternalIp}:3128 http://${clientIp}` == "client" ]]')
184 # with an explicit deny message (no --fail because we want to parse the returned message)
185 server.succeed('[[ `timeout 3 curl --proxy http://${proxyExternalIp}:3128 http://${clientIp}` == *"ERR_ACCESS_DENIED"* ]]')
186 '';
187 }
188)