
la utilidad ps, con un toque de eBPF y contexto de contenedores
psc (ps container) es un escáner de procesos rápido que utiliza iteradores eBPF y Google CEL para consultar el estado del sistema con precisión y contexto completo de contenedores.
psc utiliza iteradores eBPF para leer la información de procesos y descriptores de archivo directamente de las estructuras de datos del kernel. Este enfoque es:
/proc. Planeo agregar también una forma de acceder a ciertos campos de task_struct bajo demanda para usos avanzados, pero esto es solo una idea/proc, proporcionando visibilidad que no puede ser subvertida por rootkits de espacio de usuario o trucos de LD_PRELOADLas herramientas tradicionales de Linux como ps, lsof y ss son potentes pero inflexibles. Generan formatos fijos que requieren un extenso pipe a través de grep, awk y sed:
# Tradicional: Encontrar procesos nginx propiedad de root
ps aux | grep nginx | grep root | grep -v grep
# psc: Expresa exactamente lo que quieres decir
psc 'process.name == "nginx" && process.user == "root"'
# Tradicional: Encontrar procesos con conexiones establecidas en el puerto 443
ss -tnp | grep ESTAB | grep :443 | awk '{print $6}' | cut -d'"' -f2
# psc: Una expresión clara
psc 'socket.state == established && socket.dstPort == 443'
psc utiliza el Lenguaje de Expresión Común (CEL) para filtrar procesos. Las expresiones CEL se leen casi como lenguaje natural, haciendo que tus scripts sean autodocumentados y mantenibles. Olvídate de descifrar complejos pipelines de grep | awk | sed | xargs.
La opción -o te permite generar exactamente los campos que necesitas, eliminando por completo el post-procesamiento:
psc 'socket.state == listen' -o process.name,socket.srcPort
También hay presets de salida disponibles para imprimir rápidamente información común:
psc 'socket.type == tcp && socket.dstPort == 443' -o sockets
Las herramientas tradicionales no tienen concepto de contenedores. Obtener información de contenedores requiere analizar rutas de cgroup, consultar runtimes de contenedores y correlacionar PIDs manualmente:
# Tradicional: Encontrar procesos contenerizados (frágil, incompleto)
ps aux | xargs -I{} sh -c 'cat /proc/{}/cgroup 2>/dev/null | grep -q docker && echo {}'
# psc: Soporte nativo de contenedores
psc 'container.runtime == docker'
psc extrae automáticamente el contexto del contenedor (ID, nombre, imagen, runtime, etiquetas) para Docker, containerd, CRI-O y Podman. Depura cualquier proceso, archivo y conexión de red de un contenedor directamente desde el host:
# Mostrar todos los procesos en un contenedor específico
psc 'container.name == "my-app"' --tree
# Encontrar contenedores ejecutándose como root
psc 'container.runtime == docker && process.user == "root"'
# Listar contenedores con sus imágenes
psc 'container.id != ""' -o process.pid,process.name,container.name,container.image
En Debian/Ubuntu:
sudo apt-get install clang llvm libbpf-dev linux-headers-$(uname -r) linux-tools-$(uname -r)
En Fedora/RHEL:
sudo dnf install clang llvm libbpf-devel kernel-devel bpftool
# Generar vmlinux.h (requerido una vez por versión del kernel)
make vmlinux
# Compilar el binario
make build
O manualmente:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
go generate ./...
go build -o psc
sudo make install
psc requiere privilegios de root para cargar programas eBPF.
# Listar todos los procesos
sudo psc
# Listar todos los procesos en forma de árbol
sudo psc --tree
Pasa una expresión CEL como primer argumento para filtrar procesos:
# Filtrar por nombre de proceso
psc 'process.name == "nginx"'
# Filtrar por usuario
psc 'process.user == "root"'
# Filtrar por contenido de la línea de comandos
psc 'process.cmdline.contains("--config")'
# Filtrar por rango de PID
psc 'process.pid > 1000 && process.pid < 2000'
# Combinar condiciones
psc 'process.name == "bash" || process.name == "zsh"'
# Mostrar solo procesos contenerizados
psc 'container.id != ""'
# Filtrar por runtime de contenedor (constantes: docker, containerd, crio, podman)
psc 'container.runtime == docker'
# Filtrar por nombre de contenedor
psc 'container.name == "nginx"'
# Filtrar por imagen de contenedor
psc 'container.image.contains("nginx:latest")'
# Mostrar como árbol para ver la jerarquía de procesos del contenedor
psc 'container.runtime == docker' --tree
Comprender por qué existe un proceso a menudo requiere observar sus descriptores de archivo abiertos y conexiones de red:
# Encontrar procesos con sockets TCP en escucha
psc 'socket.type == tcp && socket.state == listen'
# Encontrar procesos con conexiones establecidas
psc 'socket.state == established'
# Encontrar procesos conectados a un puerto específico
psc 'socket.dstPort == 443'
# Encontrar procesos que usan sockets Unix
psc 'socket.family == unix'
# Encontrar procesos con archivos abiertos en /etc
psc 'file.path.startsWith("/etc")'
Campos de proceso (process.X):
name - Nombre del proceso (cadena)pid - ID del proceso (entero)ppid - ID del proceso padre (entero)tid - ID del hilo (entero)euid - ID de usuario efectivo (entero)ruid - ID de usuario real (entero)suid - ID de usuario guardado (entero)user - Nombre de usuario (cadena)cmdline - Línea de comandos completa (cadena)state - Estado del proceso (uint)Campos de capacidades (process.capabilities.X):
effective - Máscara de bits de capacidades efectivas (uint)permitted - Máscara de bits de capacidades permitidas (uint)inheritable - Máscara de bits de capacidades heredables (uint)Campos de espacios de nombres (process.namespaces.X):
net - Inodo del espacio de nombres de red (entero)pid - Inodo del espacio de nombres de PID (entero)mnt - Inodo del espacio de nombres de montaje (entero)uts - Inodo del espacio de nombres UTS (entero)ipc - Inodo del espacio de nombres IPC (entero)cgroup - Inodo del espacio de nombres de cgroup (entero)Campos de contenedor (container.X):
id - ID del contenedor (cadena)name - Nombre del contenedor (cadena)image - Imagen del contenedor (cadena)runtime - Runtime del contenedor (cadena)labels - Etiquetas del contenedor (mapa)Campos de archivo/socket (file.X o socket.X):
path - Ruta del archivo (cadena)fd - Número de descriptor de archivo (entero)srcPort - Puerto de origen (entero)dstPort - Puerto de destino (entero)type - Tipo de socket (tcp, udp)state - Estado del socket (para filtrado, usar constantes como listen, established)family - Familia de direcciones (unix, inet, inet6)unixPath - Ruta del socket Unix (cadena)fdType - Tipo de FD (file_type, socket_type)Utiliza estas sin comillas en las expresiones:
docker, containerd, crio, podmantcp, udpunix, inet, inet6established, listen, syn_sent, syn_recv, fin_wait1, fin_wait2, time_wait, close, close_wait, last_ack, closingfile_type, socket_typeNota: La salida usa nombres de estado estilo
ss:ESTAB,LISTEN,SYN-SENT, etc. Para sockets UDP, solo se muestranUNCONN(no conectado) oESTAB(conectado) ya que UDP no tiene conexión.
CEL proporciona funciones de manipulación de cadenas:
.contains("substr") - Verificar si la cadena contiene una subcadena.startsWith("prefix") - Verificar si la cadena comienza con un prefijo.endsWith("suffix") - Verificar si la cadena termina con un sufijo--tree, -t - Mostrar procesos en forma de árbol--no-color - Deshabilitar salida coloreada-o, --output - Columnas de salida personalizadas (nombres de campo separados por comas o preset)psc fields - Listar todos los campos CEL disponibles, constantes y presets de salidapsc version - Mostrar información de la versión-oLa opción -o te permite especificar exactamente qué campos mostrar. Puedes usar presets para casos de uso comunes o especificar campos individuales.
Presets:
sockets - Información del proceso + detalles completos del socket (familia, tipo, estado, direcciones, puertos)files - Información del proceso + detalles del descriptor de archivo (fd, tipo, ruta)containers - Información del proceso + detalles del contenedor (nombre, imagen, runtime)network - Vista de red compacta (pid, nombre, tipo, estado, puertos)# Usar un preset
psc 'socket.state == listen' -o sockets
psc 'container.id != ""' -o containers
# O especificar campos individuales
psc -o process.pid,process.name,process.user
psc 'socket.state == listen' -o process.pid,process.name,socket.srcPort,socket.state
Cuando la salida incluye campos de archivo/socket y el filtro coincide con varios archivos por proceso, cada coincidencia obtiene su propia fila:
$ 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
Encontrar todos los servidores web:
psc 'process.name == "nginx" || process.name == "apache2" || process.name == "httpd"'
Encontrar procesos escuchando en puertos privilegiados:
psc 'socket.state == listen && socket.srcPort < 1024'
Encontrar procesos en un espacio de nombres de red diferente (útil para inspección de contenedores/pods):
psc 'process.namespaces.net != 4026531840' -o process.pid,process.name,process.namespaces.net
Mostrar capacidades para procesos privilegiados:
psc 'process.euid == 0' -o process.pid,process.name,process.capabilities.effective,process.capabilities.permitted
Encontrar procesos que elevaron privilegios mediante binarios SUID (UID real difiere del UID efectivo):
psc 'process.ruid != process.euid'
Encontrar procesos con conexiones a servicios externos:
psc 'socket.state == established && socket.dstPort == 443'
Mostrar conexiones de red con columnas personalizadas:
psc 'socket.state == established' -o process.pid,process.name,socket.srcPort,socket.dstPort,socket.dstAddr
MIT