
Ciao, Habr!
Nella realtà moderna, a causa del crescente ruolo della containerizzazione nei processi di sviluppo, la questione della sicurezza delle diverse fasi e delle entità legate ai container non è di secondaria importanza. Effettuare controlli manuali è un compito laborioso, quindi sarebbe utile intraprendere almeno i primi passi verso l'automazione di questo processo.
In questo articolo condividerò script pronti per l'implementazione di alcune utility per la sicurezza di Docker e un'istruzione su come allestire un piccolo ambiente dimostrativo per testare questo processo. I materiali possono essere utilizzati per sperimentare come organizzare il processo di test della sicurezza delle immagini e delle istruzioni Dockerfile. È chiaro che l'infrastruttura di sviluppo e implementazione varia da un caso all'altro, quindi di seguito presenterò alcune possibili opzioni.
Utility di verifica della sicurezza
Esistono molte app e script ausiliari diversi che eseguono controlli su vari aspetti dell'infrastruttura Docker. Alcune di esse sono già state descritte in un articolo precedente (), mentre in questo materiale mi vorrei concentrare su tre di esse, che coprono la maggior parte dei requisiti di sicurezza delle immagini Docker costruite durante il processo di sviluppo. Inoltre, mostrerò un esempio di come queste tre utility possano essere collegate in un unico pipeline per effettuare controlli di sicurezza.
Hadolint
Un'utilità da console piuttosto semplice che aiuta a valutare in una prima analisi la correttezza e la sicurezza delle istruzioni dei Dockerfile (ad esempio, l'utilizzo solo di registri di immagini autorizzati o l'uso di sudo).

Dockle
Un'utilità da console che lavora con un'immagine (o con un archivio tar salvato dell'immagine), che verifica la correttezza e la sicurezza di un'immagine specifica, analizzando i suoi strati e la configurazione: quali utenti sono stati creati, quali istruzioni vengono utilizzate, quali volumi sono collegati, la presenza di password vuote, ecc. Al momento, il numero di controlli non è molto elevato ed è basato su alcune verifiche e raccomandazioni interne. per Docker.

Trivy
Questa utility è progettata per individuare vulnerabilità di due tipi: problemi di assemblaggio del sistema operativo (supportati Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) e problemi nelle dipendenze (Gemfile.lock, Pipfile.lock, composer.lock, package-lock.json, yarn.lock, Cargo.lock). Trivy è in grado di eseguire la scansione sia di un'immagine in un repository che di un'immagine locale, oltre a condurre scansioni basate su un file .tar passato contenente un'immagine Docker.

Opzioni di integrazione delle utility
Per provare le applicazioni descritte in condizioni isolate, fornirò istruzioni per installare tutte le utility nel contesto di un processo semplificato.
L'idea principale è dimostrare come sia possibile implementare un controllo automatico del contenuto di Dockerfile e delle immagini Docker che vengono create durante il processo di sviluppo.
La verifica consiste nei seguenti passaggi:
- Verifica della correttezza e della sicurezza delle istruzioni Dockerfile tramite uno strumento di linting. Hadolint
- Verifica della correttezza e della sicurezza delle immagini finali e intermedie tramite uno strumento. Dockle
- Verifica della presenza di vulnerabilità note (CVE) nell'immagine di base e in una serie di dipendenze tramite uno strumento. Trivy
Successivamente, nell'articolo, presenterò tre opzioni di integrazione di questi passaggi:
La prima è attraverso la configurazione di un pipeline CI/CD, utilizzando l'esempio di GitLab (con descrizione del processo di avvio di un'istanza di test).
La seconda è utilizzando uno script shell.
La terza è costruendo un'immagine Docker per la scansione delle immagini Docker.
Puoi scegliere l'opzione che più ti si addice, trasferirla sulla tua infrastruttura e adattarla alle tue esigenze.
Tutti i file necessari e ulteriori istruzioni si trovano anche nel repository:
Integrazione in GitLab CI/CD
Nella prima opzione vedremo come implementare controlli di sicurezza utilizzando il sistema di repository GitLab. Qui seguiremo i passaggi e analizzeremo come installare da zero un ambiente di test con GitLab, strutturare il processo di scansione e avviare le utility per controllare un Dockerfile di test e un’immagine casuale - l'applicazione JuiceShop.
Installazione di GitLab
1. Installa Docker:
sudo apt-get update && sudo apt-get install docker.io2. Aggiungi l'utente corrente al gruppo docker, in modo da poter lavorare con Docker senza usare sudo:
sudo addgroup docker3. Trova il tuo indirizzo IP:
ip addr4. Installa e avvia GitLab all'interno di un contenitore, sostituendo l'indirizzo IP nel nome host con il tuo:
docker run --detach
--hostname 192.168.1.112
--publish 443:443 --publish 80:80
--name gitlab
--restart always
--volume /srv/gitlab/config:/etc/gitlab
--volume /srv/gitlab/logs:/var/log/gitlab
--volume /srv/gitlab/data:/var/opt/gitlab
gitlab/gitlab-ce:latestAspettiamo che GitLab completi tutte le procedure necessarie per l'installazione (puoi seguire il processo attraverso l'output del file di log: docker logs -f gitlab).
5. Apriamo il nostro IP locale nel browser e vediamo la pagina che propone di cambiare la password per l'utente root:

