at 23.11-beta 5.3 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*/ 23 24import ./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 ]; 35 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 ''; 44 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 54 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 65 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 70 71 # output CA cert and server cert and key 72 mkdir -p $out 73 cp key.pem cert.pem ca.pem $out 74 ''; 75 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 87 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 ]}" 101 102 # Prepare test root prefix 103 mkdir -p usr/{bin,lib} 104 cp -rs ${pkgs.curl-impersonate}/* ${minicurl}/* usr/ 105 106 cp -r ${pkgs.curl-impersonate.src}/tests ./ 107 108 # Run tests 109 cd tests 110 pytest . --install-dir ../usr --capture-interface eth1 111 ''; 112in { 113 name = "curl-impersonate"; 114 115 meta = with lib.maintainers; { 116 maintainers = [ lilyinstarlight ]; 117 }; 118 119 nodes = { 120 web = { nodes, pkgs, lib, config, ... }: { 121 networking.firewall.allowedTCPPorts = [ 80 443 ]; 122 123 services = { 124 nginx = { 125 enable = true; 126 virtualHosts."curl-impersonate.nixos.test" = { 127 default = true; 128 addSSL = true; 129 sslCertificate = "${tls-certs}/cert.pem"; 130 sslCertificateKey = "${tls-certs}/key.pem"; 131 }; 132 }; 133 }; 134 }; 135 136 curl = { nodes, pkgs, lib, config, ... }: { 137 networking.extraHosts = lib.concatStringsSep "\n" (map (domain: "${nodes.web.networking.primaryIPAddress} ${domain}") domains); 138 139 security.pki.certificateFiles = [ "${tls-certs}/ca.pem" ]; 140 }; 141 }; 142 143 testScript = { nodes, ... }: '' 144 start_all() 145 146 with subtest("Wait for network"): 147 web.wait_for_unit("network-online.target") 148 curl.wait_for_unit("network-online.target") 149 150 with subtest("Wait for web server"): 151 web.wait_for_unit("nginx.service") 152 web.wait_for_open_port(443) 153 154 with subtest("Run curl-impersonate tests"): 155 curl.succeed("${curl-impersonate-test}") 156 ''; 157})