Migliori pratiche per l'hardening delle immagini Docker e la riduzione della superficie di attacco

Migliora la sicurezza di Docker con le migliori pratiche per l'hardening delle immagini. Impara a eseguire i container come utenti non root, a minimizzare la superficie di attacco riducendo i pacchetti, a implementare controlli di integrità (health checks) efficaci, a gestire i segreti in modo sicuro e a sfruttare le build multi-stadio (multi-stage builds). Questa guida fornisce passaggi attuabili ed esempi per creare immagini Docker più sicure e resilienti, riducendo i rischi di vulnerabilità nei tuoi deployment.

36 visualizzazioni

Migliori pratiche per l'hardening delle immagini Docker e la riduzione della superficie di attacco

Docker ha rivoluzionato il deployment delle applicazioni consentendo agli sviluppatori di impacchettare applicazioni e le loro dipendenze in container portatili e autosufficienti. Tuttavia, la facilità d'uso può talvolta mettere in secondo piano l'importanza critica della sicurezza. L'hardening delle immagini Docker è fondamentale per minimizzare la superficie di attacco e proteggere le vostre applicazioni e infrastrutture da potenziali minacce. Questo articolo delinea le migliori pratiche essenziali per la messa in sicurezza dei vostri Dockerfile, la costruzione di container più robusti e la riduzione del rischio complessivo associato ai deployment containerizzati.

Adottando queste pratiche, potete migliorare significativamente la postura di sicurezza delle vostre immagini Docker, rendendole più resilienti allo sfruttamento e garantendo un ambiente di deployment più sicuro. Approfondiremo tecniche come l'esecuzione di container con privilegi minimi, l'implementazione di controlli di integrità efficaci e l'ottimizzazione delle dimensioni delle immagini per ridurre il potenziale di vulnerabilità.

1. Eseguire i container 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 container Docker vengono eseguiti come utente root. Questo concede loro ampi privilegi, che possono essere sfruttati dagli aggressori se il container viene compromesso. L'esecuzione dell'applicazione come utente non-root riduce drasticamente il potenziale danno che un aggressore può infliggere all'interno del container.

Creazione di un utente non-root

È possibile creare un nuovo utente e gruppo all'interno del Dockerfile e quindi passare a tale utente prima di eseguire l'applicazione.

# Use an official Python runtime as a parent image
FROM python:3.9-slim

# Set the working directory
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Create a non-root user and group
RUN addgroup --system --gid 1001 appgroup && \n    adduser --system --uid 1001 --ingroup appgroup appuser

# Switch to the non-root user
USER appuser

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

Considerazioni per gli utenti non-root

  • Permessi: Assicurarsi che l'utente non-root abbia i permessi di lettura e scrittura necessari per le directory e i file richiesti dall'applicazione. Potrebbe essere necessario utilizzare chown per impostare correttamente la proprietà.
  • Associazione di porte: Gli utenti non-root possono tipicamente associare solo porte superiori a 1024. Se l'applicazione deve associare una porta privilegiata (ad esempio, 80 o 443), considerare l'uso di un proxy inverso (come Nginx o Traefik) in esecuzione sull'host o all'interno di un altro container con i permessi appropriati, oppure configurare le capacità Linux.

2. Minimizzare i pacchetti installati e le dipendenze

Ogni pacchetto installato nella vostra immagine Docker ne aumenta le dimensioni e, cosa più importante, la sua superficie di attacco. Ogni pacchetto può avere le proprie vulnerabilità che gli aggressori possono sfruttare. Pertanto, è fondamentale includere solo ciò che è assolutamente necessario.

Migliori pratiche per la gestione dei pacchetti:

  • Usare immagini base minimali: Optare per varianti slim o alpine delle immagini base ogni volta che è possibile. Queste immagini contengono solo i componenti essenziali necessari per eseguire l'applicazione, riducendo significativamente la superficie di attacco. Ad esempio, python:3.9-slim è più piccola e sicura di python:3.9.
  • Pulire dopo l'installazione: Dopo l'installazione dei pacchetti, pulire la cache del gestore pacchetti o i file temporanei. Questo non solo riduce le dimensioni dell'immagine, ma rimuove anche potenziali aree di staging per gli aggressori.
    ```dockerfile
    # Example for Debian/Ubuntu based images
    RUN apt-get update && apt-get install -y --no-install-recommends some-package && \n rm -rf /var/lib/apt/lists/*

    Example for Alpine based images

    RUN apk add --no-cache some-package
    * **Build multi-stage:** Questa è una tecnica potente per mantenere la vostra immagine finale snella. Utilizzate una fase per costruire la vostra applicazione (installando strumenti di compilazione, compilatori, ecc.) e una seconda fase pulita per copiare solo gli artefatti necessari dalla fase di build. Questo impedisce che le dipendenze di build finiscano nell'immagine di produzione.dockerfile

    --- Build Stage ---

    FROM golang:1.18-alpine AS builder
    WORKDIR /app
    COPY . .
    RUN go build -o myapp

    --- Production Stage ---

    FROM alpine:latest
    WORKDIR /app
    COPY --from=builder /app/myapp .
    CMD ["./myapp"]
    ```
    * Aggiornare regolarmente le dipendenze: Mantenere aggiornate le dipendenze dell'applicazione e le immagini base per incorporare le patch di sicurezza.

3. Implementare controlli di integrità robusti

I controlli di integrità sono cruciali per monitorare lo stato dei vostri container. Docker può utilizzare questi controlli per determinare se un container è in esecuzione correttamente e per riavviare o rimuovere automaticamente i container non integri. Un controllo di integrità ben definito aiuta a garantire che l'applicazione non sia solo in esecuzione, ma anche reattiva e funzionante come previsto.

