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