Metti una gabbia ai processi prima che ti facciano del male.
Linux Capabilities, seccomp e MAC spiegati a chi non ha tempo di leggere le man page.
"Il minimo privilegio è il massimo privilegio che puoi concederti."
— qualcuno con più downtime alle spalle di te
DAC, MAC, e il principio del minimo privilegio
Linux tradizionale ha un sistema di permessi binario: o sei root (puoi fare tutto) o non lo sei (puoi fare poco). Questo è il DAC — Discretionary Access Control: il proprietario del file decide chi può accedervi.
Il problema è che molti processi legittimi hanno bisogno di fare cose privilegiate ma non di tutto il potere di root. Un web server ha bisogno di ascoltare sulla porta 80, non di riformattare il disco.
/var/www. Fine.Profili per processo: cosa può leggere, scrivere, eseguire
Dividi i poteri di root in ~40 privilegi separati
Filtra le syscall che un processo può chiamare
Il MAC che puoi capire senza un dottorato
AppArmor (Application Armor) è un modulo LSM (Linux Security Module) che impone policy di accesso per singolo programma. È il MAC predefinito su Ubuntu, Debian, openSUSE. L'alternativa è SELinux, usato da RHEL/Fedora, che è molto più potente ma richiede sacrifici umani per essere configurato.
AppArmor lavora con i path dei file, non con le etichette degli inode come SELinux. Più semplice, meno flessibile, ma almeno ci capisci qualcosa.
/etc/nginx/"
SELinux: "questo processo con label httpd_t può accedere agli oggetti con label httpd_config_t"
Stesso risultato, SELinux è più robusto (hardlink, bind mount non lo ingannano), AppArmor è più umano.
# Controllare lo stato di AppArmor sudo apparmor_status # oppure sudo aa-status # Output tipico: apparmor module is loaded. 27 profiles are loaded. 27 profiles are in enforce mode. /usr/bin/evince /usr/sbin/nginx ... 0 profiles are in complain mode. 3 processes have profiles defined. 3 processes are in enforce mode. # Verificare se AppArmor è abilitato nel kernel cat /sys/module/apparmor/parameters/enabled Y # I profili di sistema sono qui: ls /etc/apparmor.d/
# Ubuntu/Debian (già installato) sudo apt install apparmor apparmor-utils apparmor-profiles # Tools utili sudo apt install apparmor-profiles-extra # Abilitare all'avvio (se disabilitato) sudo systemctl enable apparmor sudo systemctl start apparmor
# Mettere un profilo in complain sudo aa-complain /usr/sbin/nginx # Mettere in enforce sudo aa-enforce /usr/sbin/nginx # Disabilitare un profilo sudo aa-disable /usr/sbin/nginx # Ricaricare un profilo sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
La sintassi che sembra strana ma si impara in 10 minuti
Un profilo AppArmor ha una struttura semplice: definisci il percorso del binario, poi elenchi cosa può fare. Tutto il resto è negato implicitamente.
| Permesso | Significato | Uso tipico |
|---|---|---|
r | read | Leggere file di configurazione |
w | write | Scrivere log, state files |
a | append | Append-only ai log |
x | execute (con profilo) | Eseguire altri binari con profilo proprio |
ux | execute (unconfined) | Eseguire senza profilo — usare con parsimonia |
ix | execute (inherit) | Il figlio eredita il profilo del padre |
m | mmap executable | Librerie condivise |
l | link | Creare hard link |
k | lock | File locking |
Le abstractions sono include predefinite per scenari comuni. Invece di elencare ogni singola libreria di sistema, includi <abstractions/base> e hai fatto.
| Abstraction | Cosa include |
|---|---|
abstractions/base | libc, libpthread, librerie C fondamentali |
abstractions/nameservice | DNS, NSS, /etc/hosts, /etc/resolv.conf |
abstractions/ssl_certs | Lettura certificati CA di sistema |
abstractions/python | Python runtime e librerie |
abstractions/web-data | Directory web standard |
abstractions/user-tmp | Accesso a /tmp con pattern sicuri |
# Glob patterns nei profili /etc/myapp/ r, # solo la directory /etc/myapp/* r, # tutti i file diretti (non ricorsivo) /etc/myapp/** r, # ricorsivo: tutti i file e subdirectory /var/log/**.log rw, # tutti i .log ricorsivamente @{HOME}/.config/ r, # variabile: espande la home dell'utente
Come creare un profilo senza indovinare
aa-genprof mette il processo in complain mode, lo avvii, lo usi normalmente, poi scansiona i log e genera le regole. Funziona bene per applicazioni con comportamento prevedibile.
# Passo 1: avvia il generatore di profilo sudo aa-genprof /usr/sbin/nginx # L'output ti chiederà di avviare l'app in un altro terminale e usarla Please start the application to be profiled in another window and exercise its functionality now. Once completed, select the (S)can option below in order to scan the system logs for AppArmor events. [(S)can system log for AppArmor events] / (F)inish # In un altro terminale: avvia e usa nginx sudo systemctl start nginx curl http://localhost/ curl http://localhost/api/test # Torna a aa-genprof, premi S per scansionare i log # Per ogni accesso negato, scegli cosa fare: (A)llow / (D)eny / (I)gnore / (G)lob / (Q)uit
Se hai già un profilo e l'app ha generato nuove violazioni nei log, aa-logprof legge /var/log/syslog (o journalctl) e propone aggiornamenti.
# Analizzare i log per proporre aggiornamenti sudo aa-logprof # Oppure da un file di log specifico sudo aa-logprof -f /var/log/syslog # Vedere le violazioni in tempo reale sudo journalctl -f | grep -i apparmor # Formato del log di violazione: kernel: audit: type=1400 audit(1234567890.123:456): apparmor="DENIED" operation="open" profile="/usr/sbin/nginx" name="/etc/ssl/private/nginx.key" pid=1234 comm="nginx" requested_mask="r" denied_mask="r" # Traduzione: nginx ha cercato di leggere la chiave SSL e AppArmor l'ha bloccato # Soluzione: aggiungere /etc/ssl/private/nginx.key r, al profilo
aa-genprofaa-logprof/** su directory sensibili (troppo largo)ux su ogni exec (equivale a non avere il profilo)I poteri di root, venduti al dettaglio
Prima delle capabilities, se un programma aveva bisogno di fare cose privilegiate (aprire una porta <1024, cambiare UID, etc.) l'unico modo era SUID root: il binario gira come root anche se lo avvia un utente normale.
Il problema è ovvio: se quel binario ha un bug, l'attaccante ha una shell root. Ping, su, sudo, passwd — tutti storicamente SUID root. Le capabilities dividono i ~40 poteri di root in privilegi separati. Dai solo quello che serve.
find / -perm -4000 -type f 2>/dev/null
Se trovi qualcosa che non riconosci, indaga.
# Vedere le capability di un processo cat /proc/$(pgrep nginx)/status | grep Cap CapInh: 0000000000000000 CapPrm: 0000000000000400 CapEff: 0000000000000400 CapBnd: 000001ffffffffff CapAmb: 0000000000000000 # Decodificare il valore hex in nomi leggibili capsh --decode=0000000000000400 0x0000000000000400=cap_net_bind_service # Vedere le capability del processo corrente capsh --print
Assegnare capabilities senza SUID
Le capabilities possono essere assegnate ai file binari invece che tramite SUID. Il processo parte senza essere root ma ottiene solo i privilegi specifici che gli servono.
# Sintassi: setcap <capability>=<set> <file> # Set: e=effective, p=permitted, i=inheritable # Esempio classico: nginx su porta 80 senza root sudo setcap cap_net_bind_service=ep /usr/sbin/nginx # Verificare le capability impostate getcap /usr/sbin/nginx /usr/sbin/nginx cap_net_bind_service=ep # Ping senza SUID (moderno) getcap /bin/ping /bin/ping cap_net_raw=ep # Rimuovere tutte le capability da un binario sudo setcap -r /usr/sbin/nginx # Vedere tutte le capability nel sistema getcap -r / 2>/dev/null
Per i servizi systemd, puoi assegnare capabilities direttamente nell'unit file senza toccare il binario:
# /etc/systemd/system/myapp.service [Unit] Description=My App [Service] User=myapp Group=myapp ExecStart=/usr/bin/myapp # Aggiungi solo le capability necessarie AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE # Hardening aggiuntivo NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=strict ProtectHome=yes ReadWritePaths=/var/lib/myapp [Install] WantedBy=multi-user.target
NoNewPrivileges, PrivateTmp, ProtectSystem, ProtectHome costano zero effort e danno un sacco. Mettile sempre nei tuoi unit file. systemd-analyze security myapp.service ti dice il punteggio di sicurezza.
Filtra le syscall. Perché nginx non ha bisogno di reboot(2).
seccomp (secure computing mode) filtra le syscall che un processo può invocare. Ogni processo Linux comunica col kernel tramite ~400 syscall. Un web server ne usa forse 50. Perché dovrebbe poter chiamare reboot(), ptrace(), o init_module()?
seccomp permette di creare una allowlist o denylist di syscall. Se un processo compromesso prova a chiamare una syscall non autorizzata, viene ucciso con SIGKILL.
reboot, mount, ptrace, kexec_load). Puoi vederne il profilo di default su GitHub in moby/moby.
# seccomp con systemd (modo più semplice) [Service] # Allowlist per tipo di servizio SystemCallFilter=@system-service # preset per servizi generici SystemCallFilter=@network-io # syscall di rete SystemCallFilter=~@privileged # ~ = nega queste SystemCallFilter=~@mount # nega le syscall di mount # Preset disponibili: # @basic-io, @file-system, @io-event, @ipc, @keyring # @memlock, @module, @mount, @network-io, @obsolete # @privileged, @process, @raw-io, @reboot, @setuid # @signal, @swap, @system-service, @timer # Vedere le syscall in un preset systemd-analyze syscall-filter @privileged
# Profilo seccomp JSON per Docker (estratto) { "defaultAction": "SCMP_ACT_ERRNO", "syscalls": [ { "names": ["read", "write", "open", "close", "stat", "fstat", "lstat", "poll", "lseek", "mmap", "accept", "connect", "send", "recv" /* ... */], "action": "SCMP_ACT_ALLOW" } ] } # Applicare un profilo custom a un container docker run --security-opt seccomp=/path/to/profile.json myimage # Disabilitare seccomp (per debug, mai in produzione) docker run --security-opt seccomp=unconfined myimage
Per creare un profilo seccomp preciso, prima scopri quali syscall usa il tuo processo:
strace -c -f -S name nginx 2>&1 | tail -20 # Output: statistiche per syscall % time seconds usecs/call calls syscall 32.54 0.001234 12 100 read 28.12 0.001067 10 102 write 8.45 0.000321 8 40 epoll_wait ...
SystemCallFilterPerché --privileged è la risposta sbagliata a ogni domanda
docker run --privileged disabilita AppArmor, seccomp, e dà tutte le capabilities al container. Un container privilegiato può montare il filesystem dell'host, caricare moduli kernel, e fare praticamente tutto.
Se qualcuno ti dice di usarlo "per farlo funzionare", è la soluzione sbagliata. Trova qual è la capability specifica di cui ha bisogno.
# Vedere le capability di un container in esecuzione docker inspect mycontainer | jq '.[0].HostConfig.CapAdd' # Aggiungere solo la capability necessaria docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx # Container con profilo AppArmor custom docker run --security-opt apparmor=my-nginx-profile nginx # Vedere il profilo AppArmor di un container attivo docker inspect mycontainer | jq '.[0].HostConfig.SecurityOpt'
docker-default)securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
seccompProfile:
type: RuntimeDefault
Il modo più sicuro: fai girare il container daemon stesso come utente non-root. Podman lo fa di default. Docker richiede configurazione esplicita. Se il container runtime viene compromesso, non ha accesso root all'host.
# Podman: rootless di default podman run nginx # gira come utente normale # Docker rootless setup dockerd-rootless-setuptool.sh install export DOCKER_HOST=unix:///run/user/1000/docker.sock docker run nginx
Quando qualcosa non parte e i log non ti dicono niente di utile
AppArmor logga tutto in syslog/journald e nel log di audit. Quando qualcosa non funziona, è lì che guardi prima.
# Log in tempo reale sudo journalctl -f -k | grep -i apparmor # Tutte le violazioni di oggi sudo journalctl --since today | grep -i "apparmor.*denied" # Log di audit (se auditd è installato) sudo ausearch -m avc -ts recent # Anatomia di una violazione: apparmor="DENIED" # azione intrapresa operation="open" # cosa stava facendo profile="/usr/sbin/nginx" # quale profilo ha negato name="/etc/ssl/private/cert.key" # cosa voleva accedere pid=12345 # processo comm="nginx" # nome del comando requested_mask="r" # permesso richiesto denied_mask="r" # permesso negato
# 1. Controlla lo stato del profilo sudo aa-status | grep myapp # 2. Metti temporaneamente in complain sudo aa-complain /usr/bin/myapp # 3. Avvia l'app e guarda i log sudo journalctl -f | grep apparmor & sudo systemctl start myapp # 4. Aggiorna il profilo con aa-logprof sudo aa-logprof # 5. Torna in enforce sudo aa-enforce /usr/bin/myapp
# Errore "Operation not permitted" # potrebbe essere una capability mancante # Traccia le syscall fallite strace -e trace=all myapp 2>&1 | grep EPERM # Verifica le capability del processo cat /proc/$(pgrep myapp)/status | grep Cap capsh --decode=<hex-value> # Controlla se serve una capability specifica man 7 capabilities | grep -A3 "CAP_NET_BIND"
Profili completi per i servizi che usi davvero
# Unit file systemd con hardening completo # /etc/systemd/system/myapp.service [Unit] Description=My FastAPI App After=network.target [Service] Type=exec User=myapp Group=myapp WorkingDirectory=/opt/myapp ExecStart=/opt/myapp/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8080 # AppArmor profile (se definito) AppArmorProfile=opt.myapp.run # Capabilities AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE NoNewPrivileges=yes # Filesystem isolation PrivateTmp=yes PrivateDevices=yes ProtectSystem=strict ProtectHome=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes ReadWritePaths=/var/lib/myapp /var/log/myapp # Syscall filter SystemCallFilter=@system-service @network-io SystemCallFilter=~@privileged ~@mount ~@reboot SystemCallArchitectures=native [Install] WantedBy=multi-user.target
systemd-analyze security myapp.service. Ti dà un punteggio da 0 (massima sicurezza) a 10 (inutile). Punta sotto il 3.
Scegli la tua religione
| Feature | AppArmor | SELinux |
|---|---|---|
| Modello | Path-based | Label-based (inode) |
| Curva di apprendimento | Ragionevole | Ripida |
| Bypass con hardlink | Possibile | No |
| Bypass con bind mount | Possibile | No |
| Profili predefiniti | Molti, facili da leggere | Molto completi, complessi |
| Strumenti | aa-genprof, aa-logprof | audit2allow, semanage, restorecon |
| Dove è default | Ubuntu, Debian, SUSE | RHEL, Fedora, CentOS |
| Gestione container | Profili per immagine | Tipi/label per container |
Non è setenforce 0. Non è SELINUX=disabled in /etc/selinux/config. È audit2allow per capire cosa vuole il processo, e aggiungere la policy corretta.
# Vedere cosa ha bloccato SELinux sudo ausearch -m avc -ts recent # Generare la policy da aggiungere sudo ausearch -m avc -ts recent | audit2allow -M mypolicy sudo semodule -i mypolicy.pp # Verificare il contesto SELinux di un file ls -Z /etc/nginx/nginx.conf system_u:object_r:httpd_config_t:s0 /etc/nginx/nginx.conf # Ripristinare il contesto corretto sudo restorecon -Rv /etc/nginx/
Comandi che usi ogni giorno, in un posto solo
# Status sudo aa-status sudo apparmor_status # Modalità sudo aa-enforce /path/to/binary sudo aa-complain /path/to/binary sudo aa-disable /path/to/binary # Profili sudo aa-genprof /path/to/binary sudo aa-logprof sudo apparmor_parser -r /etc/apparmor.d/profilo sudo apparmor_parser -R /etc/apparmor.d/profilo # Log sudo journalctl -f | grep apparmor sudo dmesg | grep apparmor
# Leggere getcap /path/to/binary getcap -r / 2>/dev/null cat /proc/PID/status | grep Cap capsh --print capsh --decode=HEX # Impostare sudo setcap cap_net_bind_service=ep /bin/myapp sudo setcap -r /bin/myapp # SUID check find / -perm -4000 -type f 2>/dev/null find / -perm -2000 -type f 2>/dev/null
# Analisi sicurezza unit systemd-analyze security myapp.service # Syscall di un preset systemd-analyze syscall-filter @privileged # Docker security docker run --cap-drop ALL --cap-add NET_BIND_SERVICE img docker run --security-opt apparmor=profile img docker run --security-opt seccomp=profile.json img docker inspect container | jq '.[0].HostConfig'
# Syscall usate da un processo strace -c -f -S name /usr/bin/myapp 2>&1 # Permessi negati SELinux sudo ausearch -m avc -ts recent sudo audit2allow -a -M mypol sudo semodule -i mypol.pp # Seccomp violations (bpf) sudo dmesg | grep "seccomp" sudo journalctl | grep "SECCOMP"
/usr/share/doc/apparmor/ — documentazione locale/etc/apparmor.d/ — profili di sistema come riferimentoman apparmor.d — sintassi completa dei profiliman 7 capabilities — lista completa delle capability Linuxman 2 seccomp — interfaccia seccomp del kernelsystemd.exec(5) — tutte le direttive di hardening systemd/etc/apparmor.d/.
Firewall, VPN e certificati nello stesso posto.