1{ lib, hostPkgs, ... }:
2{
3 name = "haproxy";
4 nodes = {
5 server =
6 { pkgs, ... }:
7 {
8 services.haproxy = {
9 enable = true;
10 config = ''
11 global
12 limited-quic
13
14 defaults
15 mode http
16 timeout connect 10s
17 timeout client 10s
18 timeout server 10s
19
20 log /dev/log local0 debug err
21 option logasap
22 option httplog
23 option httpslog
24
25 backend http_server
26 server httpd [::1]:8000 alpn http/1.1
27
28 frontend http
29 bind :80
30 bind :443 ssl strict-sni crt /etc/ssl/fullchain.pem alpn h2,http/1.1
31 bind quic4@:443 ssl strict-sni crt /etc/ssl/fullchain.pem alpn h3 allow-0rtt
32
33 http-after-response add-header alt-svc 'h3=":443"; ma=60' if { ssl_fc }
34
35 http-request use-service prometheus-exporter if { path /metrics }
36 use_backend http_server
37
38 frontend http-cert-auth
39 bind :8443 ssl strict-sni crt /etc/ssl/fullchain.pem verify required ca-file /etc/ssl/cacert.crt
40 bind quic4@:8443 ssl strict-sni crt /etc/ssl/fullchain.pem verify required ca-file /etc/ssl/cacert.crt alpn h3
41
42 use_backend http_server
43 '';
44 };
45 services.httpd = {
46 enable = true;
47 virtualHosts.localhost = {
48 documentRoot = pkgs.writeTextDir "index.txt" "We are all good!";
49 adminAddr = "notme@yourhost.local";
50 listen = [
51 {
52 ip = "::1";
53 port = 8000;
54 }
55 ];
56 };
57 };
58 networking.firewall.allowedTCPPorts = [
59 80
60 443
61 8443
62 ];
63 networking.firewall.allowedUDPPorts = [
64 443
65 8443
66 ];
67 };
68 client =
69 { pkgs, ... }:
70 {
71 environment.systemPackages = [ pkgs.curlHTTP3 ];
72 };
73 };
74 testScript = ''
75 # Helpers
76 def cmd(command):
77 print(f"+{command}")
78 r = os.system(command)
79 if r != 0:
80 raise Exception(f"Command {command} failed with exit code {r}")
81
82 def openssl(command):
83 cmd(f"${lib.getExe hostPkgs.openssl} {command}")
84
85 # Generate CA.
86 openssl("req -new -newkey rsa:4096 -nodes -x509 -days 7 -subj '/C=ZZ/ST=Cloud/L=Unspecified/O=NixOS/OU=Tests/CN=CA Certificate' -keyout cacert.key -out cacert.crt")
87
88 # Generate and sign Server.
89 openssl("req -newkey rsa:4096 -nodes -subj '/CN=server/OU=Tests/O=NixOS' -keyout server.key -out server.csr")
90 openssl("x509 -req -in server.csr -out server.crt -CA cacert.crt -CAkey cacert.key -days 7")
91 cmd("cat server.crt server.key > fullchain.pem")
92
93 # Generate and sign Client.
94 openssl("req -newkey rsa:4096 -nodes -subj '/CN=client/OU=Tests/O=NixOS' -keyout client.key -out client.csr")
95 openssl("x509 -req -in client.csr -out client.crt -CA cacert.crt -CAkey cacert.key -days 7")
96 cmd("cat client.crt client.key > client.pem")
97
98 # Start the actual test.
99 start_all()
100 server.copy_from_host("fullchain.pem", "/etc/ssl/fullchain.pem")
101 server.copy_from_host("cacert.crt", "/etc/ssl/cacert.crt")
102 server.succeed("chmod 0644 /etc/ssl/fullchain.pem /etc/ssl/cacert.crt")
103
104 client.copy_from_host("cacert.crt", "/etc/ssl/cacert.crt")
105 client.copy_from_host("client.pem", "/root/client.pem")
106
107 server.wait_for_unit("multi-user.target")
108 server.wait_for_unit("haproxy.service")
109 server.wait_for_unit("httpd.service")
110
111 assert "We are all good!" in client.succeed("curl -f http://server/index.txt")
112 assert "haproxy_process_pool_allocated_bytes" in client.succeed("curl -f http://server/metrics")
113
114 with subtest("https"):
115 assert "We are all good!" in client.succeed("curl -f --cacert /etc/ssl/cacert.crt https://server/index.txt")
116
117 with subtest("https-cert-auth"):
118 # Client must succeed in authenticating with the right certificate.
119 assert "We are all good!" in client.succeed("curl -f --cacert /etc/ssl/cacert.crt --cert-type pem --cert /root/client.pem https://server:8443/index.txt")
120 # Client must fail without certificate.
121 client.fail("curl --cacert /etc/ssl/cacert.crt https://server:8443/index.txt")
122
123 with subtest("h3"):
124 assert "We are all good!" in client.succeed("curl -f --http3-only --cacert /etc/ssl/cacert.crt https://server/index.txt")
125
126 with subtest("h3-cert-auth"):
127 # Client must succeed in authenticating with the right certificate.
128 assert "We are all good!" in client.succeed("curl -f --http3-only --cacert /etc/ssl/cacert.crt --cert-type pem --cert /root/client.pem https://server:8443/index.txt")
129 # Client must fail without certificate.
130 client.fail("curl -f --http3-only --cacert /etc/ssl/cacert.crt https://server:8443/index.txt")
131
132 with subtest("reload"):
133 server.succeed("systemctl reload haproxy")
134 # wait some time to ensure the following request hits the reloaded haproxy
135 server.sleep(5)
136 assert "We are all good!" in client.succeed("curl -f http://server/index.txt")
137 '';
138}