- stdout sink documented as no-op (no data on stdout, operational logs on stderr) - clickhouse sink: document full logging (INFO connect, DEBUG batch, WARN drop/retry, ERROR flush) - architecture modules: update responsibilities for stdout and clickhouse adapters - testing section: add StdoutSink and clickhouse logging coverage - observability section: add info_logs, warn_logs, error_logs examples for clickhouse Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
951 lines
35 KiB
YAML
951 lines
35 KiB
YAML
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.
|
||
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é.
|
||
|
||
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).
|
||
- 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"
|
||
- "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,
|
||
"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)
|
||
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: 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
|
||
|