dockerfile run commands as user

dockerfile run commands as user

Sei ancora lì a lanciare i tuoi container come root perché è la strada più veloce? Smettila. Stai lasciando la porta di casa aperta con un cartello luminoso che invita i malintenzionati a entrare. La gestione dei permessi non è un optional per sistemisti pignoli. È la base. Quando scrivi le tue istruzioni per le immagini, capire come implementare Dockerfile Run Commands As User determina se la tua infrastruttura è un fortino o un castello di carte. Non parlo di teoria astratta. Parlo di evitare che un bug nel tuo codice Python o Node.js diventi un accesso totale al server ospite.

Perché l'utente root è il tuo peggior nemico in produzione

Molti sviluppatori pensano che il container sia una scatola magica isolata dal resto del mondo. Errore. Il kernel è condiviso. Se un processo gira come superuser dentro il container, e per caso riesce a evadere, ha già le chiavi del regno sulla macchina host. Nel 2019 è stata scoperta una vulnerabilità critica, la CVE-2019-5736, che permetteva di sovrascrivere il binario runc dell'host proprio grazie ai privilegi elevati. Chi non aveva configurato correttamente l'utente è rimasto col sedere per terra.

Creare un utente dedicato non serve solo alla sicurezza. Serve alla sanità mentale. Immagina di montare un volume per persistere i dati. Se il processo scrive come root, ti ritroverai con file sull'host che non puoi cancellare o modificare senza sudo. È un incubo logistico. Usare un profilo non privilegiato sistema i permessi alla radice. Non è solo questione di "best practice". È buon senso applicato al codice.

Il mito dell'isolamento perfetto

L'isolamento dei container si basa sui namespace di Linux. Funzionano bene, ma non sono perfetti. C'è sempre un rischio di "container breakout". Se il tuo server web gira come root e subisce un attacco di tipo buffer overflow, l'attaccante ottiene una shell con i massimi poteri. Se invece quel server gira come un povero utente chiamato "appuser" senza privilegi di sudo, l'attaccante rimane bloccato in una gabbia molto più stretta. Il danno resta circoscritto. Si chiama difesa in profondità.

Strategie pratiche per Dockerfile Run Commands As User

Il modo in cui dichiari chi esegue cosa cambia tutto. Di default, se non specifichi nulla, sei root. Per cambiare rotta devi prima creare l'utente e poi dirgli di entrare in azione. Non basta aggiungere una riga a caso. L'ordine conta. Ogni istruzione nel tuo file di configurazione aggiunge uno strato. Se cambi utente troppo presto, non potrai più installare pacchetti o modificare file di sistema che richiedono poteri elevati.

  1. Crea un gruppo e un utente dedicato. Usa ID specifici, tipo 1001, per evitare conflitti con gli utenti dell'host.
  2. Imposta le cartelle di lavoro con i permessi corretti prima di cambiare identità.
  3. Attiva il profilo non privilegiato solo alla fine, prima del comando di avvio.

Questa sequenza garantisce che l'immagine sia costruita con tutti i componenti necessari, ma che l'esecuzione effettiva avvenga in totale sicurezza. Se provi a installare nginx o python-dev dopo aver cambiato profilo, riceverai un bell'errore di permessi negati. Ed è giusto così.

Gestione degli ID utente e di gruppo

Un errore che vedo fare continuamente è non definire esplicitamente l'UID (User ID). Se scrivi solo il nome, Docker assegna il primo disponibile. Ma se il tuo sistema di storage condiviso si aspetta l'utente 1001 e il tuo container gli manda l'utente 1002, nulla funzionerà. Devi essere preciso. Definire l'identità numerica aiuta anche nell'integrazione con sistemi di orchestrazione come Kubernetes, dove puoi definire un SecurityContext basato proprio sugli ID numerici.

Comandi reali e configurazione dei permessi

Passiamo alla pratica. Creare un utente in una distribuzione basata su Debian o Ubuntu è diverso rispetto a farlo su Alpine Linux. Alpine è leggerissima, ma usa strumenti diversi. Su Debian userai useradd. Su Alpine userai adduser. Sembra una banalità, ma sbagliare questo comando blocca la build.

