Major fixes: - Add exclude_source_ips to mergeConfigs() - config file values now properly loaded - Add validation for exclude_source_ips (IP/CIDR format validation) - Remove JA4SENTINEL_LOG_LEVEL env var from systemd service - Config file log_level now respected without env override Debug logging improvements: - Log IP filter entries at startup (debug mode) - Track filtered packet count with atomic counter - Display filter statistics at shutdown via GetFilterStats() - New debug logs in tlsparse component Testing: - Add 6 new unit tests for exclude_source_ips and log_level config loading - Test mergeConfigs() behavior with empty/override values - Test validation of invalid IPs and CIDR ranges Documentation: - Update architecture.yml with ipfilter module - Document config loading priority and notes - Update api.Config fields (LocalIPs, ExcludeSourceIPs, LogLevel) Files changed: - internal/config/loader.go (merge, validation, helpers) - internal/config/loader_test.go (6 new tests) - internal/tlsparse/parser.go (GetFilterStats, counter) - cmd/ja4sentinel/main.go (debug logging) - packaging/systemd/ja4sentinel.service (remove env var) - architecture.yml (ipfilter module, config_loading section) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
525 lines
25 KiB
YAML
525 lines
25 KiB
YAML
version: 1
|
||
|
||
project:
|
||
name: ja4sentinel
|
||
description: >
|
||
Outil Go pour capturer le trafic réseau sur un serveur Linux,
|
||
extraire les handshakes TLS côté client, générer les signatures JA4
|
||
(via psanford/tlsfingerprint), enrichir avec des métadonnées IP/TCP,
|
||
et loguer les résultats (IP, ports, JA4, meta) vers une ou plusieurs
|
||
sorties configurables (socket UNIX par défaut, stdout, fichier, ...).
|
||
Le service est géré par systemd avec support de rotation des logs via logrotate.
|
||
La commande `systemctl reload ja4sentinel` permet de réouvrir les fichiers de log
|
||
après rotation (signal SIGHUP).
|
||
languages:
|
||
- go
|
||
goals:
|
||
- "Développement bloc par bloc avec interfaces simples et stables."
|
||
- "Focalisé sur JA4 client (le serveur est connu/local)."
|
||
- "Séparation claire des responsabilités (capture, parsing, fingerprint, output)."
|
||
- "Tests unitaires pour chaque fonction publique."
|
||
- "Tests d’intégration dans des conteneurs Docker."
|
||
- "Commentaires standardisés, code évolutif avec changements minimaux."
|
||
|
||
modules:
|
||
- name: config
|
||
path: "internal/config"
|
||
description: "Chargement et validation de la configuration (fichier, env, CLI)."
|
||
responsibilities:
|
||
- "Lire le fichier de configuration (YAML par défaut)."
|
||
- "Fusionner avec les overrides env/CLI."
|
||
- "Construire une api.AppConfig cohérente."
|
||
allowed_dependencies: []
|
||
forbidden_dependencies:
|
||
- "capture"
|
||
- "tlsparse"
|
||
- "fingerprint"
|
||
- "output"
|
||
|
||
- name: capture
|
||
path: "internal/capture"
|
||
description: "Capture des paquets réseau (pcap/raw socket) sur Linux."
|
||
responsibilities:
|
||
- "Ouvrir l’interface réseau configurée."
|
||
- "Appliquer les filtres (ports, BPF, protocole)."
|
||
- "Observer les flux TCP côté client vers les ports d’intérêt."
|
||
- "Extraire les en-têtes IP/TCP utiles (IPMeta, TCPMeta)."
|
||
- "Convertir les paquets en objets RawPacket."
|
||
allowed_dependencies:
|
||
- "config"
|
||
- "api"
|
||
forbidden_dependencies:
|
||
- "tlsparse"
|
||
- "fingerprint"
|
||
- "output"
|
||
|
||
- name: tlsparse
|
||
path: "internal/tlsparse"
|
||
description: "Extraction des ClientHello TLS côté client à partir des paquets capturés."
|
||
responsibilities:
|
||
- "Décoder les couches IP/TCP jusqu'au payload TLS."
|
||
- "Identifier le ClientHello TLS du client sur les ports configurés."
|
||
- "Assembler les segments si nécessaire pour obtenir un ClientHello complet."
|
||
- "Produire des TLSClientHello enrichis avec IPMeta et TCPMeta."
|
||
- "Filtrer les IPs source exclues via le module ipfilter (avant parsing TLS)."
|
||
- "Compter les paquets filtrés pour statistiques (GetFilterStats)."
|
||
allowed_dependencies:
|
||
- "config"
|
||
- "capture"
|
||
- "api"
|
||
- "ipfilter"
|
||
forbidden_dependencies:
|
||
- "output"
|
||
|
||
- name: fingerprint
|
||
path: "internal/fingerprint"
|
||
description: "Génération des empreintes JA4 à partir des ClientHello TLS."
|
||
responsibilities:
|
||
- "Utiliser psanford/tlsfingerprint pour analyser le ClientHello."
|
||
- "Générer la chaîne JA4 (et éventuellement JA3) côté client."
|
||
- "Encapsuler les résultats dans un type Fingerprints."
|
||
allowed_dependencies:
|
||
- "config"
|
||
- "tlsparse"
|
||
- "api"
|
||
forbidden_dependencies:
|
||
- "capture"
|
||
|
||
- name: output
|
||
path: "internal/output"
|
||
description: "Sortie asynchrone ultra-rapide des résultats (JA4 + meta)."
|
||
responsibilities:
|
||
- "Prendre en entrée les Fingerprints et les métadonnées réseau."
|
||
- "Formater les données en enregistrements log (JSON ou autre format simple)."
|
||
- "Gérer une file d'attente interne (buffer channel) pour rendre l'écriture non-bloquante pour la capture."
|
||
- "Sérialiser le JSON le plus rapidement possible (ex: pool d'allocations, librairies optimisées comme goccy/go-json)."
|
||
- "Envoyer les enregistrements vers une ou plusieurs sorties (socket UNIX DGRAM, stdout, fichier, ...)."
|
||
- "Gérer un MultiWriter pour combiner plusieurs outputs sans modifier le reste du code."
|
||
allowed_dependencies:
|
||
- "config"
|
||
- "api"
|
||
forbidden_dependencies:
|
||
- "capture"
|
||
- "tlsparse"
|
||
- "fingerprint"
|
||
|
||
- name: logging
|
||
path: "internal/logging"
|
||
description: "Logs structurés JSON pour le service (stdout/stderr)."
|
||
responsibilities:
|
||
- "Fournir une fabrique de loggers (LoggerFactory)."
|
||
- "Émettre des logs au format JSON lines sur stdout."
|
||
- "Supporter les niveaux : debug, info, warn, error."
|
||
- "Inclure timestamp, niveau, composant, message et détails optionnels."
|
||
allowed_dependencies:
|
||
- "api"
|
||
forbidden_dependencies:
|
||
- "config"
|
||
- "capture"
|
||
- "tlsparse"
|
||
- "fingerprint"
|
||
- "output"
|
||
|
||
- name: ipfilter
|
||
path: "internal/ipfilter"
|
||
description: "Filtrage des adresses IP source par correspondance IP/CIDR."
|
||
responsibilities:
|
||
- "Charger une liste d'IPs ou plages CIDR à exclure."
|
||
- "Vérifier si une IP source correspond à une entrée de la liste d'exclusion."
|
||
- "Supporter IPv4 et IPv6."
|
||
- "Validation des formats IP et CIDR lors du chargement de la config."
|
||
allowed_dependencies: []
|
||
forbidden_dependencies:
|
||
- "config"
|
||
- "capture"
|
||
- "tlsparse"
|
||
- "fingerprint"
|
||
- "output"
|
||
|
||
- name: cmd_ja4sentinel
|
||
path: "cmd/ja4sentinel"
|
||
description: "Point d'entrée de l'application (main)."
|
||
responsibilities:
|
||
- "Charger la configuration via le module config."
|
||
- "Construire les instances des modules (capture, tlsparse, fingerprint, output, logging)."
|
||
- "Brancher les modules entre eux selon l'architecture pipeline."
|
||
- "Gérer les signaux système (arrêt propre)."
|
||
- "Gérer le signal SIGHUP pour la rotation des logs (systemctl reload)."
|
||
- "Logger les statistiques du filtre IP au démarrage et à l'arrêt (debug)."
|
||
allowed_dependencies:
|
||
- "config"
|
||
- "capture"
|
||
- "tlsparse"
|
||
- "fingerprint"
|
||
- "output"
|
||
- "api"
|
||
- "logging"
|
||
forbidden_dependencies: []
|
||
|
||
api:
|
||
types:
|
||
- name: "api.ServiceLog"
|
||
description: "Log interne du service ja4sentinel (diagnostic)."
|
||
fields:
|
||
- { name: Level, type: "string", description: "niveau: debug, info, warn, error." }
|
||
- { name: Component, type: "string", description: "module concerné (capture, tlsparse, ...)." }
|
||
- { name: Message, type: "string", description: "texte du log." }
|
||
- { name: Details, type: "map[string]string", description: "infos additionnelles (erreurs, IDs...)." }
|
||
- { name: Timestamp, type: "int64", description: "Timestamp en nanosecondes (auto-rempli par le logger)." }
|
||
- { name: TraceID, type: "string", description: "ID de tracing distribué (optionnel)." }
|
||
- { name: ConnID, type: "string", description: "Identifiant de flux TCP (optionnel)." }
|
||
|
||
- name: "api.Config"
|
||
description: "Configuration réseau et TLS de base."
|
||
fields:
|
||
- { name: Interface, type: "string", description: "Nom de l'interface réseau (ex: eth0)." }
|
||
- { name: ListenPorts, type: "[]uint16", description: "Ports TCP à surveiller (ex: [443, 8443])." }
|
||
- { name: BPFFilter, type: "string", description: "Filtre BPF optionnel pour la capture." }
|
||
- { name: LocalIPs, type: "[]string", description: "IPs locales à surveiller (vide = auto-détection, exclut loopback)." }
|
||
- { name: ExcludeSourceIPs,type: "[]string", description: "IPs sources ou plages CIDR à exclure (ex: [\"10.0.0.0/8\", \"192.168.1.1\"]). Validé par le module config." }
|
||
- { name: FlowTimeoutSec, type: "int", description: "Timeout en secondes pour l'extraction du handshake TLS (défaut: 30)." }
|
||
- { name: PacketBufferSize,type: "int", description: "Taille du buffer du canal de paquets (défaut: 1000). Pour les environnements à fort trafic." }
|
||
- { name: LogLevel, type: "string", description: "Niveau de log : debug, info, warn, error (défaut: info). Configuration via fichier YAML uniquement (pas d'override env dans systemd)." }
|
||
|
||
- name: "api.IPMeta"
|
||
description: "Métadonnées IP pour fingerprinting de stack."
|
||
fields:
|
||
- { name: TTL, type: "uint8", description: "TTL initial observé." }
|
||
- { name: TotalLength, type: "uint16", description: "Taille totale du paquet IP." }
|
||
- { name: IPID, type: "uint16", description: "Identifiant IP du paquet." }
|
||
- { name: DF, type: "bool", description: "Flag Don't Fragment." }
|
||
|
||
- name: "api.TCPMeta"
|
||
description: "Métadonnées TCP pour fingerprinting de stack."
|
||
fields:
|
||
- { name: WindowSize, type: "uint16", description: "Fenêtre initiale TCP." }
|
||
- { name: MSS, type: "uint16", description: "Maximum Segment Size (option TCP)." }
|
||
- { name: WindowScale, type: "uint8", description: "Facteur de scaling (option TCP)." }
|
||
- { name: Options, type: "[]string", description: "Liste ordonnée des options TCP (ex: [MSS, SACK, TS])." }
|
||
|
||
- name: "api.RawPacket"
|
||
description: "Paquet brut capturé sur le réseau (vue minimale)."
|
||
fields:
|
||
- { name: Data, type: "[]byte", description: "Contenu brut du paquet." }
|
||
- { name: Timestamp, type: "int64", description: "Timestamp (nanos / epoch) de capture." }
|
||
|
||
- name: "api.TLSClientHello"
|
||
description: "Représentation d'un ClientHello TLS client, avec meta IP/TCP."
|
||
fields:
|
||
- { name: SrcIP, type: "string", description: "Adresse IP source (client)." }
|
||
- { name: SrcPort, type: "uint16", description: "Port source (client)." }
|
||
- { name: DstIP, type: "string", description: "Adresse IP destination (serveur)." }
|
||
- { name: DstPort, type: "uint16", description: "Port destination (serveur)." }
|
||
- { name: Payload, type: "[]byte", description: "Bytes correspondant au ClientHello TLS." }
|
||
- { name: IPMeta, type: "api.IPMeta", description: "Métadonnées IP observées côté client." }
|
||
- { name: TCPMeta, type: "api.TCPMeta", description: "Métadonnées TCP observées côté client." }
|
||
- { name: ConnID, type: "string", description: "Identifiant unique du flux TCP (extension pour corrélation)." }
|
||
- { name: SNI, type: "string", description: "Server Name Indication extrait du ClientHello (extension)." }
|
||
- { name: ALPN, type: "string", description: "ALPN protocols négociés (extension)." }
|
||
- { name: TLSVersion,type: "string", description: "Version TLS maximale annoncée (extension)." }
|
||
- { name: SynToCHMs,type: "*uint32", description: "Temps SYN->ClientHello en ms (extension pour détection comportementale)." }
|
||
|
||
- name: "api.Fingerprints"
|
||
description: "Empreintes TLS pour un flux client."
|
||
fields:
|
||
- { name: JA4, type: "string", description: "Signature JA4 client." }
|
||
- { name: JA4Hash, type: "string", description: "Hash JA4 client." }
|
||
- { name: JA3, type: "string", description: "Signature JA3 (optionnel, si calculée)." }
|
||
- { name: JA3Hash, type: "string", description: "Hash JA3 (optionnel)." }
|
||
|
||
- name: "api.LogRecord"
|
||
description: "Enregistrement de log final, sérialisé en JSON objet plat."
|
||
json_object: true
|
||
fields:
|
||
- { name: SrcIP, type: "string", json_key: "src_ip" }
|
||
- { name: SrcPort, type: "uint16", json_key: "src_port" }
|
||
- { name: DstIP, type: "string", json_key: "dst_ip" }
|
||
- { name: DstPort, type: "uint16", json_key: "dst_port" }
|
||
|
||
# IPMeta flatten
|
||
- { name: IPTTL, type: "uint8", json_key: "ip_meta_ttl" }
|
||
- { name: IPTotalLen, type: "uint16", json_key: "ip_meta_total_length" }
|
||
- { name: IPID, type: "uint16", json_key: "ip_meta_id" }
|
||
- { name: IPDF, type: "bool", json_key: "ip_meta_df" }
|
||
|
||
# TCPMeta flatten
|
||
- { name: TCPWindow, type: "uint16", json_key: "tcp_meta_window_size" }
|
||
- { name: TCPMSS, type: "*uint16", json_key: "tcp_meta_mss", optional: true, description: "Pointeur (nil si non présent, 0 si absent)." }
|
||
- { name: TCPWScale, type: "*uint8", json_key: "tcp_meta_window_scale", optional: true, description: "Pointeur (nil si non présent, 0 si absent)." }
|
||
- { name: TCPOptions, type: "string", json_key: "tcp_meta_options" }
|
||
|
||
# Fingerprints
|
||
- { name: JA4, type: "string", json_key: "ja4", description: "Le format JA4 inclut nativement ses propres hachages (parties b et c), pas besoin de ja4_hash séparé." }
|
||
- { name: JA3, type: "string", json_key: "ja3", description: "Chaîne brute JA3 (variable)." }
|
||
- { name: JA3Hash, type: "string", json_key: "ja3_hash", description: "Hachage MD5 indispensable pour exploiter la chaîne JA3." }
|
||
|
||
# --- Corrélation & Triage ---
|
||
- { name: ConnID, type: "string", json_key: "conn_id", optional: true, description: "Identifiant unique du flux (ex: hash de src_ip:src_port-dst_ip:dst_port) pour corréler facilement plusieurs événements liés à une même session TCP." }
|
||
- { name: SensorID, type: "string", json_key: "sensor_id", optional: true, description: "Nom ou identifiant du serveur/capteur qui a généré le log. Indispensable pour du déploiement à grande échelle." }
|
||
|
||
# --- Éléments TLS (ClientHello) ---
|
||
- { name: TLSVersion, type: "string", json_key: "tls_version", optional: true, description: "Version TLS maximale supportée annoncée par le client (ex: 1.2, 1.3). Utile pour repérer les clients obsolètes." }
|
||
- { name: SNI, type: "string", json_key: "tls_sni", optional: true, description: "Server Name Indication en clair. Crucial pour détecter le domaine visé par le client (C2, DGA, etc.)." }
|
||
- { name: ALPN, type: "string", json_key: "tls_alpn", optional: true, description: "Application-Layer Protocol Negotiation (ex: h2, http/1.1). Aide à différencier le trafic web légitime d'un tunnel personnalisé." }
|
||
|
||
# --- Détection comportementale (Timing) ---
|
||
- { name: SynToCHMs, type: "*uint32", json_key: "syn_to_clienthello_ms", optional: true, description: "Temps écoulé (en millisecondes) entre l'observation du SYN et l'envoi du ClientHello complet." }
|
||
|
||
# Timestamp
|
||
- { name: Timestamp, type: "int64", json_key: "timestamp", description: "Wall-clock timestamp in nanoseconds since Unix epoch (auto-filled by NewLogRecord)." }
|
||
|
||
|
||
- name: "api.OutputConfig"
|
||
description: "Configuration d’une sortie de logs."
|
||
fields:
|
||
- { name: Type, type: "string", description: "Type d’output (unix_socket, stdout, file, ...)." }
|
||
- { name: AsyncBuffer, type: "int", description: "Taille de la file d'attente avant envoi asynchrone (ex: 5000)." }
|
||
- { name: Enabled, type: "bool", description: "Active ou non cette sortie." }
|
||
- { name: Params, type: "map[string]string", description: "Paramètres spécifiques (socket_path, path, ...)." }
|
||
|
||
- name: "api.AppConfig"
|
||
description: "Configuration complète de ja4sentinel."
|
||
fields:
|
||
- { name: Core, type: "api.Config", description: "Paramètres réseau + TLS." }
|
||
- { name: Outputs, type: "[]api.OutputConfig", description: "Liste des outputs configurés." }
|
||
|
||
interfaces:
|
||
- name: "config.Loader"
|
||
description: "Charge la configuration (fichier + env + CLI)."
|
||
module: "config"
|
||
methods:
|
||
- name: "Load"
|
||
params: []
|
||
returns:
|
||
- { type: "api.AppConfig" }
|
||
- { type: "error" }
|
||
|
||
- name: "capture.Capture"
|
||
description: "Source de paquets réseau bruts côté client."
|
||
module: "capture"
|
||
methods:
|
||
- name: "Run"
|
||
params:
|
||
- { name: cfg, type: "api.Config" }
|
||
- { name: out, type: "chan<- api.RawPacket" }
|
||
returns:
|
||
- { type: "error" }
|
||
notes:
|
||
- "Doit respecter les filtres (ports, BPF) définis dans la configuration."
|
||
- "Ne connaît pas le format TLS ni JA4."
|
||
- name: "Close"
|
||
params: []
|
||
returns:
|
||
- { type: "error" }
|
||
notes:
|
||
- "Libère les ressources (handle pcap, etc.). Doit être appelé après Run()."
|
||
|
||
- name: "tlsparse.Parser"
|
||
description: "Transforme des RawPacket en TLSClientHello (côté client uniquement)."
|
||
module: "tlsparse"
|
||
methods:
|
||
- name: "Process"
|
||
params:
|
||
- { name: pkt, type: "api.RawPacket" }
|
||
returns:
|
||
- { type: "*api.TLSClientHello" }
|
||
- { type: "error" }
|
||
notes:
|
||
- "Retourne nil si le paquet ne contient pas (ou plus) de ClientHello."
|
||
- "Pour chaque flux, s'arrête une fois le ClientHello complet obtenu."
|
||
- name: "Close"
|
||
params: []
|
||
returns:
|
||
- { type: "error" }
|
||
notes:
|
||
- "Arrête les goroutines en arrière-plan et nettoie les états de flux."
|
||
|
||
- name: "fingerprint.Engine"
|
||
description: "Génère les empreintes JA4 (et JA3 éventuellement) à partir d’un ClientHello."
|
||
module: "fingerprint"
|
||
methods:
|
||
- name: "FromClientHello"
|
||
params:
|
||
- { name: ch, type: "api.TLSClientHello" }
|
||
returns:
|
||
- { type: "*api.Fingerprints" }
|
||
- { type: "error" }
|
||
notes:
|
||
- "Utilise github.com/psanford/tlsfingerprint en interne."
|
||
- "Focalisé sur le JA4 client (le côté serveur est déjà connu)."
|
||
|
||
- name: "output.Writer"
|
||
description: "Interface générique pour écrire les résultats."
|
||
module: "output"
|
||
methods:
|
||
- name: "Write"
|
||
params:
|
||
- { name: rec, type: "api.LogRecord" }
|
||
returns:
|
||
- { type: "error" }
|
||
notes:
|
||
- "Ne connaît pas la capture ni les détails de parsing TLS."
|
||
|
||
- name: "output.UnixSocketWriter"
|
||
description: "Implémentation de Writer envoyant les logs sur une socket UNIX."
|
||
module: "output"
|
||
implements: "output.Writer"
|
||
config:
|
||
- { name: socket_path, type: "string", description: "Chemin de la socket UNIX DGRAM (ex: /var/run/logcorrelator/network.sock)." }
|
||
|
||
- name: "output.MultiWriter"
|
||
description: "Combinaison de plusieurs Writer configurés."
|
||
module: "output"
|
||
implements: "output.Writer"
|
||
config:
|
||
- { name: writers, type: "[]output.Writer", description: "Liste de Writers concrets à appeler." }
|
||
|
||
- name: "output.Builder"
|
||
description: "Construit les Writers à partir de api.AppConfig."
|
||
module: "output"
|
||
methods:
|
||
- name: "NewFromConfig"
|
||
params:
|
||
- { name: cfg, type: "api.AppConfig" }
|
||
returns:
|
||
- { type: "output.Writer" }
|
||
- { type: "error" }
|
||
notes:
|
||
- "Doit supporter plusieurs outputs simultanés via un MultiWriter."
|
||
|
||
- name: "logging.LoggerFactory"
|
||
description: "Fabrique de loggers structurés JSON."
|
||
module: "logging"
|
||
methods:
|
||
- name: "NewLogger"
|
||
params:
|
||
- { name: level, type: "string" }
|
||
returns:
|
||
- { type: "api.Logger" }
|
||
- name: "NewDefaultLogger"
|
||
params: []
|
||
returns:
|
||
- { type: "api.Logger" }
|
||
notes:
|
||
- "Les logs sont émis en JSON lines sur stdout pour systemd/journald."
|
||
|
||
- name: "api.Logger"
|
||
description: "Interface de logging pour tous les modules."
|
||
module: "logging"
|
||
methods:
|
||
- name: "Debug"
|
||
params:
|
||
- { name: component, type: "string" }
|
||
- { name: message, type: "string" }
|
||
- { name: details, type: "map[string]string" }
|
||
- name: "Info"
|
||
params:
|
||
- { name: component, type: "string" }
|
||
- { name: message, type: "string" }
|
||
- { name: details, type: "map[string]string" }
|
||
- name: "Warn"
|
||
params:
|
||
- { name: component, type: "string" }
|
||
- { name: message, type: "string" }
|
||
- { name: details, type: "map[string]string" }
|
||
- name: "Error"
|
||
params:
|
||
- { name: component, type: "string" }
|
||
- { name: message, type: "string" }
|
||
- { name: details, type: "map[string]string" }
|
||
notes:
|
||
- "Tous les logs passent par stdout/stderr (pas de fichiers directs)."
|
||
|
||
architecture:
|
||
style: "pipeline"
|
||
flow:
|
||
- from: "capture.Capture"
|
||
to: "tlsparse.Parser"
|
||
via: "api.RawPacket"
|
||
- from: "tlsparse.Parser"
|
||
to: "fingerprint.Engine"
|
||
via: "api.TLSClientHello"
|
||
- from: "fingerprint.Engine"
|
||
to: "output.Writer"
|
||
via: "api.LogRecord"
|
||
constraints:
|
||
- id: "client_only"
|
||
description: "On ne calcule que les empreintes JA4 côté client (pas côté serveur)."
|
||
- id: "no_back_dependencies"
|
||
description: "Pas de dépendances en arrière (output ne dépend pas de fingerprint, etc.)."
|
||
- id: "simple_messages"
|
||
description: "Les communications entre blocs utilisent uniquement les types définis dans api.*."
|
||
- id: "no_global_state"
|
||
description: "Pas de variables globales partagées entre blocs pour la logique principale."
|
||
|
||
flow_control:
|
||
connection_states:
|
||
description: "États simplifiés d'un flux TCP pour minimiser la capture."
|
||
states:
|
||
- name: "NEW"
|
||
description: "Observation d'un SYN client sur un port surveillé, création d'un état minimal (IP/TCP meta)."
|
||
- name: "WAIT_CLIENT_HELLO"
|
||
description: "Accumulation des segments TCP nécessaires pour extraire un ClientHello complet."
|
||
- name: "JA4_DONE"
|
||
description: "JA4 calculé et logué, on arrête de suivre ce flux."
|
||
rules:
|
||
- "Pas de tableaux imbriqués ni d'objets deeply nested."
|
||
- "Toutes les métadonnées IP/TCP sont flatten sous forme de champs scalaires nommés."
|
||
- "Les noms de champs suivent la convention: ip_meta_*, tcp_meta_*, ja*."
|
||
- "Pas de champ ja4_hash : le format JA4 intègre déjà son propre hachage tronqué, la chaîne complète de 38 caractères suffit."
|
||
logrecord_schema:
|
||
# Exemple de mapping pour api.LogRecord (résumé)
|
||
- "conn_id"
|
||
- "sensor_id"
|
||
- "src_ip"
|
||
- "src_port"
|
||
- "dst_ip"
|
||
- "dst_port"
|
||
- "ip_meta_ttl"
|
||
- "ip_meta_total_length"
|
||
- "ip_meta_id"
|
||
- "ip_meta_df"
|
||
- "tcp_meta_window_size"
|
||
- "tcp_meta_mss"
|
||
- "tcp_meta_window_scale"
|
||
- "tcp_meta_options" # string joinée, ex: 'MSS,SACK,TS,NOP,WS'
|
||
- "tls_version"
|
||
- "tls_sni"
|
||
- "tls_alpn"
|
||
- "syn_to_clienthello_ms"
|
||
- "ja4"
|
||
- "ja3"
|
||
- "ja3_hash"
|
||
|
||
packaging:
|
||
rpm:
|
||
description: "Package RPM pour déploiement sur serveurs Linux."
|
||
files:
|
||
- path: "/etc/logrotate.d/ja4sentinel"
|
||
description: "Script logrotate pour la rotation des fichiers de log."
|
||
note: "Fourni par le RPM, configure la rotation quotidienne avec compression."
|
||
- path: "/etc/systemd/system/ja4sentinel.service"
|
||
description: "Unité systemd pour la gestion du service."
|
||
note: "Doit inclure Type=notify et ExecReload=/bin/kill -HUP $MAINPID pour supporter systemctl reload. PAS de variable Environment=JA4SENTINEL_LOG_LEVEL pour respecter la config fichier."
|
||
logrotate:
|
||
description: "Configuration logrotate pour la rotation des logs."
|
||
behavior:
|
||
- "Rotation quotidienne ou selon taille."
|
||
- "Compression des logs archivés."
|
||
- "Envoi du signal SIGHUP au service après rotation pour réouvrir les fichiers."
|
||
reload_mechanism:
|
||
- "systemctl reload ja4sentinel déclenche le handler SIGHUP."
|
||
- "Le service réouvre ses fichiers de log sans redémarrage complet."
|
||
|
||
config_loading:
|
||
priority:
|
||
- "1. Fichier de configuration YAML (config.yml)"
|
||
- "2. Variables d'environnement JA4SENTINEL_* (sauf log_level depuis v1.1.11)"
|
||
- "3. Arguments CLI (--config)"
|
||
notes:
|
||
- "Depuis v1.1.11, la variable JA4SENTINEL_LOG_LEVEL n'est plus définie dans le service systemd."
|
||
- "Le log_level doit être configuré exclusivement dans le fichier YAML."
|
||
- "exclude_source_ips est uniquement chargé depuis le fichier YAML (pas d'override env)."
|
||
- "La fusion des configs utilise mergeConfigs() qui préserve les valeurs non-overridées."
|
||
|