Blog

Quali clienti hanno davvero bisogno di una propria VM? Un piano di isolamento Docker a livelli

Una VM per ogni cliente è eccessiva. Ecco come decidere quanta isolamento serve a ogni tenant e automatizzare la decisione.

Sintesi

Le agenzie vanno spesso nel panico quando un cliente chiede quanto siano realmente isolati i propri dati dagli altri tenant. I namespace e i cgroups di Docker offrono un isolamento reale, ma non equivalgono a un confine hardware. Invece di mettere ogni cliente su una VM—o peggio, trattare tutti i clienti allo stesso modo—costruisci un piccolo insieme di livelli di isolamento e abbina ogni cliente a uno di essi in base a sensibilità dei dati, fiducia e conformità. Un container blindato (non-root, capability rimosse, seccomp, root in sola lettura) copre la maggior parte dei siti; i workload regolamentati o ostili richiedono una VM o un ibrido container-in-VM. Questo post offre un flusso decisionale ripetibile, una tabella di confronto e uno sguardo onesto su quando un maggiore isolamento è eccessivo.

Ti sei mai trovato in una chiamata di vendita in cui il nuovo cliente dice: “siamo nel settore sanitario, dimostrami che i nostri dati sono isolati dagli altri tuoi clienti” e preferiresti parlare d’altro?

Questo è il problema delle agenzie: non una singola implementazione perfetta, ma la stessa implementazione affidabile ripetuta su una dozzina di clienti con budget, profili di rischio ed esigenze di conformità diversi. Ecco la versione onesta. L'isolamento di Docker è reale, ma è specifico. I namespace danno a ogni container una propria visione di processi, rete e filesystem; i cgroups limitano CPU, memoria e I/O del disco, così i tenant non possono affamarsi a vicenda. Quello che non ottieni è una barriera hardware tra il container e il kernel dell'host. Se un attaccante esce dal container, si ritrova nell'unico kernel che possiedi. Il resto di questo articolo trasforma questo fatto scomodo in una decisione ripetibile: classifica ogni cliente per sensibilità dei dati e fiducia, applica un profilo di hardening di base e ricorri a una VM solo quando il costo di una violazione è superiore al costo della VM.

Aspetta, i container non sono già isolati?

Docker si basa su namespace e cgroups di Linux, e queste parole fanno un lavoro concreto. I namespace separano gli ID dei processi, gli stack di rete, i punti di montaggio e gli utenti, così un processo in un container non può vedere la tabella dei processi di un altro. I cgroups impostano limiti: dai a un container 0,5 CPU, 512 MB di memoria e un peso fisso per l'I/O del blocco, e otterrà esattamente questo. Un loop fuori controllo in un tenant viene limitato invece di far cadere il vicino. Se non hai configurato i limiti, hai saltato la cosa più basilare per cui esistono i cgroups.

Prendi una semplice app PHP nel container A. Vede il proprio filesystem, la propria interfaccia di rete, il proprio PID 1. Il container B ha lo stesso, ma una visione diversa. Questo sono i namespace. Ora allontanati e salta il limite di memoria: il container A può riempire la RAM dell'host e far strisciare il container B. È per prevenire questo che esistono i cgroups. Ma due container possono essere isolati tra loro dai namespace e condividere comunque il kernel dell'host, ed è proprio questo il punto di ogni storia di fuga dal container. Un exploit che raggiunge il kernel può potenzialmente raggiungere ogni tenant su quell'host.

“Docker è isolato” è una frase a metà. La versione accurata è “Docker isola con namespace e cgroups, e una vulnerabilità del kernel è il raggio d'esplosione.” Prima di affidare a un tenant l'esecuzione di codice non fidato, fermati un minuto a pensare. La risposta non è “non usare mai i container”—quello è il panico facile. La risposta è un sistema a livelli.

Allora perché alcuni clienti hanno bisogno di più dei namespace?

La risposta onesta è che l'isolamento non è un interruttore, ma uno spettro. A un estremo hai un container completamente condiviso dove tutti sono effettivamente in un'unica app. All'altro estremo hai una VM separata per ogni tenant con il proprio kernel. La maggior parte del lavoro delle agenzie vive nel scomodo mezzo, e il mezzo non è una scelta binaria tra “Docker va bene” e “metti una VM per tutti”.

