
Salut, Habr!
În realitatea modernă, datorită rolului tot mai crescut al containerizării în procesele de dezvoltare, problema asigurării securității diferitelor etape și entități asociate cu containerele devine din ce în ce mai importantă. Realizarea verificărilor manuale este o activitate laborioasă, așa că ar fi bine să facem cel puțin pașii inițiali spre automatizarea acestui proces.
În acest articol, voi împărtăși scripturi gata făcute pentru implementarea mai multor utilitare de securitate Docker și instrucțiuni despre cum să desfășurăm un mic demo pentru a verifica acest proces. Materialele pot fi folosite pentru a experimenta cum să organizăm procesul de testare a securității imaginilor și instrucțiunilor Dockerfile. Este clar că infrastructura de dezvoltare și implementare variază de la un caz la altul, așa că mai jos voi prezenta câteva opțiuni posibile.
Utilitare de verificare a securității
Există o mare varietate de aplicații auxiliare și scripturi care efectuează verificări ale diferitelor aspecte ale infrastructurii Docker. Unele dintre acestea au fost deja descrise în articolul anterior (), iar în acest material aș dori să mă concentrez asupra a trei dintre ele, care acoperă majoritatea cerințelor de securitate pentru imaginile Docker construite în timpul dezvoltării. În plus, voi arăta și un exemplu despre cum aceste trei utilitare pot fi unite într-un singur pipeline pentru a realiza verificările de securitate.
Hadolint
O utilitară de consolă destul de simplă, care ajută, dintr-o primă aproximare, să evaluăm corectitudinea și securitatea instrucțiunilor Dockerfile (de exemplu, utilizarea doar a registrelor de imagini permise sau utilizarea sudo).

Dockle
O utilitară de consolă care lucrează cu o imagine (sau cu un arhivă tar salvată a imaginii), care verifică corectitudinea și securitatea unei imagini specifice, analizând straturile și configurația acesteia – ce utilizatori au fost creați, ce instrucțiuni sunt folosite, ce volume sunt montate, prezența parolelor goale etc. Deocamdată, numărul verificărilor nu este foarte mare și se bazează pe câteva verificări și recomandări proprii. pentru Docker.

Trivy
Această utilitară vizează identificarea vulnerabilităților de două tipuri – problemele de construire a sistemului de operare (sunt suportate Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) și problemele cu dependențele (Gemfile.lock, Pipfile.lock, composer.lock, package-lock.json, yarn.lock, Cargo.lock). Trivy poate scana atât o imagine dintr-un depozit, cât și o imagine locală, precum și efectua scanări pe baza fișierului .tar transmis care conține imaginea Docker.

Opțiuni de implementare a utilitarelor
Pentru a încerca aplicațiile descrise în condiții izolate, voi oferi instrucțiuni pentru instalarea tuturor utilitarelor într-un proces simplificat.
Ideea principală este de a demonstra cum se poate implementa o verificare automată a conținutului Dockerfile și a imaginilor Docker care sunt create în procesul de dezvoltare.
Verificarea constă în următorii pași:
- Verificarea corectitudinii și securității instrucțiunilor Dockerfile - folosind un utilitar linters Hadolint
- Verificarea corectitudinii și securității imaginilor finale și intermediare - utilizând un utilitar Dockle
- Verificarea existenței vulnerabilităților cunoscute (CVE) în imaginea de bază și în diversele dependențe - folosind un utilitar Trivy
Mai departe în articol voi prezenta trei opțiuni pentru implementarea acestor pași:
Prima - prin configurarea unui pipeline CI/CD pe exemplul GitLab (cu descrierea procesului de ridicare a unei instanțe de test).
A doua - utilizând un script shell.
A treia - construind o imagine Docker pentru scanarea imaginilor Docker.
Puteți alege opțiunea care vi se potrivește cel mai bine, să o transferați pe infrastructura dvs. și să o adaptați nevoilor dvs.
Toate fișierele necesare și instrucțiuni suplimentare se află de asemenea în depozit:
Integrarea în GitLab CI/CD
În prima opțiune vom analiza cum se pot implementa verificările de securitate pe exemplul sistemului de depozite GitLab. Aici vom parcurge pașii și vom explica cum să configurăm de la zero un mediu de testare cu GitLab, să elaborăm procesul de scanare și să executăm utilitarele pentru a verifica un Dockerfile de test și o imagine aleatorie - aplicația JuiceShop.
Instalarea GitLab
1. Instalăm Docker:
sudo apt-get update && sudo apt-get install docker.io2. Adăugăm utilizatorul curent în grupul docker pentru a putea lucra cu Docker fără sudo:
sudo addgroup docker3. Ne găsim IP-ul:
ip addr4. Instalăm și pornim GitLab în container, înlocuind adresa IP din hostname cu a noastră:
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:latestAșteptăm ca GitLab să finalizeze toate procedurile necesare instalării (putem urmări procesul prin ieșirea fișierului de log: docker logs -f gitlab).
5. Deschidem în browser IP-ul nostru local și vedem pagina care ne cere să schimbăm parola pentru utilizatorul root:

