bitmapper
bitmapper répond à la question « à quoi cette machine parle-t-elle réellement, et quel processus le fait ? »
bitcollector lit l'état de l'hôte. bitscanner regarde vers l'extérieur, vers le segment.
bitmapper observe le trafic lui-même — les paquets en direct sur les interfaces de l'hôte,
regroupés en flux et attribués au processus propriétaire de la socket.
bitmapper livre des enregistrements de flux, pas du trafic. Depuis 0.1.0-ga, le moteur de
capture est relié à l'exportateur, et les connexions suivies atteignent la plateforme sous forme
d'enregistrements de flux : extrémités, ports, protocole, compteurs d'octets et de paquets,
état de la connexion et — lorsqu'il peut être déterminé, ce qui est rare aujourd'hui — le
processus attribué. Aucun octet de charge utile, aucune capture de
paquets, et pas la ligne de commande du processus, qui transporte couramment des identifiants
en arguments. Si vous attendiez qu'un PCAP arrive quelque part, ce n'est pas ce que cet agent
envoie.
L'attribution de processus fonctionne à peine dans le binaire publié, et le correctif n'est pas
encore publié. Dans 0.1.0-ga, l'attribution est tentée une seule fois, à la création de la
connexion, contre un instantané des tables de sockets qui ne peut pas contenir la socket d'une
nouvelle connexion sortante, et il n'existe aucune inférence à partir des sockets en écoute —
les flux arrivent donc souvent sans aucun processus (mesuré sur l'hôte de développement :
0 flux attribué sur 12), et destination_process n'est jamais renseigné. Le remplacement —
rattrapage par flux, sur les deux extrémités, et un champ attribution distinguant une
correspondance directe de socket (socket) d'une inférence (listening_socket) — n'est pas
publié. Cette inférence est encadrée : elle n'est tirée que pour une adresse de cet hôte, que
lorsqu'un seul processus exactement est prouvé détenteur de la socket en écoute, et qu'en TCP —
un flux UDP ne la porte donc jamais. Lisez
l'attribution de processus avant de bâtir quoi que ce soit
dessus.
C'est un binaire, sur une seule plateforme. 0.1.0-ga est publié pour linux/amd64
uniquement. Il n'existe aucun téléchargement macOS, Windows ou arm64, et les raisons sont
exposées ci-dessous plutôt que laissées comme un vide sur une page de
téléchargement.
Il ne capture pas tous les protocoles par défaut. capture.protocols vaut par défaut
["tcp", "udp"], que votre fichier de configuration la mentionne ou non : un déploiement
d'origine ne voit donc ni ICMP, ni ICMPv6, ni SCTP, ni GRE, ni ESP, ni AH — le noyau les
abandonne et rien dans la sortie ne le signale. L'élargir tient en
une ligne.
Des parties de cet agent ne sont toujours pas construites. Il n'y a pas d'agrégation de
topologie de services, pas de métriques Prometheus et pas de détection de politique réseau ;
plusieurs clés de configuration sont analysées sans rien faire, et il en va de même de toutes les
options de la ligne de commande sauf --config. Ce qui n'est pas câblé en
donne la liste, et elle mérite d'être lue avant de bâtir quoi que ce soit autour d'une clé.
| Version | 0.1.0-ga, commit 64ebc64, compilée avec go1.25.12 |
| Plateformes | linux/amd64 uniquement — macOS, Windows et arm64 ne sont pas publiés, et pourquoi |
| Chaîne d'approvisionnement | Compilation reproductible, SBOM CycloneDX, signature cosign, attestation du SBOM, SHA256SUMS signé — même clé de famille que les autres bits |
| État | Capture, attribue et suit les connexions ; exporte des enregistrements de flux vers la plateforme |
| Surface réseau entrante | Aucun service en écoute. Les serveurs HTTP/WebSocket et gRPC existent dans l'arborescence du code mais ne sont jamais construits, et tous deux sont livrés désactivés |
| Trafic sortant | https uniquement — enregistrement, battement de cœur et enregistrements de flux vers l'endpoint que vous configurez |
| Privilèges | Élevés : root, ou CAP_NET_RAW + CAP_NET_ADMIN |
| Prérequis de compilation | CGO et libpcap, liée statiquement dans le binaire publié |
Sa place parmi les bits
Les quatre agents répondent à quatre questions différentes, et la frontière est ce que chacun regarde :
| Regarde | Répond à | |
|---|---|---|
bitcollector | L'état propre de l'hôte | « Qu'est-ce que cette machine, et dans quel état est-elle ? » |
bitscanner | Le segment autour de l'hôte | « Qu'y a-t-il d'autre sur ce réseau dont je n'ai pas connaissance ? » |
bitmapper | Les paquets qui traversent les interfaces de l'hôte — TCP et UDP par défaut, davantage si vous le demandez | « À quoi cette machine parle-t-elle, et quel processus le fait ? » |
bitenforcer | La configuration de l'hôte | « Cette machine est-elle conforme à la politique que j'ai définie ? » |
Une table de voisins vous dit qu'un équipement existe. Une capture vous dit qu'on lui parle, par quoi, et à quelle fréquence — c'est là toute la différence entre un inventaire et une carte des dépendances.
Ce qui fonctionne aujourd'hui
Présent et câblé dans le chemin d'exécution de la commande capture :
- Capture de paquets en direct — libpcap via
gopacket, liée statiquement dans le binaire Linux publié, avec filtre BPF, snaplen, mode promiscuité et taille du tampon de capture. - Filtrage de capture côté noyau —
capture.filter,capture.protocols,capture.portsetcapture.exclude_portssont compilées en une seule expression BPF appliquée par le noyau : le trafic exclu n'est jamais copié dans l'agent. L'expression compilée est journalisée au démarrage. Attention au défaut :capture.protocolsvaut["tcp", "udp"]même si votre fichier l'omet, ce qui signifie qu'ICMP, SCTP, GRE et ESP ne sont pas capturés sauf demande explicite. Voir le filtrage. - Capture multi-interfaces — une liste nommée d'interfaces, ou bien toute interface active et non-loopback si vous n'en nommez aucune.
- Attribution de processus — présente, mais fonctionnant à peine dans le binaire publié.
PID, nom du processus, chemin de l'exécutable et utilisateur, résolus depuis les tables de
sockets de l'hôte. Dans
0.1.0-ga, la recherche est tentée une seule fois, à la création de la connexion, contre un instantané qui ne peut pas contenir la socket d'une nouvelle connexion sortante : les flux ne portent donc souvent aucun processus, etdestination_processn'est jamais renseigné. Le rattrapage par flux, l'attribution des deux extrémités et le champ de confianceattribution(socketvslistening_socket) ne sont pas dans le binaire publié, qui ne comporte aucune inférence à partir des sockets en écoute — voir l'attribution de processus pour ce qui est livré aujourd'hui et ce qui le remplace. Ce remplacement refuse aussi plutôt que de deviner : aucune identité n'est prise sur une socket en écoute si l'adresse n'est pas sur cet hôte ou s'il n'est pas prouvé qu'un seul processus la détient — une socket nginx de l'hôte de développement est détenue par 17 processus — et il n'infère jamais en UDP, qui n'a pas d'état LISTEN. Un champ processus vide est un refus, et non une panne. - Suivi de connexions à états — appariement bidirectionnel des flux, une machine à états TCP
(
SYN_SENT→ESTABLISHED→ … →CLOSED), des compteurs de paquets et d'octets par connexion, une table plafonnée en taille avec éviction, et un ramasse-miettes périodique des connexions inactives. - Flux d'événements interne au processus — les événements de paquet, de nouvelle connexion et de statistiques sont diffusés aux abonnés à l'intérieur du processus.
- Statistiques périodiques — compteurs de paquets, d'octets, d'abandons et de connexions
écrits en JSON structuré selon
performance.stats_interval. - Intégration au plan de contrôle — enregistrement de l'agent et battement de cœur lorsque
control_plane.*est configuré. - Export de flux — chaque connexion suivie passe du chemin de capture à l'exportateur et est
envoyée en POST à la plateforme sous forme d'enregistrement de flux. Chaque flux produit au
moins un enregistrement, et le dernier est marqué comme final avec des compteurs complets ;
une connexion de longue durée est rafraîchie une fois par
batch_export.batch_intervalsi ses compteurs ont bougé, de sorte que vous la voyez avant qu'elle ne se termine. - Un filtrage d'export qui filtre réellement —
output.filter(exclude_cidrs,exclude_loopback,protocols,ports) est appliqué à l'entrée, avant qu'un enregistrement ne soit construit : un flux exclu n'est donc jamais conservé en mémoire et ne peut jamais être envoyé. Les exclusions portent sur l'une ou l'autre extrémité : exclure10.0.0.0/8signifie « ne parlez pas de ce réseau à Cert-IX », et rapporter que10.1.2.3a ouvert une connexion vers l'extérieur en parle tout aussi complètement. Une entrée qui ne peut pas être compilée refuse l'exécution plutôt que de se dégrader en « tout exporter ». - Validation de configuration à échec immédiat — l'agent refuse de démarrer sur une configuration invalide ou rendue non sûre par omission, plutôt que de démarrer dans un état affaibli.
Ce qui n'est pas câblé
Énoncé explicitement, pour que personne n'ait à le découvrir en lisant le code source :
- Les serveurs HTTP/WebSocket et gRPC ne sont jamais construits. Comme rien ne les
construit, le middleware de sécurité placé derrière eux — authentification, liste
d'autorisation d'adresses IP, limitation de débit, journalisation d'audit — ne s'exécute
pas. Ne considérez pas la configuration
security.*comme une protection aujourd'hui. Le bon côté est celui qu'il faut dire honnêtement : aucun serveur n'étant construit, l'agent n'expose aucun port entrant. - Aucune agrégation de topologie de services. Les types
ServiceTopologyetServiceEdgeexistent mais ne sont jamais construits : il n'y a donc pas encore de graphe de topologie. - Aucune métrique Prometheus et aucun endpoint
/metrics. - Aucune détection de politique réseau.
Clés de configuration qui sont validées mais ne font rien
Elles s'analysent correctement, et l'agent les accepte, mais aucun code ne les consomme :
output.backend, output.kafka, output.nats, output.redis, output.webhook,
output.file, kubernetes.*, storage.*, logging.*, performance.batch_size,
performance.connection_pooling, performance.memory_limit, performance.cpu_limit.
Les options de la ligne de commande relèvent de la même catégorie, et elles sont plus nombreuses
que les clés. --log-level et --log-format sont déclarées, documentées dans --help, et
lues par rien du tout : le journaliseur est un journaliseur zap de production figé (JSON,
niveau info). Les options de capture (--interfaces, --filter, --snaplen,
--promiscuous, --buffer-size, --workers, --queue-size, --stats-interval) sont liées
sous leur nom d'option alors que la configuration est lue depuis des clés imbriquées : elles sont
donc analysées puis ignorées. --enable-grpc et --enable-http valent true par défaut dans
--help et désignent des serveurs qui ne sont jamais construits. --config est la seule
option qui change le comportement. Ne prenez pas bitmapper --help pour une description de ce
que fait cette version.
Trois choses ne figurent pas dans cette liste, et les distinctions comptent :
output.filterest lue et appliquée sur le chemin d'export depuis0.1.0-ga. Elle figurait auparavant dans cette liste, et tant que l'agent n'exportait rien du tout ce n'était que du poids mort. À partir du moment où des enregistrements de flux ont commencé à quitter l'hôte, elle serait devenue une fausse affirmation de confidentialité — un opérateur excluant un sous-réseau sensible n'obtenant aucune protection, aucune erreur, et aucun moyen de le savoir. Elle est désormais appliquée à l'entrée. Quatre clés qui ne pouvaient agir sur rien (export_packets,export_stats,min_packet_size,require_payload) ont été supprimées du schéma plutôt que laissées à s'analyser, et l'agent les nomme au démarrage si votre fichier en contient encore une.capture.protocols,capture.portsetcapture.exclude_portssont compilées dans le filtre BPF du noyau et appliquées à cet endroit. Une version antérieure de cette page listait les trois comme validées-mais-inertes, et c'était faux — voir la correction ci-dessous.capture.exclude_interfacesest lue et appliquée sur le chemin de capture actif. Voir choisir les interfaces.
capture.exclude_ports est un contrôle de confidentialité qui fonctionneJusqu'à 0.1.0-ga, cette page comptait capture.protocols, capture.ports et
capture.exclude_ports parmi les clés qui « s'analysent sans rien faire », et la
page sur la capture le répétait. C'était l'inverse de la vérité : les
trois sont compilées dans le filtre BPF et appliquées par le noyau avant qu'un paquet n'atteigne
l'agent.
capture.exclude_ports est celle sur laquelle agir. La configuration livrée avec bitmapper fixe
exclude_ports: [22] — « ne pas capturer SSH » — et cela fonctionne. Si vous avez lu l'ancien
texte et supprimé cette clé comme du poids mort, vous avez retiré un contrôle qui vous
protégeait et commencé à collecter du trafic que vous aviez délibérément exclu, sans rien qui
signale le changement. Vérifiez ce que dit votre configuration déployée avant de supposer que
vous n'êtes pas concerné.
capture.protocols demande l'attention inverse : elle vaut par défaut ["tcp", "udp"] que vous
la fixiez ou non, si bien qu'un déploiement d'origine ne capture silencieusement ni ICMP, ni
ICMPv6, ni SCTP, ni GRE, ni ESP, ni AH. Le défaut de protocole
explique comment l'élargir.
Elles font partie du schéma de configuration et leur forme est validée : un fichier écrit avec elles se charge donc. C'est exactement pourquoi elles sont listées ici : une clé qui s'analyse sans erreur et ne fait rien est le genre de chose qui se lit comme une fonctionnalité opérationnelle. Considérez qu'une clé n'a aucun effet à moins qu'elle n'apparaisse sous ce qui fonctionne aujourd'hui.
Le package d'enrichissement Kubernetes a été supprimé plutôt que laissé en sommeil. Il
n'avait aucun importateur et construisait son propre client HTTP, lequel suivait les
redirections et pouvait faire fuiter un jeton de porteur de compte de service vers une
destination en clair lorsqu'un kubeconfig pointait vers un serveur qui redirige. Le bloc
kubernetes.* demeure dans le schéma et n'est désormais consommé par rien.
L'exécuter
bitmapper est un binaire unique doté de trois commandes :
# What you have
bitmapper version
# Bitmapper 0.1.0-ga
# Git Commit: 64ebc64
# Build Date: 2026-08-10T05:56:16Z
# Which interfaces this host can capture on
bitmapper interfaces
# Capture, with the configuration you supply
bitmapper capture --config /etc/bitmapper/config.yaml
--config, --log-level et --log-format sont des options persistantes de la commande racine,
mais seule --config fait quelque chose. Il n'existe aucun moyen d'augmenter le détail de
journalisation pendant la mise en service : le journaliseur est figé en JSON, niveau info. Ce que
l'agent vous dit au démarrage, c'est le filtre de capture compilé et les
interfaces qu'il a retenues ; puis, à chaque performance.stats_interval, il journalise les
compteurs de paquets, d'octets, d'abandons et de connexions — ce sont les preuves à lire pendant
la mise en service d'un hôte.
Il lui faut des privilèges élevés
La capture de paquets est une opération privilégiée. Sur la plateforme publiée :
| Plateforme | Ce qu'il lui faut |
|---|---|
| Linux | CAP_NET_RAW et CAP_NET_ADMIN (ou root) |
Il lui faut CGO — et le binaire publié l'a déjà
La capture passe par gopacket/pcap, qui est une liaison vers une bibliothèque C. Une
compilation avec CGO_ENABLED=0 se compile quand même, mais la capture et le listage des
interfaces refusent de s'exécuter :
packet capture requires CGO (build with CGO_ENABLED=1 and libpcap installed)
C'est délibéré — un binaire qui ne capturerait silencieusement rien serait pire qu'un binaire qui dit pourquoi il ne le peut pas. C'est aussi le seul fait qui décide des plateformes publiées, et c'est pourquoi la chaîne de publication refuse tout binaire qui n'a pas été compilé avec CGO, qui n'est pas lié statiquement, ou qui ne contient pas libpcap. Voir plateformes.
Le téléchargement que vous obtenez n'exige aucune libpcap installée sur l'hôte : libpcap est liée statiquement dans le binaire publié.
Le secret d'enrôlement
Si vous configurez control_plane.*, le secret d'enrôlement est lu depuis
control_plane.enrollment_token_file. L'agent exige que ce soit un fichier ordinaire, en mode
0600 ou plus restrictif, appartenant à root ou à l'utilisateur propre de l'agent, et situé
dans un répertoire qui n'est inscriptible ni par le groupe ni par tout le monde. Un répertoire
dans lequel n'importe qui peut écrire permet à quelqu'un de remplacer le fichier : ne contrôler
que le mode du fichier lui-même reviendrait donc à contrôler la mauvaise chose.
Le contrôle repose sur POSIX. Cela couvre tous les binaires publiés, puisque 0.1.0-ga n'est
livré que sur Linux — mais il vaut la peine de savoir qu'il s'agit d'une propriété de la
plateforme et non de l'agent, si une compilation Windows devait un jour apparaître.
control_plane:
gateway_url: "https://<your-cert-ix-agent-endpoint>"
ingest_url: "https://<your-cert-ix-ingest-endpoint>"
tenant_id: "<your-tenant-id>"
enrollment_token_file: "/etc/bitmapper/enrollment.token"
Les deux URL doivent être en https://, et ingest_url est obligatoire dès lors que le bloc
du plan de contrôle est utilisé. tenant_id est envoyé dans l'en-tête X-Tenant-ID — c'est un
identifiant, pas un secret d'authentification : ce n'est donc pas lui qui authentifie
l'agent.
Le secret peut aussi provenir de l'environnement, sous le nom
BITMAPPER_CONTROL_PLANE_ENROLLMENT_TOKEN, ou être entièrement remplacé par un certificat client
mutual-TLS (control_plane.client_cert_file / client_key_file, le fichier de clé subissant le
même contrôle de permissions). Renseigner control_plane.enrollment_token directement dans le
YAML fonctionne et est déconseillé — un secret dans un fichier de configuration est un secret
sur disque.
Ce qu'il coûte à l'hôte
La capture de paquets est la chose la plus coûteuse que fasse le moindre des bits, et le coût croît avec le trafic plutôt qu'avec la taille de l'hôte. Deux réglages comptent avant tout :
- Un filtre BPF est la réduction la moins coûteuse possible — il abandonne les paquets dans le noyau avant même qu'ils ne soient copiés vers l'agent.
- La table des connexions est plafonnée en taille, avec éviction et ramasse-miettes périodique des entrées inactives, si bien qu'un hôte voyant passer beaucoup de connexions éphémères ne fait pas croître la table sans limite.
performance.memory_limit et performance.cpu_limit ne sont pas implémentées — elles
figurent dans la liste ci-dessus. Bornez le processus avec les contrôles de votre système d'init
(MemoryMax=, CPUQuota= dans une unité systemd) s'il vous faut un plafond réel.
Les flux en attente d'export sont bornés séparément par
batch_export.max_buffered_connections, et non par
performance.connection_table_size, dont la valeur par défaut placerait une seconde table à six
chiffres sur un hôte de capture. Chaque abandon est compté et journalisé ; aucun n'est
silencieux.
Plateformes
0.1.0-ga publie un binaire signé. C'est la liste de plateformes la plus étroite de la
famille, et chacune des omissions ci-dessous est une décision mesurée plutôt qu'un oubli :
| Plateforme | Publiée | Pourquoi pas |
|---|---|---|
Linux amd64 | Oui | |
Linux arm64 | Non | Ne peut pas être assemblée par un gcc amd64, et il n'existe pas de machine de compilation arm64 |
macOS amd64/arm64 | Non | Exige clang et le SDK macOS |
Windows amd64 | Non | Se compile, mais n'a jamais été exécutée sous Windows — voir ci-dessous |
La cause unique derrière tout cela est que bitmapper est le seul bit qui ne peut pas être
compilé de manière croisée. La capture, c'est gopacket → libpcap → CGO : une compilation
exige donc une véritable chaîne d'outils C et une véritable libpcap pour la cible. Tous les
autres bits sont en Go pur, et GOOS/GOARCH suffit.
Le piège que cela crée est précis et mérite d'être nommé. internal/capture porte un talon
//go:build !cgo qui se compile sur toutes les plateformes et ne capture rien. Une
compilation CGO_ENABLED=0 réussit donc — elle produit un binaire qui démarre, s'enregistre,
émet des battements de cœur, se déclare en bonne santé, et ne voit jamais un paquet. La chaîne
de publication contrôle par conséquent l'artefact plutôt que la commande de compilation : elle
refuse tout binaire qui n'a pas été compilé avec CGO, qui n'est pas lié statiquement, ou qui ne
contient pas libpcap.
Windows est le cas intéressant, et le cas honnête. Il se compile réellement — la prise en
charge Windows de gopacket est en Go pur et charge wpcap.dll à l'exécution — nous pourrions
donc le publier aujourd'hui. Il n'a jamais été exécuté sous Windows. C'est donc une lacune
de test et non une lacune de chaîne d'outils, et le publier reviendrait à livrer un binaire
signé dont personne n'a jamais vu fonctionner le chemin de capture. Un binaire signé qui ne
peut pas capturer est pire que pas de binaire, car la signature se lit comme une affirmation
d'aptitude. Il est retenu jusqu'à ce que quelqu'un l'ait exécuté.
Si vous avez besoin de ces données depuis un hôte macOS, Windows ou arm64 aujourd'hui,
bitmapper n'est pas la réponse pour cet hôte. bitcollector est livré
sur les cinq plateformes et rapporte les pairs réseau établis de cet hôte — une réponse plus
grossière que l'attribution par flux, mais une vraie réponse sur la machine dont vous
disposez.
Étapes suivantes
- Comment fonctionne la capture — interfaces, filtres BPF, attribution de processus et machine à états des connexions, et ce que chacun d'eux met en mémoire.
- bitscanner — l'agent qui trouve les équipements que votre inventaire ne contient pas.
- bitcollector — l'inventaire de l'hôte tourné vers l'intérieur.
- Vérifier vos téléchargements — l'ancre de confiance, et pourquoi elle compte plus que la signature.
- Qu'est-ce que bits ? — comment les agents s'articulent.
Cette page vous a-t-elle été utile ?