159 lines
5.3 KiB
Nix
159 lines
5.3 KiB
Nix
/*
|
|
Test suite for curl-impersonate
|
|
|
|
Abstract:
|
|
Uses the test suite from the curl-impersonate source repo which:
|
|
|
|
1. Performs requests with libcurl and captures the TLS client-hello
|
|
packets with tcpdump to compare against known-good signatures
|
|
2. Spins up an nghttpd2 server to test client HTTP/2 headers against
|
|
known-good headers
|
|
|
|
See https://github.com/lwthiker/curl-impersonate/tree/main/tests/signatures
|
|
for details.
|
|
|
|
Notes:
|
|
- We need to have our own web server running because the tests expect to be able
|
|
to hit domains like wikipedia.org and the sandbox has no internet
|
|
- We need to be able to do (verifying) TLS handshakes without internet access.
|
|
We do that by creating a trusted CA and issuing a cert that includes
|
|
all of the test domains as subject-alternative names and then spoofs the
|
|
hostnames in /etc/hosts.
|
|
*/
|
|
|
|
import ./make-test-python.nix ({ pkgs, lib, ... }: let
|
|
# Update with domains in TestImpersonate.TEST_URLS if needed from:
|
|
# https://github.com/lwthiker/curl-impersonate/blob/main/tests/test_impersonate.py
|
|
domains = [
|
|
"www.wikimedia.org"
|
|
"www.wikipedia.org"
|
|
"www.mozilla.org"
|
|
"www.apache.org"
|
|
"www.kernel.org"
|
|
"git-scm.com"
|
|
];
|
|
|
|
tls-certs = let
|
|
# Configure CA with X.509 v3 extensions that would be trusted by curl
|
|
ca-cert-conf = pkgs.writeText "curl-impersonate-ca.cnf" ''
|
|
basicConstraints = critical, CA:TRUE
|
|
subjectKeyIdentifier = hash
|
|
authorityKeyIdentifier = keyid:always, issuer:always
|
|
keyUsage = critical, cRLSign, digitalSignature, keyCertSign
|
|
'';
|
|
|
|
# Configure leaf certificate with X.509 v3 extensions that would be trusted
|
|
# by curl and set subject-alternative names for test domains
|
|
tls-cert-conf = pkgs.writeText "curl-impersonate-tls.cnf" ''
|
|
basicConstraints = critical, CA:FALSE
|
|
subjectKeyIdentifier = hash
|
|
authorityKeyIdentifier = keyid:always, issuer:always
|
|
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement
|
|
extendedKeyUsage = critical, serverAuth
|
|
subjectAltName = @alt_names
|
|
|
|
[alt_names]
|
|
${lib.concatStringsSep "\n" (lib.imap0 (idx: domain: "DNS.${toString idx} = ${domain}") domains)}
|
|
'';
|
|
in pkgs.runCommand "curl-impersonate-test-certs" {
|
|
nativeBuildInputs = [ pkgs.openssl ];
|
|
} ''
|
|
# create CA certificate and key
|
|
openssl req -newkey rsa:4096 -keyout ca-key.pem -out ca-csr.pem -nodes -subj '/CN=curl-impersonate-ca.nixos.test'
|
|
openssl x509 -req -sha512 -in ca-csr.pem -key ca-key.pem -out ca.pem -extfile ${ca-cert-conf} -days 36500
|
|
openssl x509 -in ca.pem -text
|
|
|
|
# create server certificate and key
|
|
openssl req -newkey rsa:4096 -keyout key.pem -out csr.pem -nodes -subj '/CN=curl-impersonate.nixos.test'
|
|
openssl x509 -req -sha512 -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile ${tls-cert-conf} -days 36500
|
|
openssl x509 -in cert.pem -text
|
|
|
|
# output CA cert and server cert and key
|
|
mkdir -p $out
|
|
cp key.pem cert.pem ca.pem $out
|
|
'';
|
|
|
|
# Test script
|
|
curl-impersonate-test = let
|
|
# Build miniature libcurl client used by test driver
|
|
minicurl = pkgs.runCommandCC "minicurl" {
|
|
buildInputs = [ pkgs.curl ];
|
|
} ''
|
|
mkdir -p $out/bin
|
|
$CC -Wall -Werror -o $out/bin/minicurl ${pkgs.curl-impersonate.src}/tests/minicurl.c `curl-config --libs`
|
|
'';
|
|
in pkgs.writeShellScript "curl-impersonate-test" ''
|
|
set -euxo pipefail
|
|
|
|
# Test driver requirements
|
|
export PATH="${with pkgs; lib.makeBinPath [
|
|
bash
|
|
coreutils
|
|
python3Packages.pytest
|
|
nghttp2
|
|
tcpdump
|
|
]}"
|
|
export PYTHONPATH="${with pkgs.python3Packages; makePythonPath [
|
|
pyyaml
|
|
pytest-asyncio
|
|
dpkt
|
|
]}"
|
|
|
|
# Prepare test root prefix
|
|
mkdir -p usr/{bin,lib}
|
|
cp -rs ${pkgs.curl-impersonate}/* ${minicurl}/* usr/
|
|
|
|
cp -r ${pkgs.curl-impersonate.src}/tests ./
|
|
|
|
# Run tests
|
|
cd tests
|
|
pytest . --install-dir ../usr --capture-interface eth1
|
|
'';
|
|
in {
|
|
name = "curl-impersonate";
|
|
|
|
meta = with lib.maintainers; {
|
|
maintainers = [ lilyinstarlight ];
|
|
};
|
|
|
|
nodes = {
|
|
web = { nodes, pkgs, lib, config, ... }: {
|
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
|
|
|
services = {
|
|
nginx = {
|
|
enable = true;
|
|
virtualHosts."curl-impersonate.nixos.test" = {
|
|
default = true;
|
|
addSSL = true;
|
|
sslCertificate = "${tls-certs}/cert.pem";
|
|
sslCertificateKey = "${tls-certs}/key.pem";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
curl = { nodes, pkgs, lib, config, ... }: {
|
|
networking.extraHosts = lib.concatStringsSep "\n" (map (domain: "${nodes.web.networking.primaryIPAddress} ${domain}") domains);
|
|
|
|
security.pki.certificateFiles = [ "${tls-certs}/ca.pem" ];
|
|
};
|
|
};
|
|
|
|
testScript = { nodes, ... }: ''
|
|
start_all()
|
|
|
|
with subtest("Wait for network"):
|
|
web.systemctl("start network-online.target")
|
|
curl.systemctl("start network-online.target")
|
|
web.wait_for_unit("network-online.target")
|
|
curl.wait_for_unit("network-online.target")
|
|
|
|
with subtest("Wait for web server"):
|
|
web.wait_for_unit("nginx.service")
|
|
web.wait_for_open_port(443)
|
|
|
|
with subtest("Run curl-impersonate tests"):
|
|
curl.succeed("${curl-impersonate-test}")
|
|
'';
|
|
})
|