Stabilim o nouă parolă și intrăm în GitLab.
6. Creăm un nou proiect, de exemplu cicd-test și îl inițializăm cu un fișier de start README.md:

7. Acum trebuie să instalăm GitLab Runner: agentul care va executa la cerere toate operațiile necesare.
Descărcăm ultima versiune (în acest caz — pentru 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. Îi facem executabil:
sudo chmod +x /usr/local/bin/gitlab-runner9. Adăugăm un utilizator de sistem pentru Runner și lansăm serviciul:
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 startAr trebui să arate cam așa:
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. Acum înregistrăm Runner-ul, astfel încât să poată interacționa cu instanța noastră GitLab.
Pentru aceasta, deschidem pagina Settings-CI/CD (http://OUR_IP_ADDRESS/root/cicd-test/-/settings/ci_cd) și pe tab-ul Runners găsim URL-ul și Registration token-ul:

11. Înregistrăm Runner-ul, introducând URL-ul și Registration token-ul:
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"În rezultat, obținem un GitLab complet funcțional, în care trebuie să adăugăm instrucțiuni pentru a porni utilitarele noastre. În acest caz demonstrativ, nu avem pași pentru compilarea aplicației și containerizarea acesteia, dar în realitate aceștia vor preceda pașii de scanare și vor crea imagini și Dockerfile pentru analiză.
Configurația pipeline
1. Vom adăuga în repository fișierele mydockerfile.df (acesta este un Dockerfile de test pe care îl vom verifica) și fișierul de configurație GitLab CI/CD .gitlab-cicd.yml, care enumeră instrucțiunile pentru scanere (observați punctul din numele fișierului).
Fișierul YAML de configurare conține instrucțiuni pentru lansarea a trei utilitare (Hadolint, Dockle și Trivy), care vor analiza Dockerfile-ul ales și imaginea specificată în variabila DOCKERFILE. Toate fișierele necesare pot fi găsite în repository:
Extras din mydockerfile.df (acesta este un fișier abstract cu un set de instrucțiuni arbitrare doar pentru a demonstra funcționarea utilitatii). Link direct la fișier:
Conținutul 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 rootConfigurația YAML arată astfel (fișierul în sine poate fi obținut prin linkul direct aici: ):
Conținutul .gitlab-ci.yml
variabile:
DOCKER_HOST: "tcp://docker:2375/"
DOCKERFILE: "mydockerfile.df" # numele Dockerfile-ului de analizat
DOCKERIMAGE: "bkimminich/juice-shop" # numele imaginii Docker de analizat
# DOCKERIMAGE: "knqyf263/cve-2018-11235" # imagine Docker de testare cu mai multe CVE CRITICE
SHOWSTOPPER_PRIORITY: "CRITICAL" # ce nivel de criticitate va face ca job-ul Trivy să eșueze
TRIVYCACHE: "$CI_PROJECT_DIR/.cache" # unde să cache-uiască baza de date a vulnerabilităților Trivy pentru reutilizare mai rapidă
ARTIFACT_FOLDER: "$CI_PROJECT_DIR"
servicii:
- docker:dind # pentru a putea construi imagini docker în interiorul Runner-ului
stadii:
- scanare
- raport
- publicare
HadoLint:
# Analiza de bază a instrucțiunilor Dockerfile
stagiu: scanare
imagine: 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 va ieși întotdeauna cu codul de ieșire 0
- ./hadolint-Linux-x86_64 -f json $DOCKERFILE > $ARTIFACT_FOLDER/hadolint_results.json || exit 0
artefacte:
când: întotdeauna # returnează artefactele chiar și după eșecul job-ului
căi:
- $ARTIFACT_FOLDER/hadolint_results.json
Dockle:
# Analiza celor mai bune practici despre imaginea docker (permisiuni utilizatori, instrucțiuni respectate când a fost construită imaginea, etc.)
stagiu: scanare
imagine: 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
artefacte:
când: întotdeauna # returnează artefactele chiar și după eșecul job-ului
căi:
- $ARTIFACT_FOLDER/dockle_results.json
Trivy:
# Analiza imaginii docker și a dependențelor de pachete împotriva mai multor baze de CVE
stagiu: scanare
imagine: docker:git
script:
# obținerea celei mai recente versiuni 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
# afișarea tuturor vulnerabilităților fără a face build-ul să eșueze
- ./trivy -d --cache-dir $TRIVYCACHE -f json -o $ARTIFACT_FOLDER/trivy_results.json --exit-code 0 $DOCKERIMAGE
# scrierea informațiilor despre vulnerabilități pe stdout în format ușor de citit (citirea json-ului pur nu este distractiv, nu-i așa?). Puteți elimina aceasta dacă nu este necesar.
- ./trivy -d --cache-dir $TRIVYCACHE --exit-code 0 $DOCKERIMAGE
# eșuarea build-ului dacă se găsește prioritatea SHOWSTOPPER
- ./trivy -d --cache-dir $TRIVYCACHE --exit-code 1 --severity $SHOWSTOPPER_PRIORITY --quiet $DOCKERIMAGE
artefacte:
când: întotdeauna # returnează artefactele chiar și după eșecul job-ului
căi:
- $ARTIFACT_FOLDER/trivy_results.json
cache:
căi:
- .cache
Raport:
# combinarea rezultatelor uneltelor într-un singur HTML
stagiu: raport
când: întotdeauna
imagine: 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
artefacte:
căi:
- results.htmlDacă e nevoie, se pot scana și imaginile salvate sub formă de arhivă .tar (totuși, va fi necesar să modificați parametrii de intrare pentru utilitare în fișierul YAML)
Notă: Trivy necesită instalarea pentru a funcționa rpm și git. În caz contrar, va genera erori la scanarea imaginilor bazate pe RedHat și la obținerea actualizărilor bazei de date a vulnerabilităților.
2. După adăugarea fișierelor în repository, conform instrucțiunilor din fișierul nostru de configurare, GitLab va începe automat procesul de construire și scanare. În fila CI/CD → Pipelines, se va putea vedea progresul execuției instrucțiunilor.
Ca urmare, avem patru sarcini. Trei dintre ele se ocupă în mod direct de scanare, iar ultima (Raport) adună un raport simplu din fișierele disparate cu rezultatele scanării.

În mod implicit, Trivy își oprește execuția dacă sunt detectate vulnerabilități CRITICAL în imagine sau în dependențe. În același timp, Hadolint returnează întotdeauna un cod de succes, deoarece întotdeauna există observații ca urmare a execuției sale, ceea ce duce la oprirea construcției.
În funcție de cerințele specifice, se poate configura codul de ieșire pentru ca aceste utilitare, atunci când detectează probleme de o anumită criticitate, să oprească și procesul de construcție. În cazul nostru, construcția se va opri doar dacă Trivy detectează o vulnerabilitate cu criticitatea pe care am specificat-o în variabila SHOWSTOPPER în .gitlab-ci.yml.

Rezultatul execuției fiecărei utilitare poate fi văzut în jurnalul fiecărei sarcini de scanare, direct în fișierele json din secțiunea artefacte sau în raportul HTML simplu (despre acesta mai jos):

3. Pentru a prezenta rapoartele utilitare într-o formă mai ușor de citit, se folosește un mic script Python pentru a converti trei fișiere json într-un singur fișier HTML cu o tabelă a defectelor.
Acest script este lansat ca o sarcină separată Raport, iar artefactul său final este un fișier HTML cu raportul. Sursa scriptului se află de asemenea în repository și poate fi adaptată la nevoile și culorile proprii, etc.

Script Bash
A doua opțiune este potrivită pentru cazurile în care este necesară verificarea imaginilor Docker, fără a fi parte dintr-un sistem CI/CD sau când este necesar să ai toate instrucțiunile într-un format care poate fi executat direct pe gazdă. Această variantă este acoperită de un script shell gata, care poate fi rulat pe o mașină virtuală (sau chiar reală) curată. Scriptul execută aceleași instrucțiuni ca și gitlab-runner-ul menționat mai sus.
Pentru funcționarea corectă a scriptului, Docker trebuie să fie instalat în sistem, iar utilizatorul curent trebuie să fie în grupul docker.
Scriptul poate fi descărcat de aici:
La începutul fișierului, variabilele sunt setate pentru a specifica ce imagine trebuie scanată și ce defecte de o anumită severitate vor provoca ieșirea din utilitarul Trivy cu codul de eroare specificat.
În timpul executării scriptului, toate utilitarele vor fi descărcate în directorul docker_tools, iar rezultatele acestora vor fi în directorul docker_tools/json, iar HTML-ul cu raportul va fi găsit în fișierul results.html.
Exemplu de ieșire a scriptului
~/docker_cicd$ ./docker_sec_check.sh
[+] Setarea variabilelor de mediu
[+] Instalarea pachetelor necesare
[+] Pregătirea directoarelor necesare
[+] Descărcarea unui Dockerfile de exemplu
2020-10-20 10:40:00 (45.3 MB/s) - ‘Dockerfile’ salvat [8071/8071]
[+] Trăgând imaginea pentru scanare
latest: Trăgând din bkimminich/juice-shop
[+] Rulând Hadolint
...
Dockerfile:205 DL3015 Evitați pachetele suplimentare specificând `--no-install-recommends`
Dockerfile:248 DL3002 Ultimul utilizator nu ar trebui să fie root
...
[+] Rulând Dockle
...
WARN - DKL-DI-0006: Evitați eticheta latest
* Evitați eticheta 'latest'
INFO - CIS-DI-0005: Activați încrederea în conținut pentru Docker
* exportați DOCKER_CONTENT_TRUST=1 înainte de a trage/construi Docker
...
[+] Rulând Trivy
juice-shop/frontend/package-lock.json
=====================================
Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 2, CRITICAL: 0)
+---------------------+------------------+----------+---------+-------------------------+
| LIBRARY | VULNERABILITY ID | SEVERITY | VERSION | TITLE |
+---------------------+------------------+----------+---------+-------------------------+
| object-path | CVE-2020-15256 | HIGH | 0.11.4 | Poluare a prototipului în |
| | | | | object-path |
+---------------------+------------------+ +---------+-------------------------+
| tree-kill | CVE-2019-15599 | | 1.2.2 | Injecție de cod |
+---------------------+------------------+----------+---------+-------------------------+
| webpack-subresource | CVE-2020-15262 | LOW | 1.4.1 | Chunk-uri încărcate dinamic neprotejate |
| | | | | |
+---------------------+------------------+----------+---------+-------------------------+
juice-shop/package-lock.json
============================
Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)
...
juice-shop/package-lock.json
============================
Total: 5 (CRITICAL: 5)
...
[+] Eliminarea resturilor
[+] Aranjarea output-ului pentru a arăta bine
[+] Convertirea rezultatelor JSON
[+] Scrierea rezultatelor în HTML
[+] Ieşire curată ============================================================
[+] Totul este gata. Găsiți raportul HTML rezultat în results.htmlImagine Docker cu toate utilitarele
Ca a treia opțiune, am creat două simple Dockerfile-uri pentru a construi o imagine cu utilitare de securitate. Un Dockerfile va ajuta la compilarea unui set pentru scanarea imaginii din repository, iar al doilea (Dockerfile_tar) – să creeze un set pentru scanarea unui fișier tar cu imaginea.
1. Luăm Docker file-ul corespunzător și scripturile din repository .
2. Îl lansăm pentru compilare:
docker build -t dscan:image -f docker_security.df .
3. După ce compilarea s-a încheiat, creăm un container din imagine. În acest caz, transmitem variabila de mediu DOCKERIMAGE cu numele imaginii de care suntem interesați și montăm Dockerfile-ul pe care dorim să-l analizăm, de pe mașina noastră pe fișierul /Dockerfile (rețineți că este necesar să folosiți calea absolută până la acest fișier):
docker run --rm -v $(pwd)\/results:\/results -v $(pwd)\/docker_security.df:\/Dockerfile -e DOCKERIMAGE="bkimminich\/juice-shop" dscan:image
[+] Setarea variabilelor de mediu
[+] Rularea Hadolint
\/Dockerfile:3 DL3006 Etichetați întotdeauna versiunea unei imagini în mod explicit
[+] Rularea Dockle
WARN - DKL-DI-0006: Evitați eticheta latest
* Evitați eticheta 'latest'
INFO - CIS-DI-0005: Activați încrederea în conținut pentru Docker
* exportați DOCKER_CONTENT_TRUST=1 înainte de docker pull\/build
INFO - CIS-DI-0006: Adăugați instrucțiunea HEALTHCHECK la imaginea containerului
* instrucțiune HEALTHCHECK nu găsită
INFO - DKL-LI-0003: Puneți doar fișierele necesare
* fișier inutil : juice-shop\/node_modules\/sqlite3\/Dockerfile
* fișier inutil : juice-shop\/node_modules\/sqlite3\/tools\/docker\/architecture\/linux-arm64\/Dockerfile
* fișier inutil : juice-shop\/node_modules\/sqlite3\/tools\/docker\/architecture\/linux-arm\/Dockerfile
[+] Rularea Trivy
...
juice-shop\/package-lock.json
============================
Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)
...
[+] Facem ca output-ul să arate bine
[+] Începerea modulului principal ============================================================
[+] Conversia rezultatelor JSON
[+] Scrierea rezultatelor HTML
[+] Ieșire curată ============================================================
[+] Totul este gata. Găsiți raportul HTML rezultat în results.htmlRezultate
Am discutat doar un set de bază de utilitare pentru scanarea artefactelor Docker, care, din punctul meu de vedere, acoperă eficient o mare parte din cerințele de securitate ale imaginilor. Există o mulțime de instrumente plătite și gratuite care pot efectua aceleași verificări, genera rapoarte frumoase sau funcționa exclusiv în modul console, acoperind sisteme de gestionare a containerelor etc. O prezentare a acestor instrumente și modalităților de integrare a lor ar putea apărea ceva mai târziu.
Un avantaj al setului de instrumente descris în articol este că toate sunt construite pe un cod deschis, ceea ce vă permite să experimentați cu ele și cu alte instrumente similare pentru a găsi ce se potrivește cerințelor și specificului infrastructurii dumneavoastră. Desigur, toate vulnerabilitățile care vor fi găsite trebuie studiate pentru aplicabilitate în condiții specifice, dar aceasta este o temă pentru un articol extensiv viitor.
Sper că această instrucțiune, scripturile și utilitarele vă vor fi utile și vor constitui un punct de plecare pentru crearea unei infrastructuri mai sigure în domeniul containerizării.
Sursa: habr.com