Impostiamo una nuova password e accediamo a GitLab.
6. Creiamo un nuovo progetto, ad esempio cicd-test, e lo inizializziamo con un file di avvio. README.md:

7. Ora dobbiamo installare GitLab Runner: un agente che eseguirà tutte le operazioni necessarie su richiesta.
Scarichiamo l'ultima versione (in questo caso — per Linux 64-bit):
sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd648. Rendiamolo eseguibile:
sudo chmod +x /usr/local/bin/gitlab-runner9. Aggiungiamo un utente del sistema per il Runner e avviamo il servizio:
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner startDovrebbe risultare all'incirca così:
local@osboxes:~$ sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
Runtime platform arch=amd64 os=linux pid=8438 revision=0e5417a3 version=12.0.1
local@osboxes:~$ sudo gitlab-runner start
Runtime platform arch=amd64 os=linux pid=8518 revision=0e5417a3 version=12.0.1 10. Ora registriamo il Runner affinché possa interagire con la nostra istanza di GitLab.
A tal fine, apriamo la pagina Settings-CI/CD (http://OUR_ IP_ADDRESS/root/cicd-test/-/settings/ci_cd) e nella scheda Runners troviamo l'URL e il Registration token:

11. Registriamo il Runner, inserendo URL e Registration token:
sudo gitlab-runner register
--non-interactive
--url "http:///"
--registration-token ""
--executor "docker"
--docker-privileged
--docker-image alpine:latest
--description "docker-runner"
--tag-list "docker,privileged"
--run-untagged="true"
--locked="false"
--access-level="not_protected"Alla fine otteniamo un GitLab funzionante, al quale è necessario aggiungere istruzioni per avviare i nostri strumenti. In questo caso dimostrativo non ci sono passaggi di build dell'applicazione e containerizzazione, ma in un ambiente reale essi precederanno i passaggi di scansione e formeranno immagini e Dockerfile per l'analisi.
Configurazione del pipeline
1. Aggiungiamo al repository i file mydockerfile.df (questo è un Dockerfile di test che andremo a controllare) e il file di configurazione del processo GitLab CI/CD .gitlab-cicd.yml, che elenca le istruzioni per gli scanner (fai attenzione al punto nel nome del file).
Il file di configurazione YAML contiene istruzioni per l'avvio di tre utility (Hadolint, Dockle e Trivy), che analizzeranno il Dockerfile selezionato e l'immagine specificata nella variabile DOCKERFILE. Tutti i file necessari possono essere presi dal repository:
Estratto da mydockerfile.df (questo è un file astratto con un insieme di istruzioni arbitrario solo per dimostrare il funzionamento dell'utility). Link diretto al file:
Contenuto di mydockerfile.df
FROM amd64/node:10.16.0-alpine@sha256:f59303fb3248e5d992586c76cc83e1d3700f641cbcd7c0067bc7ad5bb2e5b489 AS tsbuild
COPY package.json .
COPY yarn.lock .
RUN yarn install
COPY lib lib
COPY tsconfig.json tsconfig.json
COPY tsconfig.app.json tsconfig.app.json
RUN yarn build
FROM amd64/ubuntu:18.04@sha256:eb70667a801686f914408558660da753cde27192cd036148e58258819b927395
LABEL maintainer="Rhys Arkins "
LABEL name="renovate"
...
COPY php.ini /usr/local/etc/php/php.ini
RUN cp -a /tmp/piik/* /var/www/html/
RUN rm -rf /tmp/piwik
RUN chown -R www-data /var/www/html
ADD piwik-cli-setup /piwik-cli-setup
ADD reset.php /var/www/html/
## ENTRYPOINT ##
ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
USER rootIl file YAML di configurazione appare così (il file stesso può essere scaricato tramite il link diretto qui: ):
Contenuto di .gitlab-ci.yml
variabili:
DOCKER_HOST: "tcp://docker:2375/"
DOCKERFILE: "mydockerfile.df" # nome del Dockerfile da analizzare
DOCKERIMAGE: "bkimminich/juice-shop" # nome dell'immagine Docker da analizzare
# DOCKERIMAGE: "knqyf263/cve-2018-11235" # immagine Docker di test con diversi CVE CRITICI
SHOWSTOPPER_PRIORITY: "CRITICO" # quale livello di criticità farà fallire il lavoro Trivy
TRIVYCACHE: "$CI_PROJECT_DIR/.cache" # dove memorizzare il database di vulnerabilità di Trivy per un riutilizzo più veloce
ARTIFACT_FOLDER: "$CI_PROJECT_DIR"
servizi:
- docker:dind # per poter costruire immagini docker all'interno del Runner
stadi:
- scansione
- rapporto
- pubblica
HadoLint:
# Analisi lint base delle istruzioni del Dockerfile
stage: scansione
image: docker:git
after_script:
- cat $ARTIFACT_FOLDER/hadolint_results.json
script:
- export VERSION=$(wget -q -O - https://api.github.com/repos/hadolint/hadolint/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*//1/')
- wget https://github.com/hadolint/hadolint/releases/download/v${VERSION}/hadolint-Linux-x86_64 && chmod +x hadolint-Linux-x86_64
# NB: hadolint uscirà sempre con codice di uscita 0
- ./hadolint-Linux-x86_64 -f json $DOCKERFILE > $ARTIFACT_FOLDER/hadolint_results.json || exit 0
artifacts:
when: always # restituisci artefatti anche dopo il fallimento del lavoro
paths:
- $ARTIFACT_FOLDER/hadolint_results.json
Dockle:
# Analisi delle migliori pratiche sull'immagine docker (permissi degli utenti, istruzioni seguite quando l'immagine è stata costruita, ecc.)
stage: scansione
image: docker:git
after_script:
- cat $ARTIFACT_FOLDER/dockle_results.json
script:
- export VERSION=$(wget -q -O - https://api.github.com/repos/goodwithtech/dockle/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*//1/')
- wget https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.tar.gz && tar zxf dockle_${VERSION}_Linux-64bit.tar.gz
- ./dockle --exit-code 1 -f json --output $ARTIFACT_FOLDER/dockle_results.json $DOCKERIMAGE
artifacts:
when: always # restituisci artefatti anche dopo il fallimento del lavoro
paths:
- $ARTIFACT_FOLDER/dockle_results.json
Trivy:
# Analisi delle immagini docker e delle dipendenze dei pacchetti rispetto a diversi database di CVE
stage: scansione
image: docker:git
script:
# ottenendo l'ultima versione di Trivy
- apk add rpm
- export VERSION=$(wget -q -O - https://api.github.com/repos/knqyf263/trivy/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*//1')
- wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz && tar zxf trivy_${VERSION}_Linux-64bit.tar.gz
# visualizza tutte le vulnerabilità senza far fallire la build
- ./trivy -d --cache-dir $TRIVYCACHE -f json -o $ARTIFACT_FOLDER/trivy_results.json --exit-code 0 $DOCKERIMAGE
# scrive le informazioni sulle vulnerabilità su stdout in formato leggibile (leggere puro json non è divertente, eh?). Puoi rimuovere questo se non ne hai bisogno.
- ./trivy -d --cache-dir $TRIVYCACHE --exit-code 0 $DOCKERIMAGE
# far fallire la build se viene trovata la priorità SHOWSTOPPER
- ./trivy -d --cache-dir $TRIVYCACHE --exit-code 1 --severity $SHOWSTOPPER_PRIORITY --quiet $DOCKERIMAGE
artifacts:
when: always # restituisci artefatti anche dopo il fallimento del lavoro
paths:
- $ARTIFACT_FOLDER/trivy_results.json
cache:
paths:
- .cache
Report:
# combinazione degli output degli strumenti in un unico HTML
stage: rapporto
when: always
image: python:3.5
script:
- mkdir json
- cp $ARTIFACT_FOLDER/*.json ./json/
- pip install json2html
- wget https://raw.githubusercontent.com/shad0wrunner/docker_cicd/master/convert_json_results.py
- python ./convert_json_results.py
artifacts:
paths:
- results.htmlSe necessario, è possibile scansionare anche le immagini salvate in formato .tar-archive (tuttavia, sarà necessario modificare i parametri di input per gli strumenti nel file YAML)
NB: Trivy richiede che siano installati rpm e git. Altrimenti, restituirà errori durante la scansione delle immagini basate su RedHat e nell'ottenimento degli aggiornamenti del database delle vulnerabilità.
2. Dopo aver aggiunto i file al repository, seguendo le istruzioni nel nostro file di configurazione, GitLab avvierà automaticamente il processo di build e scansione. Nella scheda CI/CD → Pipelines sarà possibile vedere il progresso dell'esecuzione delle istruzioni.
Di conseguenza, abbiamo quattro attività. Tre di esse si occupano direttamente della scansione e l'ultima (Report) raccoglie un semplice rapporto da file disparati contenenti i risultati della scansione.

Per impostazione predefinita, Trivy interrompe la sua esecuzione se vengono rilevate vulnerabilità CRITICAL nell'immagine o nelle dipendenze. Allo stesso tempo, Hadolint restituisce sempre un codice di successo, poiché il suo esito comporta sempre delle osservazioni, il che porta all'interruzione della build.
A seconda delle esigenze specifiche, è possibile configurare il codice di uscita affinché questi strumenti interrompano anche il processo di build se trovano problemi di una certa gravità. Nel nostro caso, la build si fermerà solo se Trivy rileva una vulnerabilità con una gravità che abbiamo specificato nella variabile SHOWSTOPPER in .gitlab-ci.yml.

Il risultato del lavoro di ciascun strumento può essere visualizzato nel log di ogni attività di scansione, direttamente nei file json nella sezione artifacts o in un semplice rapporto HTML (di cui parleremo tra poco):

3. Per presentare i rapporti degli strumenti in un formato leggermente più leggibile, si utilizza un piccolo script Python per convertire tre file json in un unico file HTML con una tabella dei difetti.
Questo script viene eseguito come una singola attività Report, e il suo artefatto finale è un file HTML con il rapporto. Il sorgente dello script si trova anch'esso nel repository e può essere adattato alle proprie esigenze, colori, ecc.

Shell-script
La seconda opzione è adatta nei casi in cui è necessario controllare le immagini Docker al di fuori di un sistema CI/CD o è necessario avere tutte le istruzioni in un formato che può essere eseguito direttamente sull'host. Questa opzione è coperta da uno script shell già pronto, che può essere eseguito su una macchina virtuale (o anche reale) pulita. Lo script esegue le stesse istruzioni dello gitlab-runner sopra descritto.
Per il corretto funzionamento dello script, Docker deve essere installato nel sistema e l'utente corrente deve appartenere al gruppo docker.
Puoi trovare lo script qui:
All'inizio del file, vengono definite le variabili per specificare quale immagine deve essere scansionata e quale livello di gravità delle vulnerabilità causerà l'uscita dallo strumento Trivy con il codice di errore specificato.
Durante l'esecuzione dello script, tutti gli strumenti saranno scaricati nella directory docker_tools, i risultati del loro lavoro saranno nella directory docker_tools/json, mentre l'HTML con il rapporto si troverà nel file results.html.
Esempio di output dello script
~\/docker_cicd$ \.\/docker_sec_check.sh
[+] Impostazione delle variabili di ambiente
[+] Installazione dei pacchetti richiesti
[+] Preparazione delle directory necessarie
[+] Recupero del Dockerfile di esempio
2020-10-20 10:40:00 (45.3 MB\/s) - 'Dockerfile' salvato [8071\/8071]
[+] Estrazione dell'immagine da scansionare
latest: Estrazione da bkimminich\/juice-shop
[+] Esecuzione di Hadolint
...
Dockerfile:205 DL3015 Evita pacchetti aggiuntivi specificando `--no-install-recommends`
Dockerfile:248 DL3002 L'ULTIMO UTENTE non dovrebbe essere root
...
[+] Esecuzione di Dockle
...
WARN - DKL-DI-0006: Evita il tag latest
* Evita il tag 'latest'
INFO - CIS-DI-0005: Abilita la fiducia nei contenuti per Docker
* esporta DOCKER_CONTENT_TRUST=1 prima di docker pull\/build
...
[+] Esecuzione di Trivy
juice-shop\/frontend\/package-lock.json
=====================================
Totale: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 2, CRITICAL: 0)
+---------------------+------------------+----------+---------+-------------------------+
| LIBRERIA | ID VULNERABILITÀ | GRAVITÀ | VERSIONE | TITOLO |
+---------------------+------------------+----------+---------+-------------------------+
| object-path | CVE-2020-15256 | ALTA | 0.11.4 | Inquinamento del prototipo|
| | | | | object-path |
+---------------------+------------------+ +---------+-------------------------+
| tree-kill | CVE-2019-15599 | | 1.2.2 | Iniezione di codice |
+---------------------+------------------+----------+---------+-------------------------+
| webpack-subresource | CVE-2020-15262 | BASSA | 1.4.1 | Chunk caricati dinamicamente non protetti |
| | | | | |
+---------------------+------------------+----------+---------+-------------------------+
juice-shop\/package-lock.json
============================
Totale: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)
...
juice-shop\/package-lock.json
============================
Totale: 5 (CRITICAL: 5)
...
[+] Rimozione dei residui
[+] Rendere l'output gradevole
[+] Conversione dei risultati JSON
[+] Scrittura dei risultati in HTML
[+] Uscita pulita ============================================================
[+] Tutto è fatto. Trova il rapporto HTML risultante in results.htmlImmagine Docker con tutti gli strumenti
Come terza alternativa, ho creato due semplici Dockerfile per la creazione di un'immagine con strumenti di sicurezza. Un Dockerfile aiuterà a costruire un set per la scansione dell'immagine dal repository, il secondo (Dockerfile_tar) servirà a costruire un set per la scansione di un file tar contenente l'immagine.
1. Prendiamo il Dockerfile appropriato e gli script dal repository .
2. Lo avviamo per la costruzione:
docker build -t dscan:image -f docker_security.df .
3. Dopo il completamento della costruzione, creiamo un contenitore dall'immagine. In questo caso, passiamo la variabile di ambiente DOCKERIMAGE con il nome dell'immagine di nostro interesse e montiamo il Dockerfile che desideriamo analizzare, dal nostro computer al file /Dockerfile (si noti che è richiesto il percorso assoluto per questo file):
docker run --rm -v $(pwd)/results:/results -v $(pwd)/docker_security.df:/Dockerfile -e DOCKERIMAGE="bkimminich/juice-shop" dscan:image
[+] Impostazione delle variabili di ambiente
[+] Esecuzione di Hadolint
/Dockerfile:3 DL3006 Taggare sempre esplicitamente la versione di un'immagine
[+] Esecuzione di Dockle
WARN - DKL-DI-0006: Evitare il tag latest
* Evitare il tag 'latest'
INFO - CIS-DI-0005: Abilitare la fiducia nel contenuto per Docker
* esportare DOCKER_CONTENT_TRUST=1 prima di eseguire docker pull/build
INFO - CIS-DI-0006: Aggiungi l'istruzione HEALTHCHECK all'immagine del contenitore
* dichiarazione HEALTHCHECK non trovata
INFO - DKL-LI-0003: Inserisci solo file necessari
* file non necessario: juice-shop/node_modules/sqlite3/Dockerfile
* file non necessario: juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm64/Dockerfile
* file non necessario: juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm/Dockerfile
[+] Esecuzione di Trivy
...
juice-shop/package-lock.json
============================
Totale: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)
...
[+] Rendi l'output bello
[+] Avvio del modulo principale ============================================================
[+] Conversione dei risultati JSON
[+] Scrittura dei risultati in HTML
[+] Uscita pulita ============================================================
[+] Tutto è fatto. Trova il report HTML risultante in results.htmlRisultati
Abbiamo esaminato solo un insieme di strumenti di base per la scansione degli artefatti Docker, che, a mio avviso, copre in modo efficace una buona parte delle esigenze di sicurezza delle immagini. Esiste un gran numero di strumenti a pagamento e gratuiti che possono eseguire le stesse verifiche, generare report accattivanti o funzionare esclusivamente in modalità console, coprire sistemi di gestione dei contenitori, ecc. Un'analisi di questi strumenti e dei metodi di integrazione potrebbe arrivare in un secondo momento.
Un aspetto positivo dell'insieme di strumenti descritto nell'articolo è che tutti sono basati su codice sorgente aperto, quindi puoi sperimentarli e utilizzare altri strumenti simili per trovare ciò che si adatta meglio alle tue esigenze e alle caratteristiche dell'infrastruttura. Certamente, tutte le vulnerabilità che verranno trovate devono essere studiate per l'applicabilità in condizioni specifiche, ma questo è un tema per un futuro ampio articolo.
Spero che questa guida, gli script e gli strumenti ti siano utili e diventino un punto di partenza per creare un'infrastruttura più sicura nel campo della containerizzazione.
Fonte: habr.com
