at master 6.2 kB view raw
1/* 2 Test suite for curl-impersonate 3 4 Abstract: 5 Uses the test suite from the curl-impersonate source repo which: 6 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 11 12 See https://github.com/lwthiker/curl-impersonate/tree/main/tests/signatures 13 for details. 14 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. 22 - We started skipping the test_http2_headers test due to log format differences 23 between the nghttpd2 version in nixpkgs and the outdated one curl-impersonate 24 uses upstream for its tests. 25*/ 26 27{ pkgs, lib, ... }: 28let 29 # Update with domains in TestImpersonate.TEST_URLS if needed from: 30 # https://github.com/lwthiker/curl-impersonate/blob/main/tests/test_impersonate.py 31 domains = [ 32 "www.wikimedia.org" 33 "www.wikipedia.org" 34 "www.mozilla.org" 35 "www.apache.org" 36 "www.kernel.org" 37 "git-scm.com" 38 ]; 39 40 tls-certs = 41 let 42 # Configure CA with X.509 v3 extensions that would be trusted by curl 43 ca-cert-conf = pkgs.writeText "curl-impersonate-ca.cnf" '' 44 basicConstraints = critical, CA:TRUE 45 subjectKeyIdentifier = hash 46 authorityKeyIdentifier = keyid:always, issuer:always 47 keyUsage = critical, cRLSign, digitalSignature, keyCertSign 48 ''; 49 50 # Configure leaf certificate with X.509 v3 extensions that would be trusted 51 # by curl and set subject-alternative names for test domains 52 tls-cert-conf = pkgs.writeText "curl-impersonate-tls.cnf" '' 53 basicConstraints = critical, CA:FALSE 54 subjectKeyIdentifier = hash 55 authorityKeyIdentifier = keyid:always, issuer:always 56 keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement 57 extendedKeyUsage = critical, serverAuth 58 subjectAltName = @alt_names 59 60 [alt_names] 61 ${lib.concatStringsSep "\n" (lib.imap0 (idx: domain: "DNS.${toString idx} = ${domain}") domains)} 62 ''; 63 in 64 pkgs.runCommand "curl-impersonate-test-certs" 65 { 66 nativeBuildInputs = [ pkgs.openssl ]; 67 } 68 '' 69 # create CA certificate and key 70 openssl req -newkey rsa:4096 -keyout ca-key.pem -out ca-csr.pem -nodes -subj '/CN=curl-impersonate-ca.nixos.test' 71 openssl x509 -req -sha512 -in ca-csr.pem -key ca-key.pem -out ca.pem -extfile ${ca-cert-conf} -days 36500 72 openssl x509 -in ca.pem -text 73 74 # create server certificate and key 75 openssl req -newkey rsa:4096 -keyout key.pem -out csr.pem -nodes -subj '/CN=curl-impersonate.nixos.test' 76 openssl x509 -req -sha512 -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile ${tls-cert-conf} -days 36500 77 openssl x509 -in cert.pem -text 78 79 # output CA cert and server cert and key 80 mkdir -p $out 81 cp key.pem cert.pem ca.pem $out 82 ''; 83 84 # Test script 85 curl-impersonate-test = 86 let 87 # Build miniature libcurl client used by test driver 88 minicurl = 89 pkgs.runCommandCC "minicurl" 90 { 91 buildInputs = [ pkgs.curl ]; 92 } 93 '' 94 mkdir -p $out/bin 95 $CC -Wall -Werror -o $out/bin/minicurl ${pkgs.curl-impersonate.src}/tests/minicurl.c `curl-config --libs` 96 ''; 97 in 98 pkgs.writeShellScript "curl-impersonate-test" '' 99 set -euxo pipefail 100 101 # Test driver requirements 102 export PATH="${ 103 with pkgs; 104 lib.makeBinPath [ 105 bash 106 coreutils 107 python3Packages.pytest 108 nghttp2 109 tcpdump 110 ] 111 }" 112 export PYTHONPATH="${ 113 with pkgs.python3Packages; 114 makePythonPath [ 115 pyyaml 116 pytest-asyncio 117 dpkt 118 ts1-signatures 119 ] 120 }" 121 122 # Prepare test root prefix 123 mkdir -p usr/{bin,lib} 124 cp -rs ${pkgs.curl-impersonate}/* ${minicurl}/* usr/ 125 126 cp -r ${pkgs.curl-impersonate.src}/tests ./ 127 128 # Run tests 129 cd tests 130 pytest . --install-dir ../usr --capture-interface eth1 --exitfirst -k 'not test_http2_headers' 131 ''; 132in 133{ 134 name = "curl-impersonate"; 135 136 meta = with lib.maintainers; { 137 maintainers = [ ]; 138 }; 139 140 nodes = { 141 web = 142 { 143 nodes, 144 pkgs, 145 lib, 146 config, 147 ... 148 }: 149 { 150 networking.firewall.allowedTCPPorts = [ 151 80 152 443 153 ]; 154 155 services = { 156 nginx = { 157 enable = true; 158 virtualHosts."curl-impersonate.nixos.test" = { 159 default = true; 160 addSSL = true; 161 sslCertificate = "${tls-certs}/cert.pem"; 162 sslCertificateKey = "${tls-certs}/key.pem"; 163 }; 164 }; 165 }; 166 }; 167 168 curl = 169 { 170 nodes, 171 pkgs, 172 lib, 173 config, 174 ... 175 }: 176 { 177 networking.extraHosts = lib.concatStringsSep "\n" ( 178 map (domain: "${nodes.web.networking.primaryIPAddress} ${domain}") domains 179 ); 180 181 security.pki.certificateFiles = [ "${tls-certs}/ca.pem" ]; 182 }; 183 }; 184 185 testScript = 186 { nodes, ... }: 187 '' 188 start_all() 189 190 with subtest("Wait for network"): 191 web.systemctl("start network-online.target") 192 curl.systemctl("start network-online.target") 193 web.wait_for_unit("network-online.target") 194 curl.wait_for_unit("network-online.target") 195 196 with subtest("Wait for web server"): 197 web.wait_for_unit("nginx.service") 198 web.wait_for_open_port(443) 199 200 with subtest("Run curl-impersonate tests"): 201 curl.succeed("${curl-impersonate-test}") 202 ''; 203}