From 6d3fd3e368a6f83ba097afe276b195a2b080a948 Mon Sep 17 00:00:00 2001 From: Adrian Pistol Date: Sat, 21 Oct 2023 00:10:33 +0200 Subject: [PATCH 1/2] haproxy: 2.9.2 -> 2.9.3; Enable QUIC support This allows switching SSL Libraries between OpenSSL, QuicTLS, LibreSSL, and WolfSSL. The default is changed to QuicTLS, as it is feature-compatible with OpenSSL and not experimental. Also switched to PCRE2 and Lua 5.4. --- pkgs/tools/networking/haproxy/default.nix | 62 +++++++++++++---------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/pkgs/tools/networking/haproxy/default.nix b/pkgs/tools/networking/haproxy/default.nix index e76317b7c43e..579124a5c90e 100644 --- a/pkgs/tools/networking/haproxy/default.nix +++ b/pkgs/tools/networking/haproxy/default.nix @@ -1,39 +1,44 @@ { useLua ? true , usePcre ? true -# QUIC "is currently supported as an experimental feature" so shouldn't be enabled by default -, useQuicTls ? false , withPrometheusExporter ? true +, sslLibrary ? "quictls" , stdenv , lib , fetchurl , nixosTests , zlib , libxcrypt -, openssl ? null -, quictls ? null -, lua5_3 ? null -, pcre ? null -, systemd ? null +, wolfssl +, libressl +, quictls +, openssl +, lua5_4 +, pcre2 +, systemd }: -assert useLua -> lua5_3 != null; -assert usePcre -> pcre != null; -assert useQuicTls -> quictls != null; -assert !useQuicTls -> openssl != null; - -let sslPkg = if useQuicTls then quictls else openssl; +assert lib.assertOneOf "sslLibrary" sslLibrary [ "quictls" "openssl" "libressl" "wolfssl" ]; +let + sslPkgs = { + inherit quictls openssl libressl; + wolfssl = wolfssl.override { + variant = "haproxy"; + extraConfigureFlags = [ "--enable-quic" ]; + }; + }; + sslPkg = sslPkgs.${sslLibrary}; in stdenv.mkDerivation (finalAttrs: { pname = "haproxy"; - version = "2.9.2"; + version = "2.9.3"; src = fetchurl { url = "https://www.haproxy.org/download/${lib.versions.majorMinor finalAttrs.version}/src/haproxy-${finalAttrs.version}.tar.gz"; - hash = "sha256-hRrugw7CjBeRJGqf1EePZD0RWlY92Qf2YSzDgalSqzw="; + hash = "sha256-7VF8ZavYaUVBH2vLGMfsZXpwaTHLeB6igwY7oKdYWMA="; }; buildInputs = [ sslPkg zlib libxcrypt ] - ++ lib.optional useLua lua5_3 - ++ lib.optional usePcre pcre + ++ lib.optional useLua lua5_4 + ++ lib.optional usePcre pcre2 ++ lib.optional stdenv.isLinux systemd; # TODO: make it work on bsd as well @@ -46,20 +51,23 @@ in stdenv.mkDerivation (finalAttrs: { ]; buildFlags = [ - "USE_OPENSSL=yes" - "SSL_LIB=${sslPkg}/lib" - "SSL_INC=${sslPkg}/include" "USE_ZLIB=yes" - ] ++ lib.optionals useQuicTls [ - "USE_QUIC=1" + "USE_OPENSSL=yes" + "SSL_INC=${lib.getDev sslPkg}/include" + "SSL_LIB=${lib.getDev sslPkg}/lib" + "USE_QUIC=yes" + ] ++ lib.optionals (sslLibrary == "openssl") [ + "USE_QUIC_OPENSSL_COMPAT=yes" + ] ++ lib.optionals (sslLibrary == "wolfssl") [ + "USE_OPENSSL_WOLFSSL=yes" ] ++ lib.optionals usePcre [ - "USE_PCRE=yes" - "USE_PCRE_JIT=yes" + "USE_PCRE2=yes" + "USE_PCRE2_JIT=yes" ] ++ lib.optionals useLua [ "USE_LUA=yes" "LUA_LIB_NAME=lua" - "LUA_LIB=${lua5_3}/lib" - "LUA_INC=${lua5_3}/include" + "LUA_LIB=${lua5_4}/lib" + "LUA_INC=${lua5_4}/include" ] ++ lib.optionals stdenv.isLinux [ "USE_SYSTEMD=yes" "USE_GETADDRINFO=1" @@ -84,7 +92,7 @@ in stdenv.mkDerivation (finalAttrs: { tens of thousands of connections is clearly realistic with todays hardware. ''; - maintainers = with lib.maintainers; [ ]; + maintainers = with lib.maintainers; [ vifino ]; platforms = with lib.platforms; linux ++ darwin; mainProgram = "haproxy"; }; From 0b7a56c13c158ddc642a23b9a8be533c63674f8e Mon Sep 17 00:00:00 2001 From: Adrian Pistol Date: Mon, 23 Oct 2023 18:43:39 +0200 Subject: [PATCH 2/2] nixos/tests/haproxy: Test HTTPS, QUIC and Cert Auth --- nixos/tests/haproxy.nix | 109 +++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/nixos/tests/haproxy.nix b/nixos/tests/haproxy.nix index 555474d7f299..173093873757 100644 --- a/nixos/tests/haproxy.nix +++ b/nixos/tests/haproxy.nix @@ -1,22 +1,42 @@ -import ./make-test-python.nix ({ pkgs, ...}: { +import ./make-test-python.nix ({ lib, pkgs, ...}: { name = "haproxy"; nodes = { - machine = { ... }: { - services.haproxy = { + server = { ... }: { + services.haproxy = { enable = true; config = '' + global + limited-quic + defaults + mode http timeout connect 10s + timeout client 10s + timeout server 10s + + log /dev/log local0 debug err + option logasap + option httplog + option httpslog backend http_server - mode http - server httpd [::1]:8000 + server httpd [::1]:8000 alpn http/1.1 frontend http - bind *:80 - mode http + bind :80 + bind :443 ssl strict-sni crt /etc/ssl/fullchain.pem alpn h2,http/1.1 + bind quic4@:443 ssl strict-sni crt /etc/ssl/fullchain.pem alpn h3 allow-0rtt + + http-after-response add-header alt-svc 'h3=":443"; ma=60' if { ssl_fc } + http-request use-service prometheus-exporter if { path /metrics } use_backend http_server + + frontend http-cert-auth + bind :8443 ssl strict-sni crt /etc/ssl/fullchain.pem verify required ca-file /etc/ssl/cacert.crt + bind quic4@:8443 ssl strict-sni crt /etc/ssl/fullchain.pem verify required ca-file /etc/ssl/cacert.crt alpn h3 + + use_backend http_server ''; }; services.httpd = { @@ -30,24 +50,75 @@ import ./make-test-python.nix ({ pkgs, ...}: { }]; }; }; + networking.firewall.allowedTCPPorts = [ 80 443 8443 ]; + networking.firewall.allowedUDPPorts = [ 443 8443 ]; + }; + client = { ... }: { + environment.systemPackages = [ pkgs.curlHTTP3 ]; }; }; testScript = '' + # Helpers + def cmd(command): + print(f"+{command}") + r = os.system(command) + if r != 0: + raise Exception(f"Command {command} failed with exit code {r}") + + def openssl(command): + cmd(f"${pkgs.openssl}/bin/openssl {command}") + + # Generate CA. + 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") + + # Generate and sign Server. + openssl("req -newkey rsa:4096 -nodes -subj '/CN=server/OU=Tests/O=NixOS' -keyout server.key -out server.csr") + openssl("x509 -req -in server.csr -out server.crt -CA cacert.crt -CAkey cacert.key -days 7") + cmd("cat server.crt server.key > fullchain.pem") + + # Generate and sign Client. + openssl("req -newkey rsa:4096 -nodes -subj '/CN=client/OU=Tests/O=NixOS' -keyout client.key -out client.csr") + openssl("x509 -req -in client.csr -out client.crt -CA cacert.crt -CAkey cacert.key -days 7") + cmd("cat client.crt client.key > client.pem") + + # Start the actual test. start_all() - machine.wait_for_unit("multi-user.target") - machine.wait_for_unit("haproxy.service") - machine.wait_for_unit("httpd.service") - assert "We are all good!" in machine.succeed("curl -fk http://localhost:80/index.txt") - assert "haproxy_process_pool_allocated_bytes" in machine.succeed( - "curl -fk http://localhost:80/metrics" - ) + server.copy_from_host("fullchain.pem", "/etc/ssl/fullchain.pem") + server.copy_from_host("cacert.crt", "/etc/ssl/cacert.crt") + server.succeed("chmod 0644 /etc/ssl/fullchain.pem /etc/ssl/cacert.crt") + + client.copy_from_host("cacert.crt", "/etc/ssl/cacert.crt") + client.copy_from_host("client.pem", "/root/client.pem") + + server.wait_for_unit("multi-user.target") + server.wait_for_unit("haproxy.service") + server.wait_for_unit("httpd.service") + + assert "We are all good!" in client.succeed("curl -f http://server/index.txt") + assert "haproxy_process_pool_allocated_bytes" in client.succeed("curl -f http://server/metrics") + + with subtest("https"): + assert "We are all good!" in client.succeed("curl -f --cacert /etc/ssl/cacert.crt https://server/index.txt") + + with subtest("https-cert-auth"): + # Client must succeed in authenticating with the right certificate. + 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") + # Client must fail without certificate. + client.fail("curl --cacert /etc/ssl/cacert.crt https://server:8443/index.txt") + + with subtest("h3"): + assert "We are all good!" in client.succeed("curl -f --http3-only --cacert /etc/ssl/cacert.crt https://server/index.txt") + + with subtest("h3-cert-auth"): + # Client must succeed in authenticating with the right certificate. + 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") + # Client must fail without certificate. + client.fail("curl -f --http3-only --cacert /etc/ssl/cacert.crt https://server:8443/index.txt") with subtest("reload"): - machine.succeed("systemctl reload haproxy") + server.succeed("systemctl reload haproxy") # wait some time to ensure the following request hits the reloaded haproxy - machine.sleep(5) - assert "We are all good!" in machine.succeed( - "curl -fk http://localhost:80/index.txt" - ) + server.sleep(5) + assert "We are all good!" in client.succeed("curl -f http://server/index.txt") ''; })