notes: 2.3.0 -> 2.3.1 (#352950)
[NixPkgs.git] / nixos / tests / nginx-proxyprotocol / default.nix
blob2ff7debfcbe25592cb0477db450aa61c2472c0e9
1 let
2   certs = import ./snakeoil-certs.nix;
3 in
4 import ../make-test-python.nix ({ pkgs, ... }: {
5   name = "nginx-proxyprotocol";
7   meta = {
8     maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
9   };
11   nodes = {
12     webserver = { pkgs, lib, ... }: {
13       environment.systemPackages = [ pkgs.netcat ];
14       security.pki.certificateFiles = [
15         certs.ca.cert
16       ];
18       networking.extraHosts = ''
19         127.0.0.5 proxy.test.nix
20         127.0.0.5 noproxy.test.nix
21         127.0.0.3 direct-nossl.test.nix
22         127.0.0.4 unsecure-nossl.test.nix
23         127.0.0.2 direct-noproxy.test.nix
24         127.0.0.1 direct-proxy.test.nix
25       '';
26       services.nginx = {
27         enable = true;
28         defaultListen = [
29           { addr = "127.0.0.1"; proxyProtocol = true; ssl = true; }
30           { addr = "127.0.0.2"; }
31           { addr = "127.0.0.3"; ssl = false; }
32           { addr = "127.0.0.4"; ssl = false; proxyProtocol = true; }
33         ];
34         commonHttpConfig = ''
35           log_format pcombined '(proxy_protocol=$proxy_protocol_addr) - (remote_addr=$remote_addr) - (realip=$realip_remote_addr) - (upstream=) - (remote_user=$remote_user) [$time_local] '
36                         '"$request" $status $body_bytes_sent '
37                         '"$http_referer" "$http_user_agent"';
38           access_log /var/log/nginx/access.log pcombined;
39           error_log /var/log/nginx/error.log;
40         '';
41         virtualHosts =
42         let
43           commonConfig = {
44            locations."/".return = "200 '$remote_addr'";
45            extraConfig = ''
46             set_real_ip_from 127.0.0.5/32;
47             real_ip_header proxy_protocol;
48            '';
49          };
50         in
51         {
52           "*.test.nix" = commonConfig // {
53             sslCertificate = certs."*.test.nix".cert;
54             sslCertificateKey = certs."*.test.nix".key;
55             forceSSL = true;
56           };
57           "direct-nossl.test.nix" = commonConfig;
58           "unsecure-nossl.test.nix" = commonConfig // {
59             extraConfig = ''
60               real_ip_header proxy_protocol;
61             '';
62           };
63         };
64       };
66       services.sniproxy = {
67         enable = true;
68         config = ''
69           error_log {
70             syslog daemon
71           }
72           access_log {
73             syslog daemon
74           }
75           listener 127.0.0.5:443 {
76             protocol tls
77             source 127.0.0.5
78           }
79           table {
80             ^proxy\.test\.nix$   127.0.0.1 proxy_protocol
81             ^noproxy\.test\.nix$ 127.0.0.2
82           }
83         '';
84       };
85     };
86   };
88   testScript = ''
89     def check_origin_ip(src_ip: str, dst_url: str, failure: bool = False, proxy_protocol: bool = False, expected_ip: str | None = None):
90       check = webserver.fail if failure else webserver.succeed
91       if expected_ip is None:
92         expected_ip = src_ip
94       return check(f"curl {'--haproxy-protocol' if proxy_protocol else '''} --interface {src_ip} --fail -L {dst_url} | grep '{expected_ip}'")
96     webserver.wait_for_unit("nginx")
97     webserver.wait_for_unit("sniproxy")
98     # This should be closed by virtue of ssl = true;
99     webserver.wait_for_closed_port(80, "127.0.0.1")
100     # This should be open by virtue of no explicit ssl
101     webserver.wait_for_open_port(80, "127.0.0.2")
102     # This should be open by virtue of ssl = true;
103     webserver.wait_for_open_port(443, "127.0.0.1")
104     # This should be open by virtue of no explicit ssl
105     webserver.wait_for_open_port(443, "127.0.0.2")
106     # This should be open by sniproxy
107     webserver.wait_for_open_port(443, "127.0.0.5")
108     # This should be closed by sniproxy
109     webserver.wait_for_closed_port(80, "127.0.0.5")
111     # Sanity checks for the NGINX module
112     # direct-HTTP connection to NGINX without TLS, this checks that ssl = false; works well.
113     check_origin_ip("127.0.0.10", "http://direct-nossl.test.nix/")
114     # webserver.execute("openssl s_client -showcerts -connect direct-noproxy.test.nix:443")
115     # direct-HTTP connection to NGINX with TLS
116     check_origin_ip("127.0.0.10", "http://direct-noproxy.test.nix/")
117     check_origin_ip("127.0.0.10", "https://direct-noproxy.test.nix/")
118     # Well, sniproxy is not listening on 80 and cannot redirect
119     check_origin_ip("127.0.0.10", "http://proxy.test.nix/", failure=True)
120     check_origin_ip("127.0.0.10", "http://noproxy.test.nix/", failure=True)
122     # Actual PROXY protocol related tests
123     # Connecting through sniproxy should passthrough the originating IP address.
124     check_origin_ip("127.0.0.10", "https://proxy.test.nix/")
125     # Connecting through sniproxy to a non-PROXY protocol enabled listener should not pass the originating IP address.
126     check_origin_ip("127.0.0.10", "https://noproxy.test.nix/", expected_ip="127.0.0.5")
128     # Attack tests against spoofing
129     # Let's try to spoof our IP address by connecting direct-y to the PROXY protocol listener.
130     # FIXME(RaitoBezarius): rewrite it using Python + (Scapy|something else) as this is too much broken unfortunately.
131     # Or wait for upstream curl patch.
132     # def generate_attacker_request(original_ip: str, target_ip: str, dst_url: str):
133     #     return f"""PROXY TCP4 {original_ip} {target_ip} 80 80
134     #     GET / HTTP/1.1
135     #     Host: {dst_url}
137     #     """
138     # def spoof(original_ip: str, target_ip: str, dst_url: str, tls: bool = False, expect_failure: bool = True):
139     #   method = webserver.fail if expect_failure else webserver.succeed
140     #   port = 443 if tls else 80
141     #   print(webserver.execute(f"cat <<EOF | nc {target_ip} {port}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF"))
142     #   return method(f"cat <<EOF | nc {target_ip} {port} | grep {original_ip}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF")
144     # check_origin_ip("127.0.0.10", "http://unsecure-nossl.test.nix", proxy_protocol=True)
145     # spoof("1.1.1.1", "127.0.0.4", "direct-nossl.test.nix")
146     # spoof("1.1.1.1", "127.0.0.4", "unsecure-nossl.test.nix", expect_failure=False)
147   '';