Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/blocking-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,32 @@ jobs:
- name: Install dependencies
run: uv sync --locked --all-extras --dev

- name: Generate TLS certificates for TrackMe
run: |
mkdir -p docker/trackme/certs
openssl req -x509 -newkey rsa:2048 \
-keyout docker/trackme/certs/key.pem \
-out docker/trackme/certs/chain.pem \
-days 1 -nodes \
-subj "/CN=localhost" \
-addext "subjectAltName=IP:127.0.0.1,DNS:localhost"
# Combine system CAs with test CA so Go trusts the self-signed cert
cat /etc/ssl/certs/ca-certificates.crt docker/trackme/certs/chain.pem \
> /tmp/combined-test-cas.crt

- name: Build and start TrackMe
run: |
docker compose -f docker-compose.test.yml up -d --build
echo "Waiting for TrackMe to become ready..."
if ! timeout 90 bash -c 'until docker inspect --format "{{.State.Health.Status}}" cycletls_python-trackme-1 2>/dev/null | grep -q healthy; do sleep 2; done'; then
echo "TrackMe did not become ready in time. Container logs:"
docker logs cycletls_python-trackme-1
exit 1
fi
echo "TrackMe is ready."

- name: Run blocking tests
env:
TRACKME_URL: https://localhost:8443
SSL_CERT_FILE: /tmp/combined-test-cas.crt
run: uv run pytest -v --color=yes -m "blocking" tests/
35 changes: 33 additions & 2 deletions .github/workflows/live-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ env:

