summaryrefslogtreecommitdiffstats
path: root/nixos/tests/haproxy.nix
blob: 20bcd700bb589fad7e8fe5a2beedbf96b55308a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
{ lib, hostPkgs, ... }:
{
  name = "haproxy";
  nodes = {
    server =
      { pkgs, ... }:
      {
        services.haproxy = {
          enable = true;
          config = ''
            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
              server httpd [::1]:8000 alpn http/1.1

            frontend 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 = {
          enable = true;
          virtualHosts.localhost = {
            documentRoot = pkgs.writeTextDir "index.txt" "We are all good!";
            adminAddr = "notme@yourhost.local";
            listen = [
              {
                ip = "::1";
                port = 8000;
              }
            ];
          };
        };
        networking.firewall.allowedTCPPorts = [
          80
          443
          8443
        ];
        networking.firewall.allowedUDPPorts = [
          443
          8443
        ];
      };
    client =
      { pkgs, ... }:
      {
        environment.systemPackages = [ pkgs.curl ];
      };
  };
  testScript = ''
    import subprocess

    # Helpers
    def cmd(command):
      print(f"+{command}")
      r = subprocess.run(command, shell=True).returncode
      if r != 0:
        raise Exception(f"Command {command} failed with exit code {r}")

    def openssl(command):
      cmd(f"${lib.getExe hostPkgs.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()
    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"):
        server.succeed("systemctl reload haproxy")
        # wait some time to ensure the following request hits the reloaded haproxy
        server.sleep(5)
        assert "We are all good!" in client.succeed("curl -f http://server/index.txt")
  '';
}