NET_ADMIN è la capacità di Linux che consente a un processo di svolgere amministrazione di rete, e in un container fa esattamente questo nello spazio dei nomi di rete in cui il processo viene eseguito. Non è una licenza per aprire un socket e non è un sostituto di un dispositivo o di una rotta. La domanda interessante non è mai se la capacità sia concessa, ma quale spazio dei nomi riceva la modifica.

Cosa copre la capacità

Nel proprio spazio dei nomi, un processo che detiene NET_ADMIN può alzare e abbassare interfacce, assegnare indirizzi, aggiungere e rimuovere rotte e scrivere regole del firewall, cioè il gruppo di operazioni che capabilities(7) raccoglie sotto questa capacità. Può anche regolare impostazioni a livello di interfaccia che controllano come il kernel accoda e filtra il traffico. Un client VPN di solito ne ha bisogno di alcune all’avvio e di nient’altro dopo. Per questo la maggior parte delle immagini chiede più portata di quella che usa.

Le operazioni fuori da quello spazio dei nomi non sono coperte, e i privilegi adiacenti sono separati. La lettura e la scrittura di pacchetti grezzi appartiene a NET_RAW, e creare un dispositivo tun richiede sia la capacità sia l’accesso al nodo del dispositivo. Un processo privo del dispositivo riceve un errore di autorizzazione che sembra un problema di rete e non lo è.

Lo spazio dei nomi decide il raggio d’azione

Un container sulla propria rete bridge raggiunge il proprio loopback, le proprie interfacce e la propria tabella di routing. Concedere lì NET_ADMIN consente al client VPN di costruire un tunnel senza toccare le decisioni di routing dell’host, che è la disposizione che la maggior parte delle immagini container presuppone.

Sposta quel container sulla rete dell’host e la stessa concessione ora modifica le rotte dell’host e il firewall dell’host, perché non resta alcuno spazio dei nomi separato. Il client che doveva gestire un solo tunnel può allora reindirizzare il traffico di tutta la macchina, e un errore nel suo script di smontaggio lascia all’host regole che nessuno ha scritto. Nulla nella capacità è cambiato; è cambiato il contesto. Ecco perché lo stesso file compose si comporta diversamente su due host.

Tre prerequisiti fusi in uno

I guasti all’avvio nei container VPN riguardano di solito una di tre cose separate: la capacità, il dispositivo del tunnel e il supporto del kernel per il protocollo. Concederle tutte e tre insieme funziona, e rende anche impossibile attribuire il guasto successivo. Un frammento compose minimo concede la capacità e passa esplicitamente il nodo del dispositivo:

services:
  vpn:
    image: example/vpn-client:latest
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1

Leggi la documentazione dell’immagine per sapere quali sysctl le servono, perché un sysctl in un container può richiedere un privilegio aggiuntivo o un’impostazione a livello di host. Dove l’immagine supporta solo la modalità spazio utente, non serve alcun dispositivo e l’elenco delle capacità si riduce di conseguenza.

Concedi l’operazione ristretta, non la modalità

La modalità privilegiata consegna ogni capacità e allo stesso tempo allarga l’accesso ai dispositivi, quindi nasconde quale dei tre prerequisiti mancasse. Rende anche le modalità di guasto del container indistinguibili dalle modifiche dell’host. Preferisci un elenco esplicito anche quando è più lungo da scrivere.

Le distribuzioni rootless e con spazi dei nomi utente cambiano che cosa significa una capacità in pratica, poiché il processo può detenerla solo dentro uno spazio dei nomi mappato. Copiare un elenco di capacità da un esempio rootful a un runtime rootless e aspettarsi la stessa portata è un modo comune di perdere un pomeriggio.

I segreti appartengono a una decisione separata. Detenere NET_ADMIN non dice nulla su quale identità di peer presenti il client, quali credenziali carichi o come vengano ruotate. L’amministrazione di rete non fornisce fiducia.

Verifica la modifica e annota perché esiste la concessione

Conferma che cosa ha ricevuto davvero il container prima di fare debug del tunnel. L’ispezione qui sotto riporta le capacità aggiunte e la modalità di rete, poi mostra come appare l’elenco delle interfacce dall’interno:

docker inspect --format '{{.HostConfig.CapAdd}} {{.HostConfig.NetworkMode}} {{.HostConfig.Privileged}}' vpn
docker exec vpn sh -c 'grep CapEff /proc/self/status'
docker exec vpn ip -br addr
docker exec vpn ip route

Se la capacità è presente e l’elenco delle interfacce è ancora vuoto, il client è fallito prima di poter creare qualcosa, e i log del container sono il posto successivo da guardare. Se il tunnel esiste ma il traffico non fluisce, la tabella di routing dentro il container risponde alla domanda successiva.

Prova anche il percorso di smontaggio, perché un client che scrive regole del firewall può lasciarle dietro. Ferma il container e ricontrolla l’insieme di regole dell’host se viene usata la rete dell’host. Una concessione necessaria solo per due secondi all’avvio non dovrebbe essere descritta nel file compose come un requisito permanente senza una nota che lo spieghi.

Scrivi il motivo accanto a ogni capacità: quale operazione ne aveva bisogno e che cosa falla senza. Quella nota sopravvive agli aggiornamenti dell’immagine, ed è la differenza tra un elenco di privilegi rivisto e uno cresciuto copiando e incollando.

Articoli correlati