Best Practices per l'Indurimento delle Immagini Docker e la Riduzione della Superficie d'Attacco
Indurisci le immagini Docker utilizzando utenti non root, basi più piccole, build multi-stadio, gestione dei segreti e scansioni delle vulnerabilità.
Best Practices per l'Indurimento delle Immagini Docker e la Riduzione della Superficie d'Attacco
L'indurimento delle immagini Docker inizia con una domanda semplice: cosa c'è dentro questa immagine di cui la tua app non ha effettivamente bisogno? Utenti extra, shell, gestori di pacchetti, strumenti di build e segreti trapelati aumentano tutti il danno che un contenitore compromesso può causare.
Utilizza queste pratiche quando scrivi o rivedi un Dockerfile, specialmente prima che un'immagine raggiunga un registro condiviso o un cluster di produzione.
Esegui i Contenitori come Utenti Non Root
Uno dei principi di sicurezza più fondamentali è il principio del minimo privilegio. Per impostazione predefinita, i processi all'interno di un contenitore Docker vengono eseguiti come utente root. Ciò concede loro privilegi estesi, che possono essere sfruttati dagli aggressori se il contenitore viene compromesso. Eseguire la tua applicazione come utente non root riduce drasticamente il potenziale danno che un aggressore può infliggere all'interno del contenitore.
Creazione di un Utente Non Root
Puoi creare un nuovo utente e gruppo all'interno del tuo Dockerfile e poi passare a quell'utente prima di eseguire la tua applicazione.
# Usa un runtime Python ufficiale come immagine padre
FROM python:3.9-slim
# Imposta la directory di lavoro
WORKDIR /app
# Copia il contenuto della directory corrente nel contenitore in /app
COPY . /app
# Installa eventuali pacchetti necessari specificati in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Crea un utente e un gruppo non root
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 --ingroup appgroup appuser
# Passa all'utente non root
USER appuser
# Rendi la porta 80 disponibile al mondo esterno a questo contenitore
EXPOSE 80
# Definisci la variabile d'ambiente
ENV NAME World
# Esegui app.py all'avvio del contenitore
CMD ["python", "app.py"]
Considerazioni per gli Utenti Non Root
- Permessi: Assicurati che l'utente non root abbia i permessi di lettura e scrittura necessari per le directory e i file richiesti dalla tua applicazione. Potresti aver bisogno di usare
chownper impostare la proprietà in modo appropriato. - Binding delle Porte: Gli utenti non root possono tipicamente associarsi solo a porte superiori a 1024. Se la tua applicazione deve associarsi a una porta privilegiata (es. 80 o 443), considera l'utilizzo di un proxy inverso (come Nginx o Traefik) in esecuzione sull'host o all'interno di un altro contenitore con i permessi appropriati, o configura le capacità Linux.
Riduci al Minimo i Pacchetti e le Dipendenze Installati
Ogni pacchetto installato nella tua immagine Docker ne aumenta le dimensioni e, cosa più importante, la sua superficie d'attacco. Ogni pacchetto può avere le proprie vulnerabilità che gli aggressori possono sfruttare. Pertanto, è fondamentale includere solo ciò che è assolutamente necessario.
Best Practices per la Gestione dei Pacchetti:
- Usa Immagini di Base Minime: Considera immagini
slim, distroless o basate su Alpine quando si adattano al tuo runtime. Le immagini più piccole tendono a includere meno pacchetti, ma testa sempre la compatibilità perché Alpine usa musl libc e può comportarsi diversamente dalle immagini Debian o Ubuntu. - Pulisci Dopo l'Installazione: Dopo aver installato i pacchetti, pulisci la cache del gestore di pacchetti o i file temporanei. Questo non solo riduce le dimensioni dell'immagine, ma rimuove anche potenziali aree di staging per gli aggressori.
# Esempio per immagini basate su Debian/Ubuntu RUN apt-get update && apt-get install -y --no-install-recommends some-package && \ rm -rf /var/lib/apt/lists/* # Esempio per immagini basate su Alpine RUN apk add --no-cache some-package - Build Multi-Stadio: Questa è una tecnica potente per mantenere la tua immagine finale snella. Usi uno stadio per costruire la tua applicazione (installando strumenti di build, compilatori, ecc.) e un secondo stadio pulito per copiare solo gli artefatti necessari dallo stadio di build. Questo impedisce che le dipendenze di build finiscano nella tua immagine di produzione.
# --- Stadio di Build --- FROM golang:1.18-alpine AS builder WORKDIR /app COPY . . RUN go build -o myapp # --- Stadio di Produzione --- FROM alpine:latest WORKDIR /app COPY --from=builder /app/myapp . CMD ["./myapp"] - Aggiorna Regolarmente le Dipendenze: Mantieni aggiornate le dipendenze della tua applicazione e le immagini di base per incorporare le patch di sicurezza.
Implementa Health Check Robuste
I health check sono cruciali per monitorare lo stato dei tuoi contenitori. Docker può usarli per determinare se un contenitore funziona correttamente e per riavviare o rimuovere automaticamente i contenitori non sani. Un health check ben definito aiuta a garantire che la tua applicazione non sia solo in esecuzione ma anche reattiva e funzionante come previsto.
Definizione degli Health Check
Un'istruzione HEALTHCHECK nel tuo Dockerfile specifica un comando che Docker eseguirà periodicamente all'interno del contenitore per testarne lo stato. Se il comando esce con uno stato diverso da zero, il contenitore viene considerato non sano.
# Esempio per un'applicazione web
FROM nginx:latest
# ... altre istruzioni ...
# Controlla se il processo Nginx è in esecuzione e in ascolto sulla porta 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:80/ || exit 1
# ... altre istruzioni ...
Best Practices per gli Health Check:
- Mantienili Semplici: Il comando di health check dovrebbe essere leggero e veloce da eseguire. Evita logiche complesse che potrebbero rallentare il controllo o introdurre propri punti di errore.
- Testa le Funzionalità Chiave: Il controllo dovrebbe idealmente testare la funzionalità principale della tua applicazione, non solo se un processo è in esecuzione. Per un server web, questo potrebbe significare verificare se può rispondere a una richiesta HTTP di base.
- Configura
start-period: Per le applicazioni che richiedono tempo per inizializzarsi, usa l'opzionestart-periodper dare loro il tempo di avviarsi prima che gli health check inizino a fallire.
Gestisci in Modo Sicuro Segreti e Dati Sensibili
Non incorporare mai segreti come chiavi API, password o certificati direttamente nel tuo Dockerfile o immagine. Questi segreti diventeranno parte del layer dell'immagine e sono facilmente scopribili. Invece, usa i segreti Docker o le variabili d'ambiente gestite dalla tua piattaforma di orchestrazione (come Kubernetes o Docker Swarm) per le informazioni sensibili.
Segreti Docker in Modalità Swarm
Docker Swarm fornisce un meccanismo nativo per la gestione dei segreti. Puoi creare segreti e montarli come file nei contenitori.
# Crea un segreto
docker secret create my_api_key api_key.txt
# Distribuisci un servizio usando il segreto
docker service create --secret my_api_key my_web_app
Variabili d'Ambiente con Cautela
Sebbene le variabili d'ambiente siano comode, sono anche visibili quando si ispeziona un contenitore in esecuzione (docker inspect). Usale per dati di configurazione non sensibili. Per i dati sensibili, sono preferiti i Segreti Docker o i sistemi di gestione dei segreti esterni.
Usa Tag di Immagine Specifici
Quando fai riferimento a immagini di base o ad altre immagini nel tuo Dockerfile (es. FROM ubuntu:latest), usa sempre tag di versione specifici invece di latest. Usare latest può portare a build imprevedibili, poiché il tag latest può cambiare nel tempo, introducendo potenzialmente modifiche sostanziali o persino vulnerabilità di sicurezza a tua insaputa.
# Evita questo:
# FROM ubuntu:latest
# Preferisci questo:
FROM ubuntu:22.04
Scansiona le Immagini per Vulnerabilità
Scansiona regolarmente le tue immagini Docker per vulnerabilità note. Diversi strumenti possono aiutarti in questo, sia nella tua pipeline CI/CD che nel tuo registro.
Strumenti di Scansione Popolari
- Trivy: Uno scanner di vulnerabilità semplice e completo per contenitori. Scansiona i pacchetti del sistema operativo e le dipendenze dell'applicazione.
trivy image your-image-name:tag - Clair: Uno strumento di analisi statica open-source per rilevare vulnerabilità nelle immagini dei contenitori.
- Docker Scout: Un servizio di Docker che analizza le immagini dei contenitori per vulnerabilità e fornisce raccomandazioni.
Integrare queste scansioni nel tuo processo di build garantisce che tu sia a conoscenza e possa affrontare potenziali problemi di sicurezza prima di distribuire le tue immagini.
Comprendi i Layer delle Immagini
Le immagini Docker sono costruite a layer. Quando apporti una modifica al tuo Dockerfile, viene creato un nuovo layer. Capire come funzionano i layer può aiutarti a ottimizzare il tuo Dockerfile sia per dimensioni che per sicurezza. Posiziona le istruzioni che cambiano meno frequentemente (come l'installazione di pacchetti di base) all'inizio del Dockerfile e le istruzioni che cambiano più frequentemente (come la copia del codice dell'applicazione) più avanti. Questo sfrutta efficacemente la cache di build di Docker e può velocizzare le build.
Più importante per la sicurezza, le informazioni sensibili o le esposizioni accidentali nei layer precedenti possono persistere. Assicurati che eventuali file o comandi sensibili siano gestiti in modo che non rimangano nei layer dell'immagine finale se non sono più necessari.
Rendi l'Indurimento una Routine
Inizia con i Dockerfile che vengono inviati in produzione. Rimuovi gli strumenti di build dalle immagini finali, esegui come utente non root, fissa le immagini di base e scansiona ogni build. Quindi tratta l'indurimento delle immagini come parte della normale revisione del codice, non come una pulizia una tantum.