notes: 2.3.0 -> 2.3.1 (#352950)
[NixPkgs.git] / nixos / tests / curl-impersonate.nix
blob13eb54b5b2d0de0bc3d5e9a30d3ab145b967b9d3
1 /*
2   Test suite for curl-impersonate
4   Abstract:
5     Uses the test suite from the curl-impersonate source repo which:
7     1. Performs requests with libcurl and captures the TLS client-hello
8        packets with tcpdump to compare against known-good signatures
9     2. Spins up an nghttpd2 server to test client HTTP/2 headers against
10        known-good headers
12     See https://github.com/lwthiker/curl-impersonate/tree/main/tests/signatures
13     for details.
15   Notes:
16     - We need to have our own web server running because the tests expect to be able
17       to hit domains like wikipedia.org and the sandbox has no internet
18     - We need to be able to do (verifying) TLS handshakes without internet access.
19       We do that by creating a trusted CA and issuing a cert that includes
20       all of the test domains as subject-alternative names and then spoofs the
21       hostnames in /etc/hosts.
24 import ./make-test-python.nix ({ pkgs, lib, ... }: let
25   # Update with domains in TestImpersonate.TEST_URLS if needed from:
26   # https://github.com/lwthiker/curl-impersonate/blob/main/tests/test_impersonate.py
27   domains = [
28     "www.wikimedia.org"
29     "www.wikipedia.org"
30     "www.mozilla.org"
31     "www.apache.org"
32     "www.kernel.org"
33     "git-scm.com"
34   ];
36   tls-certs = let
37     # Configure CA with X.509 v3 extensions that would be trusted by curl
38     ca-cert-conf = pkgs.writeText "curl-impersonate-ca.cnf" ''
39       basicConstraints = critical, CA:TRUE
40       subjectKeyIdentifier = hash
41       authorityKeyIdentifier = keyid:always, issuer:always
42       keyUsage = critical, cRLSign, digitalSignature, keyCertSign
43     '';
45     # Configure leaf certificate with X.509 v3 extensions that would be trusted
46     # by curl and set subject-alternative names for test domains
47     tls-cert-conf = pkgs.writeText "curl-impersonate-tls.cnf" ''
48       basicConstraints = critical, CA:FALSE
49       subjectKeyIdentifier = hash
50       authorityKeyIdentifier = keyid:always, issuer:always
51       keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement
52       extendedKeyUsage = critical, serverAuth
53       subjectAltName = @alt_names
55       [alt_names]
56       ${lib.concatStringsSep "\n" (lib.imap0 (idx: domain: "DNS.${toString idx} = ${domain}") domains)}
57     '';
58   in pkgs.runCommand "curl-impersonate-test-certs" {
59     nativeBuildInputs = [ pkgs.openssl ];
60   } ''
61     # create CA certificate and key
62     openssl req -newkey rsa:4096 -keyout ca-key.pem -out ca-csr.pem -nodes -subj '/CN=curl-impersonate-ca.nixos.test'
63     openssl x509 -req -sha512 -in ca-csr.pem -key ca-key.pem -out ca.pem -extfile ${ca-cert-conf} -days 36500
64     openssl x509 -in ca.pem -text
66     # create server certificate and key
67     openssl req -newkey rsa:4096 -keyout key.pem -out csr.pem -nodes -subj '/CN=curl-impersonate.nixos.test'
68     openssl x509 -req -sha512 -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile ${tls-cert-conf} -days 36500
69     openssl x509 -in cert.pem -text
71     # output CA cert and server cert and key
72     mkdir -p $out
73     cp key.pem cert.pem ca.pem $out
74   '';
76   # Test script
77   curl-impersonate-test = let
78     # Build miniature libcurl client used by test driver
79     minicurl = pkgs.runCommandCC "minicurl" {
80       buildInputs = [ pkgs.curl ];
81     } ''
82       mkdir -p $out/bin
83       $CC -Wall -Werror -o $out/bin/minicurl ${pkgs.curl-impersonate.src}/tests/minicurl.c `curl-config --libs`
84     '';
85   in pkgs.writeShellScript "curl-impersonate-test" ''
86     set -euxo pipefail
88     # Test driver requirements
89     export PATH="${with pkgs; lib.makeBinPath [
90       bash
91       coreutils
92       python3Packages.pytest
93       nghttp2
94       tcpdump
95     ]}"
96     export PYTHONPATH="${with pkgs.python3Packages; makePythonPath [
97       pyyaml
98       pytest-asyncio
99       dpkt
100       ts1-signatures
101     ]}"
103     # Prepare test root prefix
104     mkdir -p usr/{bin,lib}
105     cp -rs ${pkgs.curl-impersonate}/* ${minicurl}/* usr/
107     cp -r ${pkgs.curl-impersonate.src}/tests ./
109     # Run tests
110     cd tests
111     pytest . --install-dir ../usr --capture-interface eth1
112   '';
113 in {
114   name = "curl-impersonate";
116   meta = with lib.maintainers; {
117     maintainers = [ ];
118   };
120   nodes = {
121     web = { nodes, pkgs, lib, config, ... }: {
122       networking.firewall.allowedTCPPorts = [ 80 443 ];
124       services = {
125         nginx = {
126           enable = true;
127           virtualHosts."curl-impersonate.nixos.test" = {
128             default = true;
129             addSSL = true;
130             sslCertificate = "${tls-certs}/cert.pem";
131             sslCertificateKey = "${tls-certs}/key.pem";
132           };
133         };
134       };
135     };
137     curl = { nodes, pkgs, lib, config, ... }: {
138       networking.extraHosts = lib.concatStringsSep "\n" (map (domain: "${nodes.web.networking.primaryIPAddress}  ${domain}") domains);
140       security.pki.certificateFiles = [ "${tls-certs}/ca.pem" ];
141     };
142   };
144   testScript = { nodes, ... }: ''
145     start_all()
147     with subtest("Wait for network"):
148         web.systemctl("start network-online.target")
149         curl.systemctl("start network-online.target")
150         web.wait_for_unit("network-online.target")
151         curl.wait_for_unit("network-online.target")
153     with subtest("Wait for web server"):
154         web.wait_for_unit("nginx.service")
155         web.wait_for_open_port(443)
157     with subtest("Run curl-impersonate tests"):
158         curl.succeed("${curl-impersonate-test}")
159   '';