Ecco un esempio concreto per un'app Node.js su base Debian. Prima installi le dipendenze. Poi crei l'utente. Assegni la proprietà della cartella dell'app a quell'utente. Infine, usi l'istruzione specifica per cambiare identità. Se non lo fai, i file creati durante il processo di build resteranno di proprietà del superuser, e il tuo povero processo applicativo non riuscirà a scriverci dentro quando sarà il momento di far girare i file di log o le cache temporanee.

Problemi comuni con le porte basse

C'è un dettaglio che frega quasi tutti all'inizio. I sistemi Linux proteggono le porte sotto la 1024. Solo il superuser può legarsi alla porta 80 o alla 443. Se sposti il tuo processo su un profilo limitato e cerchi di ascoltare sulla porta 80, il container crasha. Hai due strade. La prima è far girare l'app sulla porta 8080 e poi mappare la 80 esterna sulla 8080 interna. La seconda è usare le capabilities di Linux, ma è una configurazione avanzata che raramente serve se gestisci bene il mapping delle porte. Consiglio sempre la prima: è più pulita e meno soggetta a errori di configurazione del kernel.

L'importanza del comando USER nel flusso di build

L'istruzione che cambia il contesto non agisce solo sul comando finale. Influenza tutte le righe successive. Se dopo averla invocata scrivi un comando per creare una cartella, quella cartella apparterrà al nuovo profilo. Questo è utile ma può diventare una trappola se hai bisogno di fare operazioni di manutenzione. Il trucco degli esperti è raggruppare tutte le operazioni che richiedono poteri amministrativi in un unico blocco all'inizio, minimizzando il numero di layer e mantenendo l'immagine snella.

Gestire i volumi e i permessi del file system

Qui le cose si fanno complicate. I volumi sono il punto dove la sicurezza del container incontra la realtà del disco rigido. Se monti una cartella locale dentro un container dove gira un profilo limitato, devi assicurarti che quell'identità abbia i diritti di lettura e scrittura su quella cartella nell'host.

Molte persone risolvono il problema mettendo i permessi a 777. Per favore, non farlo mai. Mettere 777 significa che chiunque sulla macchina può leggere, scrivere ed eseguire quei file. È un suicidio informatico. La soluzione corretta è allineare l'UID del processo nel container con l'UID del proprietario dei file sul server. Se il tuo sviluppatore ha ID 1000, crea l'utente nel container con ID 1000. Semplice ed efficace.

🔗 Leggi di più: adding user to group in linux

Sicurezza e Rootless Docker

Esiste una tecnologia chiamata Rootless Docker che porta tutto questo a un livello superiore. In questo caso, persino il demone del servizio gira senza privilegi elevati. È una manna dal cielo per gli ambienti condivisi o per chi è ossessionato dalla sicurezza. In questo scenario, l'utilizzo corretto di Dockerfile Run Commands As User diventa ancora più naturale perché l'intero ecosistema è progettato per non toccare mai i privilegi di sistema. Nonostante sia maturata molto, questa modalità richiede ancora qualche accortezza per quanto riguarda il networking e il montaggio di determinati filesystem, ma per carichi di lavoro standard è ormai pronta per il grande salto.

Strumenti per verificare la tua configurazione

Non fidarti solo dei tuoi occhi. Esistono strumenti automatici che scansionano le tue immagini alla ricerca di configurazioni pericolose. Hadolint è uno dei migliori linter in circolazione. Se dimentichi di cambiare l'utente, lui te lo urla in faccia. Integrare questo controllo nella tua pipeline di CI/CD è fondamentale.

Un altro tool incredibile è Trivy. Non si limita a guardare il tuo file di configurazione, ma analizza l'intera immagine alla ricerca di vulnerabilità note nei pacchetti installati e ti segnala se il processo principale sta girando come root. Usare questi strumenti ti permette di dormire sonni tranquilli mentre il tuo codice è esposto su internet.

Casi studio di errori fatali

Ricordo un'azienda che gestiva dati sanitari. Avevano un'immagine personalizzata per un database NoSQL. Girava tutto come superuser. Un ricercatore di sicurezza ha trovato una falla nel plugin di amministrazione del database. Grazie ai poteri illimitati del processo, è riuscito a installare un kernel module maligno che intercettava tutto il traffico di rete della macchina host. Se avessero usato un profilo limitato, l'attaccante si sarebbe trovato bloccato in una directory vuota senza permessi per fare nulla di rilevante. Il costo della riparazione è stato enorme. La prevenzione sarebbe costata tre righe di codice.

