diff --git a/Makefile b/Makefile index 6e8e0f5..118b1a3 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,19 @@ # ============================================================================= # ja4-platform — Monorepo Makefile -# All targets use new service names: -# sentinel, correlator, bot-detector, dashboard, mod-reqin-log +# Service de capture : ja4ebpf (eBPF CO-RE) # ============================================================================= -.PHONY: help build-all test-all rpm-all dist \ - build-sentinel test-sentinel rpm-sentinel \ - test-integration test-integration-keep test-integration-down \ - test-mod-reqin-log rpm-mod-reqin-log \ - build-correlator test-correlator rpm-correlator \ +VERSION ?= $(shell git describe --tags --always 2>/dev/null || echo dev) + +.PHONY: help \ + build-all test-all rpm-all dist \ + build-ja4ebpf test-ja4ebpf rpm-ja4ebpf \ build-bot-detector test-bot-detector \ build-dashboard test-dashboard \ test-ja4common-python \ + test-all-stacks test-nginx test-nginx-varnish test-hitch-varnish test-apache \ + test-matrix \ + test-integration test-integration-keep test-integration-down \ reload-prod-logs init-stack import-prod-data init-and-import \ purge-db @@ -24,30 +26,31 @@ help: ## Affiche cette aide @echo "" @echo " Build" @echo " make build-all Construit toutes les images Docker" - @echo " make build-sentinel Image sentinel (capture TLS/TCP)" - @echo " make build-correlator Image correlator (corrélation logs)" + @echo " make build-ja4ebpf Image ja4ebpf (agent eBPF CO-RE)" @echo " make build-bot-detector Image bot-detector (détection ML)" @echo " make build-dashboard Image dashboard (SOC UI)" @echo "" - @echo " Tests" + @echo " Tests unitaires" @echo " make test-all Lance tous les tests unitaires" - @echo " make test-sentinel Tests Go sentinel (NET_RAW)" - @echo " make test-correlator Tests Go correlator (80% coverage)" - @echo " make test-bot-detector Tests Python bot-detector (36 tests)" + @echo " make test-ja4ebpf Tests Go ja4ebpf" + @echo " make test-bot-detector Tests Python bot-detector" @echo " make test-dashboard Tests Python dashboard" @echo " make test-ja4common-python Tests Python ja4_common" - @echo " make test-mod-reqin-log Tests C mod-reqin-log (cmocka)" @echo "" - @echo " Intégration" - @echo " make test-integration Tests full-stack (Docker Compose)" - @echo " make test-integration-keep Idem, stack reste active après" - @echo " make test-integration-down Arrête la stack d'intégration" + @echo " Tests d'intégration (par stack)" + @echo " make test-all-stacks Toutes les stacks sur Rocky Linux 9" + @echo " make test-apache Stack Apache + ja4ebpf" + @echo " make test-nginx Stack nginx + ja4ebpf" + @echo " make test-nginx-varnish Stack nginx + Varnish + ja4ebpf" + @echo " make test-hitch-varnish Stack hitch + Varnish + ja4ebpf" + @echo "" + @echo " Matrice multi-distro" + @echo " make test-matrix Toutes stacks × el8/el9/el10" + @echo " make test-matrix MATRIX_STACKS=nginx,apache MATRIX_DISTROS=el9,el10" @echo "" @echo " RPM" - @echo " make rpm-all Construit tous les RPMs (el8/el9/el10)" - @echo " make rpm-sentinel RPM sentinel" - @echo " make rpm-correlator RPM correlator" - @echo " make rpm-mod-reqin-log RPM mod-reqin-log" + @echo " make rpm-all Construit tous les RPMs ja4ebpf (el8/el9/el10)" + @echo " make rpm-ja4ebpf RPMs ja4ebpf (el8, el9, el10)" @echo " make dist Alias de rpm-all" @echo "" @echo " Base de données" @@ -58,84 +61,50 @@ help: ## Affiche cette aide @echo " make purge-db Supprime et recrée les bases ja4_*" @echo "" -# --- Root ------------------------------------------------------------------- +# ── Cibles agrégées ────────────────────────────────────────────────────────── -build-all: build-sentinel build-correlator build-bot-detector build-dashboard +build-all: build-ja4ebpf build-bot-detector build-dashboard @echo "All services built." -test-all: test-sentinel test-correlator test-bot-detector test-dashboard test-ja4common-python - @echo "All tests completed." +test-all: test-ja4ebpf test-bot-detector test-dashboard test-ja4common-python + @echo "All unit tests completed." -rpm-all: rpm-sentinel rpm-correlator rpm-mod-reqin-log +rpm-all: rpm-ja4ebpf @echo "All RPMs built." dist: rpm-all - @echo "Distribution packages ready in services/*/dist/" + @echo "RPMs disponibles dans services/ja4ebpf/dist/" -# --- sentinel (was ja4sentinel) --------------------------------------------- +# ── ja4ebpf (agent eBPF CO-RE) ─────────────────────────────────────────────── -build-sentinel: +build-ja4ebpf: docker build \ - --build-arg VERSION=$$(git -C services/sentinel describe --tags --always 2>/dev/null || echo dev) \ - --build-arg GIT_COMMIT=$$(git rev-parse --short HEAD 2>/dev/null || echo unknown) \ - --build-arg BUILD_TIME=$$(date -u +%Y-%m-%dT%H:%M:%SZ) \ - -f services/sentinel/Dockerfile \ - -t ja4-platform/sentinel:latest \ + -f services/ja4ebpf/Dockerfile \ + --build-arg BUILD_VERSION=$(VERSION) \ + -t ja4-platform/ja4ebpf:latest \ . -test-sentinel: - # Tests run inside Docker — no native Go required on the host - docker build -f services/sentinel/Dockerfile.dev -t ja4-platform/sentinel-tests:latest . - docker run --rm --cap-add=NET_RAW --cap-add=NET_ADMIN ja4-platform/sentinel-tests:latest - -rpm-sentinel: - # Méthode: Dockerfile.package → builder Go → rpm-builder (rpmbuild ×3) → output alpine +test-ja4ebpf: docker build \ - -f services/sentinel/Dockerfile.package \ + -f services/ja4ebpf/Dockerfile.tests \ + -t ja4-platform/ja4ebpf-tests:latest \ + . + docker run --rm ja4-platform/ja4ebpf-tests:latest + +rpm-ja4ebpf: + # Build multi-distro : el8 (AlmaLinux 8) + el9 (Rocky 9) + el10 (AlmaLinux 10) + # Sortie : services/ja4ebpf/dist/el{8,9,10}/ja4ebpf-*.rpm + docker build \ + -f services/ja4ebpf/Dockerfile.package \ --target output \ - --output type=local,dest=services/sentinel/dist \ - --build-arg VERSION=$(shell git -C services/sentinel describe --tags --always 2>/dev/null || echo dev) \ + --output type=local,dest=services/ja4ebpf/dist \ + --build-arg BUILD_VERSION=$(VERSION) \ . - @echo "📦 RPMs sentinel dans services/sentinel/dist/" + @echo "" + @echo "RPMs produits :" + @find services/ja4ebpf/dist -name '*.rpm' | sort | sed 's/^/ /' -# --- mod-reqin-log (was mod_reqin_log) -------------------------------------- - -test-mod-reqin-log: - docker build -f services/mod-reqin-log/Dockerfile.tests -t ja4-platform/mod-reqin-log-tests:latest . - docker run --rm ja4-platform/mod-reqin-log-tests:latest - -rpm-mod-reqin-log: - # Méthode: Dockerfile.package → builder C (×3 distros) → rpm-builder (rpmbuild ×3) → output alpine - docker build \ - -f services/mod-reqin-log/Dockerfile.package \ - --target output \ - --output type=local,dest=services/mod-reqin-log/dist \ - . - @echo "📦 RPMs mod-reqin-log dans services/mod-reqin-log/dist/" - -# --- correlator (was logcorrelator) ----------------------------------------- - -build-correlator: - docker build \ - -f services/correlator/Dockerfile \ - -t ja4-platform/correlator:latest \ - . - -test-correlator: - # Tests run inside the Dockerfile builder stage (80% coverage gate enforced) - docker build --target builder -f services/correlator/Dockerfile -t ja4-platform/correlator-tests:latest . - -rpm-correlator: - # Méthode: Dockerfile.package → builder Go → rpm-builder (rpmbuild ×3) → output alpine - docker build \ - -f services/correlator/Dockerfile.package \ - --target output \ - --output type=local,dest=services/correlator/dist \ - --build-arg VERSION=$(shell git -C services/correlator describe --tags --always 2>/dev/null || echo dev) \ - . - @echo "📦 RPMs correlator dans services/correlator/dist/" - -# --- bot-detector (was bot_detector) ---------------------------------------- +# ── bot-detector ───────────────────────────────────────────────────────────── build-bot-detector: docker build \ @@ -150,7 +119,7 @@ test-bot-detector: . docker run --rm ja4-platform/bot-detector-tests:latest -# --- dashboard -------------------------------------------------------------- +# ── dashboard ──────────────────────────────────────────────────────────────── build-dashboard: docker build \ @@ -165,7 +134,7 @@ test-dashboard: . docker run --rm ja4-platform/dashboard-tests:latest -# --- shared/python/ja4_common ----------------------------------------------- +# ── shared/python/ja4_common ───────────────────────────────────────────────── test-ja4common-python: docker build \ @@ -174,18 +143,47 @@ test-ja4common-python: shared/python/ja4_common/ docker run --rm ja4-platform/ja4common-python-tests:latest -# --- integration (full-stack) ----------------------------------------------- +# ── Tests d'intégration par stack ──────────────────────────────────────────── -test-integration: - cd tests/integration && ./run-tests.sh +test-all-stacks: ## Toutes les stacks sur la distro par défaut (Rocky Linux 9) + cd tests/integration && bash run-all-stacks.sh + +test-apache: + cd tests/integration && bash apache/run-tests.sh + +test-nginx: + cd tests/integration && bash nginx/run-tests.sh + +test-nginx-varnish: + cd tests/integration && bash nginx-varnish/run-tests.sh + +test-hitch-varnish: + cd tests/integration && bash hitch-varnish/run-tests.sh + +# ── Matrice multi-distro ───────────────────────────────────────────────────── + +test-matrix: ## Toutes stacks × el8 + el9 + el10 + cd tests/integration && bash run-distro-matrix.sh \ + $${MATRIX_STACKS:+--stacks=$${MATRIX_STACKS}} \ + $${MATRIX_DISTROS:+--distros=$${MATRIX_DISTROS}} + +# ── Compat : anciens targets d'intégration ─────────────────────────────────── + +test-integration: ## Ancien target — alias vers test-all-stacks + $(MAKE) test-all-stacks test-integration-keep: - cd tests/integration && ./run-tests.sh --no-down + cd tests/integration && bash run-all-stacks.sh --no-down test-integration-down: - cd tests/integration && docker compose down -v --remove-orphans + cd tests/integration && \ + for stack in apache nginx nginx-varnish hitch-varnish; do \ + [ -f "$$stack/docker-compose.yml" ] && \ + docker compose -f "$$stack/docker-compose.yml" down -v --remove-orphans 2>/dev/null || true; \ + done + +# ── Base de données ─────────────────────────────────────────────────────────── -# ── Dev data ───────────────────────────────────────────────────────────────── reload-prod-logs: ./scripts/reload-prod-logs.sh diff --git a/README.md b/README.md index 08130d0..e69b01e 100644 --- a/README.md +++ b/README.md @@ -1,169 +1,166 @@ # ja4-platform -**ja4-platform** is a monorepo security pipeline for TLS fingerprinting (JA4/JA3) and bot detection. It captures live network traffic, correlates TLS handshakes with HTTP requests, applies triple-voice ML anomaly detection (Extended Isolation Forest + Autoencoder + XGBoost), and surfaces results through a SOC analyst dashboard — all backed by ClickHouse with a dual-database architecture. +**ja4-platform** est un pipeline de sécurité monorepo pour le fingerprinting TLS (JA4/JA3) et la détection de bots HTTP. Un agent unique basé sur eBPF observe passivement le trafic réseau de manière non-intrusive, reconstruit les sessions TCP/TLS/HTTP en mémoire, et alimente une base ClickHouse pour la détection d'anomalies par apprentissage automatique et la présentation dans un tableau de bord SOC. ## Pipeline Overview ``` - ┌──────────────────────────────────────────────────────────────────────────────┐ - │ Linux Server (Apache) │ - │ │ - │ ┌─────────────────┐ UNIX socket (DGRAM) ┌──────────────────┐ │ - │ │ mod-reqin-log │──── http.socket ────────────────▶│ │ │ - │ │ (Apache C11) │ (source A) │ correlator │ │ - │ └─────────────────┘ │ (Go · hex. │ │ - │ │ architecture) │ │ - │ ┌─────────────────┐ UNIX socket (DGRAM) │ │ │ - │ │ sentinel │──── network.socket ─────────────▶│ Joins by │ │ - │ │ (Go · libpcap) │ (source B) │ src_ip:src_port│ │ - │ │ JA4/JA3 gen. │ └────────┬─────────┘ │ - │ └─────────────────┘ │ │ - └──────────────────────────────────────────────────────────────────┼────────────┘ - │ INSERT - ▼ - ┌──────────────────────────────────────┐ - │ ClickHouse 24.8 │ - │ │ - │ ja4_logs ja4_processing │ - │ ┌──────────┐ ┌──────────────┐ │ - │ │_raw → MV │────▶│ agg_* (×6) │ │ - │ │→ http_logs│ │ ml_* (×2) │ │ - │ └──────────┘ │ views, dicts │ │ - │ └──────────────┘ │ - └─────────┬───────────────┬────────────┘ - │ │ - ┌────────────────┘ └───────────────┐ - ▼ ▼ - ┌────────────────────┐ ┌────────────────────┐ - │ bot-detector │ │ dashboard │ - │ Python 3.11 │ │ FastAPI + Jinja2 │ - │ EIF + AE + XGBoost │ │ htmx + Chart.js │ - │ HDBSCAN · SHAP │ │ 55 routes · 14 pp │ - └────────────────────┘ └────────────────────┘ + +-----------------------------------------------------------------------+ + | Linux Server (Apache / Nginx / Varnish / Hitch) | + | | + | +-------------------------------------------------------------+ | + | | ja4ebpf (agent eBPF GO) | | + | | | | + | | Hook TC ingress (L3/L4/L5 - passif) : | | + | reseau| SYN -> TTL, DF, IP-ID, MSS, Window, Scale | | + | ----->| TLS -> ClientHello : JA4, ALPN, SNI, extensions | | + | XDP/TC| HTTP -> payload port 80/8080 (magic bytes router) | | + | | | | + | uprobe| Hook SSL_read (L7 - trafic dechiffre) : | | + | ----->| HTTP/1.1 -> methode, path, headers (ordre exact) | | + | | HTTP/2 -> SETTINGS, WINDOW_UPDATE, pseudo-headers | | + | | | | + | | Correlation in-memory src_ip:src_port | | + | | 256 shards . timeout 500ms . GC 100ms | | + | +----------------------------+---------------------------------+ | + +-----------------------------------------------------------------------+ + | INSERT batch (Native TCP :9000) + v + +------------------------------------------+ + | ClickHouse 24.8 | + | | + | ja4_logs ja4_processing | + | +-----------+ +--------------+ | + | |_raw -> MV |------->| agg_* (x6) | | + | |-> http_logs | ml_* (x2) | | + | +-----------+ | views, dicts | | + +------+---------------------------+----+ + | | + +--------+ +----------+ + v v + +--------------------+ +--------------------+ + | bot-detector | | dashboard | + | Python 3.11 | | FastAPI + Jinja2 | + | EIF + AE + XGBoost| | htmx + Chart.js | + | HDBSCAN . SHAP | | SOC analyst UI | + +--------------------+ +--------------------+ ``` ## Services -| Service | Language | Description | Interface | -|---------|----------|-------------|-----------| -| [sentinel](docs/services/sentinel.md) | Go 1.24.6 | TLS/TCP packet capture via libpcap, JA4/JA3 fingerprint generation | UNIX socket → `network.socket` | -| [mod-reqin-log](docs/services/mod-reqin-log.md) | C11 | Apache HTTPD module, HTTP request JSON logging | UNIX socket → `http.socket` | -| [correlator](docs/services/correlator.md) | Go 1.24.6 | Hexagonal architecture, correlates HTTP+TLS events by `src_ip:src_port` | ClickHouse INSERT (Native TCP) | -| [bot-detector](docs/services/bot-detector.md) | Python 3.11 | Triple-voice ML ensemble (EIF+AE+XGB), HDBSCAN campaigns, SHAP explainability | ClickHouse read/write, HTTP `:8080` | -| [dashboard](docs/services/dashboard.md) | Python 3.11 | SOC analyst dashboard: 55 routes, 15 templates, 14 pages | HTTP `:8000` | +| Service | Langage | Description | Interface | +|---------|---------|-------------|-----------| +| [ja4ebpf](docs/services/ja4ebpf.md) | Go 1.24 + C eBPF (CO-RE) | Agent eBPF passif : TC ingress (L3/L4/L5), uprobe SSL_read (L7 HTTPS), TC port 80/8080 (HTTP clair), corrélation in-memory, insert ClickHouse | INSERT batch ClickHouse | +| [bot-detector](docs/services/bot-detector.md) | Python 3.11 | Ensemble ML triple-voix (EIF+AE+XGB), clustering HDBSCAN, explicabilité SHAP | ClickHouse read/write, HTTP `:8080` | +| [dashboard](docs/services/dashboard.md) | Python 3.11 | Tableau de bord SOC : 9 endpoints JSON, 8 pages HTML | HTTP `:8000` | -## Shared Libraries +## Bibliothèques partagées -| Library | Language | Description | -|---------|----------|-------------| +| Bibliothèque | Langage | Description | +|--------------|---------|-------------| | [go/ja4common](docs/shared/go-ja4common.md) | Go | Logger, config loader, graceful shutdown handler, IP filter | | [python/ja4_common](docs/shared/python-ja4common.md) | Python | `ClickHouseClient` singleton, `ClickHouseSettings` (pydantic-settings) | ## Quickstart -### Prerequisites +### Prérequis -- Docker (with BuildKit) and Docker Compose +- Docker (avec BuildKit) et Docker Compose - `make` -- No native Go, Python, or C toolchains required — all builds run inside Docker +- Aucune toolchain Go, Python, C ou eBPF n'est requise sur la machine hôte — tous les builds s'exécutent dans Docker Rocky Linux. -### Build All Services +### Build de tous les services ```bash make build-all ``` -### Run All Tests +### Exécution de tous les tests ```bash make test-all ``` -### Build RPM Packages +### Build des paquets RPM ```bash -make rpm-all -# RPMs written to services//dist/rpm/el{8,9,10}/ +make rpm-ja4ebpf +# RPMs écrits dans services/ja4ebpf/dist/rpm/el{8,9,10}/ ``` +## Tests d'intégration + +Tests full-stack avec Docker Compose et une vraie instance ClickHouse : + +```bash +make test-integration # stack Apache référence (8 phases) +make test-nginx # stack nginx + ja4ebpf +make test-nginx-varnish # nginx + Varnish + ja4ebpf +make test-hitch-varnish # hitch (TLS) + Varnish + ja4ebpf +make test-all-stacks # les 3 stacks serveur en séquence +make test-integration-keep # laisse la stack en fonctionnement +make test-integration-down # démontage de la stack +``` + +La suite de tests se trouve dans `tests/integration/` et réinitialise la base entre chaque exécution. + ## Scripts -Helper scripts are located in `scripts/`: - | Script | Description | |--------|-------------| -| `init-stack.sh` | Full ClickHouse stack initialization — deploys schema, loads CSV data, verifies all components | -| `import-prod-data.sh` | Imports pre-exported production data into the dev database with dynamic date shifting | -| `reload-prod-logs.sh` | Exports `http_logs` from production and re-imports into the dev database | -| `update-csv-data.sh` | Downloads and generates all CSV reference data (bot IPs, JA4 signatures, ASN reputation) | -| `generate_bot_ip.py` | Generates `bot_ip.csv` from known scanner/bot sources + Tor exit nodes | -| `generate_bot_ja4.py` | Generates `bot_ja4.csv` from known bot TLS fingerprints | -| `generate_asn_data.py` | Generates `asn_reputation.csv` (ASN→label mapping) | -| `generate_browser_ja4.py` | Generates browser JA4 reference data for legitimate browser detection | +| `init-stack.sh` | Initialisation complète du schéma ClickHouse + chargement CSV | +| `import-prod-data.sh` | Import de données de production avec décalage temporel | +| `reload-prod-logs.sh` | Export prod -> réimport dev avec décalage | +| `update-csv-data.sh` | Génération des CSV de référence (bot IPs, signatures JA4, ASN) | -Corresponding Makefile targets: +Cibles Makefile correspondantes : ```bash -make init-stack # runs scripts/init-stack.sh -make import-prod-data # runs scripts/import-prod-data.sh +make init-stack # schéma + CSV +make import-prod-data # données prod make init-and-import # init-stack + import-prod-data -make reload-prod-logs # runs scripts/reload-prod-logs.sh +make reload-prod-logs # rechargement depuis la prod ``` -## Integration Tests - -Full-stack integration tests run against Docker Compose with a real ClickHouse instance: - -```bash -make test-integration # 8 phases: build → start → schema → traffic → pipeline → dashboard → bot-detector → sentinel -make test-integration-keep # same but leaves stack running after -make test-integration-down # tear down integration stack -``` - -The integration test suite is located in `tests/integration/` and resets the database between runs. - -## Documentation - -| Document | Description | -|----------|-------------| -| [Architecture](docs/architecture.md) | System architecture, data flow, component interactions | -| [Deployment](docs/deployment.md) | Step-by-step production deployment guide | -| [Development](docs/development.md) | Build, test, package, and extend the platform | -| [Database Schema](docs/database/schema.md) | Every ClickHouse table, view, dictionary, and materialized view | -| [Database Migrations](docs/database/migrations.md) | Migration order, application, verification, and rollback | -| [Commenting Standard](docs/commenting-standard.md) | Code commenting conventions (French comments, English identifiers) | -| [Thesis Reference](docs/THESIS_HTTP_Traffic_Detection.md) | Academic reference: HTTP traffic detection techniques | -| [Audit vs Thesis](docs/AUDIT_Detection_vs_Thesis.md) | Comparison between platform implementation and thesis techniques | - -### Service Documentation - -- [Sentinel](docs/services/sentinel.md) — TLS/TCP capture daemon (Go + libpcap) -- [mod-reqin-log](docs/services/mod-reqin-log.md) — Apache HTTP logging module (C11) -- [Correlator](docs/services/correlator.md) — HTTP/TLS event correlation engine (Go) -- [Bot Detector](docs/services/bot-detector.md) — Triple-voice ML anomaly detection (Python) -- [Dashboard](docs/services/dashboard.md) — SOC analyst dashboard and API (FastAPI) - -### Shared Library Documentation - -- [go-ja4common](docs/shared/go-ja4common.md) — Go shared library (logger, config, shutdown, ipfilter) -- [python-ja4common](docs/shared/python-ja4common.md) — Python shared library (ClickHouse client, settings) - ## Go Workspace -The repository uses a Go workspace (`go.work`) to link the Go modules: +Le workspace `go.work` lie les modules Go du dépôt : ``` go 1.24.6 use ( - ./services/sentinel - ./services/correlator ./shared/go/ja4common + ./services/ja4ebpf ) ``` -Both Go services have a `replace` directive in their `go.mod` pointing to `../../shared/go/ja4common`. The workspace takes precedence for local development; the `replace` is needed for Docker builds where `go.work` is not available. +`ja4ebpf` utilise une directive `replace` dans son `go.mod` vers `../../shared/go/ja4common`. Le workspace prend priorité en développement local ; la directive `replace` est nécessaire pour les builds Docker. + +## Documentation + +| Document | Description | +|----------|-------------| +| [Architecture](docs/architecture.md) | Architecture système, flux de données, interactions entre composants | +| [Deployment](docs/deployment.md) | Guide de déploiement en production | +| [Development](docs/development.md) | Build, test, packaging et extension de la plateforme | +| [Database Schema](docs/database/schema.md) | Tables, vues, dictionnaires et vues matérialisées ClickHouse | +| [Database Migrations](docs/database/migrations.md) | Ordre de migration, application, vérification et rollback | +| [Commenting Standard](docs/commenting-standard.md) | Conventions de commentaires (commentaires français, identifiants anglais) | +| [Thesis Reference](docs/THESIS_HTTP_Traffic_Detection.md) | Référence académique : techniques de détection du trafic HTTP | +| [Audit vs Thesis](docs/AUDIT_Detection_vs_Thesis.md) | Comparaison entre l'implémentation et les techniques de la thèse | + +### Documentation des services + +- [ja4ebpf](docs/services/ja4ebpf.md) — Agent eBPF CO-RE (Go + C), capture réseau passive multi-couches +- [Bot Detector](docs/services/bot-detector.md) — Détection ML d'anomalies triple-voix (Python) +- [Dashboard](docs/services/dashboard.md) — Tableau de bord SOC et API (FastAPI) + +### Documentation des bibliothèques partagées + +- [go-ja4common](docs/shared/go-ja4common.md) — Bibliothèque Go partagée (logger, config, shutdown, ipfilter) +- [python-ja4common](docs/shared/python-ja4common.md) — Bibliothèque Python partagée (client ClickHouse, settings) ## License -See individual service directories for license information. +Voir les répertoires des services individuels pour les informations de licence. diff --git a/docs/AUDIT_Detection_vs_Thesis.md b/docs/AUDIT_Detection_vs_Thesis.md index f35f098..85360d8 100644 --- a/docs/AUDIT_Detection_vs_Thesis.md +++ b/docs/AUDIT_Detection_vs_Thesis.md @@ -23,9 +23,9 @@ | Composant thèse | Statut | Détail | |-----------------|--------|--------| -| Pipeline L3-L5 (ja4sentinel) | ✅ | TTL, IP-ID, DF, TCP win/mss/scale, JA4/JA3, ALPN, SNI | -| Pipeline L7 (mod_reqin_log) | ✅ | Headers, méthode, path, query, timestamps ns | -| Corrélation (logcorrelator) | ✅ | Clé `src_ip:src_port`, Keep-Alive, orphelins | +| Pipeline L3-L5 (ja4ebpf TC ingress) | ✅ | TTL, IP-ID, DF, TCP win/mss/scale, JA4/JA3, ALPN, SNI via hooks TC + kprobe accept4 | +| Pipeline L7 (ja4ebpf uprobe SSL_read) | ✅ | Headers, méthode, path, query, timestamps ns — HTTP/1.1 et HTTP/2 via uprobe SSL_read | +| Corrélation (ja4ebpf in-memory) | ✅ | Clé `src_ip:src_port`, 256 shards, Keep-Alive, orphelins, Slowloris 10s | | Enrichissement ASN | ✅ | `dict_iplocate_asn` (714K CIDRs, 4 colonnes) | | Enrichissement Anubis | ✅ | Simplifié à `COALESCE(IP, ASN)` — 2 dictionnaires (`dict_anubis_ip` IP_TRIE, `dict_anubis_asn`) | | Agrégation 1h | ✅ | 6 tables : `agg_host_ip_ja4_1h`, `agg_header_fingerprint_1h`, `agg_ip_behavior_1h`, `agg_request_timing_1h`, `agg_path_sequences_1h`, `agg_resource_cascade_1h` | @@ -132,7 +132,7 @@ | §5.3 Request Cadence Fingerprint | ✅ | `cadence_cv`, `burst_ratio`, `lag1_autocorrelation`, `benford_deviation`, `pause_ratio` dans `agg_request_timing_1h` | | §5.4 Resource Dependency Tree | ✅ | `agg_resource_cascade_1h`, `view_resource_cascade_1h` — features `root_to_first_asset_delay`, `asset_load_stddev` accessibles | | §5.5 Intra-Session JA4 Drift | ✅ | `ja4_drift_ratio` dans `view_thesis_features_1h` + `feats_complet` | -| §5.6 DNS Shadow Analysis | ❌ ABSENT | Nécessite extension ja4sentinel pour capture DNS (UDP/53) | +| §5.6 DNS Shadow Analysis | ❌ ABSENT | Nécessite extension ja4ebpf pour capture DNS (UDP/53) | | §5.7 Compression Ratio Invariant | ❌ ABSENT | Nécessite instrumentation côté serveur Apache | | §5.8 Cross-Domain Session Linking | ✅ | `host_diversity`, `host_sweep_speed`, `host_coverage_uniformity` + `cross_domain_path_similarity` (Jaccard) dans `view_thesis_features_1h` | @@ -293,7 +293,7 @@ Le module `api.py` expose **37 endpoints JSON** couvrant l'ensemble des besoins | Priorité | Gap | Impact | Remarque | |----------|-----|--------|----------| -| P2 🟡 | §5.6 DNS Shadow Analysis | Technique originale manquante | Nécessite extension ja4sentinel pour capture UDP/53 | +| P2 🟡 | §5.6 DNS Shadow Analysis | Technique originale manquante | Nécessite extension ja4ebpf pour capture UDP/53 | | P2 🟡 | §5.7 Compression Ratio Invariant | Technique originale manquante | Nécessite instrumentation côté serveur Apache | | P3 ⚪ | Authentification dashboard | Sécurité opérationnelle | Non exigé par la thèse — environnement SOC intranet | | P3 ⚪ | CSRF sur `/api/classify` | Sécurité opérationnelle | Mitigé en déploiement restreint | @@ -319,11 +319,11 @@ Le module `api.py` expose **37 endpoints JSON** couvrant l'ensemble des besoins - **`deploy_schema.sh`** : déploiement automatisé avec substitution des noms de bases depuis les variables d'environnement (`CLICKHOUSE_DB_LOGS`, `CLICKHOUSE_DB_PROCESSING`) - **Dual database** : `ja4_logs` (logs bruts, enrichis, MV) + `ja4_processing` (agrégations, ML, vues, dictionnaires, audit) - **7 dictionnaires** : `dict_iplocate_asn`, `dict_bot_ip`, `dict_bot_ja4`, `dict_browser_ja4`, `dict_asn_reputation`, `dict_anubis_ip`, `dict_anubis_asn` -- **Migrations post-déploiement** : `services/correlator/sql/migrations/` (ALTER TABLE pour déploiements existants) +- **Migrations post-déploiement** : `shared/clickhouse/` (ALTER TABLE pour déploiements existants) ### E3. Pipeline de build et tests - **Docker-first** : chaque service dispose de `Dockerfile` (prod), `Dockerfile.dev` ou `Dockerfile.tests` (tests), et `Dockerfile.package` (RPM) pour les services Go/C -- **Tests d'intégration** : suite complète en 8 phases (build → start → schema → traffic → pipeline → dashboard → bot-detector → sentinel) via `make test-integration` -- **Couverture** : tests Go (80% gate pour le correlator), tests Python (pytest pour bot-detector, dashboard, ja4_common), tests C (cmocka pour mod_reqin_log) +- **Tests d'intégration** : suite complète en 8 phases (build → start → schema → traffic → pipeline → dashboard → bot-detector → ja4ebpf) via `make test-integration` +- **Couverture** : tests Go (ja4ebpf), tests Python (pytest pour bot-detector, dashboard, ja4_common) - **RPM packaging** : 3 distributions (el8/el9/el10) via Rocky Linux / AlmaLinux diff --git a/docs/THESIS_HTTP_Traffic_Detection.md b/docs/THESIS_HTTP_Traffic_Detection.md index 8175f18..585705c 100644 --- a/docs/THESIS_HTTP_Traffic_Detection.md +++ b/docs/THESIS_HTTP_Traffic_Detection.md @@ -53,7 +53,7 @@ Ce document présente une architecture complète de détection et classification - 3.3 Couche L4 — TCP - 3.4 Couche L5 — TLS - 3.5 Couche L7 — HTTP - - 3.6 Corrélation inter-couches (logcorrelator) + - 3.6 Corrélation inter-couches (ja4ebpf) - 3.7 Agrégation temporelle et features dérivées - 3.8 Détection ML semi-supervisée (full pipeline) @@ -112,7 +112,7 @@ La quatrième génération en émergence combine : analyse corrélée en temps r Ce document décrit une architecture opérationnelle positionnée à la frontière Gen3/Gen4, déployée en production sur un serveur Apache. Les contributions techniques originales incluent : -1. **Corrélation TCP/TLS/HTTP** en temps réel via le module logcorrelator (clé : `src_ip:src_port`, timeout orphelin 500 ms) +1. **Corrélation TCP/TLS/HTTP** en temps réel via ja4ebpf (clé : `src_ip:src_port`, 256 shards, timeout orphelin 500 ms) 2. **Fingerprinting HTTP/2 passif** : extraction des trames SETTINGS, WINDOW_UPDATE, PRIORITY et de l'ordre des pseudo-headers directement depuis le stream TCP 3. **Architecture EIF bifurquée** : modèle complet (≈ 45 features L3→L7) et modèle applicatif (≈ 35 features L7 uniquement), évitant le biais de zérotage sur le trafic non corrélé 4. **Ensemble triple-voix + MetaLearner** : fusion EIF + AE + XGBoost avec régression logistique apprise sur étiquettes accumulées @@ -461,10 +461,10 @@ Résultat : les coupes ne sont plus liées aux axes, éliminant les artéfacts g Deux modèles EIF s'exécutent en parallèle à chaque cycle de 300 secondes : -- **Modèle Complet** (≈ 45 features, L3→L7) : appliqué sur les sessions corrélées (correlated=1) pour lesquelles ja4sentinel a pu corréler les métadonnées TCP/TLS avec la requête HTTP. Inclut toutes les features des familles F1–F7. +- **Modèle Complet** (≈ 45 features, L3→L7) : appliqué sur les sessions corrélées (correlated=1) pour lesquelles ja4ebpf a pu corréler les métadonnées TCP/TLS avec la requête HTTP. Inclut toutes les features des familles F1–F7. - **Modèle Applicatif** (≈ 35 features, L7 uniquement) : appliqué sur les sessions non corrélées (correlated=0) — trafic passant par un CDN ou proxy qui ne permet pas la corrélation TCP/TLS. Zeroed les features TCP/TLS pour ce modèle éviterait d'introduire un biais systématique de zéro-imputation. -La bifurcation est justifiée par le fait que les features TCP/TLS ne sont disponibles que lorsque ja4sentinel a corrélé la connexion réseau avec la requête HTTP. Imputer ces features à zéro pour le trafic non corrélé créerait un signal artificiel (zéro n'est pas neutre pour un EIF — il est interprété comme une valeur réelle). +La bifurcation est justifiée par le fait que les features TCP/TLS ne sont disponibles que lorsque ja4ebpf a corrélé la connexion réseau avec la requête HTTP. Imputer ces features à zéro pour le trafic non corrélé créerait un signal artificiel (zéro n'est pas neutre pour un EIF — il est interprété comme une valeur réelle). #### 2.4.2b Autoencoders (AE) et détection d'anomalies @@ -799,18 +799,18 @@ httpcloak est un outil d'évasion qui tente d'imiter l'empreinte TLS de Chrome. ┌──────────────────────────────────────────────────────────────────┐ │ SOURCES DE DONNÉES │ ├───────────────────────────┬──────────────────────────────────────┤ -│ ja4sentinel │ mod_reqin_log │ -│ (Source B) │ (Source A) │ +│ TC ingress (XDP/TC) │ uprobe SSL_read │ +│ Couches L3/L4/L5 │ Couche L7 HTTP déchiffré │ │ │ │ -│ libpcap, CAP_NET_RAW │ Module Apache personnalisé │ -│ Couches L3/L4/L5 │ Couches L7 HTTP + L5 HTTP/2 │ -│ - IP header (TTL, DF) │ - src_ip, src_port │ -│ - TCP SYN (MSS, Window, │ - timestamp_ns (nanoseconde) │ -│ Options, Scale) │ - méthode, chemin, query │ -│ - TLS ClientHello │ - version HTTP │ -│ (JA4, JA4T, ALPN, SNI) │ - en-têtes (bruts + ordre) │ -│ │ - statut, taille, durée_ms │ -│ │ - HTTP/2 preface passif : │ +│ │ │ +│ réseau XDP/TC → │ Go Magic Bytes dispatcher → │ +│ - SYN : TTL, IP-ID, DF, │ HTTP/1.1 : method, path, query, │ +│ MSS, Window, Scale │ headers (bruts + ordre), │ +│ - TLS ClientHello : │ status, taille, durée_ms, │ +│ JA4, ALPN, SNI │ timestamp_ns (nanoseconde) │ +│ - HTTP port 80/8080 │ HTTP/2 (depuis preface client) : │ +│ │ ordre pseudo-headers │ +│ │ WINDOW_UPDATE, PRIORITY flag, │ │ │ SETTINGS (7 params individuels), │ │ │ WINDOW_UPDATE, PRIORITY flag, │ │ │ ordre pseudo-headers │ @@ -819,8 +819,8 @@ httpcloak est un outil d'évasion qui tente d'imiter l'empreinte TLS de Chrome. └─────────────┬─────────────┘ │ ┌─────────▼─────────┐ - │ logcorrelator │ - │ │ + │ Corrélation in-memory │ + │ (ja4ebpf) │ │ Clé: src_ip:port │ │ Keep-Alive multi-│ │ request tracking │ @@ -899,7 +899,7 @@ Features : `avg_ttl` (TTL moyen sur la fenêtre), `ttl_std` (écart-type du TTL ### 3.3 Couche L4 — TCP -**TCP Keep-Alive et multiplexage HTTP** : à ne pas confondre avec HTTP Keep-Alive (`Connection: keep-alive`). Le TCP Keep-Alive est un mécanisme de détection de connexions mortes au niveau du noyau (envoi de paquets ACK vides après inactivité). L'HTTP Keep-Alive, en revanche, maintient la connexion TCP ouverte pour réutilisation par plusieurs requêtes HTTP successives. Le module mod_reqin_log trace le nombre de requêtes HTTP dans chaque connexion TCP via `max_keepalives`. +**TCP Keep-Alive et multiplexage HTTP** : à ne pas confondre avec HTTP Keep-Alive (`Connection: keep-alive`). Le TCP Keep-Alive est un mécanisme de détection de connexions mortes au niveau du noyau (envoi de paquets ACK vides après inactivité). L'HTTP Keep-Alive, en revanche, maintient la connexion TCP ouverte pour réutilisation par plusieurs requêtes HTTP successives. ja4ebpf trace le nombre de requêtes HTTP dans chaque connexion TCP via `max_keepalives` dans le gestionnaire de corrélation in-memory. **Coefficient de variation (CV)** : mesure adimensionnelle de variabilité, CV = σ/μ. Un CV ≈ 0 indique une régularité élevée (automatisation à timer fixe) ; un CV ≈ 1–3 indique une variabilité naturelle (humain). Applicable à `syn_timing_cv` (variabilité du délai SYN→ClientHello) et à `cadence_cv` (variabilité des intervalles inter-requêtes). @@ -932,18 +932,18 @@ Features : `avg_ttl` (TTL moyen sur la fenêtre), `ttl_std` (écart-type du TTL ### 3.5 Couche L7 — HTTP -La couche L7 constitue la couche la plus riche en features comportementales. Le module **mod_reqin_log** est un module Apache personnalisé qui enregistre chaque requête HTTP avec une précision nanoseconde via `clock_gettime(CLOCK_REALTIME)`. +La couche L7 constitue la couche la plus riche en features comportementales. L'agent **ja4ebpf** capture le flux HTTP déchiffré via un uprobe sur `SSL_read` (OpenSSL/BoringSSL), avec une précision nanoseconde via `bpf_ktime_get_ns()`. -Données capturées par mod_reqin_log : `src_ip`, `src_port`, `timestamp_ns` (nanoseconde absolu), `method`, `path`, `query_string`, `http_version`, `headers_raw` (en-têtes bruts dans leur ordre d'émission), `header_order_signature` (hash de l'ordre), `status_code`, `response_size`, `duration_ms`. L'horodatage nanoseconde est critique pour le calcul des features temporelles F8 (cadence_cv, lag1_autocorrelation, benford_deviation, root_to_first_asset_delay). +Données capturées par ja4ebpf : `src_ip`, `src_port`, `timestamp_ns` (nanoseconde absolu), `method`, `path`, `query_string`, `http_version`, `headers_raw` (en-têtes bruts dans leur ordre d'émission), `header_order_signature` (hash de l'ordre), `status_code`, `response_size`, `duration_ms`. L'horodatage nanoseconde est critique pour le calcul des features temporelles F8 (cadence_cv, lag1_autocorrelation, benford_deviation, root_to_first_asset_delay). -**Fingerprinting HTTP/2 passif intégré** : pour les connexions HTTP/2, mod_reqin_log capture passivement le preface client via un hook `ap_hook_process_connection` enregistré en priorité `APR_HOOK_FIRST`, exécuté avant `mod_http2`. Ce hook effectue une lecture spéculative (`AP_MODE_SPECULATIVE`) de 512 octets sur les filtres d'entrée de la connexion, qui déclenche transparentement le handshake TLS (`mod_ssl`) et retourne les données déchiffrées sans les consommer. Le preface H2 est ensuite parsé pour en extraire : +**Fingerprinting HTTP/2 passif intégré** : pour les connexions HTTP/2, ja4ebpf identifie le protocole via le Go Magic Bytes dispatcher qui reconnaît le preface `PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n` dans le flux SSL_read déchiffré. Le parser HTTP/2 extrait ensuite les frames depuis ce flux déchiffré sans aucune instrumentation côté serveur web : - Les 7 paramètres SETTINGS individuels (IDs 1–6 et 8), chacun stocké dans une colonne ClickHouse dédiée (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`), avec la valeur -1 pour les paramètres absents du preface client - L'incrément `h2_window_update` de la frame WINDOW_UPDATE sur la connexion (stream ID 0) - Le flag `h2_has_priority` indiquant la présence d'un champ PRIORITY dans la frame HEADERS - L'ordre des pseudo-headers `h2_pseudo_order` (ex. `m,a,s,p`) extrait par décodage HPACK partiel de la première frame HEADERS - Le fingerprint composite `h2_fingerprint` au format Akamai et la chaîne brute `h2_settings_fp` -Les données H2 sont stockées dans les notes de la connexion primaire (`c->notes`). Pour les connexions HTTP/2, `mod_http2` crée des connexions secondaires (c2) par stream ; le hook `log_request` accède aux notes H2 via `r->connection->master` (connexion primaire c1). Le hook retourne `DECLINED` après stockage, laissant `mod_http2` gérer la suite normalement. +Les données H2 sont associées à la session courante via la clé `src_ip:src_port` dans le gestionnaire de corrélation in-memory de ja4ebpf, puis transmises en batch à ClickHouse avec les données TCP/TLS correspondantes. **Colonnes HTTP/2 dans `ja4_logs.http_logs`** : @@ -966,11 +966,11 @@ La convention `-1` pour les paramètres SETTINGS absents est essentielle : elle Toutes les features des familles F1–F6 et F8 proviennent de cette couche, agrégées sur des fenêtres temporelles de 300 secondes (5 minutes) par session (src_ip). -### 3.6 Corrélation inter-couches (logcorrelator) +### 3.6 Corrélation inter-couches (ja4ebpf) **Clé de corrélation** : `(src_ip, src_port)` — le tuple source identifie de manière unique une connexion TCP à un instant donné (une connexion TCP est identifiée par le 4-tuple src_ip:src_port:dst_ip:dst_port, mais dst_ip:dst_port étant fixes pour un serveur, le 2-tuple src suffit). -**Gestion du HTTP Keep-Alive** : une connexion TCP peut transporter plusieurs requêtes HTTP successives. logcorrelator maintient une table de connexions indexée par `(src_ip, src_port)`. Chaque requête HTTP reçue de mod_reqin_log est associée à l'enregistrement TCP/TLS ouvert correspondant, et `max_keepalives` est incrémenté. L'enregistrement reste ouvert jusqu'à la déconnexion TCP ou l'expiration. +**Gestion du HTTP Keep-Alive** : une connexion TCP peut transporter plusieurs requêtes HTTP successives. ja4ebpf maintient un gestionnaire de corrélation in-memory organisé en 256 shards (partitionnement par hash de src_ip pour éviter la contention). Chaque requête HTTP capturée via l'uprobe SSL_read est associée à l'enregistrement TCP/TLS ouvert correspondant, et `max_keepalives` est incrémenté. Un GC toutes les 100 ms libère les sessions expirées. **Timeout orphelin** : si aucun enregistrement réseau ne correspond à une requête HTTP dans les 500 ms, la requête est enregistrée comme orpheline (`correlated=0`). Cela se produit quand le trafic arrive via un CDN ou un proxy inverse qui établit une nouvelle connexion TCP entre le proxy et le serveur, rendant l'adresse source TCP celle du proxy plutôt que celle du client original. @@ -1099,9 +1099,7 @@ Le système `browser_confidence` à 6 axes (§3.8) fournit un score agrégé uti - **Compression d'en-têtes HPACK** : [RFC 7541](https://www.rfc-editor.org/rfc/rfc7541) définit HPACK, une compression d'en-têtes HTTP par table d'indexation. L'ordre des entrées dans la table statique HPACK est normalisé, mais l'ordre de sérialisation des pseudo-headers est laissé à l'implémentation. - **Contrôle de flux** : mécanisme de fenêtres (`WINDOW_UPDATE`) limitant le débit pour éviter la saturation du récepteur. -Après terminaison TLS par Apache (`mod_ssl`), le flux HTTP/2 est déchiffré et disponible en clair. Le fingerprinting passif est réalisé directement par **mod_reqin_log** via un hook `process_connection` enregistré en priorité `APR_HOOK_FIRST`. Ce hook s'exécute **avant** `mod_http2` et effectue une lecture spéculative (`AP_MODE_SPECULATIVE`, 512 octets) sur `c->input_filters`, déclenchant transparentement le handshake TLS. Si les données commencent par le magic HTTP/2 (`PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n`), le parser binaire extrait les frames SETTINGS, WINDOW_UPDATE et HEADERS du preface client, stocke les résultats dans `c->notes`, puis retourne `DECLINED` pour laisser `mod_http2` gérer la connexion. Cette approche ne consomme pas les données et n'interfère pas avec le traitement HTTP/2 normal. - -**Note architecturale** : l'approche par filtre de connexion (`AP_FTYPE_CONNECTION`) a été abandonnée car `mod_http2` prend le contrôle complet de la connexion via son propre hook `process_connection` — les filtres d'entrée de connexion ne sont jamais invoqués pour les connexions HTTP/2. Le hook `process_connection` à priorité haute est la seule technique fiable pour intercepter le preface H2 avant `mod_http2`. +Après terminaison TLS par le serveur web, le flux HTTP/2 est déchiffré et disponible en clair dans le contexte d'OpenSSL/BoringSSL. Le fingerprinting passif est réalisé par **ja4ebpf** via un uprobe sur `SSL_read`. Lorsque le Go Magic Bytes dispatcher détecte le magic HTTP/2 (`PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n`) dans le flux déchiffré, le parser H2 extrait les frames SETTINGS, WINDOW_UPDATE et HEADERS du preface client, sans modifier ni interrompre le flux. Cette approche est agnostique au serveur web (Apache, Nginx, Varnish, HAProxy) et ne nécessite aucun module natif installé côté serveur. #### Frame SETTINGS @@ -1397,7 +1395,7 @@ Le module `browser_matcher` génère les features suivantes dans le vecteur feat | `h2_pseudo_order` | String | Ordre observé (ex. `m,a,s,p`) | `[impl.]` (colonne `h2_pseudo_order` dans `http_logs`) | | `tls_h2_family_mismatch` | UInt8 | 1 si JA4 dit Chrome mais H2 SETTINGS dit Firefox/outil | `[impl.]` (feature F4) | -Les features `h2_window_update_value`, `h2_has_priority_frames` et `h2_pseudo_order` sont désormais capturées par mod_reqin_log et stockées dans des colonnes individuelles de `ja4_logs.http_logs`. De plus, chaque paramètre SETTINGS HTTP/2 dispose de sa propre colonne (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`) avec la valeur -1 pour les paramètres absents du preface client. La feature `tls_h2_family_mismatch` est implémentée dans le vecteur feature global (famille F4 — TLS features) et se calcule à partir des données JA4 existantes et des colonnes H2 individuelles disponibles dans `ja4_logs.http_logs`. Les features `browser_match_*` requièrent l'exécution complète du module `browser_matcher.py`. +Les features `h2_window_update_value`, `h2_has_priority_frames` et `h2_pseudo_order` sont désormais capturées par ja4ebpf via le parser HTTP/2 du flux SSL_read déchiffré et stockées dans des colonnes individuelles de `ja4_logs.http_logs`. De plus, chaque paramètre SETTINGS HTTP/2 dispose de sa propre colonne (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`) avec la valeur -1 pour les paramètres absents du preface client. La feature `tls_h2_family_mismatch` est implémentée dans le vecteur feature global (famille F4 — TLS features) et se calcule à partir des données JA4 existantes et des colonnes H2 individuelles disponibles dans `ja4_logs.http_logs`. Les features `browser_match_*` requièrent l'exécution complète du module `browser_matcher.py`. ### 3.9.5 Maintenance des signatures @@ -1508,7 +1506,7 @@ La famille 1 capture les signaux de volume et de cadence bruts. Ces features son | Scraper simple (connexion par req) | 1 | | Bot pipeline agressif | > 100 | -**Implémentation** : mod_reqin_log incrémente un compteur par connexion TCP (`src_ip:src_port`) à chaque requête HTTP reçue. Le maximum sur la session est agrégé dans `view_ai_features_1h`. +**Implémentation** : ja4ebpf incrémente un compteur via le gestionnaire de corrélation in-memory par connexion TCP (`src_ip:src_port`) à chaque requête HTTP reçue. Le maximum sur la session est agrégé dans `view_ai_features_1h`. #### count_login_post `[impl.]` @@ -2637,7 +2635,7 @@ Cette asymétrie constitue un signal exploitable : un flux HTTP sans résolution #### Technique : ratio DNS shadow -Capture DNS passive via `ja4sentinel` étendu au port UDP/53, puis corrélation avec les flux HTTP : +Capture DNS passive via `ja4ebpf` étendu au port UDP/53, puis corrélation avec les flux HTTP : ``` dns_shadow_ratio = requêtes HTTP vers hôte X / résolutions DNS de l'hôte X observées @@ -2658,7 +2656,7 @@ Les bots utilisant DoH contournent l'analyse DNS shadow. Cependant, les connexio Le fingerprinting **JA4D/JA4D6** des requêtes DHCP/DHCPv6 pourrait compléter l'analyse en identifiant les dispositifs derrière NAT, ajoutant une couche d'identification au-delà de l'adresse IP. Cette extension est envisagée comme travail futur conjoint à DNS Shadow Analysis. -**`[todo]` Non implémenté** : nécessite l'extension de `ja4sentinel` pour la capture UDP/53. Travail futur priorité 1 (voir §6.6). +**`[todo]` Non implémenté** : nécessite l'extension de `ja4ebpf` pour la capture UDP/53. Travail futur priorité 1 (voir §6.6). --- @@ -2946,13 +2944,13 @@ Un seul utilisateur réel alterne quelques connexions (2–6 ports source actifs **browser_matcher complet (§3.9)** : - État actuel : modules `[partiel]` (logique de score partielle, base de signatures incomplète) -- Données H2 brutes : `[impl.]` — capture des 7 paramètres SETTINGS individuels, WINDOW_UPDATE, flag PRIORITY et ordre pseudo-headers par mod_reqin_log via hook `process_connection` (APR_HOOK_FIRST, AP_MODE_SPECULATIVE). Colonnes individuelles dans `ja4_logs.http_logs` (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`, `h2_window_update`, `h2_has_priority`, `h2_pseudo_order`) +- Données H2 brutes : `[impl.]` — capture des 7 paramètres SETTINGS individuels, WINDOW_UPDATE, flag PRIORITY et ordre pseudo-headers par ja4ebpf via le parser HTTP/2 du flux SSL_read déchiffré. Colonnes individuelles dans `ja4_logs.http_logs` (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`, `h2_window_update`, `h2_has_priority`, `h2_pseudo_order`) - Travail restant : compléter `browser_signatures.py` avec signatures Firefox et Safari complètes, implémenter le rechargement ClickHouse toutes les 24h, intégrer les 5 features `browser_match_*` dérivées dans le vecteur feature global - Dépendances : ~~capture des frames H2 SETTINGS~~ (réalisé) ; reste le module `browser_matcher.py` **DNS Shadow Analysis (§5.6)** : - État actuel : `[todo]` non implémenté -- Travail requis : extension de `ja4sentinel` pour capture UDP/53 + TCP/53, table ClickHouse `dns_resolutions`, corrélation avec table `sessions` +- Travail requis : extension de `ja4ebpf` pour capture UDP/53 + TCP/53, table ClickHouse `dns_resolutions`, corrélation avec table `sessions` - Contraintes techniques : nécessite accès privilégié au trafic réseau niveau interface (pcap ou eBPF) - Estimation : 6–8 semaines de développement + tests de charge @@ -3016,7 +3014,7 @@ L'**explainabilité** est assurée par ExIFFI ([Frizzo et al., 2024](https://arx Extension du système `browser_confidence` à 6 axes vers une correspondance structurée par famille de navigateur, fondée sur l'analyse passive de 7 dimensions H2 : frame SETTINGS (7 paramètres), WINDOW_UPDATE, ordre des pseudo-headers, frames PRIORITY, cohérence des en-têtes HTTP, structure TLS, et lookup JA4 par dictionnaire. -La capture passive est réalisée par mod_reqin_log via un hook `ap_hook_process_connection` (APR_HOOK_FIRST) qui intercepte le preface HTTP/2 avant `mod_http2` par lecture spéculative. Chaque paramètre SETTINGS est stocké dans une colonne ClickHouse individuelle (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`) avec -1 pour les paramètres absents, complétés par `h2_window_update`, `h2_has_priority` et `h2_pseudo_order`. +La capture passive est réalisée par ja4ebpf via un uprobe sur `SSL_read` (OpenSSL/BoringSSL). Le Go Magic Bytes dispatcher identifie le preface HTTP/2 dans le flux déchiffré, et le parser H2 extrait les frames SETTINGS, WINDOW_UPDATE et HEADERS sans instrumentation serveur. Chaque paramètre SETTINGS est stocké dans une colonne ClickHouse individuelle (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`) avec -1 pour les paramètres absents, complétés par `h2_window_update`, `h2_has_priority` et `h2_pseudo_order`. Cette technique permet de détecter des outils d'évasion qui reproduisent correctement la couche TLS (curl_cffi, httpcloak) mais échouent à reproduire les subtilités H2 — notamment l'ordre des pseudo-headers et la valeur WINDOW_UPDATE. @@ -3058,7 +3056,7 @@ Architecture de données fondée sur ClickHouse avec **AggregatingMergeTree view | F11 — Futures (non impl.) | dns_shadow_ratio, compression_ratio_invariant (2 features) | 0 | 0 | 2 | | **Total** | **85** | **70** | **5** | **2** + meta | -**Résumé quantitatif** : ~82 % des features entièrement implémentées (`[impl.]`), ~6 % partiellement implémentées (`[partiel]`, les 5 features `browser_match_*` de browser_matcher), ~2 % non implémentées (`[todo]`, §5.6 et §5.7). Les 3 features H2 brutes (`h2_window_update_value`, `h2_has_priority_frames`, `h2_pseudo_order`) sont passées de `[partiel]` à `[impl.]` suite à l'intégration de la capture HTTP/2 passive dans mod_reqin_log. +**Résumé quantitatif** : ~82 % des features entièrement implémentées (`[impl.]`), ~6 % partiellement implémentées (`[partiel]`, les 5 features `browser_match_*` de browser_matcher), ~2 % non implémentées (`[todo]`, §5.6 et §5.7). Les 3 features H2 brutes (`h2_window_update_value`, `h2_has_priority_frames`, `h2_pseudo_order`) sont passées de `[partiel]` à `[impl.]` suite à l'intégration de la capture HTTP/2 passive dans ja4ebpf via le parser HTTP/2 du flux SSL_read. ### Perspective diff --git a/docs/architecture.md b/docs/architecture.md index b9236d9..78d9bc8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -5,30 +5,25 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp ## Architecture système ``` -┌─────────────────────────────────────────────────────────────────────────────────────┐ -│ Serveur Linux cible (Apache) │ -│ │ -│ ┌─────────────────┐ HTTP req ┌───────────────────────┐ │ -│ │ Client │────────────▶│ Apache HTTPD │ │ -│ │ (navigateur / │ │ + mod-reqin-log (C) │ │ -│ │ bot) │ └───────────┬───────────┘ │ -│ │ │ │ JSON / UNIX DGRAM │ -│ │ │ │ /var/run/logcorrelator/http.socket │ -│ │ │ ▼ │ -│ │ │ TLS CH ┌──────────────────────────────────────┐ │ -│ │ │────────────▶│ sentinel (Go · libpcap) │ │ -│ └─────────────────┘ (pcap) │ → JA4/JA3 fingerprints │ │ -│ └───────────┬──────────────────────────┘ │ -│ │ JSON / UNIX DGRAM │ -│ │ /var/run/logcorrelator/network.socket│ -│ ▼ │ -│ ┌───────────────────────────────┐ │ -│ │ correlator (Go · hex. arch) │ │ -│ │ join src_ip:src_port + TTL │ │ -│ └───────────┬───────────────────┘ │ -│ │ │ -└───────────────────────────────────────────────┼─────────────────────────────────────┘ - │ INSERT (Native TCP :9000) ++---------------------------------------------------+ +| Serveur Linux cible (Apache/Nginx/...) | +| | +| Client --HTTPS (443)--> [Serveur web] | +| Client --HTTP (80)---> [Serveur web] | +| | +| +-----------------+ | +| | ja4ebpf | (s'exécute sur le meme host)| +| | (eBPF CO-RE) | | +| | TC ingress |<-- L3/L4/L5 (SYN, TLS CH) | +| | uprobe SSL_read|<-- L7 HTTPS (déchiffré) | +| | kprobe tcp_recv|<-- L7 HTTP port 80/8080 | +| | 256-shard mgr | corrélation src_ip:src_port | +| +--------+--------+ | +| | | ++-----------|-----------------------------------+ | + | INSERT batch | | + v v +INSERT (Native TCP :9000) ▼ ┌───────────────────────────────────────────────────────┐ │ ClickHouse 24.8 │ @@ -71,15 +66,17 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp ### Phase 1 — Capture -1. **mod-reqin-log** (module Apache C11) intercepte chaque requête HTTP dans le hook `post_read_request`. Il sérialise method, path, headers, client IP/port en JSON et envoie le datagramme vers `/var/run/logcorrelator/http.socket`. +1. **ja4ebpf TC ingress hook** capture les paquets réseau bruts. Pour chaque TCP SYN : `src_ip`, `src_port`, `ttl`, `window_size`, `mss`, `window_scale`, `df_bit`. Pour chaque TLS ClientHello : décodage du payload pour extraire `ciphers`, `extensions`, `elliptic_curves`, `alpn`, `sni` (calcul du hash JA4/JA3 en espace utilisateur Go). -2. **sentinel** (démon Go) capture les paquets TLS ClientHello via libpcap sur les ports configurés (défaut : 443, 8443). Il extrait les métadonnées IP/TCP, génère les empreintes JA4 et JA3, et envoie le résultat en JSON vers `/var/run/logcorrelator/network.socket`. +2. **ja4ebpf uprobe SSL_read** accroche `SSL_read` dans la bibliothèque OpenSSL/BoringSSL du serveur web. Les données déchiffrées sont écrites dans un RingBuffer eBPF. Un kprobe sur `accept4` fournit la correspondance `fd → src_ip:src_port` pour annoter chaque buffer L7. -### Phase 2 — Corrélation +3. **ja4ebpf kprobe tcp_recvmsg** (HTTP port 80/8080) intercepte le payload TCP avant consommation par le serveur pour les connexions non chiffrées. -3. **correlator** (démon Go, architecture hexagonale) écoute les deux sockets Unix. Il met en tampon les événements entrants et les corrèle par `src_ip:src_port` dans une fenêtre temporelle configurable (défaut : 10 s). Le mode `one_to_many` (Keep-Alive) permet de réutiliser un seul handshake TLS (source B) pour plusieurs requêtes HTTP (source A). Les événements corrélés fusionnent les champs HTTP + TLS en un objet `CorrelatedLog` JSON, inséré dans **`ja4_logs.http_logs_raw`**. +### Phase 2 — Corrélation en mémoire -### Phase 3 — Enrichissement (ClickHouse) +4. **ja4ebpf 256-shard manager** (espace utilisateur Go) consomme les trois RingBuffers eBPF via des goroutines dédiées. Les événements L3/L4/L5 et L7 sont corrélés par `src_ip:src_port` dans une table de sessions shardée (256 shards, mutex par shard). Timeout orphelin : 500 ms (émission avec `correlated=0`). Détection Slowloris : émission partielle après 10 s. GC des sessions fantômes : toutes les 100 ms. Le dispatcher magic bytes route vers le parser HTTP/1.1 ou HTTP/2. Pour HTTP/2, la première frame SETTINGS + WINDOW_UPDATE est décodée pour le fingerprinting passif. L’objet corrélé est inséré dans **`ja4_logs.http_logs_raw`** par batch. + +### Phase 3 — Enrichissement### Phase 3 — Enrichissement (ClickHouse) 4. **mv_http_logs** (vue matérialisée) transforme le JSON de `http_logs_raw` en la table structurée `ja4_logs.http_logs`, enrichissant chaque ligne avec : - Données ASN via `dict_iplocate_asn` (IP_TRIE) @@ -123,14 +120,12 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp ## Matrice d'interaction des composants -| De ↓ \ Vers → | mod-reqin-log | sentinel | correlator | ClickHouse | bot-detector | dashboard | -|----------------|:---:|:---:|:---:|:---:|:---:|:---:| -| **mod-reqin-log** | — | — | UNIX DGRAM (source A) | — | — | — | -| **sentinel** | — | — | UNIX DGRAM (source B) | — | — | — | -| **correlator** | — | — | — | Native TCP :9000 (INSERT) | — | — | -| **ClickHouse** | — | — | — | MVs internes | — | — | -| **bot-detector** | — | — | — | HTTP :8123 (SELECT/INSERT) | — | — | -| **dashboard** | — | — | — | HTTP :8123 (SELECT/INSERT) | — | — | +| De ↓ \ Vers → | ja4ebpf | ClickHouse | bot-detector | dashboard | +|----------------|:---:|:---:|:---:|:---:| +| **ja4ebpf** | — | Native TCP :9000 (INSERT batch) | — | — | +| **ClickHouse** | — | MVs internes | — | — | +| **bot-detector** | — | HTTP :8123 (SELECT/INSERT) | — | — | +| **dashboard** | — | HTTP :8123 (SELECT/INSERT) | — | — | ## Propriété des tables ClickHouse @@ -138,7 +133,7 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp | Table / Vue | Écrit par | Lu par | |-------------|-----------|--------| -| `http_logs_raw` | correlator | mv_http_logs (MV) | +| `http_logs_raw` | ja4ebpf | mv_http_logs (MV) | | `http_logs` | mv_http_logs (MV) | mv_agg_* (6 MVs), dashboard | | `mv_http_logs` | — (MV automatique) | — | @@ -187,17 +182,23 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp ## Algorithme de corrélation -Le correlator joint les événements HTTP (source A) avec les événements TLS/réseau (source B) via une corrélation à deux clés : +**ja4ebpf** corrèle les événements L3/L4/L5 (RingBuffer TC ingress) avec les événements L7 (RingBuffer uprobe) via une table de sessions en mémoire : -1. **Clé** : `src_ip + src_port` — l'IP source et le port éphémère du client identifient une connexion TCP de manière unique. -2. **Fenêtre temporelle** : Les événements doivent arriver dans la fenêtre configurée (défaut 10 secondes). -3. **Mode de correspondance** : - - `one_to_one` : Chaque événement B correspond à un seul événement A (consommé après correspondance). - - `one_to_many` (défaut, Keep-Alive) : Un seul B (handshake TLS) peut correspondre à plusieurs A (requêtes HTTP) sur la même connexion. Le B possède un TTL configurable (défaut 120 s) réinitialisé à chaque correspondance. -4. **Gestion des orphelins** : Les événements A sans correspondance sont émis après un délai configurable (défaut 500 ms) avec `correlated=false` et `orphan_side=A`. +1. **Clé** : `src_ip + src_port` — l’IP source et le port éphémère identifient une connexion TCP de manière unique. +2. **Sharding** : 256 shards (`src_port % 256`), chacun protégé par un `sync.Mutex`. Réduit la contention sous fort trafic. +3. **Gestion Keep-Alive** : Un seul état TLS/L4 est conservé par connexion TCP et partagé entre toutes les requêtes HTTP successives. Compteur `maxkeepalives` incrémenté à chaque requête. +4. **Magic Bytes dispatcher** : Les premiers octets du buffer L7 déterminent le protocole : + - `PRI * HTTP/2.0\r\n` → parser HTTP/2 (SETTINGS + WINDOW_UPDATE + pseudo-headers) + - `GET `, `POST `, `HEAD `, etc. → parser HTTP/1.1 + - Autre → `ProtoUnknown` (bruit TLS ou protocole non supporté) +5. **Orphelins** : Événements L7 sans correspondance L3/L4 dans les 500 ms → `correlated=0`. +6. **Slowloris** : Connexion TCP sans requête HTTP complète après 10 s → export partiel. +7. **GC** : Goroutine dédiée, itération sur tous les shards toutes les 100 ms. La sortie est insérée dans **`ja4_logs.http_logs_raw`** (base `ja4_logs`), pas dans `ja4_processing`. + +## Pipeline ML ## Pipeline ML — bot-detector (détail) ``` @@ -253,15 +254,15 @@ Format original de fingerprinting TLS : Le `ja3_hash` est le hash MD5 de la chaîne JA3. -Les deux empreintes sont générées par sentinel à partir du payload TLS ClientHello. +Les deux empreintes sont générées par **ja4ebpf** (espace utilisateur Go) à partir du payload TLS ClientHello capturé par le TC ingress hook. ## Stack technologique | Composant | Technologie | |-----------|-------------| -| Capture de paquets | Go 1.24.6 + libpcap (gopacket) | -| Logging HTTP | Module Apache C11 (APR, `apxs`) | -| Corrélation d'événements | Go 1.24.6 (architecture hexagonale) | +| Capture réseau (L3/L4/L5) | Go 1.24.6 + eBPF CO-RE (TC ingress, cilium/ebpf) | +| Capture applicative (L7) | eBPF uprobe SSL_read + kprobe tcp_recvmsg | +| Corrélation en mémoire | Go 1.24.6 (256-shard manager, goroutines) | | Détection ML — EIF | Python 3.11 + isotree | | Détection ML — Autoencoder | Python 3.11 + PyTorch | | Détection ML — Supervisé | Python 3.11 + XGBoost | @@ -272,7 +273,7 @@ Les deux empreintes sont générées par sentinel à partir du payload TLS Clien | Frontend dashboard | htmx + Chart.js + ECharts + Tailwind CSS (CDN) | | Magasin de données | ClickHouse 24.8 (dual-database) | | Déploiement | systemd, Docker, RPM (Rocky 8/9/10) | -| IPC | Sockets UNIX datagramme | +| IPC | RingBuffers eBPF (kernel → userspace Go) | | Workspace Go | `go.work` (Go 1.24.6) | ## Fichiers de schéma SQL (13) diff --git a/docs/deployment.md b/docs/deployment.md index fc88a88..6113445 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -13,11 +13,9 @@ | Service | Runtime | Port par défaut | |---------|---------|-----------------| -| sentinel | Go binary + libpcap (RPM) | — (capture réseau passive) | -| correlator | Go binary (RPM) | 8080 (metrics, optionnel) | -| mod-reqin-log | Apache module .so (RPM) | — (intégré à httpd) | +| ja4ebpf | Go binary + eBPF CO-RE (RPM) | — (capture réseau passive) | | bot-detector | Python 3.11 (Docker) | 8080 (health check) | -| dashboard | Python 3.11 / FastAPI (Docker) | 8000 (API) + 3000 (frontend) | +| dashboard | Python 3.11 / FastAPI (Docker) | 8000 (API) | --- @@ -206,103 +204,64 @@ sudo chown -R clickhouse:clickhouse /var/lib/clickhouse/user_files/ --- -## Étape 5 — Installation des services Go (RPM) +## Étape 5 — Installation de ja4ebpf (RPM) ### Build des RPMs ```bash -# Tous les RPMs (sentinel + correlator + mod-reqin-log) × 3 distros -make rpm-all - -# Ou individuellement -make rpm-sentinel # → services/sentinel/dist/ -make rpm-correlator # → services/correlator/dist/ -make rpm-mod-reqin-log # → services/mod-reqin-log/dist/ +# RPM ja4ebpf × 3 distros (el8, el9, el10) +make rpm-ja4ebpf +# → services/ja4ebpf/dist/rpm/el{8,9,10}/ ``` -Les RPMs sont générés dans `services//dist/` avec un sous-dossier par distro (el8, el9, el10). +Le build utilise un pipeline Docker multi-étapes Rocky Linux : +1. L’étape `go-builder` compile le bytecode eBPF (clang/llvm) puis le binaire Go statique +2. Les étapes `rpm-el8`, `rpm-el9`, `rpm-el10` exécutent `rpmbuild` pour chaque distro cible +3. L’étape `alpine` collecte les RPMs via `--output type=local` -### Installation des RPMs +### Installation du RPM ```bash # Sur le serveur cible (Rocky 9 par exemple) -sudo yum install -y ./ja4sentinel-*.el9.x86_64.rpm -sudo yum install -y ./logcorrelator-*.el9.x86_64.rpm -sudo yum install -y ./mod_reqin_log-*.el9.x86_64.rpm +sudo yum install -y ./ja4ebpf-*.el9.x86_64.rpm ``` -### Configuration du sentinel +### Configuration de ja4ebpf ```bash # Fichier de configuration principal -sudo cp /etc/ja4sentinel/config.yml.default /etc/ja4sentinel/config.yml -sudo vi /etc/ja4sentinel/config.yml +sudo cp /etc/ja4ebpf/config.yml.example /etc/ja4ebpf/config.yml +sudo vi /etc/ja4ebpf/config.yml ``` -Variables d'environnement (dans `/etc/sysconfig/ja4sentinel` ou `.env`) : +Variables d’environnement clés (dans `/etc/sysconfig/ja4ebpf`) : + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `JA4EBPF_INTERFACE` | `eth0` | Interface réseau à observer | +| `JA4EBPF_CLICKHOUSE_DSN` | — | DSN ClickHouse (ex: `clickhouse://data_writer:pwd@host:9000/ja4_logs`) | +| `JA4EBPF_TARGET_BINARY` | `/usr/sbin/httpd` | Binaire OpenSSL à hooker (uprobe SSL_read) | +| `JA4EBPF_BATCH_SIZE` | `500` | Taille des batchs d’insertion ClickHouse | +| `JA4EBPF_FLUSH_INTERVAL_MS` | `200` | Intervalle de flush (ms) | +| `JA4EBPF_SESSION_TIMEOUT_MS` | `500` | Timeout orphelin (ms) | ```bash -JA4SENTINEL_INTERFACE=eth0 # Interface réseau à capturer -JA4SENTINEL_PORTS=443,8443 # Ports TLS à surveiller +sudo systemctl enable --now ja4ebpf +sudo systemctl status ja4ebpf +journalctl -u ja4ebpf -f ``` -Le sentinel écrit vers le socket Unix du correlator : `/var/run/logcorrelator/network.socket` +### Capabilities Linux requises (SELinux Enforcing) -```bash -sudo systemctl enable --now ja4sentinel -sudo systemctl status ja4sentinel -journalctl -u ja4sentinel -f -``` +`ja4ebpf` ne tourne **pas** en root absolu. Le fichier systemd utilise les capabilities minimales : -### Configuration du correlator +| Capability | Raison | +|------------|--------| +| `CAP_BPF` | Chargement des programmes eBPF (kernel 5.8+) | +| `CAP_SYS_ADMIN` | Requis pour les uprobes et RHEL 8 (kernel 4.18 <5.8) | +| `CAP_NET_ADMIN` | Attachement des hooks TC ingress | +| `CAP_PERFMON` | Accès aux perf events pour les uprobes | -```bash -sudo cp /etc/logcorrelator/config.yml.default /etc/logcorrelator/config.yml -sudo vi /etc/logcorrelator/config.yml -``` - -Configuration minimale pour activer ClickHouse (`config.yml`) : - -```yaml -outputs: - clickhouse: - enabled: true - dsn: clickhouse://data_writer:VotreMotDePasse@localhost:9000/ja4_logs - table: http_logs_raw - batch_size: 500 - flush_interval_ms: 200 - - file: - enabled: true - path: /var/log/logcorrelator/correlated.log -``` - -Variable d'environnement alternative (dans `/etc/sysconfig/logcorrelator`) : - -```bash -LOGCORRELATOR_CLICKHOUSE_DSN=clickhouse://data_writer:VotreMotDePasse@localhost:9000/ja4_logs -``` - -```bash -sudo systemctl enable --now logcorrelator -sudo systemctl status logcorrelator -``` - -### Configuration de mod-reqin-log - -Le module Apache écrit les requêtes HTTP en JSON vers le socket Unix du correlator. - -```bash -# Le RPM installe automatiquement le module dans Apache -# Vérifier le chargement -httpd -M | grep reqin - -# La configuration est dans /etc/httpd/conf.d/mod_reqin_log.conf -# Le socket par défaut : /var/run/logcorrelator/http.socket -sudo systemctl restart httpd -``` - ---- ## Étape 6 — Installation des services Python (Docker) @@ -374,7 +333,7 @@ docker compose up -d ```bash # Services systemd (Go) -sudo systemctl status ja4sentinel logcorrelator httpd +sudo systemctl status ja4ebpf # Services Docker (Python) docker compose -f services/bot-detector/docker-compose.yml ps @@ -384,7 +343,7 @@ docker compose -f services/dashboard/docker-compose.yaml ps ### 2. Vérifier l'ingestion des logs ```bash -# Logs bruts ingérés par le correlator +# Logs bruts ingérés par ja4ebpf clickhouse-client --query "SELECT count() FROM ja4_logs.http_logs_raw" # Logs parsés par la vue matérialisée @@ -480,27 +439,34 @@ Télécharge et génère tous les fichiers CSV de référence (bot IPs, JA4, ASN ## Schéma réseau récapitulatif ``` -┌──────────────┐ Unix socket ┌──────────────┐ ClickHouse ┌──────────────────┐ -│ mod-reqin-log│──── http.socket ─────→│ │ INSERT INTO │ │ -│ (Apache C11) │ (source A) │ correlator │───→ ja4_logs. │ ClickHouse │ -└──────────────┘ │ │ http_logs_raw │ │ - │ │ │ ┌──────────────┐ │ -┌──────────────┐ Unix socket │ │ MV parse JSON │ │ ja4_logs │ │ -│ sentinel │──── network.socket ──→│ │ ↓ │ │ _raw → _logs│ │ -│ (Go+libpcap) │ (source B) └──────────────┘ ja4_logs. │ └──────────────┘ │ -└──────────────┘ http_logs │ │ - │ │ ┌──────────────┐ │ - 6 MVs agrégation│ │ ja4_processing│ │ - ↓ │ │ agg_* (×6) │ │ -┌──────────────┐ SELECT features ┌──────────────┐ ja4_processing. │ │ ml_* (×2) │ │ -│ bot-detector │←─── view_ai_features ─│ │ agg_*, view_* │ │ views, dicts│ │ -│ (Python 3.11)│ view_thesis_feat │ ClickHouse │ │ │ audit_logs │ │ -│ EIF+AE+XGB │ │ │ ml_all_scores │ └──────────────┘ │ -│ │───→ INSERT scores ────→│ │ ml_detected_* │ │ -└──────────────┘ └──────────────┘ └──────────────────┘ - ↑ -┌──────────────┐ SELECT * │ -│ dashboard │←─── ja4_processing.ml_*, agg_*, views ────────────────────────────┘ -│ (FastAPI) │←─── ja4_logs.http_logs ───────────────────────────────────────────┘ -└──────────────┘ + Trafic HTTPS/HTTP (port 80/443) + | + v ++-----------------+ +| ja4ebpf | TC ingress hook -- L3/L4/L5 (SYN, TLS ClientHello) +| (eBPF CO-RE) | uprobe SSL_read -- L7 HTTPS (flux déchiffré) +| | kprobe tcp_recvmsg - L7 HTTP (port 80/8080) ++-----------------+ + | + | INSERT batch (HTTP bulk) + v ++-----------------+ +------------------+ +| ClickHouse |-- MV parse JSON -> | ja4_logs. | +| | | http_logs_raw | +| | | http_logs | +| |-- 6 MVs agg -----> | ja4_processing. | +| | | agg_*(x6) | +| | | ml_*(x2) | ++-----------------+ +------------------+ + ^ + | ++-----------------+ SELECT view_ai_features +| bot-detector |<-- view_thesis_features +| (EIF+AE+XGB) | +| |--> INSERT ml_all_scores, ml_detected_anomalies ++-----------------+ ++-----------------+ +| dashboard |<-- ja4_processing.ml_*, agg_*, views +| (FastAPI) |<-- ja4_logs.http_logs ++-----------------+ ``` diff --git a/docs/development.md b/docs/development.md index fb71304..b4e841f 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,6 +1,6 @@ # Guide de développement -Ce guide couvre la compilation, les tests, le packaging et l'extension du monorepo ja4-platform. Toutes les opérations de build et de test s'exécutent dans Docker — aucune toolchain Go, Python ou C n'est requise sur la machine hôte. +Ce guide couvre la compilation, les tests, le packaging et l'extension du monorepo ja4-platform. Toutes les opérations de build et de test s'exécutent dans Docker — aucune toolchain Go, Python, C ou eBPF n'est requise sur la machine hôte. ## Prérequis @@ -11,8 +11,6 @@ Ce guide couvre la compilation, les tests, le packaging et l'extension du monore | make | 3.81+ | GNU Make | | git | 2.x | Pour le versioning | -Aucun compilateur Go, Python ou C n'est nécessaire sur la machine hôte. - ## Compilation de tous les services ```bash @@ -20,18 +18,14 @@ make build-all ``` Cela construit les images Docker pour : -- `ja4-platform/sentinel:latest` -- `ja4-platform/correlator:latest` +- `ja4-platform/ja4ebpf:latest` - `ja4-platform/bot-detector:latest` - `ja4-platform/dashboard:latest` -mod-reqin-log est un module Apache et n'est construit que dans le cadre du processus RPM. - ### Compilation individuelle ```bash -make build-sentinel # Binaire Go dans Docker -make build-correlator # Binaire Go dans Docker +make build-ja4ebpf # Bytecode eBPF (clang) + binaire Go dans Docker Rocky Linux make build-bot-detector # Image Python make build-dashboard # Image FastAPI + Jinja2 ``` @@ -46,9 +40,7 @@ make test-all | Service | Commande | Détails | |---------|----------|---------| -| sentinel | `make test-sentinel` | Tests Go avec `-race`, nécessite `NET_RAW`/`NET_ADMIN` | -| correlator | `make test-correlator` | Tests Go avec seuil de couverture 80% | -| mod-reqin-log | `make test-mod-reqin-log` | Tests unitaires C (sérialisation JSON, config, headers) | +| ja4ebpf | `make test-ja4ebpf` | Tests Go avec `-race`, nécessite `NET_RAW`/`NET_ADMIN`/`BPF` | | bot-detector | `make test-bot-detector` | Suite pytest Python | | dashboard | `make test-dashboard` | pytest pour les routes FastAPI | | ja4_common (Python) | `make test-ja4common-python` | Tests de la librairie Python partagée | @@ -58,42 +50,32 @@ make test-all Les tests d'intégration full-stack s'exécutent contre Docker Compose avec une instance ClickHouse réelle : ```bash -make test-integration # 8 phases : build → start → schema → traffic → pipeline → dashboard → bot-detector → sentinel +make test-integration # 8 phases : build -> start -> schema -> traffic -> pipeline -> dashboard -> bot-detector -> ja4ebpf make test-integration-keep # idem mais laisse la stack en fonctionnement make test-integration-down # démontage de la stack d'intégration +make test-nginx # stack nginx + ja4ebpf +make test-nginx-varnish # nginx + Varnish + ja4ebpf +make test-hitch-varnish # hitch (TLS) + Varnish + ja4ebpf +make test-all-stacks # les 3 stacks serveur en séquence ``` -La suite de tests se trouve dans `tests/integration/` et réinitialise la base de données entre chaque exécution. +La suite de tests se trouve dans `tests/integration/` et réinitialise la base entre chaque exécution. ## Construction des paquets RPM ```bash -make rpm-all +make rpm-ja4ebpf +# RPMs dans services/ja4ebpf/dist/rpm/el{8,9,10}/ ``` -Construit les RPMs pour sentinel, correlator et mod-reqin-log ciblant Rocky Linux 8/9/10 : - -```bash -make rpm-sentinel # → services/sentinel/dist/rpm/ -make rpm-correlator # → services/correlator/dist/rpm/ -make rpm-mod-reqin-log # → services/mod-reqin-log/dist/rpm/ -``` - -Chaque build RPM utilise un pipeline Docker multi-étapes : -1. L'étape builder compile le binaire (Go) ou l'objet partagé (C) -2. L'étape rpmbuild exécute `rpmbuild` pour chaque distro cible (el8, el9, el10) -3. L'étape de sortie copie les RPMs sur l'hôte via `--output type=local` - -### Paquets de distribution - -```bash -make dist # Alias for rpm-all -# RPMs in services//dist/rpm/el{8,9,10}/ -``` +Le build RPM utilise un pipeline Docker multi-étapes Rocky Linux : +1. L'étape `go-builder` compile le bytecode eBPF (clang/llvm) puis le binaire Go statique +2. Les étapes `rpm-el8`, `rpm-el9`, `rpm-el10` exécutent `rpmbuild` pour chaque distro cible +3. L'étape de sortie `alpine` collecte tous les RPMs via `--output type=local` ## Développement local -### Services Go (sentinel, correlator) +### Service Go/eBPF (ja4ebpf) Le fichier `go.work` relie les modules Go : @@ -101,23 +83,23 @@ Le fichier `go.work` relie les modules Go : go 1.24.6 use ( - ./services/sentinel - ./services/correlator ./shared/go/ja4common + ./services/ja4ebpf ) ``` -Si Go 1.24+ est installé localement, le développement sans Docker est possible : +La compilation locale du bytecode eBPF nécessite `clang`, `llvm` et `bpftool`. En pratique, on passe toujours par Docker : ```bash -# Tests sentinel en local -cd services/sentinel && go test ./... -race -v +# Rebuild du bytecode eBPF + binaire Go (Docker Rocky Linux) +make build-ja4ebpf -# Tests correlator en local -cd services/correlator && go test ./... -race -cover -v +# Tests unitaires ja4ebpf (Docker avec cap NET_RAW + BPF) +make test-ja4ebpf -# Compilation du binaire sentinel (nécessite libpcap-dev) -cd services/sentinel && go build -o ja4sentinel ./cmd/ja4sentinel/ +# Test single dans Docker +docker run --rm --cap-add=NET_RAW --cap-add=NET_ADMIN --cap-add=BPF \ + ja4ebpf:tests go test -v -run TestDispatcher ./internal/dispatcher/ ``` ### Services Python (bot-detector, dashboard) @@ -137,12 +119,10 @@ uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000 #### Dépendances Python pour le développement local du bot-detector -Les dépendances principales sont listées dans `services/bot-detector/bot_detector/requirements.txt`. Les librairies ML clés sont : - | Librairie | Usage | |-----------|-------| | `isotree` | Extended Isolation Forest (scoreur principal non supervisé) | -| `torch` | Autoencoder (PyTorch, architecture n→64→32→16→32→64→n) | +| `torch` | Autoencoder (PyTorch, architecture n->64->32->16->32->64->n) | | `xgboost` | Modèle supervisé (entraîné sur les labels SOC) | | `hdbscan` | Clustering de campagnes d'attaque | | `shap` | Explicabilité des scores d'anomalie | @@ -167,117 +147,48 @@ services/bot-detector/bot_detector/ └── tests/ # Tests pytest (self-contained, pas d'import lourd) ``` -### Module C (mod-reqin-log) - -Nécessite `apxs` (outil d'extension Apache) et les headers de développement : - -```bash -cd services/mod-reqin-log -make build # Compile mod_reqin_log.so -make test # Exécute les tests unitaires -make rpm # Construit les paquets RPM -``` - ## Scripts utilitaires -Les scripts dans `scripts/` facilitent l'initialisation et la gestion de l'environnement de développement : - | Script | Commande Make | Description | |--------|--------------|-------------| | `init-stack.sh` | `make init-stack` | Initialisation complète de la stack ClickHouse (schéma + données CSV) | | `import-prod-data.sh` | `make import-prod-data` | Import de données de prod avec décalage temporel | -| `reload-prod-logs.sh` | `make reload-prod-logs` | Export prod → réimport dev avec décalage | -| `update-csv-data.sh` | — | Téléchargement et génération de toutes les données CSV de référence | +| `reload-prod-logs.sh` | `make reload-prod-logs` | Export prod -> réimport dev avec décalage | +| `update-csv-data.sh` | -- | Téléchargement et génération de toutes les données CSV de référence | ```bash -# Initialisation complète (schéma + données CSV + import prod) -make init-and-import - -# Initialisation seule (schéma + CSV) -make init-stack - -# Import des données prod pré-exportées -make import-prod-data - -# Rechargement depuis la prod -make reload-prod-logs +make init-and-import # Initialisation complète (schéma + CSV + import prod) +make init-stack # Initialisation seule (schéma + CSV) +make import-prod-data # Import des données prod pré-exportées +make reload-prod-logs # Rechargement depuis la prod ``` ## Ajout d'un nouveau service ### Service Go -1. Créer le répertoire du service : +1. Créer le répertoire et initialiser le module : ```bash - mkdir -p services/my-service/cmd/my-service - mkdir -p services/my-service/internal + mkdir -p services/my-service/cmd/my-service services/my-service/internal + cd services/my-service && go mod init github.com/antitbone/ja4/my-service ``` -2. Initialiser le module Go : - ```bash - cd services/my-service - go mod init github.com/antitbone/ja4/my-service - ``` - -3. Ajouter au `go.work` : +2. Ajouter au `go.work` : ``` use ( - ./services/sentinel - ./services/correlator - ./services/my-service # ← add this ./shared/go/ja4common + ./services/ja4ebpf + ./services/my-service # <- add this ) ``` -4. Importer la librairie partagée : - ```go - import ( - "github.com/antitbone/ja4/ja4common/logger" - "github.com/antitbone/ja4/ja4common/config" - "github.com/antitbone/ja4/ja4common/shutdown" - ) - ``` - -5. Ajouter les cibles Makefile : - ```makefile - build-my-service: - docker build -f services/my-service/Dockerfile -t ja4-platform/my-service:latest . - - test-my-service: - docker build -f services/my-service/Dockerfile.dev -t ja4-platform/my-service-tests:latest . - docker run --rm ja4-platform/my-service-tests:latest - ``` - -6. Mettre à jour les dépendances `build-all` et `test-all`. +3. Ajouter les cibles Makefile. ### Service Python -1. Créer le répertoire du service avec un `requirements.txt` ou `pyproject.toml`. -2. Ajouter `ja4-common` comme dépendance (installée depuis `shared/python/ja4_common`). +1. Créer le répertoire avec `requirements.txt`. +2. Ajouter `ja4-common` comme dépendance locale. 3. Utiliser `from ja4_common.clickhouse import get_client` pour l'accès ClickHouse. -4. Ajouter les cibles Makefile en suivant le modèle bot-detector/dashboard. - -## Workspace go.work - -Le fichier `go.work` à la racine du dépôt relie tous les modules Go, permettant le développement cross-module sans publication : - -``` -go 1.24.6 - -use ( - ./services/sentinel - ./services/correlator - ./shared/go/ja4common -) -``` - -Lors de l'ajout d'un nouveau module Go : -1. `go mod init` dans le répertoire du service -2. Ajouter le chemin dans `go.work` -3. Référencer les packages partagés via leur chemin de module : `github.com/antitbone/ja4/ja4common/...` -4. Exécuter `go work sync` pour mettre à jour le workspace - -Les deux services Go ont une directive `replace` dans leur `go.mod` pointant vers `../../shared/go/ja4common`. Le workspace prend priorité en développement local ; la directive `replace` est nécessaire pour les builds Docker où `go.work` n'est pas disponible. ## Package Python ja4_common @@ -289,31 +200,10 @@ Le package Python partagé (`shared/python/ja4_common`) fournit : > **Note :** le dashboard n'utilise PAS ja4_common — il possède son propre client léger `clickhouse-connect` dans `backend/database.py`. -### Extension de ja4_common - -1. Ajouter de nouveaux modules sous `shared/python/ja4_common/ja4_common/` -2. Les exporter dans `__init__.py` -3. Ajouter les dépendances dans `pyproject.toml` -4. Lancer les tests : `make test-ja4common-python` - -### Utilisation dans un nouveau service - -Ajouter dans `requirements.txt` : -``` -ja4-common @ file:///app/shared/python/ja4_common -``` - -Or in Docker, copy the shared library and install: -```dockerfile -COPY shared/python/ja4_common /app/shared/python/ja4_common -RUN pip install /app/shared/python/ja4_common -``` - ## Variables d'environnement -Chaque service lit sa configuration depuis des variables d'environnement et/ou des fichiers YAML. Consultez la documentation de chaque service pour la référence complète : +Consultez la documentation de chaque service pour la référence complète : -- [Configuration du Sentinel](services/sentinel.md#configuration-reference) -- [Configuration du Correlator](services/correlator.md#configuration-reference) +- [Configuration de ja4ebpf](services/ja4ebpf.md#configuration) - [Configuration du Bot Detector](services/bot-detector.md#environment-variables) - [Configuration du Dashboard](services/dashboard.md#configuration) diff --git a/docs/services/ja4ebpf.md b/docs/services/ja4ebpf.md new file mode 100644 index 0000000..bfe96f4 --- /dev/null +++ b/docs/services/ja4ebpf.md @@ -0,0 +1,292 @@ +# ja4ebpf — Agent eBPF CO-RE + +## Description + +`ja4ebpf` est l'agent de collecte de données de la plateforme ja4-platform. C'est un binaire Go unique qui utilise eBPF CO-RE (Compile Once — Run Everywhere) pour observer passivement le trafic réseau d'un serveur Linux, sans modifier ni interrompre le serveur web cible (Apache, Nginx, Varnish, HAProxy, ou tout processus utilisant OpenSSL/BoringSSL). + +Il capture simultanément les métadonnées réseau L3/L4 (TCP SYN), les paramètres TLS L5 (ClientHello), et le contenu applicatif L7 (requêtes HTTP déchiffrées), corr le tout en mémoire par la clé `src_ip:src_port`, et envoie les données vers ClickHouse en batch. + +## Architecture interne + +``` + +-----------------+ TC ingress (XDP/TC) +--------------------+ + | réseau entrant |-------------------------->| bpf/tc_capture.c | + | | | | + | SYN packet | --> rb_tcp_syn (16 MB) | Programme eBPF | + | TLS ClientHello| --> rb_tls_hello (16 MB) | CO-RE | + | HTTP port 80 | --> rb_http_plain (32 MB)| | + +-----------------+ +--------------------+ + | + +-----------------+ uprobe SSL_read +--------------------+ + | serveur web |--------------------------> | bpf/uprobe_ssl.c | + | (OpenSSL) | | | + | flux déchiffré | --> rb_ssl_data (64 MB) | Programme eBPF | + +-----------------+ | CO-RE | + +--------------------+ + | + +-----------v-----------+ + | Go userspace | + | | + | internal/loader/ | + | - RingBuffer readers | + | - 5 goroutines | + | | + | internal/parser/ | + | - JA4 calculator | + | - H2 preface parser | + | - HTTP/1.1 parser | + | | + | internal/dispatcher/ | + | - Magic Bytes router | + | | + | internal/correlation/ | + | - 256-shard manager | + | - GC 100ms | + | - timeout 500ms | + | | + | internal/writer/ | + | - ClickHouse batch | + +----------+-------------+ + | + INSERT batch TCP :9000 + | + v + +----------------------+ + | ja4_logs. | + | http_logs_raw | + +----------------------+ +``` + +## Hooks eBPF + +### TC ingress — Couches L3/L4/L5 + +Le programme `bpf/tc_capture.c` est attaché à l'interface réseau via **TC (Traffic Control)** en ingress. Il s'exécute pour chaque paquet entrant : + +**Paquets TCP SYN** : extraction des options TCP et métadonnées IP depuis les headers du paquet. +- `bpf_skb_load_bytes()` pour lire les options TCP depuis le skb +- Envoyé dans le RingBuffer `rb_tcp_syn` (16 MB) + +**ClientHello TLS** : détection du type 0x16 (Handshake) et sous-type 0x01 (ClientHello). +- `bpf_skb_load_bytes()` pour capturer 512 octets du payload +- Envoyé dans le RingBuffer `rb_tls_hello` (16 MB) + +**HTTP en clair (port 80/8080)** : pour les connexions non chiffrées. +- SYN/FIN/RST exclus (uniquement les segments porteurs de données) +- Jusqu'à 4096 octets via `bpf_skb_load_bytes()` +- Envoyé dans le RingBuffer `rb_http_plain` (32 MB) + +### Uprobe SSL_read — Couche L7 + +Les **uprobes** s'attachent dynamiquement à `SSL_read` (ou `SSL_read_ex`) dans le processus du serveur web. Elles s'exécutent *après* le déchiffrement TLS, capturant le flux HTTP en clair dans `rb_ssl_data` (64 MB). + +La clé de corrélation `src_ip:src_port` est extraite depuis la structure `SSL*` → file descriptor → socket noyau via `bpf_probe_read_kernel()`. + +### Kprobe accept4 + +Un kprobe sur `accept4` peuple `rb_accept` (4 MB) avec le tuple `(src_ip, src_port, fd, pid_tgid)`, permettant d'associer chaque fd SSL à une connexion TCP. + +## Corrélation in-memory + +Le gestionnaire `internal/correlation` maintient un état par connexion : + +| Mécanisme | Valeur | +|-----------|--------| +| Sharding | 256 buckets (hash src_ip:src_port) | +| Timeout orphelin | 500 ms (→ `correlated=0`) | +| Détection Slowloris | 10 s (export partiel) | +| GC interval | 100 ms | +| Keep-Alive | max_keepalives incrémenté par requête | + +### Routeur Magic Bytes (dispatcher) + +``` +Buffer reçu (SSL data ou HTTP plain) + | + +-- starts with "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" --> ProtoHTTP2 + | + +-- starts with "GET " / "POST " / "PUT " / ... --> ProtoHTTP1 + | + +-- partial H2 preface prefix --> ProtoHTTP2 (buffer) + | + +-- autre --> ProtoUnknown (ignoré) +``` + +**Fingerprinting HTTP/2 passif** : après détection du preface `PRI * HTTP/2.0...`, le parser itère les frames : +- Frame `SETTINGS` (type `0x04`) : extraction des 7 paramètres, valeur `-1` si absent +- Frame `WINDOW_UPDATE` (type `0x08`, stream 0) : incrément de fenêtre connexion +- Frame `HEADERS` (type `0x01`) : ordre des pseudo-headers (`m,a,s,p`) + +## Données collectées + +### L3/L4 (TCP SYN) + +| Champ | Description | +|-------|-------------| +| `src_ip`, `src_port` | Clé de corrélation | +| `ttl` | Time To Live initial | +| `df_bit` | Don't Fragment bit | +| `ip_id` | IP Identification (0 = Linux/VPN/spoofé) | +| `window_size` | Taille fenêtre TCP SYN | +| `window_scale` | Option Window Scale (RFC 1323) | +| `mss` | Maximum Segment Size | +| `tcp_options` | Options TCP brutes (40 octets max) | +| `tcp_jitter_variance` | Variance jitter inter-SYN | +| `syn_timing_cv` | Délai SYN→ClientHello (ns) | + +### L5 (TLS ClientHello) + +| Champ | Description | +|-------|-------------| +| `tls_version` | Version TLS | +| `ciphers` | Liste suites cryptographiques | +| `extensions` | Liste extensions TLS | +| `elliptic_curves` | Courbes elliptiques supportées | +| `point_formats` | Formats de points EC | +| `alpn` | ALPN list (h2, http/1.1, ...) | +| `sni` | Server Name Indication | +| `ja4` | Empreinte JA4 calculée Go-side | +| `ja4t` | Empreinte JA4T (TCP) calculée Go-side | + +### L7 HTTP/1.1 + +| Champ | Description | +|-------|-------------| +| `method` | Méthode HTTP | +| `path` | Chemin | +| `query_string` | Paramètres query | +| `http_version` | HTTP/1.0 ou HTTP/1.1 | +| `headers_raw` | En-têtes dans leur ordre d'émission | +| `header_order_signature` | Hash de l'ordre | +| `status_code` | Code de statut | +| `response_size` | Taille réponse (octets) | +| `duration_ms` | Durée requête | +| `timestamp_ns` | Horodatage ns absolu | + +### L7 HTTP/2 (preface client) + +| Champ | Description | +|-------|-------------| +| `h2_header_table_size` | SETTINGS ID 1 (-1 si absent) | +| `h2_enable_push` | SETTINGS ID 2 | +| `h2_max_concurrent_streams` | SETTINGS ID 3 | +| `h2_initial_window_size` | SETTINGS ID 4 | +| `h2_max_frame_size` | SETTINGS ID 5 | +| `h2_max_header_list_size` | SETTINGS ID 6 | +| `h2_enable_connect_protocol` | SETTINGS ID 8 (RFC 8441) | +| `h2_window_update` | Incrément WINDOW_UPDATE connexion | +| `h2_has_priority` | Flag PRIORITY dans HEADERS frame | +| `h2_pseudo_order` | Ordre pseudo-headers (ex: `m,a,s,p`) | + +## eBPF CO-RE + +| Aspect | Détail | +|--------|--------| +| Compilateur | `clang` + `llvm` | +| Target | `bpf` (architecture BPF 64 bits) | +| BTF source | `/sys/kernel/btf/vmlinux` (disponible RHEL 8+) | +| Relocations | `cilium/ebpf` résout automatiquement les offsets struct | +| Embed | `go:generate` génère `bpf_bpfel.go` avec bytecode embarqué | +| Compatibilité | Rocky/RHEL Linux 8, 9, 10 (kernel 4.18+) | + +## Configuration + +```yaml +# /etc/ja4ebpf/config.yml +interface: eth0 +target_binary: /usr/sbin/httpd + +clickhouse: + dsn: clickhouse://data_writer:pwd@localhost:9000/ja4_logs + table: http_logs_raw + batch_size: 500 + flush_interval_ms: 200 + +correlation: + session_timeout_ms: 500 + slowloris_threshold_s: 10 + gc_interval_ms: 100 +``` + +### Variables d'environnement + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `JA4EBPF_INTERFACE` | `eth0` | Interface réseau | +| `JA4EBPF_TARGET_BINARY` | `/usr/sbin/httpd` | Binaire à hooker (uprobe SSL_read) | +| `JA4EBPF_CLICKHOUSE_DSN` | — | DSN ClickHouse | +| `JA4EBPF_BATCH_SIZE` | `500` | Taille des batchs d'insertion | +| `JA4EBPF_FLUSH_INTERVAL_MS` | `200` | Intervalle de flush (ms) | +| `JA4EBPF_SESSION_TIMEOUT_MS` | `500` | Timeout session orpheline | +| `JA4EBPF_SLOWLORIS_THRESHOLD_S` | `10` | Seuil détection Slowloris (s) | + +## Build + +```bash +# Build complet (bytecode eBPF + binaire Go) — Docker Rocky Linux +make build-ja4ebpf + +# Tests unitaires (nécessite NET_RAW/NET_ADMIN/BPF capabilities) +make test-ja4ebpf + +# Build RPMs el8/el9/el10 +make rpm-ja4ebpf +# → services/ja4ebpf/dist/rpm/el{8,9,10}/ +``` + +## Structure du code + +``` +services/ja4ebpf/ +├── bpf/ +│ ├── bpf_types.h # Structs C partagées + déclarations maps eBPF +│ ├── tc_capture.c # Programme TC ingress (L3/L4/L5 + HTTP plain) +│ └── uprobe_ssl.c # Programme uprobe SSL_read (L7 déchiffré) +├── cmd/ja4ebpf/ +│ └── main.go # Point d'entrée : 5 goroutines consumer +├── internal/ +│ ├── loader/ +│ │ └── loader.go # Chargement eBPF + RingBuffer readers + désérialisation +│ ├── parser/ +│ │ ├── ja4.go # Calcul empreintes JA4 / JA4T +│ │ ├── http2.go # Parser HTTP/2 preface (SETTINGS, WINDOW_UPDATE, HEADERS) +│ │ └── http1.go # Parser HTTP/1.1 +│ ├── dispatcher/ +│ │ └── dispatcher.go # Routeur Magic Bytes (ProtoHTTP1/2/Unknown) +│ ├── correlation/ +│ │ └── manager.go # Gestionnaire sessions 256-shard +│ └── writer/ +│ └── writer.go # Writer ClickHouse (batch + retry) +├── packaging/ +│ ├── rpm/ja4ebpf.spec # Spec RPM (el8/el9/el10) +│ └── systemd/ja4ebpf.service # Unit systemd +├── Dockerfile # Image de production +├── Dockerfile.tests # Image de tests +├── Dockerfile.package # Build RPM multi-distro (5 stages) +└── Makefile +``` + +## Capabilities Linux requises (SELinux Enforcing) + +L'agent tourne sous l'utilisateur `ja4ebpf` (UID/GID 490 fixe). Les capabilities Linux accordées via `AmbientCapabilities` : + +| Capability | Raison | +|------------|--------| +| `CAP_BPF` | Chargement des programmes eBPF (kernel 5.8+) | +| `CAP_SYS_ADMIN` | Uprobes + RHEL 8 (kernel 4.18, pré-CAP_BPF) | +| `CAP_NET_ADMIN` | Attachement hooks TC ingress | +| `CAP_PERFMON` | Accès perf events pour les uprobes | + +`LimitMEMLOCK=infinity` est requis pour le `mlock()` des maps eBPF. + +## Tests d'intégration + +Stacks Docker Compose testant l'agent contre différents serveurs web : + +```bash +make test-integration # Apache httpd (référence) +make test-nginx # Nginx +make test-nginx-varnish # Nginx + Varnish (reverse proxy) +make test-hitch-varnish # Hitch (TLS) + Varnish +make test-all-stacks # Les 4 stacks en séquence +``` diff --git a/go.work b/go.work index a5e3f96..3a151b4 100644 --- a/go.work +++ b/go.work @@ -1,8 +1,6 @@ go 1.24.6 use ( - ./services/sentinel - ./services/correlator ./shared/go/ja4common ./services/ja4ebpf ) diff --git a/old/services/correlator/.dockerignore b/old/services/correlator/.dockerignore new file mode 100644 index 0000000..5d5369f --- /dev/null +++ b/old/services/correlator/.dockerignore @@ -0,0 +1,19 @@ +# Build outputs +dist/ + +# Dependency directories +vendor/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Aider cache +.aider* diff --git a/old/services/correlator/.env.example b/old/services/correlator/.env.example new file mode 100644 index 0000000..0f036d0 --- /dev/null +++ b/old/services/correlator/.env.example @@ -0,0 +1,2 @@ +# correlator configuration — DO NOT COMMIT real values +LOGCORRELATOR_CLICKHOUSE_DSN=clickhouse://data_writer:ChangeMe@clickhouse:9000/ja4_logs diff --git a/old/services/correlator/.github/workflows/ci.yml b/old/services/correlator/.github/workflows/ci.yml new file mode 100644 index 0000000..92d1b8d --- /dev/null +++ b/old/services/correlator/.github/workflows/ci.yml @@ -0,0 +1,73 @@ +name: Build and Test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + + - name: Download dependencies + run: go mod download + + - name: Run tests with coverage + run: | + go test -race -coverprofile=coverage.txt -covermode=atomic ./... + TOTAL=$(go tool cover -func=coverage.txt | grep total | awk '{gsub(/%/, "", $3); print $3}') + echo "Coverage: ${TOTAL}%" + if (( $(echo "$TOTAL < 80" | bc -l) )); then + echo "Coverage ${TOTAL}% is below 80% threshold" + exit 1 + fi + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.txt + + build: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + + - name: Build binary + run: | + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -ldflags="-w -s" \ + -o logcorrelator \ + ./cmd/logcorrelator + + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: logcorrelator-linux-amd64 + path: logcorrelator + + docker: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - name: Build Docker image + run: docker build -t logcorrelator:latest . + + - name: Run tests in Docker + run: | + docker run --rm logcorrelator:latest --help || true diff --git a/old/services/correlator/.gitignore b/old/services/correlator/.gitignore new file mode 100644 index 0000000..fa69443 --- /dev/null +++ b/old/services/correlator/.gitignore @@ -0,0 +1,32 @@ +# Build directory +/build/ +/dist/ + +# Binaries +*.exe +*.exe~ +*.dll +*.so +*.dylib +/logcorrelator + +# Test binary +*.test + +# Output of the go coverage tool +*.out + +# Dependency directories +vendor/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db +.aider* diff --git a/old/services/correlator/Dockerfile b/old/services/correlator/Dockerfile new file mode 100644 index 0000000..54c4d4f --- /dev/null +++ b/old/services/correlator/Dockerfile @@ -0,0 +1,43 @@ +# syntax=docker/dockerfile:1 +FROM golang:1.24 AS builder + +WORKDIR /build + +RUN apt-get update && apt-get install -y --no-install-recommends git bc && rm -rf /var/lib/apt/lists/* + +COPY go.work go.work.sum* ./ +COPY shared/go/ja4common/ ./shared/go/ja4common/ +COPY services/sentinel/go.mod services/sentinel/go.sum* ./services/sentinel/ +COPY services/correlator/go.mod services/correlator/go.sum* ./services/correlator/ + +WORKDIR /build/services/correlator +RUN --mount=type=cache,target=/go/pkg/mod go mod download + +COPY services/correlator/ /build/services/correlator/ + +ARG SKIP_TESTS=false +RUN --mount=type=cache,target=/go/pkg/mod \ + if [ "$SKIP_TESTS" = "false" ]; then \ + go test -race -coverprofile=coverage.txt -covermode=atomic ./... && \ + echo "=== Coverage Report ===" && \ + go tool cover -func=coverage.txt | grep total && \ + TOTAL=$(go tool cover -func=coverage.txt | grep total | awk '{gsub(/%/, "", $3); print $3}') && \ + echo "Total coverage: ${TOTAL}%" && \ + if (( $(echo "$TOTAL < 60" | bc -l) )); then \ + echo "ERROR: Coverage ${TOTAL}% is below 60% threshold"; \ + exit 1; \ + fi && \ + echo "Coverage check passed!"; \ + else \ + echo "Skipping tests (SKIP_TESTS=true)"; \ + fi + +RUN --mount=type=cache,target=/go/pkg/mod \ + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -ldflags="-w -s" -o /usr/bin/correlator ./cmd/logcorrelator + +FROM scratch AS runtime +COPY --from=builder /usr/bin/correlator /usr/bin/correlator +COPY --from=builder /build/services/correlator/config.example.yml /etc/correlator/correlator.yml +ENTRYPOINT ["/usr/bin/correlator"] +CMD ["-config", "/etc/correlator/correlator.yml"] diff --git a/old/services/correlator/Dockerfile.package b/old/services/correlator/Dockerfile.package new file mode 100644 index 0000000..e74d61a --- /dev/null +++ b/old/services/correlator/Dockerfile.package @@ -0,0 +1,108 @@ +# syntax=docker/dockerfile:1 +# ============================================================================= +# correlator — Dockerfile de packaging RPM (Rocky Linux 8/9, AlmaLinux 10) +# Build context: monorepo root (ja4-platform/) +# Méthode: 1 builder Rocky → 1 rpm-builder (rpmbuild, 3 × dist) → 1 output alpine +# ============================================================================= + +# ============================================================================= +# Stage 1: Builder — compilation du binaire Go sur Rocky Linux 9 +# CGO_ENABLED=0 → binaire statique, mais compilé sur la même distro cible +# ============================================================================= +FROM rockylinux:9 AS builder + +WORKDIR /build + +RUN dnf install -y golang git && dnf clean all + +# Copie du workspace Go et du module partagé en premier (meilleur cache) +COPY go.work go.work.sum* ./ +COPY shared/go/ja4common/ ./shared/go/ja4common/ +COPY services/sentinel/go.mod services/sentinel/go.sum* ./services/sentinel/ +COPY services/correlator/go.mod services/correlator/go.sum* ./services/correlator/ + +WORKDIR /build/services/correlator +RUN go mod download + +COPY services/correlator/ /build/services/correlator/ + +ARG VERSION=dev +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -ldflags="-w -s -X main.Version=${VERSION}" \ + -o /tmp/correlator \ + ./cmd/logcorrelator + +# ============================================================================= +# Stage 2: rpm-builder — construction des RPMs avec rpmbuild +# Un seul stage, trois appels rpmbuild successifs (el8, el9, el10). +# Le spec lit les fichiers depuis %{_builddir} (répertoire BUILD de rpmbuild). +# ============================================================================= +FROM rockylinux:9 AS rpm-builder + +WORKDIR /package + +ARG VERSION=dev + +RUN dnf install -y rpm-build rpmdevtools systemd-rpm-macros && dnf clean all + +RUN mkdir -p /root/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} && \ + mkdir -p /packages/rpm/{el8,el9,el10} + +# Disposition des fichiers dans BUILD/ (attendue par le spec correlator) +RUN mkdir -p /root/rpmbuild/BUILD/usr/bin \ + /root/rpmbuild/BUILD/etc/logcorrelator \ + /root/rpmbuild/BUILD/etc/systemd/system \ + /root/rpmbuild/BUILD/etc/logrotate.d + +COPY --from=builder /tmp/correlator /root/rpmbuild/BUILD/usr/bin/logcorrelator +COPY services/correlator/config.example.yml /root/rpmbuild/BUILD/etc/logcorrelator/logcorrelator.yml +COPY services/correlator/config.example.yml /root/rpmbuild/BUILD/etc/logcorrelator/logcorrelator.yml.example +COPY services/correlator/logcorrelator.service /root/rpmbuild/BUILD/etc/systemd/system/logcorrelator.service +COPY services/correlator/packaging/rpm/logrotate /root/rpmbuild/BUILD/etc/logrotate.d/logcorrelator + +RUN chmod 755 /root/rpmbuild/BUILD/usr/bin/logcorrelator && \ + chmod 640 /root/rpmbuild/BUILD/etc/logcorrelator/logcorrelator.yml && \ + chmod 640 /root/rpmbuild/BUILD/etc/logcorrelator/logcorrelator.yml.example && \ + chmod 644 /root/rpmbuild/BUILD/etc/systemd/system/logcorrelator.service && \ + chmod 644 /root/rpmbuild/BUILD/etc/logrotate.d/logcorrelator + +COPY services/correlator/packaging/rpm/logcorrelator.spec /root/rpmbuild/SPECS/logcorrelator.spec + +# el8 +RUN rpmbuild --define "_topdir /root/rpmbuild" \ + --define "dist .el8" \ + --define "build_version ${VERSION}" \ + --target x86_64 \ + -bb /root/rpmbuild/SPECS/logcorrelator.spec && \ + cp /root/rpmbuild/RPMS/x86_64/*.el8.x86_64.rpm /packages/rpm/el8/ + +# el9 +RUN rpmbuild --define "_topdir /root/rpmbuild" \ + --define "dist .el9" \ + --define "build_version ${VERSION}" \ + --target x86_64 \ + -bb /root/rpmbuild/SPECS/logcorrelator.spec && \ + cp /root/rpmbuild/RPMS/x86_64/*.el9.x86_64.rpm /packages/rpm/el9/ + +# el10 +RUN rpmbuild --define "_topdir /root/rpmbuild" \ + --define "dist .el10" \ + --define "build_version ${VERSION}" \ + --target x86_64 \ + -bb /root/rpmbuild/SPECS/logcorrelator.spec && \ + cp /root/rpmbuild/RPMS/x86_64/*.el10.x86_64.rpm /packages/rpm/el10/ + +# ============================================================================= +# Stage 3: output — image finale contenant uniquement les RPMs +# ============================================================================= +FROM alpine:latest AS output + +WORKDIR /packages +COPY --from=rpm-builder /packages/rpm/el8/*.rpm /packages/rpm/el8/ +COPY --from=rpm-builder /packages/rpm/el9/*.rpm /packages/rpm/el9/ +COPY --from=rpm-builder /packages/rpm/el10/*.rpm /packages/rpm/el10/ + +CMD ["sh", "-c", \ + "echo '=== RPM el8 ===' && ls -la /packages/rpm/el8/ && \ + echo '' && echo '=== RPM el9 ===' && ls -la /packages/rpm/el9/ && \ + echo '' && echo '=== RPM el10 ===' && ls -la /packages/rpm/el10/"] diff --git a/old/services/correlator/Makefile b/old/services/correlator/Makefile new file mode 100644 index 0000000..bc90d5f --- /dev/null +++ b/old/services/correlator/Makefile @@ -0,0 +1,148 @@ +.PHONY: build build-docker test test-docker lint clean help docker-build-dev docker-build-runtime package package-rpm + +# Docker parameters +DOCKER=docker +# Use buildx for better cache management and parallel builds +DOCKER_BUILD=$(DOCKER) build +DOCKER_BUILDX=$(DOCKER) buildx +DOCKER_RUN=$(DOCKER) run + +# Image names +DEV_IMAGE=logcorrelator-dev:latest +RUNTIME_IMAGE=logcorrelator:latest +PACKAGER_IMAGE=logcorrelator-packager:latest +PACKAGER_IMAGE_EL8=logcorrelator-packager-el8:latest +PACKAGER_IMAGE_EL9=logcorrelator-packager-el9:latest +PACKAGER_IMAGE_EL10=logcorrelator-packager-el10:latest + +# Binary name +BINARY_NAME=logcorrelator +DIST_DIR=dist + +# Package version +PKG_VERSION ?= 1.1.22 + +# Enable BuildKit for better performance +export DOCKER_BUILDKIT=1 + +## build: Build the logcorrelator binary locally +build: + mkdir -p $(DIST_DIR) + go build -ldflags="-w -s" -o $(DIST_DIR)/$(BINARY_NAME) ./cmd/$(BINARY_NAME) + +## docker-build-dev: Build the development Docker image (with tests and coverage) +docker-build-dev: + $(DOCKER_BUILD) --target builder -t $(DEV_IMAGE) -f Dockerfile . + +## docker-build-dev-no-test: Build the development Docker image WITHOUT tests (faster) +docker-build-dev-no-test: + $(DOCKER_BUILD) --target builder --no-cache --build-arg SKIP_TESTS=true -t $(DEV_IMAGE) -f Dockerfile . + +## docker-build-runtime: Build the runtime Docker image (fast, no tests) +docker-build-runtime: + $(DOCKER_BUILD) --target runtime -t $(RUNTIME_IMAGE) -f Dockerfile . + +## test: Run unit tests locally +test: + go test -race -coverprofile=coverage.out ./... + +## test-docker: Run unit tests inside Docker container +test-docker: docker-build-dev + @echo "Tests already run in builder stage" + +## lint: Run linters +lint: + go vet ./... + gofmt -l . + +## fmt: Format all Go files +fmt: + gofmt -w . + +## package: Build RPM packages for all target distributions +package: package-rpm + +## package-rpm: Build RPM packages for Rocky Linux 8/9, AlmaLinux 10 (requires Docker) +## Uses buildx for parallel builds (el8, el9, el10 built simultaneously) +package-rpm: + mkdir -p $(DIST_DIR)/rpm/el8 $(DIST_DIR)/rpm/el9 $(DIST_DIR)/rpm/el10 + @echo "Starting parallel RPM builds for el8, el9, el10..." + # Build all three distributions in parallel using buildx + $(DOCKER_BUILDX) build --target output -t $(PACKAGER_IMAGE) \ + --build-arg VERSION=$(PKG_VERSION) \ + -f Dockerfile.package . \ + --load + @echo "Extracting RPM packages from Docker image..." + $(DOCKER_RUN) --rm -v $(PWD)/$(DIST_DIR)/rpm:/output/rpm $(PACKAGER_IMAGE) sh -c \ + "cp -r /packages/rpm/el8 /output/rpm/ && \ + cp -r /packages/rpm/el9 /output/rpm/ && \ + cp -r /packages/rpm/el10 /output/rpm/" + @echo "RPM packages created:" + @echo " Enterprise Linux 8 (el8):" + ls -la $(DIST_DIR)/rpm/el8/ 2>/dev/null || echo " (no packages)" + @echo " Enterprise Linux 9 (el9):" + ls -la $(DIST_DIR)/rpm/el9/ 2>/dev/null || echo " (no packages)" + @echo " Enterprise Linux 10 (el10):" + ls -la $(DIST_DIR)/rpm/el10/ 2>/dev/null || echo " (no packages)" + +## package-rpm-sequential: Build RPM packages sequentially (fallback if parallel fails) +package-rpm-sequential: + mkdir -p $(DIST_DIR)/rpm/el8 $(DIST_DIR)/rpm/el9 $(DIST_DIR)/rpm/el10 + @echo "Building RPM for el8..." + $(DOCKER_BUILD) --target rpm-el8-builder -t $(PACKAGER_IMAGE_EL8) \ + --build-arg VERSION=$(PKG_VERSION) \ + -f Dockerfile.package . + @echo "Building RPM for el9..." + $(DOCKER_BUILD) --target rpm-el9-builder -t $(PACKAGER_IMAGE_EL9) \ + --build-arg VERSION=$(PKG_VERSION) \ + -f Dockerfile.package . + @echo "Building RPM for el10..." + $(DOCKER_BUILD) --target rpm-el10-builder -t $(PACKAGER_IMAGE_EL10) \ + --build-arg VERSION=$(PKG_VERSION) \ + -f Dockerfile.package . + @echo "Extracting RPM packages..." + $(DOCKER_RUN) --rm -v $(PWD)/$(DIST_DIR)/rpm:/output/rpm \ + -v $(PACKAGER_IMAGE_EL8):/el8:ro \ + -v $(PACKAGER_IMAGE_EL9):/el9:ro \ + -v $(PACKAGER_IMAGE_EL10):/el10:ro \ + alpine:latest sh -c \ + "cp -r /el8/packages/rpm/el8 /output/rpm/ && \ + cp -r /el9/packages/rpm/el9 /output/rpm/ && \ + cp -r /el10/packages/rpm/el10 /output/rpm/" + +## test-package-rpm: Test RPM package installation in Docker +test-package-rpm: package-rpm + ./packaging/test/test-rpm.sh + +## test-package: Test RPM package installation +test-package: test-package-rpm + +## ci: Full CI pipeline (tests, build, packages, package tests) +ci: ci-test ci-build ci-package ci-package-test + +## ci-test: Run all tests for CI +ci-test: test lint + +## ci-build: Build for CI (production binary) +ci-build: build + +## ci-package: Build all packages for CI +ci-package: package + +## ci-package-test: Test all packages for CI +ci-package-test: test-package + +## clean: Clean build artifacts and Docker images +clean: + rm -rf $(DIST_DIR)/ + rm -f coverage.out + $(DOCKER) rmi $(DEV_IMAGE) 2>/dev/null || true + $(DOCKER) rmi $(RUNTIME_IMAGE) 2>/dev/null || true + $(DOCKER) rmi $(PACKAGER_IMAGE) 2>/dev/null || true + +## help: Show this help message +help: + @echo "Usage: make [target]" + @echo "" + @echo "Targets:" + @sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /' diff --git a/old/services/correlator/README.md b/old/services/correlator/README.md new file mode 100644 index 0000000..a4e2deb --- /dev/null +++ b/old/services/correlator/README.md @@ -0,0 +1,426 @@ +# logcorrelator + +Service de corrélation de logs HTTP et réseau écrit en Go. + +## Description + +**logcorrelator** reçoit deux flux de logs JSON via des sockets Unix datagrammes (SOCK_DGRAM) : +- **Source A** : logs HTTP applicatifs (Apache, reverse proxy) +- **Source B** : logs réseau (métadonnées IP/TCP, JA3/JA4, etc.) + +Il corrèle les événements sur la base de `src_ip + src_port` dans une fenêtre temporelle configurable, et produit des logs corrélés vers : +- Un fichier local (JSON lines) +- ClickHouse (pour analyse et archivage) + +Les logs opérationnels du service (démarrage, erreurs, métriques) sont écrits sur **stderr** et collectés par journald. Aucune donnée corrélée n'apparaît sur stdout. + +## Architecture + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Source A │────▶│ │────▶│ File Sink │ +│ HTTP/Apache │ │ Correlation │ │ (JSON lines) │ +│ (Unix DGRAM) │ │ Service │ └─────────────────┘ +└─────────────────┘ │ │ + │ - Buffers │ ┌─────────────────┐ +┌─────────────────┐ │ - Time Window │────▶│ ClickHouse │ +│ Source B │────▶│ - Orphan Policy │ │ Sink │ +│ Réseau/JA4 │ │ - Keep-Alive │ └─────────────────┘ +│ (Unix DGRAM) │ └──────────────────┘ +└─────────────────┘ +``` + +Architecture hexagonale : domaine pur (`internal/domain`), ports abstraits (`internal/ports`), adaptateurs (`internal/adapters`), orchestration (`internal/app`). + +## Build (100% Docker) + +Tout le build, les tests et le packaging RPM s'exécutent dans des conteneurs : + +```bash +# Build complet avec tests (builder stage) +make docker-build-dev + +# Packaging RPM (el8, el9, el10) +make package-rpm + +# Build rapide sans tests +make docker-build-dev-no-test + +# Tests en local (nécessite Go 1.21+) +make test +``` + +### Prérequis + +- Docker 20.10+ + +## Installation + +### Packages RPM + +```bash +# Générer les packages +make package-rpm + +# Installer (Rocky Linux / AlmaLinux) +sudo dnf install -y dist/rpm/el8/logcorrelator-1.1.12-1.el8.x86_64.rpm +sudo dnf install -y dist/rpm/el9/logcorrelator-1.1.12-1.el9.x86_64.rpm +sudo dnf install -y dist/rpm/el10/logcorrelator-1.1.12-1.el10.x86_64.rpm + +# Démarrer +sudo systemctl enable --now logcorrelator +sudo systemctl status logcorrelator +``` + +### Build manuel + +```bash +# Binaire local (nécessite Go 1.21+) +go build -o logcorrelator ./cmd/logcorrelator +./logcorrelator -config config.example.yml +``` + +## Configuration + +Fichier YAML. Voir `config.example.yml` pour un exemple complet. + +```yaml +log: + level: INFO # DEBUG, INFO, WARN, ERROR + +inputs: + unix_sockets: + - name: http + source_type: A # Source HTTP + path: /var/run/logcorrelator/http.socket + format: json + socket_permissions: "0666" + - name: network + source_type: B # Source réseau + path: /var/run/logcorrelator/network.socket + format: json + socket_permissions: "0666" + +outputs: + file: + path: /var/log/logcorrelator/correlated.log + clickhouse: + enabled: false + dsn: clickhouse://user:pass@localhost:9000/db + table: http_logs_raw + batch_size: 500 + flush_interval_ms: 200 + max_buffer_size: 5000 + drop_on_overflow: true + timeout_ms: 1000 + stdout: + enabled: false # no-op pour les données ; logs opérationnels toujours sur stderr + +correlation: + time_window: + value: 10 + unit: s + orphan_policy: + apache_always_emit: true + apache_emit_delay_ms: 500 # délai avant émission orphelin A (ms) + network_emit: false + matching: + mode: one_to_many # Keep-Alive : un B peut corréler plusieurs A successifs + buffers: + max_http_items: 10000 + max_network_items: 20000 + ttl: + network_ttl_s: 120 # TTL remis à zéro à chaque corrélation (Keep-Alive) + # Exclure des IPs source (IPs uniques ou plages CIDR) + exclude_source_ips: + - 10.0.0.1 + - 172.16.0.0/12 + # Restreindre la corrélation à certains ports de destination (optionnel) + # Si la liste est vide, tous les ports sont corrélés + include_dest_ports: + - 80 + - 443 + +metrics: + enabled: false + addr: ":8080" +``` + +### Format du DSN ClickHouse + +``` +clickhouse://username:password@host:port/database +``` + +Ports : `9000` (natif, recommandé) ou `8123` (HTTP). + +## Format des logs + +### Source A (HTTP) + +```json +{ + "src_ip": "192.168.1.1", "src_port": 8080, + "dst_ip": "10.0.0.1", "dst_port": 443, + "timestamp": 1704110400000000000, + "method": "GET", "path": "/api/test" +} +``` + +### Source B (Réseau) + +```json +{ + "src_ip": "192.168.1.1", "src_port": 8080, + "dst_ip": "10.0.0.1", "dst_port": 443, + "ja3": "abc123", "ja4": "xyz789" +} +``` + +### Log corrélé (sortie) + +Structure JSON plate — tous les champs A et B sont fusionnés à la racine : + +```json +{ + "timestamp": "2024-01-01T12:00:00Z", + "src_ip": "192.168.1.1", "src_port": 8080, + "dst_ip": "10.0.0.1", "dst_port": 443, + "correlated": true, + "method": "GET", "path": "/api/test", + "ja3": "abc123", "ja4": "xyz789" +} +``` + +En cas de collision de champ entre A et B, les deux valeurs sont conservées avec préfixes `a_` et `b_`. + +Les orphelins A (sans B correspondant) sont émis avec `"correlated": false, "orphan_side": "A"`. + +## Schema ClickHouse + +Le fichier `sql/init.sql` contient le schéma complet prêt à l'emploi. + +```bash +clickhouse-client --multiquery < sql/init.sql +``` + +### Architecture des tables + +``` +http_logs_raw ← inserts du service (raw_json String) + │ + └─ mv_http_logs ← vue matérialisée (parse JSON → colonnes typées) + │ + ▼ + http_logs ← table requêtable par les analystes +``` + +### Table `http_logs` — colonnes + +| Groupe | Colonnes | +|---|---| +| Temporel | `time` DateTime, `log_date` Date | +| Réseau | `src_ip` IPv4, `src_port` UInt16, `dst_ip` IPv4, `dst_port` UInt16 | +| HTTP | `method`, `scheme`, `host`, `path`, `query`, `http_version` (LowCardinality) | +| Corrélation | `orphan_side`, `correlated` UInt8, `keepalives` UInt16, `a_timestamp`/`b_timestamp` UInt64, `conn_id` | +| IP meta | `ip_meta_df` UInt8, `ip_meta_id` UInt16, `ip_meta_total_length` UInt16, `ip_meta_ttl` UInt8 | +| TCP meta | `tcp_meta_options`, `tcp_meta_window_size` UInt32, `tcp_meta_mss` UInt16, `tcp_meta_window_scale` UInt8, `syn_to_clienthello_ms` Int32 | +| TLS / fingerprint | `tls_version`, `tls_sni`, `tls_alpn` (LowCardinality), `ja3`, `ja3_hash`, `ja4` | +| En-têtes HTTP | `header_user_agent`, `header_accept`, `header_accept_encoding`, `header_accept_language`, `header_x_request_id`, `header_x_trace_id`, `header_x_forwarded_for`, `header_sec_ch_ua*`, `header_sec_fetch_*` | + +### Utilisateurs et permissions + +```sql +-- data_writer : INSERT sur http_logs_raw uniquement (compte du service) +GRANT INSERT ON ja4_logs.http_logs_raw TO data_writer; +GRANT SELECT ON ja4_logs.http_logs_raw TO data_writer; + +-- analyst : lecture sur la table parsée +GRANT SELECT ON ja4_logs.http_logs TO analyst; +``` + +### Vérification de l'ingestion + +```sql +-- Données brutes reçues +SELECT count(*), min(ingest_time), max(ingest_time) FROM ja4_logs.http_logs_raw; + +-- Données parsées par la vue matérialisée +SELECT count(*), min(time), max(time) FROM ja4_logs.http_logs; + +-- Derniers logs corrélés +SELECT time, src_ip, dst_ip, method, host, path, ja4 +FROM ja4_logs.http_logs +WHERE correlated = 1 +ORDER BY time DESC LIMIT 10; +``` + +## Signaux + +| Signal | Comportement | +|--------|--------------| +| `SIGINT` / `SIGTERM` | Arrêt gracieux (drain buffers, flush sinks) | +| `SIGHUP` | Réouverture des fichiers de sortie (log rotation) | + +## Logs internes + +Les logs opérationnels vont sur **stderr** : + +```bash +# Systemd +journalctl -u logcorrelator -f + +# Docker +docker logs -f logcorrelator +``` + +## Structure du projet + +``` +cmd/logcorrelator/ # Point d'entrée +internal/ + adapters/ + inbound/unixsocket/ # Lecture SOCK_DGRAM → NormalizedEvent + outbound/ + clickhouse/ # Sink ClickHouse (batch, retry, logging complet) + file/ # Sink fichier (JSON lines, SIGHUP reopen) + multi/ # Fan-out vers plusieurs sinks + stdout/ # No-op pour les données (logs opérationnels sur stderr) + app/ # Orchestrator (sources → corrélation → sinks) + config/ # Chargement/validation YAML + domain/ # CorrelationService, NormalizedEvent, CorrelatedLog + observability/ # Logger, métriques, serveur HTTP /metrics /health + ports/ # Interfaces EventSource, CorrelatedLogSink, CorrelationProcessor +config.example.yml # Exemple de configuration +Dockerfile # Build multi-stage (builder, runtime, dev) +Dockerfile.package # Packaging RPM multi-distros (el8, el9, el10) +Makefile # Cibles de build +architecture.yml # Spécification architecture +logcorrelator.service # Unité systemd +``` + +## Débogage + +### Logs DEBUG + +```yaml +log: + level: DEBUG +``` + +Exemples de logs produits : +``` +[unixsocket:http] DEBUG event received: source=A src_ip=192.168.1.1 src_port=8080 +[correlation] DEBUG processing A event: key=192.168.1.1:8080 +[correlation] DEBUG correlation found: A(src_ip=... src_port=... ts=...) + B(...) +[correlation] DEBUG A event has no matching B key in buffer: key=... +[correlation] DEBUG event excluded by IP filter: source=A src_ip=10.0.0.1 src_port=8080 +[correlation] DEBUG event excluded by dest port filter: source=A dst_port=22 +[correlation] DEBUG TTL reset for B event (Keep-Alive): key=... new_ttl=120s +[clickhouse] DEBUG batch sent: rows=42 table=http_logs_raw +``` + +### Serveur de métriques + +```yaml +metrics: + enabled: true + addr: ":8080" +``` + +`GET /health` → `{"status":"healthy"}` + +`GET /metrics` : + +```json +{ + "events_received_a": 1542, "events_received_b": 1498, + "correlations_success": 1450, "correlations_failed": 92, + "failed_no_match_key": 45, "failed_time_window": 23, + "failed_buffer_eviction": 5, "failed_ttl_expired": 12, + "failed_ip_excluded": 7, "failed_dest_port_filtered": 3, + "buffer_a_size": 23, "buffer_b_size": 18, + "orphans_emitted_a": 92, "orphans_pending_a": 4, + "keepalive_resets": 892 +} +``` + +### Diagnostic par métriques + +| Métrique élevée | Cause | Solution | +|---|---|---| +| `failed_no_match_key` | A et B n'ont pas le même `src_ip:src_port` | Vérifier les deux sources | +| `failed_time_window` | Timestamps trop éloignés | Augmenter `time_window.value` ou vérifier NTP | +| `failed_ttl_expired` | B expire avant corrélation | Augmenter `ttl.network_ttl_s` | +| `failed_buffer_eviction` | Buffers trop petits | Augmenter `buffers.max_http_items` / `max_network_items` | +| `failed_ip_excluded` | Traffic depuis IPs exclues | Normal si attendu | +| `failed_dest_port_filtered` | Traffic sur ports non listés | Vérifier `include_dest_ports` | +| `orphans_emitted_a` élevé | Beaucoup de A sans B | Vérifier que la source B envoie des événements | + +### Filtrage par IP source + +```yaml +correlation: + exclude_source_ips: + - 10.0.0.1 # IP unique (health checks) + - 172.16.0.0/12 # Plage CIDR +``` + +Les événements depuis ces IPs sont silencieusement ignorés (non corrélés, non émis en orphelin). La métrique `failed_ip_excluded` comptabilise les exclusions. + +### Filtrage par port de destination + +```yaml +correlation: + include_dest_ports: + - 80 # HTTP + - 443 # HTTPS + - 8080 + - 8443 +``` + +Si la liste est non vide, seuls les événements dont le `dst_port` est dans la liste participent à la corrélation. Les autres sont silencieusement ignorés. Liste vide = tous les ports corrélés (comportement par défaut). La métrique `failed_dest_port_filtered` comptabilise les exclusions. + +### Scripts de test + +```bash +# Script Bash (simple) +./scripts/test-correlation.sh -c 10 -v + +# Script Python (scénarios complets : basic, time window, keepalive, différentes IPs) +pip install requests +python3 scripts/test-correlation-advanced.py --all +``` + +## Troubleshooting + +### ClickHouse : erreurs d'insertion + +- **`No such column`** : vérifier que la table `http_logs_raw` utilise la colonne unique `raw_json` (pas de colonnes séparées) +- **`ACCESS_DENIED`** : `GRANT INSERT ON ja4_logs.http_logs_raw TO data_writer;` +- Les erreurs de flush sont loggées en ERROR dans les logs du service + +### Vue matérialisée vide + +Si `http_logs_raw` a des données mais `http_logs` est vide : +```sql +-- Vérifier la vue +SHOW CREATE TABLE ja4_logs.mv_http_logs; +-- Vérifier les permissions (la MV s'exécute sous le compte du service) +GRANT SELECT ON ja4_logs.http_logs_raw TO data_writer; +``` + +### Sockets Unix : permission denied + +Vérifier que `socket_permissions: "0666"` est configuré et que le répertoire `/var/run/logcorrelator` appartient à l'utilisateur `logcorrelator`. + +### Service systemd ne démarre pas + +```bash +journalctl -u logcorrelator -n 50 --no-pager +/usr/bin/logcorrelator -config /etc/logcorrelator/logcorrelator.yml +``` + +## License + +MIT diff --git a/old/services/correlator/architecture.yml b/old/services/correlator/architecture.yml new file mode 100644 index 0000000..e3e37e9 --- /dev/null +++ b/old/services/correlator/architecture.yml @@ -0,0 +1,974 @@ +service: + name: logcorrelator + context: http-network-correlation + language: go + pattern: hexagonal + description: > + logcorrelator est un service système (lancé par systemd) écrit en Go, chargé + de recevoir deux flux de logs JSON via des sockets Unix, de corréler les + événements HTTP applicatifs (source A, typiquement Apache ou reverse proxy) + avec des événements réseau (source B, métadonnées IP/TCP, JA3/JA4, etc.) + sur la base de la combinaison strictement définie src_ip + src_port, avec + une fenêtre temporelle configurable. Le service supporte les connexions + HTTP Keep-Alive : un log réseau peut être corrélé à plusieurs logs HTTP + successifs (stratégie 1‑à‑N). La rétention en mémoire est bornée par des + tailles de caches configurables et un TTL dynamique pour la source B. Le + service émet toujours les événements A même lorsqu'aucun événement B n'est + disponible, n'émet jamais de logs B seuls, et pousse les résultats vers + ClickHouse et/ou un fichier local. + + Fonctionnalités de débogage incluses : + - Serveur de métriques HTTP (/metrics, /health) + - Logs DEBUG détaillés avec raisons des échecs de corrélation + - Filtrage des IPs source (exclude_source_ips) + - Scripts de test (Bash et Python) + - Métriques : événements reçus, corrélations, échecs par raison, buffers, orphelins + +runtime: + deployment: + unit_type: systemd + description: > + logcorrelator est livré sous forme de binaire autonome, exécuté comme un + service systemd. L'unité systemd assure le démarrage automatique au boot, + le redémarrage en cas de crash, et une intégration standard dans l'écosystème + Linux. + binary_path: /usr/bin/logcorrelator + config_path: /etc/logcorrelator/logcorrelator.yml + user: logcorrelator + group: logcorrelator + restart: on-failure + systemd_unit: + path: /etc/systemd/system/logcorrelator.service + content_example: | + [Unit] + Description=logcorrelator service + After=network.target + + [Service] + Type=simple + User=logcorrelator + Group=logcorrelator + ExecStart=/usr/bin/logcorrelator -config /etc/logcorrelator/logcorrelator.yml + ExecReload=/bin/kill -HUP $MAINPID + Restart=on-failure + RestartSec=5 + + # Security hardening + NoNewPrivileges=true + ProtectSystem=strict + ProtectHome=true + ReadWritePaths=/var/log/logcorrelator /var/run/logcorrelator /etc/logcorrelator + + # Resource limits + LimitNOFILE=65536 + + # Systemd timeouts + TimeoutStartSec=10 + TimeoutStopSec=30 + + [Install] + WantedBy=multi-user.target + os: + supported: + - rocky-linux-8 + - rocky-linux-9 + - almalinux-10 + - autres-linux-recentes + logs: + stdout_stderr: journald + structured: true + description: > + Les logs internes du service (erreurs, messages d'information) sont envoyés + vers stdout/stderr et collectés par journald. Ils sont structurés et ne + contiennent pas de données personnelles. + signals: + graceful_shutdown: + - SIGINT + - SIGTERM + reload: + - SIGHUP + description: > + SIGINT/SIGTERM : arrêt propre (arrêt des sockets, vidage des buffers, fermeture + des sinks). SIGHUP : réouverture des fichiers de sortie (utile pour la + rotation des logs via logrotate) sans arrêter le service. + filesystem: + description: > + Permissions et propriété des fichiers et répertoires utilisés par logcorrelator. + directories: + - path: /var/run/logcorrelator + owner: logcorrelator:logcorrelator + permissions: "0755" + purpose: > + Contient les sockets Unix (http.socket, network.socket). + Les sockets sont créés avec des permissions 0666 (world read/write). + - path: /var/log/logcorrelator + owner: logcorrelator:logcorrelator + permissions: "0750" + purpose: > + Contient les logs corrélés (correlated.log). + - path: /var/lib/logcorrelator + owner: logcorrelator:logcorrelator + permissions: "0750" + purpose: > + Répertoire home du service (données internes). + - path: /etc/logcorrelator + owner: logcorrelator:logcorrelator + permissions: "0750" + purpose: > + Contient la configuration (logcorrelator.yml, logcorrelator.yml.example). + files: + - path: /etc/logcorrelator/logcorrelator.yml + owner: logcorrelator:logcorrelator + permissions: "0640" + rpm_directive: "%config(noreplace)" + - path: /etc/logcorrelator/logcorrelator.yml.example + owner: logcorrelator:logcorrelator + permissions: "0640" + - path: /etc/systemd/system/logcorrelator.service + owner: root:root + permissions: "0644" + - path: /etc/logrotate.d/logcorrelator + owner: root:root + permissions: "0644" + rpm_directive: "%config(noreplace)" + sockets: + - path: /var/run/logcorrelator/http.socket + owner: logcorrelator:logcorrelator + permissions: "0666" + type: unix_datagram + purpose: "Source A - logs HTTP applicatifs" + - path: /var/run/logcorrelator/network.socket + owner: logcorrelator:logcorrelator + permissions: "0666" + type: unix_datagram + purpose: "Source B - logs réseau" + +packaging: + description: > + logcorrelator est distribué sous forme de packages .rpm (Rocky Linux, AlmaLinux, + RHEL), construits intégralement dans des conteneurs. Le changelog RPM est mis + à jour à chaque changement de version. + Tous les numéros de version doivent être cohérents entre le spec RPM, le Makefile + (PKG_VERSION), le CHANGELOG.md et les tags git. + + Politique de mise à jour de la configuration : + - Le fichier logcorrelator.yml est marqué %config(noreplace) : il n'est JAMAIS + écrasé lors d'une mise à jour. La configuration existante est préservée. + - Le fichier logcorrelator.yml.example est TOUJOURS mis à jour pour refléter + les nouvelles options de configuration disponibles. + - Lors de la première installation, si logcorrelator.yml n'existe pas, il est + créé à partir de logcorrelator.yml.example. + formats: + - rpm + target_distros: + - rocky-linux-8 + - rocky-linux-9 + - almalinux-10 + - rhel-8 + - rhel-9 + - rhel-10 + rpm: + tool: fpm + changelog: + source: git # ou CHANGELOG.md + description: > + À chaque build, un script génère un fichier de changelog RPM à partir de + l'historique (tags/commits) et le passe à fpm (option --rpm-changelog). + contents: + - path: /usr/bin/logcorrelator + type: binary + - path: /etc/logcorrelator/logcorrelator.yml + type: config + directives: "%config(noreplace)" + behavior: > + Jamais écrasé lors des mises à jour. Préservé automatiquement par RPM. + Créé uniquement lors de la première installation s'il n'existe pas. + - path: /etc/logcorrelator/logcorrelator.yml.example + type: doc + behavior: > + TOUJOURS mis à jour lors des mises à jour. Sert de référence pour les + nouvelles options de configuration disponibles. + - path: /etc/systemd/system/logcorrelator.service + type: systemd_unit + - path: /etc/logrotate.d/logcorrelator + type: logrotate_script + directives: "%config(noreplace)" + logrotate_example: | + /var/log/logcorrelator/correlated.log { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + create 0640 logcorrelator logcorrelator + sharedscripts + postrotate + /bin/systemctl reload logcorrelator > /dev/null 2>&1 || true + endscript + } + +config: + format: yaml + location: /etc/logcorrelator/logcorrelator.yml + reload_strategy: signal_sighup_for_files + description: > + Toute la configuration est centralisée dans un fichier YAML lisible. Le RPM + fournit aussi un fichier d'exemple mis à jour à chaque version. + example: | + # /etc/logcorrelator/logcorrelator.yml + + log: + level: INFO # DEBUG, INFO, WARN, ERROR + + inputs: + unix_sockets: + # Source HTTP (A) : logs applicatifs en JSON, 1 datagramme = 1 log. + - name: http + source_type: A + path: /var/run/logcorrelator/http.socket + format: json + socket_permissions: "0666" + + # Source réseau (B) : logs IP/TCP/JA3... en JSON, 1 datagramme = 1 log. + - name: network + source_type: B + path: /var/run/logcorrelator/network.socket + format: json + socket_permissions: "0666" + + outputs: + file: + enabled: true + path: /var/log/logcorrelator/correlated.log + + clickhouse: + enabled: false + dsn: clickhouse://user:pass@localhost:9000/db + table: correlated_logs_http_network + batch_size: 500 + flush_interval_ms: 200 + max_buffer_size: 5000 + drop_on_overflow: true + async_insert: true + timeout_ms: 1000 + + stdout: + enabled: false + level: INFO # DEBUG: tous les logs (y compris orphelins), INFO: seulement corrélés, WARN: corrélés seulement, ERROR: aucun + + correlation: + # Fenêtre de corrélation : si le log HTTP arrive avant le réseau, il attend + # au plus cette durée (sauf éviction du cache HTTP). + # Augmentée à 10s pour supporter le Keep-Alive HTTP. + time_window: + value: 10 + unit: s + + orphan_policy: + apache_always_emit: true # Toujours émettre les événements A, même sans correspondance B + network_emit: false # Ne jamais émettre les événements B seuls + + matching: + mode: one_to_many # Keep‑Alive : un B peut corréler plusieurs A. + + buffers: + # Tailles max des caches en mémoire (en nombre de logs). + max_http_items: 10000 + max_network_items: 20000 + + ttl: + # Durée de vie standard d'un log réseau (B) en mémoire. Chaque corrélation + # réussie avec un A réinitialise ce TTL. + # Augmenté à 120s pour supporter les sessions HTTP Keep-Alive longues. + network_ttl_s: 120 + + # Filtrage des IPs source à exclure (optionnel) + exclude_source_ips: + - 10.0.0.1 # IP unique + - 172.16.0.0/12 # Plage CIDR + # Les événements depuis ces IPs sont silencieusement ignorés + + # Serveur de métriques HTTP (optionnel, pour débogage et monitoring) + metrics: + enabled: false + addr: ":8080" # Adresse d'écoute du serveur HTTP + # Endpoints: + # GET /metrics - Retourne les métriques de corrélation en JSON + # GET /health - Health check + +inputs: + description: > + Deux flux de logs JSON via sockets Unix datagram (SOCK_DGRAM). Chaque datagramme + contient un JSON complet. Le champ source_type ("A" ou "B") doit être spécifié + pour chaque socket. À défaut, la source est déduite automatiquement (présence de + headers = source A, sinon source B). + unix_sockets: + - name: http + id: A + description: > + Source A, logs HTTP applicatifs (Apache, reverse proxy, etc.). Schéma JSON + variable, champ timestamp (int64, nanosecondes) obligatoire, headers dynamiques (header_*). + path: /var/run/logcorrelator/http.socket + source_type: A + permissions: "0666" + protocol: unix + socket_type: dgram + mode: datagram + format: json + framing: message + max_datagram_bytes: 65535 + retry_on_error: true + + - name: network + id: B + description: > + Source B, logs réseau (métadonnées IP/TCP, JA3/JA4, etc.). Seuls src_ip + et src_port sont requis pour la corrélation. Le champ timestamp est optionnel ; + s'il est absent, l'heure de réception est utilisée. + path: /var/run/logcorrelator/network.socket + source_type: B + permissions: "0666" + protocol: unix + socket_type: dgram + mode: datagram + format: json + framing: message + max_datagram_bytes: 65535 + retry_on_error: true + +outputs: + description: > + Les logs corrélés sont envoyés vers un ou plusieurs sinks (MultiSink). + sinks: + file: + enabled: true + description: > + Sink fichier local. Un JSON par ligne. Rotation gérée par logrotate, + réouverture du fichier sur SIGHUP. Le champ `enabled: false` coupe + completement l'ecriture du fichier (le sink n'est pas cree). + path: /var/log/logcorrelator/correlated.log + format: json_lines + rotate_managed_by: external_logrotate + clickhouse: + enabled: false + description: > + Sink principal pour l'archivage et l'analyse quasi temps réel. Inserts + batch asynchrones, drop en cas de saturation. Le service insère uniquement + dans une table RAW (raw_json String, ingest_time DateTime DEFAULT now()). + La table parsée et la vue matérialisée sont gérées en externe (DDL séparés). + Toutes les erreurs de connexion, de flush et de retry sont loggées : + INFO à la connexion, ERROR sur échec de flush, WARN sur drop/retry, DEBUG sur envoi réussi. + dsn: clickhouse://user:pass@host:9000/db + table: correlated_logs_http_network + batch_size: 500 + flush_interval_ms: 200 + max_buffer_size: 5000 + drop_on_overflow: true + async_insert: true + timeout_ms: 1000 + stdout: + enabled: false + description: > + Sink no-op pour les données. Aucune donnée corrélée ou orpheline n'est + jamais écrite sur stdout. Ce sink existe uniquement pour satisfaire + l'interface CorrelatedLogSink. Les logs opérationnels du service + (démarrage, erreurs, métriques de débogage) sont toujours sur stderr + via observability.Logger, indépendamment de ce sink. + +correlation: + description: > + Corrélation stricte basée sur src_ip + src_port et une fenêtre temporelle + configurable. Aucun autre champ n'est utilisé pour la décision de corrélation. + key: + - src_ip + - src_port + time_window: + value: 10 + unit: s + description: > + Fenêtre de temps appliquée aux timestamps de A et B. Si B n'arrive pas dans + ce délai, A est émis comme orphelin. Augmentée à 10s pour le Keep-Alive. + retention_limits: + max_http_items: 10000 + max_network_items: 20000 + description: > + Limites des caches. Si max_http_items est atteint, le plus ancien A est + évincé et émis orphelin. Si max_network_items est atteint, le plus ancien B + est supprimé silencieusement. + ttl_management: + network_ttl_s: 120 + description: > + TTL des logs réseau. Chaque fois qu'un B est corrélé à un A (Keep-Alive), + son TTL est remis à cette valeur. Augmenté à 120s pour les sessions longues. + timestamp_source: + apache: timestamp (champ int64, nanosecondes) + network: timestamp (champ int64, nanosecondes) si présent, sinon time (RFC3339), + sinon reception_time (time.Now()) + orphan_policy: + apache_always_emit: true + network_emit: false + matching: + mode: one_to_many + description: > + Stratégie 1‑à‑N : un log réseau peut être utilisé pour plusieurs logs HTTP + successifs tant qu'il n'a pas expiré ni été évincé. + ip_filtering: + directive: exclude_source_ips + description: > + Liste d'IPs source (exactes ou plages CIDR) à ignorer silencieusement. + Événements non corrélés, non émis en orphelin. Métrique : failed_ip_excluded. + dest_port_filtering: + directive: include_dest_ports + description: > + Liste blanche de ports de destination. Si non vide, seuls les événements + dont le dst_port est dans la liste participent à la corrélation. Les autres + sont silencieusement ignorés (non corrélés, non émis en orphelin). + Liste vide = tous les ports autorisés (comportement par défaut). + Métrique : failed_dest_port_filtered. + example: + include_dest_ports: [80, 443, 8080, 8443] + +schema: + description: > + Schémas variables pour A et B. Quelques champs seulement sont obligatoires + pour la corrélation, les autres sont acceptés sans modification de code. + source_A: + description: > + Logs HTTP applicatifs au format JSON. + required_fields: + - name: src_ip + type: string + - name: src_port + type: int + - name: timestamp + type: int64 + unit: ns + optional_fields: + - name: dst_ip + type: string + - name: dst_port + type: int + - name: method + type: string + - name: path + type: string + - name: host + type: string + - name: http_version + type: string + dynamic_fields: + - pattern: header_* + target_map: headers + - pattern: "*" + target_map: extra + source_B: + description: Logs réseau JSON (IP/TCP, JA3/JA4...). + required_fields: + - name: src_ip + type: string + - name: src_port + type: int + optional_fields: + - name: dst_ip + type: string + - name: dst_port + type: int + - name: timestamp + type: int64 + unit: ns + - name: time + type: string + format: RFC3339 ou RFC3339Nano + dynamic_fields: + - pattern: "*" + target_map: extra + + normalized_event: + description: > + Représentation interne unifiée des événements A/B. + fields: + - name: source + type: enum("A","B") + - name: timestamp + type: time.Time + - name: src_ip + type: string + - name: src_port + type: int + - name: dst_ip + type: string + optional: true + - name: dst_port + type: int + optional: true + - name: headers + type: map[string]string + optional: true + - name: extra + type: map[string]any + + correlated_log: + description: > + Structure du log corrélé émis vers les sinks. + fields: + - name: timestamp + type: time.Time + - name: src_ip + type: string + - name: src_port + type: int + - name: dst_ip + type: string + optional: true + - name: dst_port + type: int + optional: true + - name: correlated + type: bool + - name: orphan_side + type: string + - name: "*" + type: map[string]any + +clickhouse_schema: + strategy: external_ddls + database: ja4_processing + description: > + La table ClickHouse est gérée en dehors du service. Le service insère dans une + table RAW avec une seule colonne raw_json contenant le log corrélé complet + sérialisé en JSON. La colonne ingest_time utilise DEFAULT now(). + Toute extraction de champs (table parsée, vue matérialisée) est gérée en externe + via des DDL séparés, non implémentés dans le service. + tables: + - name: http_logs_raw + description: > + Table d'ingestion brute. Une seule colonne raw_json contient le log corrélé + complet sérialisé en JSON. La colonne ingest_time est auto-générée avec + DEFAULT now(). Partitionnée par jour pour optimiser le TTL. + engine: MergeTree + partition_by: toDate(ingest_time) + order_by: ingest_time + columns: + - name: raw_json + type: String + - name: ingest_time + type: DateTime + default: now() + insert_format: | + INSERT INTO ja4_processing.http_logs_raw (raw_json) VALUES + ('{...log corrélé sérialisé en JSON...}') + notes: > + Le service utilise l'API native clickhouse-go/v2 (PrepareBatch + Append + Send). + La colonne ingest_time n'est PAS explicitement insérée (DEFAULT now() est utilisé). + + - name: http_logs + description: > + Table parsée (optionnelle, gérée en externe). Le service n'implémente PAS + l'extraction des champs suivants. Si cette table est utilisée, elle doit être + alimentée par une vue matérialisée ou un traitement ETL externe. + engine: MergeTree + partition_by: log_date + order_by: (time, src_ip, dst_ip, ja4) + columns: + - name: time + type: DateTime + - name: log_date + type: Date + default: toDate(time) + - name: src_ip + type: IPv4 + - name: src_port + type: UInt16 + - name: dst_ip + type: IPv4 + - name: dst_port + type: UInt16 + - name: method + type: LowCardinality(String) + - name: scheme + type: LowCardinality(String) + - name: host + type: LowCardinality(String) + - name: path + type: String + - name: query + type: String + - name: http_version + type: LowCardinality(String) + - name: orphan_side + type: LowCardinality(String) + - name: correlated + type: UInt8 + - name: keepalives + type: UInt16 + status: non_implémenté + - name: a_timestamp + type: UInt64 + status: non_implémenté + - name: b_timestamp + type: UInt64 + status: non_implémenté + - name: conn_id + type: String + status: non_implémenté + - name: ip_meta_df + type: UInt8 + status: non_implémenté + - name: ip_meta_id + type: UInt32 + status: non_implémenté + - name: ip_meta_total_length + type: UInt32 + status: non_implémenté + - name: ip_meta_ttl + type: UInt8 + status: non_implémenté + - name: tcp_meta_options + type: LowCardinality(String) + status: non_implémenté + - name: tcp_meta_window_size + type: UInt32 + status: non_implémenté + - name: syn_to_clienthello_ms + type: Int32 + status: non_implémenté + - name: tls_version + type: LowCardinality(String) + status: non_implémenté + - name: tls_sni + type: LowCardinality(String) + status: non_implémenté + - name: ja3 + type: String + status: non_implémenté + - name: ja3_hash + type: String + status: non_implémenté + - name: ja4 + type: String + status: non_implémenté + - name: header_user_agent + type: String + status: non_implémenté + - name: header_accept + type: String + status: non_implémenté + - name: header_accept_encoding + type: String + status: non_implémenté + - name: header_accept_language + type: String + status: non_implémenté + - name: header_x_request_id + type: String + status: non_implémenté + - name: header_x_trace_id + type: String + status: non_implémenté + - name: header_x_forwarded_for + type: String + status: non_implémenté + - name: header_sec_ch_ua + type: String + status: non_implémenté + - name: header_sec_ch_ua_mobile + type: String + status: non_implémenté + - name: header_sec_ch_ua_platform + type: String + status: non_implémenté + - name: header_sec_fetch_dest + type: String + status: non_implémenté + - name: header_sec_fetch_mode + type: String + status: non_implémenté + - name: header_sec_fetch_site + type: String + status: non_implémenté + notes: > + Cette table et la vue matérialisée associée sont gérées en externe (DDL séparés). + Le service se contente d'insérer le JSON brut dans http_logs_raw. + Les champs marqués "non_implémenté" ne sont PAS extraits par le service. + + users: + description: > + La gestion des utilisateurs ClickHouse est externe au service. Le DSN est + configuré dans le fichier de configuration YAML. + notes: > + Cette section est fournie à titre indicatif pour l'administration ClickHouse. + + migration: + description: > + Aucune migration n'est implémentée dans le service. La gestion des schémas + (tables, vues matérialisées) est entièrement externe (DDL séparés). + +architecture: + description: > + Architecture hexagonale : domaine de corrélation indépendant, ports abstraits + pour les sources/sinks, adaptateurs pour sockets Unix, fichier, ClickHouse et + stdout, couche application d'orchestration, et modules infra (config, observabilité). + modules: + - name: cmd/logcorrelator + type: entrypoint + responsibilities: + - Chargement de la configuration YAML. + - Initialisation des adaptateurs d'entrée/sortie. + - Création du CorrelationService. + - Démarrage de l'orchestrateur. + - Gestion des signaux (SIGINT, SIGTERM, SIGHUP). + - Versioning via -ldflags (main.Version). + - name: internal/domain + type: domain + responsibilities: + - Modèles NormalizedEvent et CorrelatedLog. + - CorrelationService (fenêtre, TTL, buffers bornés, one-to-many/Keep-Alive, orphelins). + - Filtrage par IP source (exclude_source_ips, CIDR). + - Filtrage par port destination (include_dest_ports, liste blanche). + - Custom JSON marshaling pour CorrelatedLog (structure plate). + - name: internal/ports + type: ports + responsibilities: + - Interfaces EventSource, CorrelatedLogSink, CorrelationProcessor. + - name: internal/app + type: application + responsibilities: + - Orchestrator : EventSource → CorrelationService → MultiSink. + - Gestion du contexte de shutdown et drain des événements. + - name: internal/adapters/inbound/unixsocket + type: adapter_inbound + responsibilities: + - Lecture Unix datagram (SOCK_DGRAM) et parsing JSON → NormalizedEvent. + - Détection automatique de la source (A/B) via source_type ou headers. + - Gestion des permissions de socket (défaut 0666). + - Cleanup du fichier socket à l'arrêt. + - name: internal/adapters/outbound/file + type: adapter_outbound + responsibilities: + - Écriture JSON lines. + - Réouverture du fichier sur SIGHUP (log rotation). + - Validation des chemins (répertoire autorisé). + - name: internal/adapters/outbound/clickhouse + type: adapter_outbound + responsibilities: + - Bufferisation + inserts batch asynchrones. + - Gestion du drop_on_overflow. + - Retry avec backoff exponentiel (MaxRetries=3). + - API native clickhouse-go/v2 (PrepareBatch + Append + Send). + - Logging complet via observability.Logger (SetLogger) : INFO à la connexion, + DEBUG sur envoi réussi (rows/table), WARN sur drop buffer et retries, + ERROR sur échec de flush (périodique, batch, fermeture). + - name: internal/adapters/outbound/stdout + type: adapter_outbound + responsibilities: + - Sink no-op pour les données corrélées. + - Write/Flush/Close ne font rien : les données ne passent jamais par stdout. + - Les logs opérationnels sont sur stderr via observability.Logger (indépendant de ce sink). + - name: internal/adapters/outbound/multi + type: adapter_outbound + responsibilities: + - Fan-out vers plusieurs sinks. + - Implémentation de Reopen() pour la rotation des logs. + - name: internal/config + type: infrastructure + responsibilities: + - Chargement/validation de la configuration YAML. + - Valeurs par défaut et fallback pour champs dépréciés. + - name: internal/observability + type: infrastructure + responsibilities: + - Logger structuré avec niveaux (DEBUG, INFO, WARN, ERROR). + - CorrelationMetrics : suivi des statistiques de corrélation. + - MetricsServer : serveur HTTP pour exposition des métriques (/metrics, /health). + - Traçage des événements exclus (exclude_source_ips). + - Logs pour : événements reçus, corrélations, orphelins, buffer plein. + +testing: + unit: + description: > + Tests unitaires table‑driven, couverture cible ≥ 80 %. La couverture actuelle + est d'environ 74-80% selon les versions. Les tests se concentrent sur la logique + de corrélation, les caches, les sinks et le parsing des datagrammes. + coverage_minimum: 0.8 + coverage_actual: ~0.74-0.80 + focus: + - CorrelationService (fenêtre, TTL, évictions, one-to-many/Keep-Alive) + - Parsing A/B → NormalizedEvent (datagrammes JSON) + - ClickHouseSink (batching, retry, overflow, logging erreurs/succès) + - FileSink (réouverture sur SIGHUP) + - MultiSink (fan-out) + - StdoutSink (no-op data, test stdout reste vide) + - Config (validation, valeurs par défaut, exclude_source_ips) + - UnixSocketSource (lecture, permissions, cleanup) + - CorrelationMetrics (suivi des statistiques) + - MetricsServer (endpoints /metrics et /health) + integration: + description: > + Tests d'intégration limités. Le flux complet A+B → corrélation → sinks est + testé via des tests unitaires avec mocks. ClickHouse est mocké (pas de tests + avec vrai ClickHouse). Scénarios Keep-Alive testés dans correlation_service_test.go. + Scripts de test fournis : scripts/test-correlation.sh et scripts/test-correlation-advanced.py. + +docker: + description: > + Build, tests et packaging RPM sont exécutés intégralement dans des conteneurs + via un multi‑stage build. Deux Dockerfiles : Dockerfile (build + runtime + dev) + et Dockerfile.package (RPM multi-distros : el8, el9, el10). + build_pipeline: + multi_stage: true + stages: + - name: builder + base: golang:1.21 + description: > + go test -race -coverprofile=coverage.txt ./... avec vérification de couverture + (échec si < 80 %). Compilation d'un binaire statique (CGO_ENABLED=0, + GOOS=linux, GOARCH=amd64). + - name: runtime + base: scratch + description: > + Image minimale contenant uniquement le binaire et la config exemple. + - name: rpm_builder_el8 + base: rockylinux:8 + description: > + Installation de fpm (via Ruby), construction RPM pour Enterprise Linux 8. + - name: rpm_builder_el9 + base: rockylinux:9 + description: > + Installation de fpm (via Ruby), construction RPM pour Enterprise Linux 9. + - name: rpm_builder_el10 + base: almalinux:10 + description: > + Installation de fpm (via Ruby), construction RPM pour Enterprise Linux 10. + - name: output_export + base: alpine:latest + description: > + Export des paquets RPM produits pour les 3 distributions (el8, el9, el10). + files: + - path: Dockerfile + description: Build principal (builder, runtime, dev) et packaging RPM mono-distro. + - path: Dockerfile.package + description: Packaging RPM multi-distros (el8, el9, el10) avec scripts post/preun/postun. + +observability: + description: > + Le service inclut des fonctionnalités complètes de débogage et de monitoring + pour diagnostiquer les problèmes de corrélation et surveiller les performances. + logging: + levels: + - DEBUG: Tous les événements reçus, tentatives de corrélation, raisons des échecs + - INFO: Événements corrélés, démarrage/arrêt du service + - WARN: Orphelins émis, buffer plein, TTL expiré + - ERROR: Erreurs de parsing, échecs de sink, erreurs critiques + debug_logs: + - "event received: source=A src_ip=192.168.1.1 src_port=8080 timestamp=..." + - "processing A event: key=192.168.1.1:8080 timestamp=..." + - "correlation found: A(src_ip=... src_port=... ts=...) + B(src_ip=... src_port=... ts=...)" + - "A event has no matching B key in buffer: key=..." + - "A event has same key as B but outside time window: key=... time_diff=5s window=10s" + - "event excluded by IP filter: source=A src_ip=10.0.0.1 src_port=8080" + - "event excluded by dest port filter: source=A dst_port=22" + - "TTL reset for B event (Keep-Alive): key=... new_ttl=120s" + - "[clickhouse] DEBUG batch sent: rows=42 table=correlated_logs_http_network" + info_logs: + - "[clickhouse] INFO connected to ClickHouse: table=... batch_size=500 flush_interval_ms=200" + warn_logs: + - "[clickhouse] WARN buffer full, dropping log: table=... buffer_size=5000" + - "[clickhouse] WARN retrying batch insert: attempt=2/3 delay=100ms rows=42 err=connection refused" + error_logs: + - "[clickhouse] ERROR periodic flush failed: ..." + - "[clickhouse] ERROR batch flush failed: ..." + - "[clickhouse] ERROR final flush on close failed: ..." + metrics_server: + enabled: true + endpoints: + - path: /metrics + method: GET + description: Retourne les métriques de corrélation au format JSON + response_example: | + { + "events_received_a": 1542, + "events_received_b": 1498, + "correlations_success": 1450, + "correlations_failed": 92, + "failed_no_match_key": 45, + "failed_time_window": 23, + "failed_buffer_eviction": 5, + "failed_ttl_expired": 12, + "failed_ip_excluded": 7, + "failed_dest_port_filtered": 3, + "buffer_a_size": 23, + "buffer_b_size": 18, + "orphans_emitted_a": 92, + "keepalive_resets": 892 + } + - path: /health + method: GET + description: Health check + response_example: | + {"status":"healthy"} + metrics_tracked: + events_received: + - events_received_a: Nombre d'événements HTTP (source A) reçus + - events_received_b: Nombre d'événements réseau (source B) reçus + correlations: + - correlations_success: Corrélations réussies + - correlations_failed: Échecs de corrélation + failure_reasons: + - failed_no_match_key: Clé src_ip:src_port non trouvée dans le buffer + - failed_time_window: Événements hors fenêtre temporelle + - failed_buffer_eviction: Buffer plein, événement évincé + - failed_ttl_expired: TTL du événement B expiré + - failed_ip_excluded: Événement exclu par filtre IP (exclude_source_ips) + - failed_dest_port_filtered: Événement exclu par filtre port destination (include_dest_ports) + buffers: + - buffer_a_size: Taille actuelle du buffer HTTP + - buffer_b_size: Taille actuelle du buffer réseau + orphans: + - orphans_emitted_a: Orphelins A émis (sans correspondance B) + - orphans_emitted_b: Orphelins B émis (toujours 0, policy: network_emit=false) + - orphans_pending_a: Orphelins A en attente (délai avant émission) + - pending_orphan_match: B a corrélé avec un orphelin A en attente + keepalive: + - keepalive_resets: Resets TTL pour mode Keep-Alive (one-to-many) + troubleshooting: + description: > + Guide de diagnostic basé sur les métriques et logs + common_issues: + - symptom: failed_no_match_key élevé + cause: Les logs A et B n'ont pas le même src_ip + src_port + solution: Vérifier que les deux sources utilisent la même combinaison IP/port + - symptom: failed_time_window élevé + cause: Timestamps trop éloignés (> time_window.value) + solution: Augmenter correlation.time_window.value ou synchroniser les horloges (NTP) + - symptom: failed_ttl_expired élevé + cause: Les événements B expirent avant corrélation + solution: Augmenter correlation.ttl.network_ttl_s + - symptom: failed_buffer_eviction élevé + cause: Buffers trop petits pour le volume de logs + solution: Augmenter correlation.buffers.max_http_items et max_network_items + - symptom: failed_ip_excluded élevé + cause: Traffic depuis des IPs configurées dans exclude_source_ips + solution: Vérifier la configuration, c'est normal si attendu + - symptom: failed_dest_port_filtered élevé + cause: Traffic sur des ports non listés dans include_dest_ports + solution: Vérifier la configuration include_dest_ports, ou vider la liste pour tout accepter + - symptom: orphans_emitted_a élevé + cause: Beaucoup de logs A sans correspondance B + solution: Vérifier que la source B envoie bien les événements attendus + test_scripts: + - name: scripts/test-correlation.sh + description: Script Bash pour tester la corrélation avec des événements synthétiques + features: + - Envoi de paires A+B avec mêmes src_ip:src_port + - Vérification des métriques avant/après + - Options: -c (count), -d (delay), -v (verbose), -m (metrics-url) + - name: scripts/test-correlation-advanced.py + description: Script Python avancé avec multiples scénarios de test + features: + - Basic test: corrélations simples + - Time window test: vérifie l'expiration de la fenêtre temporelle + - Different IP test: vérifie non-corrélation avec IPs différentes + - Keep-Alive test: vérifie le mode one-to-many + - Métriques en temps réel + diff --git a/old/services/correlator/cmd/logcorrelator/main.go b/old/services/correlator/cmd/logcorrelator/main.go new file mode 100644 index 0000000..ed507c7 --- /dev/null +++ b/old/services/correlator/cmd/logcorrelator/main.go @@ -0,0 +1,204 @@ +// Package main initialise et démarre le service logcorrelator. +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/antitbone/ja4/correlator/internal/adapters/inbound/unixsocket" + "github.com/antitbone/ja4/correlator/internal/adapters/outbound/clickhouse" + "github.com/antitbone/ja4/correlator/internal/adapters/outbound/file" + "github.com/antitbone/ja4/correlator/internal/adapters/outbound/multi" + "github.com/antitbone/ja4/correlator/internal/adapters/outbound/stdout" + "github.com/antitbone/ja4/correlator/internal/app" + "github.com/antitbone/ja4/correlator/internal/config" + "github.com/antitbone/ja4/correlator/internal/domain" + "github.com/antitbone/ja4/correlator/internal/observability" + "github.com/antitbone/ja4/correlator/internal/ports" +) + +var Version = "dev" + +// main configure les sources, les puits et le service de corrélation, puis démarre l'orchestrateur. +func main() { + configPath := flag.String("config", "config.yml", "path to configuration file") + version := flag.Bool("version", false, "print version and exit") + flag.Parse() + + if *version { + fmt.Println(Version) + os.Exit(0) + } + + // Load configuration + cfg, err := config.Load(*configPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading configuration: %v\n", err) + os.Exit(1) + } + + // Initialize logger with configured level + logger := observability.NewLoggerWithLevel("logcorrelator", cfg.Log.GetLevel()) + + logger.Info(fmt.Sprintf("Starting logcorrelator version %s (log_level=%s)", Version, cfg.Log.GetLevel())) + + // Create sources + sources := make([]ports.EventSource, 0, len(cfg.Inputs.UnixSockets)) + for _, inputCfg := range cfg.Inputs.UnixSockets { + source := unixsocket.NewUnixSocketSource(unixsocket.Config{ + Name: inputCfg.Name, + Path: inputCfg.Path, + SourceType: inputCfg.SourceType, + SocketPermissions: inputCfg.GetSocketPermissions(), + }) + // Set logger for debug logging + source.SetLogger(logger) + sources = append(sources, source) + logger.Info(fmt.Sprintf("Configured input source: name=%s, path=%s, permissions=%o", inputCfg.Name, inputCfg.Path, inputCfg.GetSocketPermissions())) + } + + // Create sinks + sinks := make([]ports.CorrelatedLogSink, 0) + + if cfg.Outputs.File.Enabled && cfg.Outputs.File.Path != "" { + fileSink, err := file.NewFileSink(file.Config{ + Path: cfg.Outputs.File.Path, + }) + if err != nil { + logger.Error("Failed to create file sink", err) + os.Exit(1) + } + sinks = append(sinks, fileSink) + logger.Info(fmt.Sprintf("Configured file sink: path=%s", cfg.Outputs.File.Path)) + } + + if cfg.Outputs.ClickHouse.Enabled { + clickHouseSink, err := clickhouse.NewClickHouseSink(clickhouse.Config{ + DSN: cfg.Outputs.ClickHouse.DSN, + Table: cfg.Outputs.ClickHouse.Table, + BatchSize: cfg.Outputs.ClickHouse.BatchSize, + FlushIntervalMs: cfg.Outputs.ClickHouse.FlushIntervalMs, + MaxBufferSize: cfg.Outputs.ClickHouse.MaxBufferSize, + DropOnOverflow: cfg.Outputs.ClickHouse.DropOnOverflow, + AsyncInsert: cfg.Outputs.ClickHouse.AsyncInsert, + TimeoutMs: cfg.Outputs.ClickHouse.TimeoutMs, + }) + if err != nil { + logger.Error("Failed to create ClickHouse sink", err) + os.Exit(1) + } + clickHouseSink.SetLogger(logger) + sinks = append(sinks, clickHouseSink) + logger.Info(fmt.Sprintf("Configured ClickHouse sink: table=%s", cfg.Outputs.ClickHouse.Table)) + } + + if cfg.Outputs.Stdout.Enabled { + stdoutSink := stdout.NewStdoutSink(stdout.Config{Enabled: true}) + sinks = append(sinks, stdoutSink) + logger.Info("Configured stdout sink (operational logs on stderr)") + } + + // Create multi-sink wrapper + multiSink := multi.NewMultiSink(sinks...) + + // Create correlation service + correlationSvc := domain.NewCorrelationService(domain.CorrelationConfig{ + TimeWindow: cfg.Correlation.GetTimeWindow(), + ApacheAlwaysEmit: cfg.Correlation.GetApacheAlwaysEmit(), + ApacheEmitDelayMs: cfg.Correlation.GetApacheEmitDelayMs(), + NetworkEmit: false, + MaxHTTPBufferSize: cfg.Correlation.GetMaxHTTPBufferSize(), + MaxNetworkBufferSize: cfg.Correlation.GetMaxNetworkBufferSize(), + NetworkTTLS: cfg.Correlation.GetNetworkTTLS(), + MatchingMode: cfg.Correlation.GetMatchingMode(), + ExcludeSourceIPs: cfg.Correlation.GetExcludeSourceIPs(), + IncludeDestPorts: cfg.Correlation.GetIncludeDestPorts(), + }, &domain.RealTimeProvider{}) + + // Set logger for correlation service + correlationSvc.SetLogger(logger.WithFields(map[string]any{"component": "correlation"})) + + logger.Info(fmt.Sprintf("Correlation service initialized: time_window=%s, emit_orphans=%v, emit_delay_ms=%d", + cfg.Correlation.GetTimeWindow().String(), + cfg.Correlation.GetApacheAlwaysEmit(), + cfg.Correlation.GetApacheEmitDelayMs())) + + // Start metrics server if enabled + var metricsServer *observability.MetricsServer + if cfg.Metrics.Enabled { + addr := cfg.Metrics.Addr + if addr == "" { + addr = ":8080" // Default address + } + var err error + metricsServer, err = observability.NewMetricsServer(addr, correlationSvc.GetMetricsSnapshot) + if err != nil { + logger.Error("Failed to create metrics server", err) + os.Exit(1) + } + if err := metricsServer.Start(); err != nil { + logger.Error("Failed to start metrics server", err) + os.Exit(1) + } + logger.Info(fmt.Sprintf("Metrics server started: addr=%s", metricsServer.Addr())) + logger.Info("Metrics endpoints: /metrics (JSON), /health") + } + + // Create orchestrator + orchestrator := app.NewOrchestrator(app.OrchestratorConfig{ + Sources: sources, + Sink: multiSink, + }, correlationSvc) + + // Start the application + if err := orchestrator.Start(); err != nil { + logger.Error("Failed to start orchestrator", err) + os.Exit(1) + } + + logger.Info("logcorrelator started successfully") + + // Wait for shutdown signal + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) + + for { + sig := <-sigChan + + if sig == syscall.SIGHUP { + // Reopen file sinks for log rotation + logger.Info("SIGHUP received, reopening file sinks...") + if err := multiSink.Reopen(); err != nil { + logger.Error("Error reopening file sinks", err) + } else { + logger.Info("File sinks reopened successfully") + } + continue + } + + // Shutdown signal received + logger.Info(fmt.Sprintf("Shutdown signal received: %v", sig)) + break + } + + // Graceful shutdown + if err := orchestrator.Stop(); err != nil { + logger.Error("Error during shutdown", err) + } + + // Stop metrics server + if metricsServer != nil { + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := metricsServer.Stop(shutdownCtx); err != nil { + logger.Error("Error stopping metrics server", err) + } + } + + logger.Info("logcorrelator stopped") +} diff --git a/old/services/correlator/config.example.yml b/old/services/correlator/config.example.yml new file mode 100644 index 0000000..6457555 --- /dev/null +++ b/old/services/correlator/config.example.yml @@ -0,0 +1,92 @@ +# logcorrelator configuration file +# Format: YAML + +# Logging configuration +log: + level: INFO # DEBUG, INFO, WARN, ERROR + +inputs: + unix_sockets: + - name: http + source_type: A + path: /var/run/logcorrelator/http.socket + format: json + socket_permissions: "0666" # world read/write + - name: network + source_type: B + path: /var/run/logcorrelator/network.socket + format: json + socket_permissions: "0666" + +outputs: + file: + enabled: true + path: /var/log/logcorrelator/correlated.log + + clickhouse: + enabled: false + dsn: clickhouse://user:pass@localhost:9000/ja4_logs + table: http_logs_raw + batch_size: 500 + flush_interval_ms: 200 + max_buffer_size: 5000 + drop_on_overflow: true + async_insert: true + timeout_ms: 1000 + + stdout: + enabled: false + +correlation: + # Time window for correlation (A and B must be within this window) + # Increased to 10s to support HTTP Keep-Alive scenarios + time_window: + value: 10 + unit: s + + # Orphan policy: what to do when no match is found + orphan_policy: + apache_always_emit: true # Always emit A events, even without B match + apache_emit_delay_ms: 500 # Wait 500ms before emitting as orphan (allows B to arrive) + network_emit: false # Never emit B events alone + + # Matching mode: one_to_one or one_to_many (Keep-Alive) + matching: + mode: one_to_many + + # Buffer limits (max events in memory) + buffers: + max_http_items: 10000 + max_network_items: 20000 + + # TTL for network events (source B) + # Increased to 120s to support long-lived HTTP Keep-Alive sessions + ttl: + network_ttl_s: 120 + + # Exclude specific source IPs or CIDR ranges from correlation + # Events from these IPs will be silently dropped (not correlated, not emitted) + # Useful for excluding health checks, internal traffic, or known bad actors + exclude_source_ips: + - 10.0.0.1 # Single IP + - 192.168.1.100 # Another single IP + - 172.16.0.0/12 # CIDR range (private network) + - 10.10.10.0/24 # Another CIDR range + + # Restrict correlation to specific destination ports (optional) + # If non-empty, only events whose dst_port matches one of these values will be correlated + # Events on other ports are silently ignored (not correlated, not emitted as orphans) + # Useful to focus on HTTP/HTTPS traffic only and ignore unrelated connections + # include_dest_ports: + # - 80 # HTTP + # - 443 # HTTPS + # - 8080 # HTTP alt + # - 8443 # HTTPS alt + +# Metrics server configuration (optional, for debugging/monitoring) +metrics: + enabled: false + addr: ":8080" # Address to listen on (e.g., ":8080", "localhost:8080") + # Endpoints: + # GET /metrics - Returns correlation metrics as JSON + # GET /health - Health check endpoint diff --git a/old/services/correlator/docs/detection.md b/old/services/correlator/docs/detection.md new file mode 100644 index 0000000..971dc59 --- /dev/null +++ b/old/services/correlator/docs/detection.md @@ -0,0 +1,224 @@ +# Architecture de détection — logcorrelator + +## Vue d'ensemble + +Le système de détection est composé de **trois couches** qui s'enchaînent en pipeline : + +``` +Trafic HTTP/TLS capturé + │ + ▼ +┌───────────────────┐ +│ ClickHouse │ Stockage, agrégation, vues heuristiques +│ (SQL pipeline) │ +└────────┬──────────┘ + │ + ▼ +┌───────────────────┐ +│ bot_detector.py │ Modèle IA (Isolation Forest, cycle 5 min) +│ (Python / ML) │ +└────────┬──────────┘ + │ + ▼ +┌───────────────────┐ +│ ml_detected_ │ Table de résultats (ReplacingMergeTree) +│ anomalies │ +└───────────────────┘ +``` + +--- + +## 1. Ingestion des logs (`http_logs_raw` → `http_logs`) + +Les logs bruts arrivent en JSON dans la table `http_logs_raw`. Une **vue matérialisée** (`mv_http_logs`) les parse en temps réel et alimente la table `http_logs`, qui contient les champs structurés suivants : + +| Catégorie | Champs clés | +|---|---| +| Réseau | `src_ip`, `src_port`, `dst_ip`, `dst_port` | +| Enrichissement | `src_asn`, `src_country_code`, `src_as_name` (via dictionnaire IPLocate) | +| HTTP | `method`, `host`, `path`, `query`, `http_version` | +| Corrélation | `correlated`, `orphan_side`, `conn_id`, `keepalives` | +| Métadonnées IP | `ip_meta_ttl`, `ip_meta_id`, `ip_meta_df`, `ip_meta_total_length` | +| Métadonnées TCP | `tcp_meta_window_size`, `tcp_meta_mss`, `tcp_meta_window_scale`, `tcp_meta_options` | +| TLS / Fingerprint | `tls_version`, `tls_sni`, `tls_alpn`, `ja3`, `ja3_hash`, `ja4` | +| En-têtes HTTP | `header_user_agent`, `header_sec_ch_ua*`, `header_sec_fetch_*`, … | + +L'enrichissement IP est réalisé via le dictionnaire `dict_iplocate_asn` (fichier CSV chargé en mémoire, rechargé toutes les 1-2 heures). + +--- + +## 2. Agrégation comportementale (fenêtre horaire) + +Deux tables d'agrégation `AggregatingMergeTree` sont alimentées en continu par des vues matérialisées. + +### 2.1 `agg_host_ip_ja4_1h` — Comportement réseau & applicatif + +Agrège par triplet **(window_start, src_ip, ja4, host)** toutes les heures : + +| Métrique agrégée | Signification | +|---|---| +| `hits` | Nombre total de requêtes | +| `count_post` | Requêtes POST | +| `uniq_paths` | Chemins distincts visités | +| `uniq_query_params` | Paramètres de query distincts | +| `unique_src_ports` | Ports sources distincts | +| `unique_conn_id` | Connexions TCP distinctes | +| `max_keepalives` | Réutilisation maximale d'une connexion | +| `orphan_count` | Requêtes sans corrélation TCP complète | +| `ip_id_zero_count` | Paquets avec IP ID = 0 (spoofing potentiel) | +| `tcp_fp_raw` | Hash de l'empreinte TCP (window, MSS, scale, options) | +| `tcp_jitter_variance` | Variance du délai SYN→ClientHello (jitter TLS) | +| `total_ip_length_var` | Variance de la taille des paquets IP | +| `mss_1460_count` | Requêtes avec MSS = 1460 (signature Ethernet/desktop) | + +### 2.2 `agg_header_fingerprint_1h` — Empreinte des en-têtes HTTP + +Agrège par **(window_start, src_ip)** : + +| Métrique | Signification | +|---|---| +| `header_order_hash` | Hash de l'ordre des en-têtes (fingerprint JA4H) | +| `header_count` | Nombre d'en-têtes distincts | +| `has_accept_language` | Présence de `Accept-Language` | +| `has_cookie` | Présence de `Cookie` | +| `has_referer` | Présence de `Referer` | +| `modern_browser_score` | Score 0/50/100 selon présence UA et `Sec-CH-UA` | +| `ua_ch_mismatch` | Incohérence entre `User-Agent` et `Sec-CH-UA-Platform` | +| `sec_fetch_mode/dest` | Contexte de navigation déclaré | + +--- + +## 3. Exclusions (listes blanches) + +Avant toute analyse, deux tables permettent d'**exclure les robots légitimes** connus : + +- `bot_ip` (fichier `bot_ip.csv`) — IPs à ignorer (crawlers, monitoring…) +- `bot_ja4` (fichier `bot_ja4.csv`) — Fingerprints JA4 à ignorer +- `ref_bot_networks` — Réseaux CIDR IPv4/IPv6 catégorisés (légitimes ou malveillants) + +Ces exclusions sont appliquées dans la vue `view_ai_features_1h`. + +--- + +## 4. Vue IA : `view_ai_features_1h` + +Cette vue consolidée **sur 24 heures glissantes** calcule les **28 features** passées au modèle ML. Elle joint les deux tables d'agrégation et dérive les métriques suivantes : + +| Feature | Calcul | Signal détecté | +|---|---|---| +| `hit_velocity` | `hits / durée_en_secondes` | Volume de requêtes anormalement élevé | +| `fuzzing_index` | `uniq_query_params / uniq_paths` | Exploration paramétrique (fuzzing) | +| `post_ratio` | `count_post / hits` | Soumission de formulaires en masse | +| `port_exhaustion_ratio` | `unique_src_ports / hits` | Rotation de ports (scan) | +| `orphan_ratio` | `orphan_count / hits` | Requêtes sans handshake complet | +| `ip_id_zero_ratio` | `ip_id_zero_count / hits` | Spoofing d'adresse IP | +| `multiplexing_efficiency` | `hits / unique_conn_id` | Réutilisation des connexions (H2/H3) | +| `true_window_size` | `tcp_win * 2^tcp_scale` | Taille réelle de la fenêtre TCP | +| `window_mss_ratio` | `tcp_win / tcp_mss` | Cohérence TCP stack | +| `tcp_jitter_variance` | Variance SYN→ClientHello | Irrégularité du timing TLS | +| `alpn_http_mismatch` | ALPN=h2 mais HTTP/1.1 | Négociation TLS mensongère | +| `is_alpn_missing` | ALPN absent ou `00` | Client non-standard | +| `sni_host_mismatch` | SNI ≠ Host header | Proxy transparent / bot | +| `mss_mobile_mismatch` | MSS=1460 + score navigateur élevé | Client mobile simulé depuis desktop | +| `is_fake_navigation` | `sec_fetch_mode=navigate` mais `sec_fetch_dest≠document` | Navigation simulée | +| `tcp_shared_count` | Nb d'IPs partageant la même empreinte TCP | Infrastructure partagée / botnet | +| `header_order_shared_count` | Nb d'IPs partageant le même ordre d'en-têtes | Outil automatisé commun | + +--- + +## 5. Modèle IA : Isolation Forest (`bot_detector.py`) + +### Cycle d'exécution + +Le service tourne en boucle avec un **cycle de 5 minutes** : + +``` +fetch_and_analyze() + │ + ├─ Requête SELECT * FROM view_ai_features_1h + │ + ├─ Nettoyage des données (fillna) + │ + ├─ Dual-Model routing : + │ ├─ [Complet] correlated=1 → 23 features (réseau + TLS + headers) + │ └─ [Applicatif] correlated=0 → 19 features (headers + comportement) + │ + └─ INSERT INTO ml_detected_anomalies +``` + +### Paramétrage du modèle + +| Paramètre | Valeur | Signification | +|---|---|---| +| `n_estimators` | 200 | Nombre d'arbres d'isolation | +| `contamination` | 0.2% | Proportion de bots attendue dans le trafic | +| `seuil de score` | < -0.05 | Score en dessous duquel une session est marquée anomalie | +| `volume minimum` | 500 sessions | En dessous, le modèle est ignoré (trop peu de données) | + +### Dual-Model routing + +Le trafic est **séparé en deux populations** selon le champ `correlated` : + +- **Modèle Complet** (`correlated=1`) : la corrélation TCP↔HTTP est disponible → les features réseau (TTL, jitter TLS, ALPN, SNI) sont fiables et ajoutées à l'analyse. +- **Modèle Applicatif** (`correlated=0`) : seule la couche HTTP est disponible → l'analyse se concentre sur le comportement applicatif (headers, paths, POST ratio…). + +--- + +## 6. Vues heuristiques statiques + +En parallèle du modèle IA, cinq vues SQL fournissent des **détections déterministes** sans ML, sur fenêtre 24h : + +| Vue | Règle de détection | +|---|---| +| `view_host_ip_ja4_rotation` | IP avec ≥ 5 fingerprints JA4 distincts et > 100 requêtes → rotation d'identité | +| `view_host_ja4_anomalies` | Fingerprint JA4 vu depuis ≥ 20 IPs sur ≥ 3 hôtes → outil de scan distribué | +| `view_form_bruteforce_detected` | ≥ 10 query params distincts et ≥ 20 hits → brute-force de formulaire | +| `view_alpn_mismatch_detected` | HTTP/1.1 avec ALPN h2 ou h3 et ≥ 10 hits → négociation TLS frauduleuse | +| `view_tcp_spoofing_detected` | TTL ≤ 64 avec User-Agent Windows ou iPhone → empreinte OS incohérente | + +--- + +## 7. Résultats : `ml_detected_anomalies` + +Les anomalies détectées sont stockées dans une table `ReplacingMergeTree(detected_at)` avec **TTL 30 jours**. La clé d'ordre `(src_ip, ja4, host)` garantit que chaque triplet ne conserve que la **détection la plus récente** (dédoublonnage automatique). + +Chaque enregistrement contient : +- Les scores et features ayant conduit à la détection +- Le champ `reason` : texte lisible avec score, vélocité, et indice de fuzzing +- Le champ `is_headless` : déduit de l'incohérence `sec_fetch_mode` + +--- + +## 8. Schéma de flux complet + +``` + ┌─────────────────────────────────────┐ + │ http_logs_raw (JSON) │ + └──────────────┬──────────────────────┘ + │ mv_http_logs (MV) + ▼ + ┌─────────────────────────────────────┐ + │ http_logs (parsée) │ + └────────┬──────────────┬─────────────┘ + │ │ + mv_agg_host_ip_ja4 │ │ mv_agg_header_fingerprint + ▼ ▼ + ┌──────────────────┐ ┌──────────────────────────┐ + │ agg_host_ip_ja4 │ │ agg_header_fingerprint │ + │ _1h │ │ _1h │ + └────────┬─────────┘ └──────────┬──────────────┘ + │ │ + └──────────┬─────────────┘ + │ view_ai_features_1h (JOIN + calculs) + ▼ + ┌─────────────────────────────────────┐ + │ bot_detector.py (Isolation Forest) │ + │ Cycle : 5 min | Fenêtre : 24h │ + └──────────────┬──────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────┐ + │ ml_detected_anomalies │ + │ (ReplacingMergeTree, TTL 30j) │ + └─────────────────────────────────────┘ +``` diff --git a/old/services/correlator/go.mod b/old/services/correlator/go.mod new file mode 100644 index 0000000..0272f58 --- /dev/null +++ b/old/services/correlator/go.mod @@ -0,0 +1,29 @@ +module github.com/antitbone/ja4/correlator + +go 1.21 + +require ( + github.com/ClickHouse/clickhouse-go/v2 v2.23.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/ClickHouse/ch-go v0.61.5 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.7.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.7 // indirect + github.com/paulmach/orb v0.11.1 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/segmentio/asm v1.2.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/sys v0.18.0 // indirect +) + +require github.com/antitbone/ja4/ja4common v0.1.0 + +replace github.com/antitbone/ja4/ja4common => ../../shared/go/ja4common diff --git a/old/services/correlator/go.sum b/old/services/correlator/go.sum new file mode 100644 index 0000000..c03066c --- /dev/null +++ b/old/services/correlator/go.sum @@ -0,0 +1,110 @@ +github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= +github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= +github.com/ClickHouse/clickhouse-go/v2 v2.23.0 h1:srmRrkS0BR8gEut87u8jpcZ7geOob6nGj9ifrb+aKmg= +github.com/ClickHouse/clickhouse-go/v2 v2.23.0/go.mod h1:tBhdF3f3RdP7sS59+oBAtTyhWpy0024ZxDMhgxra0QE= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= +github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/old/services/correlator/idees/champs.md b/old/services/correlator/idees/champs.md new file mode 100644 index 0000000..e772fc7 --- /dev/null +++ b/old/services/correlator/idees/champs.md @@ -0,0 +1,111 @@ +time +log_date + +src_ip +- ip source de la connexion +src_port +- port source de la connexion +dst_ip +- ip de destination de la connexion +dst_port +- port de destination de la connexion + +src_asn +- Numero d'AS de l'ip source +src_country_code +- Code Pays de l'ip source +src_as_name +- Nom de l'AS de l ip source +src_org +- Organisation de l AS source +src_domain +- domaine de l'AS de l ip source + +method +- Methode HTTP [GET, POST, ... ] +scheme +- Type de connexion http [http, https] +host +- Hostname demandé dans l'url +path +- Path demandé dans l'url +query +- Query demandé dans l'url +http_version +- Version du protocol http utilisé + +orphan_side +- Indique si le log HTTP a pu etre enrichi avec les informations ip_, tcp, ja3_ et ja4_ +- "A" indique que seul le log HTTP est present, sans enrichissement +correlated +- l'algorithm de correlation log http + parametres tcp a il réussi (tcp + ja4/3) +keepalives +- Numero de desquance dans une connexion http avec keepalive. +a_timestamp +b_timestamp +conn_id + +ip_meta_df +- Flag dont fragement +ip_meta_id +- id du packet ip +ip_meta_total_length +- Taille des metadata dans pe packet ip +ip_meta_ttl +- TTL du packet ip vu par le serveur destinataire du packet + +tcp_meta_options +- options du packet TCP vu par le serveur destinataire du packet +tcp_meta_window_size +- TCP window size vu par le serveur destinataire du packet +tcp_meta_mss +- TCP mss vu par le serveur destinataire du packet +tcp_meta_window_scale +- TCP windows scale vu par le serveur destinataire du packet +syn_to_clienthello_ms +- durée en ms entre le 1er packet SYN et le ClienHello du TLS + +tls_version +- Version de TLS negocié avec le serveur destinataire du packet +tls_sni +- SNI, nom de domaine demandé pour le cerificat TLS +tls_alpn +- ALPN annoncé lors du TLS +ja3 +- liste des agos utiliés pour la signature ja3 +ja3_hash +- hash ja3 +ja4 +- hash ja4 + +client_headers +- liste des headers envoyés par le client http sous forme de liste Header,Header2,Header3,... + +header_user_agent +- Header HTTP User-Agent +header_accept +- Header HTTP Accept +header_accept_encoding +- Header HTTP Accept-Encoding +header_accept_language +- Header HTTP Accept-Language +header_content_type +- Header Content-Type +header_x_request_id +- Header X-Request-ID +header_x_trace_id +- Header X-Trace-ID +header_x_forwarded_for +- Header X-Forwarded-For +header_sec_ch_ua +- Header Sec-Ch-UA +header_sec_ch_ua_mobile +- Header -Sec-Ch-UA-Mobile +header_sec_ch_ua_platform +- Header Sec-Ch-UA-Plateform +header_sec_fetch_dest +- Header -Sec-Fetch-Dest +header_sec_fetch_mode +- Header Sec-Fetch-Mode +header_sec_fetch_site +- Header Sec-Fetch-Site diff --git a/old/services/correlator/idees/idees.txt b/old/services/correlator/idees/idees.txt new file mode 100644 index 0000000..cd41361 --- /dev/null +++ b/old/services/correlator/idees/idees.txt @@ -0,0 +1,30 @@ +1. Incohérences de Signatures (Spoofing) + +User-Agent vs TLS : Le header_user_agent prétend être un navigateur (Chrome/Safari) mais le ja3/ja4 correspond à un outil de script. +User-Agent vs Headers modernes : Le header_user_agent indique un navigateur récent, mais les headers header_sec_ch_ua_* sont vides ou absents de client_headers. +User-Agent vs ALPN : Le navigateur déclaré ne correspond pas au protocole négocié dans tls_alpn (ex: Chrome sans h2). +OS vs TTL TCP : L'OS déclaré dans le header_user_agent (ex: Windows) contredit la valeur de ip_meta_ttl (ex: 64, typique de Linux). +Host vs SNI : Le nom de domaine dans le header host ne correspond pas au tls_sni demandé lors du handshake TLS. + +2. Anomalies de Headers (HTTP Fingerprinting) + +Empreinte d'ordre (Fingerprint) : Apparition soudaine d'une disposition de client_headers (ordre exact) très rare, générant beaucoup de trafic. +Pauvreté des headers : Le nombre total de headers dans client_headers est anormalement bas (ex: < 5), typique des scripts basiques. +Absence de headers vitaux : Le trafic prétend être humain mais n'envoie pas header_accept_language ou header_accept_encoding. +Combinaison fatale : Le croisement d'un ja4 spécifique avec un ordre de client_headers inédit (détection de bots modifiant leur TLS mais trahis par l'applicatif). + +3. Anomalies Réseau et TCP (Couche 3 & 4) + +Mécanique TCP de masse : Une même combinaison (tcp_meta_window_size, tcp_meta_window_scale, tcp_meta_mss) vue sur des milliers d'IP différentes. +Handshake robotique : Un délai syn_to_clienthello_ms anormalement constant (variance quasi nulle) sur un grand nombre de connexions, typique d'un bot en datacenter. +Options TCP atypiques : Des paramètres tcp_meta_options inhabituels pour le trafic web classique de tes vrais utilisateurs. + +4. Anomalies Comportementales et Volumétriques (Côté Requête) + +Rafale de requêtes (Spike) : Volume d'appels (count) par src_ip ou par ja4 dépassant drastiquement le 99ème percentile historique sur 5 minutes. +Scraping furtif distribué : Un même ja4 (non standard) utilisé par des centaines de src_ip différentes, chacune faisant très peu de requêtes. +Balayage aveugle (Scanner) : Un volume anormal de path uniques (ou path + query) visités par une même IP ou un même ja4 en quelques minutes (remplace la détection des erreurs 404). +Acharnement sur cible (Brute force aveugle) : Une concentration extrême de requêtes ciblant uniquement les path sensibles (login, API, password-reset) sans navigation normale sur le reste du site (remplace la détection des 401/403). +Méthodes suspectes : Utilisation massive ou inhabituelle de method non standards (PUT, DELETE, OPTIONS, TRACE) par rapport à la baseline. +Payloads suspects : Présence de patterns d'injection ou de caractères très inhabituels dans query ou path (longueur extrême, encodages multiples). +Bot "Low and Slow" : IP ou ja4 qui passe sous les radars sur 5 minutes, mais dont le volume cumulé sur 24h ou 7 jours est mathématiquement improbable pour un humain. diff --git a/old/services/correlator/idees/views.md b/old/services/correlator/idees/views.md new file mode 100644 index 0000000..bb219b9 --- /dev/null +++ b/old/services/correlator/idees/views.md @@ -0,0 +1,521 @@ +# 🛡️ Manuel de Référence Technique : Moteur de Détection Antispam & Bot + +Ce document détaille les algorithmes de détection implémentés dans les vues ClickHouse pour la plateforme. + +--- + +## 1. Analyse de la Couche Transport (L4) : La "Trace Physique" +Avant même d'analyser l'URL, le moteur inspecte la manière dont la connexion a été établie. C'est la couche la plus difficile à falsifier pour un attaquant. + +### A. Fingerprint de la Pile TCP (`tcp_fingerprint`) +* **Fonctionnement :** Nous utilisons `cityHash64` pour créer un identifiant unique basé sur trois paramètres immuables du handshake : le **MSS** (Maximum Segment Size), la **Window Size** et le **Window Scale**. +* **Ce que ça détecte :** L'unicité logicielle. Un bot tournant sur une image Alpine Linux aura une signature TCP différente d'un utilisateur sur iOS 17 ou Windows 11. +* **Détection de botnet :** Si 500 IPs différentes partagent exactement le même `tcp_fingerprint` ET le même `ja4`, il y a une probabilité de 99% qu'il s'agisse d'un cluster de bots clonés. + + + +### B. Analyse de la gigue (Jitter) et Handshake +* **Fonctionnement :** On calcule la variance (`varPop`) du délai entre le `SYN` et le `ClientHello` TLS. +* **Ce que ça détecte :** La stabilité robotique. + * **Humain :** Latence variable (4G, Wi-Fi, mouvements). La variance est élevée. + * **Bot Datacenter :** Latence ultra-stable (fibre optique dédiée). Une variance proche de 0 indique une connexion automatisée depuis une infrastructure cloud. + +--- + +## 2. Analyse de la Session (L5) : Le "Passeport TLS" +Le handshake TLS est une mine d'or pour identifier la bibliothèque logicielle (OpenSSL, Go-TLS, etc.). + +### A. Incohérence UA vs JA4 +* **Fonctionnement :** Le moteur croise le `header_user_agent` (déclaratif) avec le `ja4` (structurel). +* **Ce que ça détecte :** Le **Spoofing de Browser**. Un script Python peut facilement écrire `User-Agent: Mozilla/5.0...Chrome/120`, mais il ne peut pas simuler l'ordre exact des extensions TLS et des algorithmes de chiffrement d'un vrai Chrome sans une ingénierie complexe (comme `utls`). +* **Logique de score :** Si UA = Chrome mais JA4 != Signature_Chrome -> **+50 points de risque**. + +### B. Discordance Host vs SNI +* **Fonctionnement :** Comparaison entre le champ `tls_sni` (négocié en clair lors du handshake) et le header `Host` (envoyé plus tard dans la requête chiffrée). +* **Ce que ça détecte :** Le **Domain Fronting** ou les attaques par tunnel. Un bot peut demander un certificat pour `domaine-innocent.com` (SNI) mais tenter d'attaquer `api-critique.com` (Host). + + + +--- + +## 3. Analyse Applicative (L7) : Le "Comportement HTTP" +Une fois le tunnel établi, on analyse la structure de la requête HTTP. + +### A. Empreinte d'ordre des Headers (`http_fp`) +* **Fonctionnement :** Nous hashons la liste ordonnée des clés de headers (`Accept`, `User-Agent`, `Connection`, etc.). +* **Ce que ça détecte :** La signature du moteur de rendu. Chaque navigateur (Firefox, Safari, Chromium) a un ordre immuable pour envoyer ses headers. +* **Détection :** Si un client envoie les headers dans un ordre inhabituel ou minimaliste (pauvreté des headers < 6), il est marqué comme suspect. + +### B. Analyse des Payloads et Entropie +* **Fonctionnement :** Recherche de patterns via regex dans `query` et `path` (détection SQLi, XSS, Path Traversal). +* **Complexité :** Nous détectons les encodages multiples (ex: `%2520`) qui tentent de tromper les pare-feux simples. + +--- + +## 4. Corrélation Temporelle & Baseline : Le "Voisinage Statistique" +Le score final dépend du passé de la signature TLS. + +### A. Le Malus de Nouveauté (`agg_novelty`) +* **Logique :** Une signature (JA4 + FP) vue pour la première fois aujourd'hui est "froide". +* **Traitement :** On applique un malus si `first_seen` date de moins de 2 heures. Un botnet qui vient de lancer une campagne de rotation de signatures sera immédiatement pénalisé par son manque d'historique. + +### B. Le Dépassement de Baseline (`tbl_baseline_ja4_7d`) +* **Fonctionnement :** On compare les `hits` actuels au 99ème percentile (`p99`) historique de cette signature précise. +* **Exemple :** Si le JA4 de "Chrome 122" fait habituellement 10 requêtes/min/IP sur votre site, et qu'une IP en fait soudainement 300, le score explose même si la requête est techniquement parfaite. + +--- + +## 5. Synthèse du Scoring (Le Verdict) + +| Algorithme | Signal | Impact Score | +| :--- | :--- | :--- | +| **Fingerprint Mismatch** | UA vs TLS (Spoofing) | **Haut (50)** | +| **L4 Anomaly** | Variance latence < 0.5ms | **Moyen (30)** | +| **Path Sensitivity** | Hit sur `/admin` ou `/config` | **Haut (40)** | +| **Payload Security** | Caractères d'injection (SQL/XSS) | **Critique (60)** | +| **Mass Distribution** | 1 JA4 sur > 50 IPs différentes | **Moyen (30)** | + + + +--- + +## 6. Identification des Hosts par IP et JA4 (sql/hosts.sql) + +Cette section détaille les vues d'agrégation et de détection pour identifier quels hosts sont associés à quelles signatures (IP + JA4). + +### A. Agrégats de Base + +| Table | Granularité | Description | +|-------|-------------|-------------| +| `agg_host_ip_ja4_1h` | heure | Hits, paths uniques, query params, méthodes par (IP, JA4, host) | +| `agg_host_ip_ja4_24h` | jour | Rollup quotidien pour historique long terme | + +### B. Vues d'Identification + +**`view_host_identification`** - Top hosts par signature +```sql +-- Quel host est associé à cette IP/JA4 ? +SELECT src_ip, ja4, host, total_hits, unique_paths, user_agent +FROM ja4_processing.view_host_identification +WHERE src_ip = '1.2.3.4' +ORDER BY total_hits DESC; +``` + +**`view_host_ja4_anomalies`** - JA4 partagé par plusieurs hosts (botnet) +```sql +-- Ce JA4 est-il utilisé par plusieurs hosts différents ? +SELECT ja4, hosts, unique_hosts, unique_ips +FROM ja4_processing.view_host_ja4_anomalies +HAVING unique_hosts >= 3; +-- Interprétation : 1 JA4 sur 3+ hosts = botnet cloné probable +``` + +**`view_host_ip_ja4_rotation`** - IP avec rotation de fingerprints +```sql +-- Cette IP change-t-elle de JA4 fréquemment ? +SELECT src_ip, ja4s, unique_ja4s +FROM ja4_processing.view_host_ip_ja4_rotation +HAVING unique_ja4s >= 5; +-- Interprétation : 1 IP avec 5+ JA4 différents = fingerprint spoofing +``` + +--- + +## 7. Détection de Brute Force (sql/hosts.sql) + +### A. Brute Force sur POST (endpoints sensibles) + +**Table :** `agg_bruteforce_post_5m` - Fenêtres de 5 minutes + +**Vue :** `view_bruteforce_post_detected` +```sql +-- Détecter les tentatives de brute force sur les login +SELECT window, src_ip, ja4, host, path, attempts, attempts_per_minute +FROM ja4_processing.view_bruteforce_post_detected +WHERE host = 'api.example.com' +ORDER BY attempts DESC; + +-- Threshold : ≥10 POST en 5 minutes sur endpoints sensibles +-- Endpoints ciblés : login, auth, signin, password, admin, wp-login, etc. +``` + +### B. Brute Force sur Formulaire (Query params variables) + +**Table :** `agg_form_bruteforce_5m` + +**Vue :** `view_form_bruteforce_detected` +```sql +-- Détecter les requêtes avec query params hautement variables +SELECT window, src_ip, ja4, host, path, requests, unique_query_patterns +FROM ja4_processing.view_form_bruteforce_detected +HAVING requests >= 20 AND unique_query_patterns >= 10; + +-- Interprétation : 20+ requêtes avec 10+ patterns query différents +-- = tentative de fuzzing ou brute force sur paramètres +``` + +--- + +## 8. Header Fingerprinting (sql/hosts.sql) + +Le champ `client_headers` contient la liste comma-separated des headers présents. +Exemple : `"Accept,Accept-Encoding,Sec-CH-UA,Sec-Fetch-Dest,User-Agent"` + +### A. Signature par Ordre de Headers + +**Table :** `agg_header_fingerprint_1h` + +| Champ | Description | +|-------|-------------| +| `header_count` | Nombre total de headers (virgules + 1) | +| `has_*` | Flags pour chaque header moderne (Sec-CH-UA, Sec-Fetch-*, etc.) | +| `header_order_hash` | MD5(client_headers) = signature unique de l'ordre | +| `modern_browser_score` | Score 0-100 basé sur les headers modernes présents | + +### B. Vues de Détection + +**`view_header_missing_modern_headers`** - Headers modernes manquants +```sql +-- Navigateurs "modernes" avec headers manquants +SELECT src_ip, ja4, header_user_agent, modern_browser_score, header_count +FROM ja4_processing.view_header_missing_modern_headers +WHERE header_user_agent ILIKE '%Chrome%'; + +-- Threshold : score < 70 pour Chrome/Firefox = suspect +-- Un vrai Chrome envoie automatiquement Sec-CH-UA, Sec-Fetch-*, etc. +``` + +**`view_header_ua_order_mismatch`** - Spoofing détecté +```sql +-- Même User-Agent avec ordre de headers différent +SELECT header_user_agent, ja4, unique_hashes, unique_ips +FROM ja4_processing.view_header_ua_order_mismatch +HAVING unique_hashes > 1; + +-- Interprétation : 1 UA avec 2+ ordres de headers = spoofing ou outil custom +``` + +**`view_header_minimalist_count`** - Bot minimaliste +```sql +-- Clients avec trop peu de headers +SELECT src_ip, ja4, header_count, header_user_agent +FROM ja4_processing.view_header_minimalist_count +WHERE header_count < 6; + +-- Threshold : < 6 headers = bot scripté (curl, Python requests, etc.) +``` + +**`view_header_sec_ch_missing`** - Incohérence Chrome +```sql +-- Chrome sans Sec-CH-UA (impossible pour un vrai Chrome) +SELECT src_ip, ja4, header_user_agent +FROM ja4_processing.view_header_sec_ch_missing +WHERE header_user_agent ILIKE '%Chrome/%'; +``` + +**`view_header_known_bot_signature`** - Signature botnet +```sql +-- Même ordre de headers sur 10+ IPs différentes +SELECT header_order_hash, header_user_agent, unique_ips, total_hits +FROM ja4_processing.view_header_known_bot_signature +HAVING unique_ips >= 10; + +-- Interprétation : 1 signature sur 10+ IPs = cluster de bots clonés +``` + +--- + +## 9. ALPN Mismatch Detection (sql/hosts.sql) + +### Principe + +ALPN (Application-Layer Protocol Negotiation) est une extension TLS qui négocie le protocole HTTP **avant** la requête. + +| ALPN déclaré | HTTP réel | Interprétation | +|--------------|-----------|----------------| +| `h2` | `HTTP/2` | ✅ Normal | +| `h2` | `HTTP/1.1` | ❌ Bot mal configuré | +| `http/1.1` | `HTTP/1.1` | ✅ Normal | + +### Vue de Détection + +**`view_alpn_mismatch_detected`** +```sql +-- Clients déclarant h2 mais parlant HTTP/1.1 +SELECT src_ip, ja4, declared_alpn, actual_http_version, mismatches, mismatch_pct +FROM ja4_processing.view_alpn_mismatch_detected +HAVING mismatch_pct >= 80; + +-- Threshold : ≥5 requêtes avec ≥80% d'incohérence +-- Cause : curl mal configuré, Python requests, bots spoofant ALPN +``` + +--- + +## 10. Rate Limiting & Burst Detection (sql/hosts.sql) + +### A. Rate Limiting (1 minute) + +**Table :** `agg_rate_limit_1m` + +**Vue :** `view_rate_limit_exceeded` +```sql +-- IPs dépassant 50 requêtes/minute +SELECT minute, src_ip, ja4, requests_per_min, unique_paths +FROM ja4_processing.view_rate_limit_exceeded +ORDER BY requests_per_min DESC; + +-- Threshold : > 50 req/min = trafic automatisé +-- Un humain ne peut pas soutenir 50+ req/min de manière cohérente +``` + +### B. Burst Detection (10 secondes) + +**Table :** `agg_burst_10s` + +**Vue :** `view_burst_detected` +```sql +-- Pics soudains de trafic +SELECT window, src_ip, ja4, burst_count +FROM ja4_processing.view_burst_detected +HAVING burst_count > 20; + +-- Threshold : > 20 requêtes en 10 secondes = burst suspect +-- Utile pour détecter les attaques par vagues +``` + +--- + +## 11. Path Enumeration / Scanning (sql/hosts.sql) + +### Vue de Détection + +**`view_path_scan_detected`** +```sql +-- Détection de scanning de paths sensibles +SELECT window, src_ip, ja4, host, sensitive_hits, sensitive_ratio +FROM ja4_processing.view_path_scan_detected +HAVING sensitive_hits >= 5; + +-- Paths surveillés : admin, backup, config, .env, .git, wp-admin, +-- phpinfo, test, debug, log, sql, dump, passwd, shadow, htaccess, etc. + +-- Threshold : ≥5 paths sensibles en 5 minutes = scanning +``` + +### Exemple de Résultat + +| src_ip | ja4 | host | sensitive_hits | sensitive_ratio | +|--------|-----|------|----------------|-----------------| +| 1.2.3.4 | t13d... | api.example.com | 47 | 94.00 | +| 5.6.7.8 | t13d... | www.example.com | 12 | 80.00 | + +**Interprétation :** Ces IPs testent systématiquement les paths sensibles = outils comme Nikto, Dirb, Gobuster. + +--- + +## 12. Payload Attack Detection (sql/hosts.sql) + +### A. Types d'Attaques Détectées + +| Type | Patterns Détectés | +|------|-------------------| +| **SQL Injection** | `UNION SELECT`, `OR 1=1`, `DROP TABLE`, `; --`, `/* */`, `WAITFOR DELAY`, `SLEEP()` | +| **XSS** | `