feat: ja4-platform monorepo — 5 services unified, tests & RPM builds standardized
Services: - ja4sentinel: TLS/JA4 fingerprint capture daemon (Go, libpcap) - logcorrelator: JA4 log correlation engine (Go, ClickHouse) - mod_reqin_log: Apache module (C, JSON request logging) - bot_detector: ML bot detection pipeline (Python) - dashboard: FastAPI/Streamlit analytics UI (Python) Shared libraries: - shared/go/ja4common: logger, config, shutdown, ipfilter (Go module) - shared/python/ja4_common: ClickHouseClient, ClickHouseSettings (Python package) - shared/clickhouse/: canonical SQL migrations (10 files) Build & packaging: - Unified 3-stage Dockerfile.package for Go RPMs (el8/el9/el10) - go.work workspace linking sentinel, correlator, ja4common - Makefile with test-all, build-all, rpm-* targets Fixes applied: - go.work: 1.21 → 1.24.6 (required by sentinel) - correlator Dockerfiles: golang:1.21 → golang:1.24 - replace directives in go.mod for ja4common local path - pyproject.toml: setuptools.backends → setuptools.build_meta - Removed static libpcap linking (unavailable on Rocky 9) - Fixed data races in output/writers_test.go (sync.Mutex + atomic.Int32) - Rewrote corrupted test files (logger_test.go × 2) Test coverage: - correlator: 67.1% total (unixsocket 80.5%, config 91.7%, app 83.3%, multi 87.7%, stdout 100%) - sentinel: all 10 packages pass (api, capture, config, fingerprint, ipfilter, logging, output, tlsparse) Documentation: - README.md + docs/ (architecture, development, 5 services, shared libs, DB schema & migrations) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
974
services/correlator/architecture.yml
Normal file
974
services/correlator/architecture.yml
Normal file
@ -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: mabase_prod
|
||||
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 mabase_prod.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
|
||||
|
||||
Reference in New Issue
Block a user