Ottimizzare le immagini per la produzione

Essere sicuri non significa essere lenti. Un'immagine ben costruita è anche più veloce da scaricare e avviare. Ogni volta che cambi identità, non stai appesantendo il sistema. Stai solo cambiando dei metadati nel file JSON che descrive l'immagine.

  1. Usa immagini di base minimali come distroless o Alpine.
  2. Riduci al minimo i software installati. Meno roba c'è, meno roba può rompersi o essere usata contro di te.
  3. Pulisci la cache dei pacchetti (come /var/lib/apt/lists/*) nello stesso layer in cui installi il software.

Questi passaggi, combinati con una gestione ferrea delle identità, creano un ambiente solido. Non lasciarti scoraggiare se all'inizio i permessi ti sembrano un ostacolo. Ogni "Permission Denied" che ricevi durante lo sviluppo è una potenziale falla che hai chiuso prima che diventasse un problema reale.

Il ruolo delle variabili d'ambiente

Spesso dobbiamo passare segreti o configurazioni tramite variabili d'ambiente. Se il tuo processo gira con poteri elevati, quelle variabili sono più esposte. Anche se l'isolamento dovrebbe proteggerle, limitare chi può leggerle è sempre una mossa saggia. Alcuni servizi leggono le configurazioni da file protetti che solo l'utente specifico può aprire. Questo è il gold standard della configurazione sicura.

Verso un'automazione consapevole

Il futuro della gestione dei container va verso l'astrazione totale, ma le basi restano le stesse. Che tu stia usando Amazon ECS, Google Cloud Run o un cluster on-premise, la responsabilità della sicurezza del processo interno rimane tua. Le piattaforme cloud offrono spesso strumenti per forzare l'esecuzione non privilegiata, ma è meglio che sia l'immagine stessa a essere sicura per design, indipendentemente da dove viene lanciata.

Le autorità come l'Agenzia per la Cybersicurezza Nazionale in Italia sottolineano costantemente l'importanza di ridurre la superficie d'attacco delle applicazioni cloud-native. Seguire queste linee guida non è solo un esercizio tecnico, ma un dovere verso la protezione dei dati degli utenti.

Checklist finale per la messa in sicurezza

Prima di fare il push della tua immagine sul registry, fatti queste domande. C'è un'istruzione che definisce un profilo non root? Gli ID sono definiti numericamente per evitare ambiguità? I file sensibili hanno permessi restrittivi? Se la risposta è sì a tutto, allora sei sulla strada giusta.

  • Verifica che l'utente non abbia una shell valida (usa /sbin/nologin o /bin/false).
  • Controlla che non ci siano file con il bit SUID o SGID settato inutilmente.
  • Testa il container facendolo girare con l'opzione --read-only per vedere se tenta di scrivere in posti non autorizzati.

Implementare correttamente questi concetti richiede un piccolo sforzo iniziale ma paga dividendi enormi in termini di stabilità e protezione. Non aspettare l'incidente per correre ai ripari. Il momento di agire è adesso, partendo da quel piccolo file che definisce come nasce il tuo software.

Passi pratici per agire subito

  1. Apri i tuoi progetti attuali e cerca dove manca la definizione dell'utente.
  2. Inserisci la creazione di un gruppo e un utente con UID 1001 nel blocco iniziale.
  3. Sposta tutti i comandi di installazione e configurazione del sistema prima del cambio di identità.
  4. Usa il comando chown per dare i diritti sulla cartella di lavoro al nuovo utente.
  5. Inserisci il comando per cambiare il contesto appena prima dell'esecuzione dell'applicativo.
  6. Testa l'immagine localmente per assicurarti che non ci siano errori di scrittura sui file di log o sulle cartelle temporanee.
  7. Scansiona il risultato finale con un linter per confermare che non siano rimasti buchi neri nella configurazione.
GS

Gabriele Serra

Gabriele Serra segue i temi più discussi del momento con spirito critico e attenzione all'impatto sociale delle notizie.