
l'utilitaire ps, avec une touche eBPF et contexte de conteneur
psc (ps container) est un scanner de processus rapide qui utilise les itérateurs eBPF et Google CEL pour interroger l'état du système avec précision et un contexte de conteneur complet.
psc utilise les itérateurs eBPF pour lire les informations sur les processus et les descripteurs de fichier directement depuis les structures de données du noyau. Cette approche est :
/proc. Je prévois également d'ajouter un moyen d'accéder à certains champs de task_struct à la demande pour des utilisations avancées, mais ce n'est qu'une idée/proc, offrant une visibilité qui ne peut pas être subvertie par les rootkits en espace utilisateur ou les astuces LD_PRELOADLes outils Linux traditionnels comme ps, lsof et ss sont puissants mais rigides. Ils produisent des formats fixes qui nécessitent un long tuyautage via grep, awk et sed :
# Traditionnel : trouver les processus nginx appartenant à root
ps aux | grep nginx | grep root | grep -v grep
# psc : exprimez exactement ce que vous voulez
psc 'process.name == "nginx" && process.user == "root"'
# Traditionnel : trouver les processus avec des connexions établies sur le port 443
ss -tnp | grep ESTAB | grep :443 | awk '{print $6}' | cut -d'"' -f2
# psc : une expression claire
psc 'socket.state == established && socket.dstPort == 443'
psc utilise le Common Expression Language (CEL) pour filtrer les processus. Les expressions CEL se lisent presque comme un langage naturel, rendant vos scripts auto-documentés et maintenables. Finis les pipelines complexes de grep | awk | sed | xargs.
Le flag -o vous permet de produire exactement les champs dont vous avez besoin, éliminant tout post-traitement :
psc 'socket.state == listen' -o process.name,socket.srcPort
Des préréglages de sortie sont également disponibles pour afficher rapidement les informations courantes :
psc 'socket.type == tcp && socket.dstPort == 443' -o sockets
Les outils traditionnels n'ont pas de notion de conteneurs. Obtenir des informations sur les conteneurs nécessite d'analyser les chemins cgroup, d'interroger les moteurs d'exécution de conteneurs et de corréler manuellement les PID :
# Traditionnel : trouver les processus dans des conteneurs (fragile, incomplet)
ps aux | xargs -I{} sh -c 'cat /proc/{}/cgroup 2>/dev/null | grep -q docker && echo {}'
# psc : support natif des conteneurs
psc 'container.runtime == docker'
psc extrait automatiquement le contexte des conteneurs (ID, nom, image, moteur d'exécution, labels) pour Docker, containerd, CRI-O et Podman. Déboguez les processus, fichiers et connexions réseau de n'importe quel conteneur directement depuis l'hôte :
# Afficher tous les processus d'un conteneur spécifique
psc 'container.name == "my-app"' --tree
# Trouver les conteneurs tournant en tant que root
psc 'container.runtime == docker && process.user == "root"'
# Lister les conteneurs avec leurs images
psc 'container.id != ""' -o process.pid,process.name,container.name,container.image
Sur Debian/Ubuntu :
sudo apt-get install clang llvm libbpf-dev linux-headers-$(uname -r) linux-tools-$(uname -r)
Sur Fedora/RHEL :
sudo dnf install clang llvm libbpf-devel kernel-devel bpftool
# Générer vmlinux.h (nécessaire une fois par version du noyau)
make vmlinux
# Construire le binaire
make build
Ou manuellement :
bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
go generate ./...
go build -o psc
sudo make install
psc nécessite les privilèges root pour charger les programmes eBPF.
# Lister tous les processus
sudo psc
# Lister tous les processus en arborescence
sudo psc --tree
Passez une expression CEL comme premier argument pour filtrer les processus :
# Filtrer par nom de processus
psc 'process.name == "nginx"'
# Filtrer par utilisateur
psc 'process.user == "root"'
# Filtrer par contenu de la ligne de commande
psc 'process.cmdline.contains("--config")'
# Filtrer par plage de PID
psc 'process.pid > 1000 && process.pid < 2000'
# Combinaison de conditions
psc 'process.name == "bash" || process.name == "zsh"'
# Afficher uniquement les processus conteneurisés
psc 'container.id != ""'
# Filtrer par moteur d'exécution de conteneur (constantes : docker, containerd, crio, podman)
psc 'container.runtime == docker'
# Filtrer par nom de conteneur
psc 'container.name == "nginx"'
# Filtrer par image de conteneur
psc 'container.image.contains("nginx:latest")'
# Afficher en arborescence pour voir la hiérarchie des processus du conteneur
psc 'container.runtime == docker' --tree
Comprendre pourquoi un processus existe nécessite souvent d'examiner ses descripteurs de fichier ouverts et ses connexions réseau :
# Trouver les processus avec des sockets TCP en écoute
psc 'socket.type == tcp && socket.state == listen'
# Trouver les processus avec des connexions établies
psc 'socket.state == established'
# Trouver les processus connectés à un port spécifique
psc 'socket.dstPort == 443'
# Trouver les processus utilisant des sockets Unix
psc 'socket.family == unix'
# Trouver les processus ayant des fichiers ouverts dans /etc
psc 'file.path.startsWith("/etc")'
Champs des processus (process.X) :
name - Nom du processus (chaîne)pid - ID du processus (entier)ppid - ID du processus parent (entier)tid - ID du thread (entier)euid - ID utilisateur effectif (entier)ruid - ID utilisateur réel (entier)suid - ID utilisateur enregistré (entier)user - Nom d'utilisateur (chaîne)cmdline - Ligne de commande complète (chaîne)state - État du processus (uint)Champs des capacités (process.capabilities.X) :
effective - Masque de bits des capacités effectives (uint)permitted - Masque de bits des capacités autorisées (uint)inheritable - Masque de bits des capacités héritables (uint)Champs des espaces de noms (process.namespaces.X) :
net - Inode de l'espace de noms réseau (entier)pid - Inode de l'espace de noms PID (entier)mnt - Inode de l'espace de noms de montage (entier)uts - Inode de l'espace de noms UTS (entier)ipc - Inode de l'espace de noms IPC (entier)cgroup - Inode de l'espace de noms cgroup (entier)Champs des conteneurs (container.X) :
id - ID du conteneur (chaîne)name - Nom du conteneur (chaîne)image - Image du conteneur (chaîne)runtime - Moteur d'exécution du conteneur (chaîne)labels - Labels du conteneur (map)Champs des fichiers/sockets (file.X ou socket.X) :
path - Chemin du fichier (chaîne)fd - Numéro de descripteur de fichier (entier)srcPort - Port source (entier)dstPort - Port de destination (entier)type - Type de socket (tcp, udp)state - État du socket (pour le filtrage, utiliser des constantes comme listen, established)family - Famille d'adresses (unix, inet, inet6)unixPath - Chemin du socket Unix (chaîne)fdType - Type de FD (file_type, socket_type)Utilisez-les sans guillemets dans les expressions :
docker, containerd, crio, podmantcp, udpunix, inet, inet6established, listen, syn_sent, syn_recv, fin_wait1, fin_wait2, , , , , Note : La sortie utilise les noms d'état de style
ss:ESTAB,LISTEN,SYN-SENT, etc. Pour les sockets UDP, seulsUNCONN(non connecté) ouESTAB(connecté) sont affichés car UDP est sans connexion.
CEL fournit des fonctions de manipulation de chaînes :
.contains("sous-chaîne") - Vérifie si la chaîne contient une sous-chaîne.startsWith("préfixe") - Vérifie si la chaîne commence par un préfixe.endsWith("suffixe") - Vérifie si la chaîne se termine par un suffixe--tree, -t - Affiche les processus en arborescence--no-color - Désactive la sortie colorée-o, --output - Colonnes de sortie personnalisées (noms de champs ou préréglages séparés par des virgules)psc fields - Liste tous les champs CEL, constantes et préréglages de sortie disponiblespsc version - Affiche les informations de version-oLe flag -o vous permet de spécifier exactement les champs à afficher. Vous pouvez utiliser des préréglages pour les cas d'utilisation courants ou spécifier des champs individuels.
Préréglages :
sockets - Infos processus + détails complets du socket (famille, type, état, adresses, ports)files - Infos processus + détails du descripteur de fichier (fd, type, chemin)containers - Infos processus + détails du conteneur (nom, image, moteur d'exécution)network - Vue réseau compacte (pid, nom, type, état, ports)# Utiliser un préréglage
psc 'socket.state == listen' -o sockets
psc 'container.id != ""' -o containers
# Ou spécifier des champs individuels
psc -o process.pid,process.name,process.user
psc 'socket.state == listen' -o process.pid,process.name,socket.srcPort,socket.state
Lorsque la sortie inclut des champs de fichier/socket et que le filtre correspond à plusieurs fichiers par processus, chaque correspondance obtient sa propre ligne :
$ psc 'socket.state == listen' -o network
PID NAME TYPE STATE SRCPORT DSTPORT
1234 nginx tcp LISTEN 80 0
1234 nginx tcp LISTEN 443 0
5678 sshd tcp LISTEN 22 0
Trouver tous les serveurs web :
psc 'process.name == "nginx" || process.name == "apache2" || process.name == "httpd"'
Trouver les processus en écoute sur des ports privilégiés :
psc 'socket.state == listen && socket.srcPort < 1024'
Trouver les processus dans un espace de noms réseau différent (utile pour l'inspection de conteneurs/pods) :
psc 'process.namespaces.net != 4026531840' -o process.pid,process.name,process.namespaces.net
Afficher les capacités des processus privilégiés :
psc 'process.euid == 0' -o process.pid,process.name,process.capabilities.effective,process.capabilities.permitted
Trouver les processus ayant élevé leurs privilèges via des binaires SUID (l'UID réel diffère de l'UID effectif) :
psc 'process.ruid != process.euid'
Trouver les processus avec des connexions vers des services externes :
psc 'socket.state == established && socket.dstPort == 443'
Afficher les connexions réseau avec des colonnes personnalisées :
psc 'socket.state == established' -o process.pid,process.name,socket.srcPort,socket.dstPort,socket.dstAddr
MIT
time_waitcloseclose_waitlast_ackclosingfile_type, socket_type