jobs:
live-tests:
name: Live Tests (Python 3.12)
name: Live Tests (Python ${{ matrix.python }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python: ['3.10', '3.11', '3.12', '3.13']

steps:
- uses: actions/checkout@v4
Expand All @@ -30,11 +34,38 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
python-version: '3.12'
python-version: ${{ matrix.python }}
enable-cache: true

- name: Install dependencies
run: uv sync --locked --all-extras --dev

- name: Generate TLS certificates for TrackMe
run: |
mkdir -p docker/trackme/certs
openssl req -x509 -newkey rsa:2048 \
-keyout docker/trackme/certs/key.pem \
-out docker/trackme/certs/chain.pem \
-days 1 -nodes \
-subj "/CN=localhost" \
-addext "subjectAltName=IP:127.0.0.1,DNS:localhost"
# Combine system CAs with test CA so Go trusts the self-signed cert
cat /etc/ssl/certs/ca-certificates.crt docker/trackme/certs/chain.pem \
> /tmp/combined-test-cas.crt

- name: Build and start TrackMe
run: |
docker compose -f docker-compose.test.yml up -d --build
echo "Waiting for TrackMe to become ready..."
if ! timeout 90 bash -c 'until docker inspect --format "{{.State.Health.Status}}" cycletls_python-trackme-1 2>/dev/null | grep -q healthy; do sleep 2; done'; then
echo "TrackMe did not become ready in time. Container logs:"
docker logs cycletls_python-trackme-1
exit 1
fi
echo "TrackMe is ready."

- name: Run live tests
env:
TRACKME_URL: https://localhost:8443
SSL_CERT_FILE: /tmp/combined-test-cas.crt
run: uv run pytest -v --color=yes --reruns=3 -m "live and not blocking" tests/
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ site/

# Continuous Claude cache (local only)
.claude/cache/

# Generated test certificates for local TrackMe instance
docker/trackme/certs/
14 changes: 14 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
trackme:
build:
context: ./docker/trackme
network_mode: host
volumes:
- ./docker/trackme/certs:/app/certs:ro
- ./docker/trackme/config.json:/app/config.json:ro
healthcheck:
test: ["CMD-SHELL", "wget -qO /dev/null --no-check-certificate https://localhost:8443/api/clean && echo ok || exit 1"]
interval: 5s
timeout: 10s
retries: 12
start_period: 60s
18 changes: 18 additions & 0 deletions docker/trackme/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM golang:1.24-alpine AS builder

RUN apk add --no-cache build-base libpcap-dev git

RUN git clone https://github.com/pagpeter/TrackMe /trackme

WORKDIR /trackme
RUN go mod download
RUN go build -o /out/trackme ./cmd/main.go

FROM alpine:latest
RUN apk add --no-cache libpcap ca-certificates

WORKDIR /app
COPY --from=builder /out/trackme .
COPY --from=builder /trackme/static ./static/

CMD ["./trackme"]
12 changes: 12 additions & 0 deletions docker/trackme/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"log_to_db": false,
"tls_port": "8443",
"http_port": "8080",
"cert_file": "certs/chain.pem",
"key_file": "certs/key.pem",
"host": "0.0.0.0",
"http_redirect": "https://localhost:8443",
"device": "",
"cors_key": "X-CORS",
"enable_quic": false
}
39 changes: 39 additions & 0 deletions scripts/setup-trackme-certs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Generates self-signed TLS certs for the local TrackMe test instance.
# Usage:
# ./scripts/setup-trackme-certs.sh # generate certs only
# ./scripts/setup-trackme-certs.sh --install-ca # generate + install CA into system trust store

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CERT_DIR="$SCRIPT_DIR/../docker/trackme/certs"

mkdir -p "$CERT_DIR"

if [ ! -f "$CERT_DIR/chain.pem" ] || [ ! -f "$CERT_DIR/key.pem" ]; then
echo "Generating self-signed TLS certificates for TrackMe..."
openssl req -x509 -newkey rsa:2048 \
-keyout "$CERT_DIR/key.pem" \
-out "$CERT_DIR/chain.pem" \
-days 365 -nodes \
-subj "/CN=localhost" \
-addext "subjectAltName=IP:127.0.0.1,DNS:localhost"
echo "Certificates written to $CERT_DIR/"
else
echo "Certificates already exist in $CERT_DIR/, skipping generation."
fi

if [ "${1:-}" = "--install-ca" ]; then
echo "Installing CA certificate into system trust store..."
sudo cp "$CERT_DIR/chain.pem" /usr/local/share/ca-certificates/trackme-test.crt
sudo update-ca-certificates
echo "CA certificate installed. Go clients will now trust the local TrackMe instance."
fi

# Always print the SSL_CERT_FILE hint for no-sudo usage
COMBINED="/tmp/combined-trackme-cas.crt"
cat /etc/ssl/certs/ca-certificates.crt "$CERT_DIR/chain.pem" > "$COMBINED"
echo ""
echo "To run tests without sudo, set SSL_CERT_FILE before pytest:"
echo " SSL_CERT_FILE=$COMBINED TRACKME_URL=https://localhost:8443 uv run pytest -m blocking tests/"
22 changes: 20 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import sys
import os

# TrackMe base URL — override with TRACKME_URL env var to point at a local instance
_TRACKME_URL = os.environ.get("TRACKME_URL", "https://tls.peet.ws")

# Add parent directory to path to import cycletls
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

Expand All @@ -17,8 +20,17 @@ def cycletls_client():
"""
Session-scoped CycleTLS client fixture.
Creates a single client instance for all tests.

Connection reuse is disabled by default so that TrackMe-style servers
(which close connections after every response) don't leave a stale cached
connection in the global Go transport pool for the next test.
"""
client = CycleTLS()
_orig = client.request
def _no_reuse(method, url, **kwargs):
kwargs.setdefault("enable_connection_reuse", False)
return _orig(method, url, **kwargs)
client.request = _no_reuse
yield client
client.close()

Expand All @@ -37,13 +49,19 @@ def cycletls_client_function():
@pytest.fixture
def test_url():
"""Base test URL for most tests."""
return "https://tls.peet.ws/api/clean"
return f"{_TRACKME_URL}/api/clean"


@pytest.fixture
def ja3_test_url():
"""TLS fingerprint test URL (replacement for defunct ja3er.com)."""
return "https://tls.peet.ws/api/clean"
return f"{_TRACKME_URL}/api/clean"


@pytest.fixture(scope="session")
def trackme_url():
"""TrackMe base URL. Set TRACKME_URL env var to point at a local instance."""
return _TRACKME_URL


@pytest.fixture
Expand Down
7 changes: 5 additions & 2 deletions tests/integration/test_api.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import os
import pytest
from cycletls import CycleTLS, Request

_TRACKME_URL = os.environ.get("TRACKME_URL", "https://tls.peet.ws")

@pytest.fixture
def simple_request():
"""returns a simple request interface"""
return Request(url="https://tls.peet.ws/api/clean", method="get")
return Request(url=f"{_TRACKME_URL}/api/clean", method="get")

def test_api_call():
cycle = CycleTLS()
result = cycle.get("https://tls.peet.ws/api/clean")
result = cycle.get(f"{_TRACKME_URL}/api/clean")

cycle.close()
assert result.status_code == 200
Expand Down
Loading
Loading