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, stdout, fichier, ...). 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." allowed_dependencies: - "config" - "capture" - "api" 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: 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)." 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: 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: "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: "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: "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: "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"