Definizione dei controlli di integrità:

Un'istruzione HEALTHCHECK nel vostro Dockerfile specifica un comando che Docker eseguirà periodicamente all'interno del container per testarne l'integrità. Se il comando termina con uno stato diverso da zero, il container è considerato non integro.

# Example for a web application
FROM nginx:latest

# ... other instructions ...

# Check if the Nginx process is running and listening on port 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \n  CMD curl -f http://localhost:80/ || exit 1

# ... other instructions ...

Migliori pratiche per i controlli di integrità:

  • Mantenerli semplici: Il comando del controllo di integrità dovrebbe essere leggero e veloce da eseguire. Evitare logiche complesse che potrebbero rallentare il controllo o introdurre propri punti di fallimento.
  • Testare le funzionalità chiave: Il controllo dovrebbe idealmente testare le funzionalità principali della vostra applicazione, non solo se un processo è in esecuzione. Per un server web, questo potrebbe significare verificare se è in grado di rispondere a una richiesta HTTP di base.
  • Configurare start-period: Per le applicazioni che richiedono tempo per l'inizializzazione, utilizzare l'opzione start-period per dare loro il tempo di avviarsi prima che i controlli di integrità inizino a fallire.

4. Gestire in modo sicuro segreti e dati sensibili

Non incorporare mai segreti come chiavi API, password o certificati direttamente nel Dockerfile o nell'immagine. Questi segreti diventeranno parte dello strato dell'immagine e saranno facilmente scopribili. Utilizzare invece i segreti Docker o le variabili d'ambiente gestite dalla vostra piattaforma di orchestrazione (come Kubernetes o Docker Swarm) per le informazioni sensibili.

Segreti Docker (Modalità Swarm):

Docker Swarm fornisce un meccanismo nativo per la gestione dei segreti. È possibile creare segreti e montarli come file nei container.

# Create a secret
docker secret create my_api_key api_key.txt

# Deploy a service using the secret
docker service create --secret my_api_key my_web_app

Variabili d'ambiente (con cautela):

Sebbene le variabili d'ambiente siano convenienti, sono anche visibili quando si ispeziona un container in esecuzione (docker inspect). Utilizzatele per dati di configurazione non sensibili. Per i dati sensibili, sono preferibili i Segreti Docker o sistemi di gestione dei segreti esterni.

5. Utilizzare tag di immagine specifici

Quando si fa riferimento a immagini base o ad altre immagini nel Dockerfile (ad esempio, FROM ubuntu:latest), utilizzare sempre tag di versione specifici anziché latest. L'uso di latest può portare a build imprevedibili, poiché il tag latest può cambiare nel tempo, introducendo potenzialmente modifiche importanti o persino vulnerabilità di sicurezza a vostra insaputa.

# Avoid this:
# FROM ubuntu:latest

# Prefer this:
FROM ubuntu:22.04

6. Scansionare le immagini alla ricerca di vulnerabilità

Scansionate regolarmente le vostre immagini Docker alla ricerca di vulnerabilità note. Diversi strumenti possono aiutarvi in questo, sia nella vostra pipeline CI/CD che nel vostro registry.

Strumenti di scansione popolari:

  • Trivy: Uno scanner di vulnerabilità semplice e completo per container. Scansiona pacchetti OS e dipendenze delle applicazioni.
    bash trivy image your-image-name:tag
  • Clair: Uno strumento di analisi statica open-source per rilevare vulnerabilità nelle immagini container.
  • Docker Scout: Un servizio di Docker che analizza le immagini container alla ricerca di vulnerabilità e fornisce raccomandazioni.

L'integrazione di queste scansioni nel vostro processo di build assicura che siate consapevoli e possiate affrontare potenziali problemi di sicurezza prima di distribuire le vostre immagini.

7. Comprendere gli strati delle immagini

Le immagini Docker sono costruite a strati. Quando si apporta una modifica al Dockerfile, viene creato un nuovo strato. Comprendere come funzionano gli strati può aiutarvi a ottimizzare il Dockerfile sia per le dimensioni che per la sicurezza. Posizionate le istruzioni che cambiano meno frequentemente (come l'installazione di pacchetti base) prima nel Dockerfile, e le istruzioni che cambiano più frequentemente (come la copia del codice dell'applicazione) dopo. Questo sfrutta efficacemente la cache di build di Docker e può accelerare le build.

Ancora più importante per la sicurezza, informazioni sensibili o esposizioni accidentali negli strati precedenti possono persistere. Assicurarsi che qualsiasi file o comando sensibile sia gestito in modo tale da non rimanere negli strati finali dell'immagine se non sono più necessari.

Conclusione

L'hardening delle immagini Docker è un processo continuo che richiede attenzione ai dettagli e l'adesione alle migliori pratiche di sicurezza. Eseguendo i container come utenti non-root, minimizzando le dipendenze, implementando controlli di integrità robusti, gestendo in modo sicuro i segreti, utilizzando tag di immagine specifici e scansionando regolarmente le vulnerabilità, è possibile ridurre significativamente la superficie di attacco delle vostre applicazioni containerizzate. Queste pratiche non riguardano solo la conformità; sono fondamentali per la costruzione di sistemi software sicuri, affidabili e resilienti nell'era dei container.

Iniziate esaminando i vostri Dockerfile esistenti e implementando queste raccomandazioni in modo incrementale. La vostra postura di sicurezza ve ne sarà grata.