Files
ja4sentinel/architecture.yml
Jacquin Antoine 5fa3c3c293 init
2026-02-25 03:15:53 +01:00

482 lines
21 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 dinté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 linterface réseau configurée."
- "Appliquer les filtres (ports, BPF, protocole)."
- "Observer les flux TCP côté client vers les ports dinté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 jusquau 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: cmd_ja4sentinel
path: "cmd/ja4sentinel"
description: "Point dentrée de lapplication (main)."
responsibilities:
- "Charger la configuration via le module config."
- "Construire les instances des modules (capture, tlsparse, fingerprint, output)."
- "Brancher les modules entre eux selon larchitecture pipeline."
- "Gérer les signaux système (arrêt propre)."
allowed_dependencies:
- "config"
- "capture"
- "tlsparse"
- "fingerprint"
- "output"
- "api"
forbidden_dependencies: []
api:
types:
- name: "api.Config"
description: "Configuration réseau et TLS de base."
fields:
- { name: Interface, type: "string", description: "Nom de linterface 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: "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 dun 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 envoyé vers les outputs."
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: IPMeta, type: "api.IPMeta", description: "Métadonnées IP." }
- { name: TCPMeta, type: "api.TCPMeta", description: "Métadonnées TCP." }
- { name: Fingerprints, type: "api.Fingerprints", description: "Empreintes JA4/JA3 associées." }
- name: "api.OutputConfig"
description: "Configuration dune sortie de logs."
fields:
- { name: Type, type: "string", description: "Type doutput (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, sarrête une fois le ClientHello complet obtenu."
- name: "fingerprint.Engine"
description: "Génère les empreintes JA4 (et JA3 éventuellement) à partir dun 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."
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 dun flux TCP pour minimiser la capture."
states:
- name: "NEW"
description: "Observation dun SYN client sur un port surveillé, création dun é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:
- "Pour chaque flux (srcIP, srcPort, dstIP, dstPort), on sarrê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 narrive jamais."
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 dinté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 derreur 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 dinté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 dinté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 denvironnement (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 denvironnement 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 dun 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 dAPI 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 dAPI 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 limpact des changements à lintérieur dun module."
guidelines:
- "Les interfaces de api.interfaces servent de contrat stable entre modules."
- "Les changements internes (optimisations, refactoring) ne doivent pas casser ces interfaces."
- "Lorsquun changement dAPI est inévitable, marquer lancienne 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 sappuyer 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 dintégration."