Ciò che spinge un cliente verso destra non è la sua dimensione. Sono quattro domande:

  • Conservano dati regolamentati? Cartelle cliniche, dettagli di carte di pagamento, qualsiasi cosa che un regolatore chiamerebbe sensibile.
  • Una violazione del loro tenant ha un percorso realistico verso un altro tenant? Se possono eseguire codice arbitrario, sì.
  • Ti fidi del codice e delle persone che lo implementano? Un cliente che assume il freelance più economico non ha lo stesso livello di fiducia di un cliente il cui team di sviluppo conosci.
  • Il loro contratto dice “dedicato”, “isolato” o “privato”? Se sì, hai già promesso un livello; l'unico compito ora è scegliere quello giusto.

Se non puoi ancora rispondere a queste domande, metti il cliente in un livello di base e annota le ipotesi. Non è un audit di sicurezza; è un controllo di sanità che ripeti a ogni onboarding.

Come decido per ogni cliente senza eseguire un audit di sicurezza ogni volta?

Crea una piccola tabella e rispettala. Non hai bisogno di una matrice con quaranta celle. Quattro livelli copriranno quasi ogni cliente che un'agenzia vede.

Posizione del clienteCosa li separa davveroDa usare quando
Livello 1: App/container condivisoSolo logica applicativaUtility interne, dati a basso rischio, progetti in cui tutti sono esplicitamente in un unico sistema di login
Livello 2: Stesso host, container separatiNamespace e cgroupsLa maggior parte dei siti marketing, moduli di contatto, nessun dato sensibile
Livello 3: Container blindatoLivello 2 + non-root, capability rimosse, seccomp, root in sola lettura, segmentazione di reteE-commerce, PII, codice personalizzato di cui non ti fidi completamente
Livello 4: VM per tenantHypervisor e kernel separatoSanità, finanza, pratiche di conformità, codice non fidato, vicini rumorosi

Ecco come funziona nella pratica. Un cliente panetteria con un modulo di contatto e un link a Instagram va al Livello 2: un container su un host condiviso, networking Docker predefinito, limiti di risorse, lavoro fatto. Un negozio online che memorizza nomi, indirizzi e reindirizzamenti di pagamento dei clienti va al Livello 3: stesso host condiviso, ma il container gira come utente non-root, non ha capability extra del kernel, usa un profilo seccomp ed espone solo la porta 443. Un portale di accettazione medica che conserva informazioni sanitarie protette va al Livello 4: una VM per tenant, perché il costo di una violazione non è “lo puliremo noi” ma “non possiamo dimostrare al cliente che lo abbiamo preso sul serio”.

Il trucco è che non stai ripensando l'architettura per ogni cliente. Scegli una riga da una tabella su cui hai già concordato. È così che un'agenzia di cinque persone può gestire cento siti senza cento ossessioni di sicurezza separate. Significa anche che il prossimo cliente non riceverà una risposta che dipende da quale membro del team ha risposto al telefono. Per il dibattito architetturale più profondo dietro queste scelte, questa guida alla progettazione dei livelli di isolamento multi-tenant copre i compromessi in dettaglio.

Come appare davvero un container blindato?

Smettiamola di dire “blindato” e diventiamo concreti. Questo è ciò che significa il Livello 3 per un tipico cliente WordPress o PHP.

Prima, cambia utente. La maggior parte delle immagini ufficiali gira ancora come root di default; nel tuo Dockerfile, crea un utente non-root e fai girare l'app come quell'utente. Questo rimuove immediatamente il modo più comune con cui un compromesso del container diventa un compromesso dell'host. Secondo, elimina le capability che non ti servono. Esegui con --cap-drop ALL e aggiungi solo una, di solito NET_BIND_SERVICE, così l'app può ascoltare sulla porta 80. Da solo, questo è un cambiamento più grande di quanto molti si aspettino. Terzo, rendi il filesystem di root in sola lettura con --read-only e monta le directory scrivibili (upload, directory dati del database) come volumi o tmpfs. Quarto, applica un profilo seccomp e, se il tuo host lo supporta, AppArmor o SELinux. Infine, metti il container su una rete Docker dedicata ed esponi solo le porte che devono essere realmente raggiungibili.

Facciamo un esempio con WordPress. L'immagine base probabilmente gira come root, quindi aggiungi un passaggio useradd e una direttiva USER. Esegui il container con un limite di memoria e un limite CPU, così un'esplosione di traffico di plugin non danneggia il vicino. Monti /var/www/html/wp-content/uploads come volume scrivibile. Imposti --read-only. Lo colleghi a una rete che non ha alcun flag --privileged nelle vicinanze. Il risultato è un container che prima era “un sito WordPress” e ora è “un sito WordPress che risulta essere più blindato della maggior parte dei server privati virtuali”.

