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 des résultats (JA4 + meta) vers différentes destinations." 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)." - "Envoyer les enregistrements vers une ou plusieurs sorties (socket UNIX, 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" } - { name: JA4Hash, type: "string", json_key: "ja4_hash" } - { name: JA3, type: "string", json_key: "ja3" } - { name: JA3Hash, type: "string", json_key: "ja3_hash" } # 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: 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 (ex: /var/run/ja4sentinel.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: - "Suivi unidirectionnel : uniquement le flux entrant du client vers la machine locale (srcIP:srcPort -> dstIP:dstPort)." - "Pour chaque flux, on s'arrête dès que JA4 + IPMeta + TCPMeta sont obtenus et logués." - "Un timeout par flux doit être défini pour éviter de garder un état si le ClientHello n'arrive jamais." - "Le flow key est au format : `srcIP:srcPort->dstIP:dstPort` (pas de suivi bidirectionnel)." testing: policy: description: > Chaque fonction publique (exportée) doit avoir des tests unitaires, et les principaux flux (capture -> tlsparse -> fingerprint -> output) doivent être couverts par des tests d’intégration automatisés. applies_to: - "toutes les méthodes des interfaces listées dans api.interfaces" requirements: - id: "test_skeletons" description: "Pour chaque nouvelle fonction exportée, créer au minimum un squelette de test dans un fichier *_test.go." - id: "error_cases" description: "Les cas d’erreur doivent être couverts par au moins un test (inputs invalides, erreurs réseau, parsing raté, etc.)." - id: "mock_dependencies" description: "Les dépendances entre modules (Capture, Parser, Engine, Writer) doivent être mockables via interfaces." levels: - name: "unit" description: "Tests sur chaque module isolé (config, capture, tlsparse, fingerprint, output)." tools: - "go test" rules: - "Pas de dépendance réseau réelle." - "Les interactions entre modules sont mockées." - name: "integration" description: "Tests bout-à-bout dans Docker avec trafic TLS client simulé." tools: - "go test" - "docker" - "docker compose" scenarios: - id: "tls_client_ja4_to_outputs" description: > Injecter un flux TLS de test côté client, vérifier que ja4sentinel capture les paquets client, génère JA4, enrichit avec meta IP/TCP, et écrit les enregistrements attendus vers les outputs configurés. steps: - "Démarrer un serveur TLS de test dans un conteneur." - "Démarrer ja4sentinel dans un autre conteneur, connecté au même réseau." - "Émettre une connexion TLS client de test." - "Lire la socket UNIX et/ou les autres outputs et vérifier JA4, IP, ports, meta." assertions: - "Au moins un LogRecord est reçu." - "Les champs JA4 ne sont pas vides." - "Les IP/ports correspondent au flux client." - "IPMeta et TCPMeta sont cohérents (TTL, window, options...)." ci_cd: goals: - "Construire et tester ja4sentinel de façon reproductible dans des conteneurs Docker." - "Fournir une commande unique pour lancer tous les tests (unitaires + intégration)." docker: images: - name: "ja4sentinel-dev" description: "Image de développement et de test (Go + outils réseau)." base: "golang:1.23-alpine" includes: - "go toolchain" - "libpcap et headers" - "outils de test (go test, gotestsum, etc.)" usage: build_cmd: "docker build -t ja4sentinel-dev -f Dockerfile.dev ." test_cmd: "docker run --rm -v $(pwd):/app -w /app ja4sentinel-dev make test" - name: "ja4sentinel-runtime" description: "Image minimale pour exécuter le binaire en production." base: "alpine:latest" includes: - "binaire ja4sentinel" - "libpcap runtime si nécessaire" usage: build_cmd: "docker build -t ja4sentinel-runtime -f Dockerfile ." rules: - "Le build de production doit partir du code testé." - "Les tests d’intégration réseau se font dans des conteneurs, pas directement sur la machine hôte." make_targets: - name: "build" description: "Compile le binaire ja4sentinel pour la plateforme cible." commands: - "go build ./cmd/ja4sentinel" - name: "test" description: "Lance les tests unitaires Go." commands: - "go test ./..." - name: "test-integration" description: "Lance les tests d’intégration dans Docker (capture TLS client + outputs)." commands: - "docker compose -f docker-compose.test.yml up --build --abort-on-container-exit" - name: "lint" description: "Lance les linters (go vet, staticcheck, etc.)." commands: - "go vet ./..." config_guidelines: sources: - "Fichier de configuration YAML (par défaut config.yml)." - "Variables d’environnement (JA4SENTINEL_*)." - "Flags CLI." rules: - "Le module config est la seule source de vérité pour les paramètres (interface, ports, filtres, outputs)." - "Les autres modules ne lisent jamais les variables d’environnement ni les fichiers de config directement." code_style: comments: goals: - "Commentaires standardisés, lisibles, utiles pour la génération de documentation." - "Expliquer le pourquoi et les décisions, pas répéter le code." rules: - id: "godoc_exported" description: > Chaque fonction, type ou méthode exporté (nom capitalisé) doit avoir un commentaire de documentation respectant le style GoDoc. examples: - "// FromClientHello génère les empreintes JA4 à partir d’un ClientHello TLS client." - id: "why_not_what" description: "Les commentaires doivent expliquer le pourquoi plutôt que le ‘quoi’ lorsque le code est clair." - id: "no_duplicate_code" description: "Un commentaire ne doit pas simplement répéter ce que le code dit déjà." - id: "update_with_code" description: "Si le code est modifié, les commentaires adjacents doivent être revus et mis à jour." formatting: - "Utiliser // pour les commentaires, pas /* ... */ sauf cas exceptionnel." - "Commentaires de doc au-dessus des déclarations, sans ligne vide." evolution: api_stability: strategy: "semver_like" goals: - "Éviter les changements majeurs d’API une fois le projet stabilisé." - "Limiter les modifications à ce qui est nécessaire (fixes, extensions compatibles)." rules: - id: "no_breaking_changes_without_reason" description: > Ne pas modifier les signatures des fonctions des interfaces définies dans api.interfaces sans raison solide et documentée. - id: "prefer_extension_over_modification" description: > Pour ajouter un comportement, préférer ajouter une nouvelle fonction ou un nouveau type, plutôt que changer une signature existante. - id: "document_changes" description: > Toute modification d’API doit être reflétée dans architecture.yml et, si nécessaire, dans un ADR séparé. refactoring: goals: - "Autoriser le refactoring interne sans changer les contrats externes." - "Limiter l’impact des changements à l’intérieur d’un module." guidelines: - "Les interfaces de api.interfaces servent de contrat stable entre modules." - "Les changements internes (optimisations, refactoring) ne doivent pas casser ces interfaces." - "Lorsqu’un changement d’API est inévitable, marquer l’ancienne API comme dépréciée avant de la supprimer." dev_tools: usage_guidelines: - "Les outils IA doivent lire architecture.yml avant de générer ou modifier du code." - "Les outils de test peuvent s'appuyer sur ci_cd.docker et ci_cd.make_targets pour générer des scripts / pipelines." future_extensions: - "Génération automatique de Dockerfile.dev et Dockerfile à partir de cette section." - "Génération de fichiers docker-compose.test.yml pour les scénarios d'intégration." packaging: description: > ja4sentinel est distribué sous forme de packages .deb (Debian/Ubuntu) et .rpm (Rocky Linux/RHEL/CentOS), construits intégralement dans Docker avec fpm. formats: - deb - rpm target_distros: deb: - debian-12+ - ubuntu-22.04+ rpm: - rocky-linux-8+ - rocky-linux-9+ - rhel-8+ - rhel-9+ tool: fpm build_pipeline: dockerfile: Dockerfile.package stages: - name: builder description: > Compilation du binaire Go avec CGO_ENABLED=1 pour libpcap. GOOS=linux GOARCH=amd64 pour un binaire statique. - name: package_builder description: > Installation de fpm, rpm, dpkg-dev. Création de l'arborescence et exécution de fpm pour générer DEB et RPM. - name: output description: > Image Alpine minimale contenant les packages dans /packages/deb et /packages/rpm. files: binary: source: dist/ja4sentinel-linux-amd64 dest: /usr/bin/ja4sentinel mode: "0755" systemd: source: packaging/systemd/ja4sentinel.service dest: /usr/lib/systemd/system/ja4sentinel.service mode: "0644" config: - source: packaging/systemd/config.yml dest: /etc/ja4sentinel/config.yml.default mode: "0640" config_file: true - source: packaging/systemd/config.yml dest: /usr/share/ja4sentinel/config.yml mode: "0640" directories: - path: /var/lib/ja4sentinel mode: "0750" - path: /var/log/ja4sentinel mode: "0750" - path: /var/run/ja4sentinel mode: "0750" - path: /etc/ja4sentinel mode: "0750" maintainer_scripts: deb: postinst: packaging/deb/postinst prerm: packaging/deb/prerm postrm: packaging/deb/postrm rpm: post: packaging/deb/postinst preun: packaging/deb/prerm postun: packaging/deb/postrm dependencies: deb: - systemd - libpcap0.8 rpm: - systemd - libpcap >= 1.9.0 verify: deb: command: docker run --rm -v $(pwd)/build/deb:/packages debian:latest sh -c "apt-get update && apt-get install -y /packages/*.deb" rpm: command: docker run --rm -v $(pwd)/build/rpm:/packages rockylinux:8 sh -c "dnf install -y /packages/*.rpm" service: systemd: unit_name: "ja4sentinel.service" description: "JA4 client fingerprinting daemon" wanted_by: "multi-user.target" exec: binary_path: "/usr/local/bin/ja4sentinel" args: - "--config" - "/etc/ja4sentinel/config.yml" user_group: user: "ja4sentinel" group: "ja4sentinel" runtime: working_directory: "/var/lib/ja4sentinel" pid_file: "/run/ja4sentinel.pid" restart: "on-failure" restart_sec: 5 environment_prefix: "JA4SENTINEL_" logging: type: "journald" journal_identifier: "ja4sentinel" expectations: - "Le binaire écrit les logs de service sur stdout/stderr." - "Les messages doivent inclure au minimum un niveau (INFO/ERROR) et un composant." security: capabilities: - "CAP_NET_RAW" - "CAP_NET_ADMIN" sandboxing: - "NoNewPrivileges=yes" - "ProtectSystem=full" - "ProtectHome=true" - "PrivateTmp=true" integration_rules: - "Le binaire doit s’arrêter proprement sur SIGTERM (systemd stop)." - "Le module cmd_ja4sentinel gère les signaux et termine la capture proprement." - "Les chemins (config, socket UNIX, logs) doivent être compatibles avec FHS (/etc, /var/run, /var/log)." - "Le module cmd_ja4sentinel capture SIGTERM/SIGINT et déclenche un arrêt propre (stop capture, flush outputs, fermer socket UNIX)." - "Le processus doit retourner un code de sortie non nul en cas d’erreur fatale au démarrage." logging: strategy: description: > ja4sentinel écrit ses logs techniques sur stdout/stderr au format JSON lines, afin que systemd/journald puissent les collecter et les filtrer. format: "json_lines" fields: - "timestamp" - "level" - "component" # capture, tlsparse, fingerprint, output, service, ... - "message" - "trace_id" # optionnel - "conn_id" # optionnel (identifiant de flux TCP) rules: - "Pas d’écriture directe dans des fichiers de log techniques depuis le code (stdout/stderr uniquement)." - "Les logs techniques du daemon passent par stdout/stderr (systemd/journald)." - "Les outputs métiers (LogRecord JA4) sont gérés par le module output, vers socket UNIX et/ou fichier JSON." json_format: description: > Les LogRecord métiers (JA4 + métadonnées) sont sérialisés en JSON objet plat, avec des champs nommés explicitement pour ingestion dans ClickHouse. 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_*, ja4*." logrecord_schema: # Exemple de mapping pour api.LogRecord (résumé) - "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' - "ja4" - "ja4_hash" - "ja3" - "ja3_hash"