Se fare tutto a mano ti sembra fragile, c'è un percorso di mezzo più semplice: l'isolamento avanzato dei container di Docker, che utilizza l'isolamento dello user namespace e un runtime container sicuro. È una scorciatoia legittima, ma non è un lasciapassare per saltare non-root o la rimozione delle capability. Il tenant ha comunque bisogno di un'immagine sensata. La differenza è che la superficie d'attacco rivolta al kernel si riduce senza che tu debba diventare un esperto di seccomp da un giorno all'altro. Se vuoi la sequenza esatta per un singolo tenant, la guida passo passo all'hardening dell'isolamento trasforma questa sezione in comandi copia-incolla.

Quando smetto di aggiungere livelli e do loro semplicemente una VM?

Ecco la parte controcorrente: più isolamento non è automaticamente meglio. Le VM ti danno isolamento a livello hardware, un kernel separato e una superficie d'attacco molto più piccola se il kernel ospite cade. È esattamente ciò che i clienti di sanità e finanza si aspettano quando dicono “vogliamo essere isolati”. Ma ogni VM aggiunge costi di patching, backup e calcolo, e moltiplica il lavoro per mantenere aggiornata una flotta. Se metti una VM per ogni cliente perché un cliente una volta ti ha detto che Docker lo spaventava, hai comprato teatro della sicurezza con soldi veri.

Una VM è la risposta giusta quando il rischio per tenant è superiore al costo operativo di una VM per tenant. Questo significa dati regolamentati, requisiti di conformità scritti, codice di terze parti non fidato, o un cliente che vuole eliminare un vicino rumoroso. È anche la risposta giusta quando il contratto del cliente promette letteralmente un ambiente dedicato, perché “container” non è ciò che immaginano quando firmano per “dedicato”.

Ma una VM non giustifica un container sciatto. Una trappola comune è mettere il cliente in una VM e poi saltare l'hardening perché “la VM lo protegge”. La VM protegge l'host dal tenant, non il tenant dalla propria cattiva immagine. Vuoi comunque non-root, capability rimosse e seccomp dentro quella VM. L'approccio ibrido—container dentro una VM—è spesso il punto ottimale: la VM fornisce il confine per le conversazioni di conformità, e il container ti dà il flusso di lavoro di deployment che già conosci. C'è una versione più lunga di quel dibattito in Dovrebbe ogni tenant avere la propria VM?, ma la risposta breve è che la VM serve al contratto, non alla paura.

Come rendo tutto questo ripetibile per ogni cliente?

Lo rendi ripetibile trasformando il sistema a livelli in un modello, non in un ricordo. Tieni una directory di file Compose, uno per livello: tier2-baseline, tier3-locked, tier4-vm-hybrid. Quando arriva un nuovo cliente, copia il modello, modifica le variabili d'ambiente e conosci già la forma dell'isolamento prima di scrivere una riga di nuova infrastruttura.

Poi scrivi la decisione. Non un rapporto di sicurezza di 400 pagine, ma un breve paragrafo nel repository del cliente: quali dati conservano, a quale livello sono, perché e cosa li sposterebbe a un livello superiore. Quel paragrafo vale più di cento regole firewall, perché è la cosa che puoi mostrare al prossimo revisore o al prossimo cliente preoccupato. Ti evita anche di dover ricordare perché la panetteria ha ricevuto il Livello 2 e il negozio e-commerce il Livello 3 dopo che la chiamata di vendita originale è svanita.

Automatizza i controlli noiosi. Fai sì che la tua CI analizzi ogni immagine del cliente e faccia fallire una build se gira come root, se ha tutte le capability, o se cerca di pubblicare una porta diversa da quelle consentite dal livello. Niente di tutto ciò è esotico; serve solo a garantire che il modello non venga accidentalmente rotto da uno sviluppatore ben intenzionato. Se stai comunque costruendo il flusso di lavoro di hosting circostante, il pezzo sulle strategie di hosting Docker pronte per la produzione copre la parte che viene dopo la definizione dei container.

Niente di tutto questo è affascinante. Nessun post del blog renderà “isolamento dei tenant” eccitante come un diagramma architetturale greenfield. Ma questa è la differenza tra un'agenzia che risponde “quanto siamo isolati?” con un “totalmente” incrociando le dita e una che può mostrare un livello, una configurazione e una ragione. I container non sono una parete magica. Le VM non sono un proiettile magico. Un sistema a livelli è solo una decisione che scrivi e riutilizzi—e per un'agenzia, ripetibile è il vero gioco.

Sources (5)