Je kunt niet beveiligen wat je niet begrijpt. En bij container images betekent begrijpen dat je precies weet wat erin zit — elk package, elke library, elke potentiële kwetsbaarheid.
De meeste teams behandelen hun container images als black boxes. Ze pullen een base image, voegen hun code toe, en pushen naar productie. Maar die base image? Die bevat honderden packages die je niet expliciet hebt gekozen. Elk daarvan kan bekende kwetsbaarheden hebben.
Trivy maakt het onzichtbare zichtbaar. Het is een open-source vulnerability scanner die je precies vertelt wat er in je images zit en welke risico’s ze dragen.
Waarom Trivy?
Er zijn veel container scanners. Ik koos Trivy omdat:
- Enkele binary — Geen database server, geen complexe setup
- Snelheid — Scans zijn klaar in seconden, niet minuten
- Uitgebreid — OS packages, language dependencies, IaC misconfiguraties
- CI-vriendelijk — Exit codes en meerdere output formaten
- Gratis — Geen licentie complexiteit
Andere opties zoals Snyk, Anchore, of Clair zijn valide keuzes, maar Trivy’s eenvoud past bij de low-friction filosofie die ik waardeer.
Basis Pipeline Integratie
Hier is een minimale GitLab CI configuratie:
stages:
- build
- scan
- push
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker build -t $IMAGE_TAG .
- docker save $IMAGE_TAG -o image.tar
artifacts:
paths:
- image.tar
expire_in: 1 hour
scan:
stage: scan
image: aquasec/trivy:latest
script:
- trivy image --input image.tar --exit-code 1 --severity HIGH,CRITICAL
allow_failure: false
push:
stage: push
image: docker:24
services:
- docker:24-dind
script:
- docker load -i image.tar
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker push $IMAGE_TAG
needs:
- build
- scan
Belangrijke punten:
- Build eerst, scan daarna — De image wordt opgeslagen als tarball artifact
- Exit code 1 — Faalt de pipeline bij HIGH of CRITICAL kwetsbaarheden
- Push alleen na succesvolle scan — Geen kwetsbare images bereiken de registry
De Output Begrijpen
Wanneer Trivy kwetsbaarheden vindt, krijg je zoiets:
┌──────────────────┬────────────────┬──────────┬────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version │
├──────────────────┼────────────────┼──────────┼────────────────────────────────────┤
│ openssl │ CVE-2024-0727 │ HIGH │ 3.0.2-0ubuntu1.10 │
│ libcurl4 │ CVE-2024-2398 │ MEDIUM │ 7.81.0-1ubuntu1.15 │
│ python3.10 │ CVE-2024-0450 │ HIGH │ 3.10.12-1~22.04.3 │
└──────────────────┴────────────────┴──────────┴────────────────────────────────────┘
Dit is begrip. Je weet nu:
- Welke packages problemen hebben
- Hoe ernstig die problemen zijn
- Welke versies getroffen zijn
Zonder deze zichtbaarheid hoop je maar dat er niets kwaadaardigs in je containers draait.
Severity Drempels
Niet elke kwetsbaarheid verdient pipeline failure. Configureer drempels gebaseerd op je risico tolerantie:
# Fail alleen op critical - snel maar risicovol
trivy image --exit-code 1 --severity CRITICAL
# Fail op high en critical - gebalanceerd
trivy image --exit-code 1 --severity HIGH,CRITICAL
# Fail op medium en hoger - strikt
trivy image --exit-code 1 --severity MEDIUM,HIGH,CRITICAL
Mijn aanbeveling: Begin met HIGH,CRITICAL. Je kunt later strenger worden, maar te strikt beginnen leidt tot alert fatigue en genegeerde waarschuwingen.
False Positives Afhandelen
Soms rapporteert Trivy kwetsbaarheden die niet van toepassing zijn op jouw use case. Handel ze af met een .trivyignore bestand:
# CVE raakt ons code pad niet
CVE-2024-0727
# Nog geen fix beschikbaar, tracking in JIRA-1234
CVE-2024-2398
Voeg dit toe aan je scan:
trivy image --input image.tar --ignorefile .trivyignore --exit-code 1
Belangrijk: Documenteer waarom je elke CVE negeert. Je toekomstige zelf zal je huidige zelf bedanken.
Output Formaten voor Verschillende Behoeften
Trivy ondersteunt meerdere output formaten:
# Menselijk leesbare tabel (standaard)
trivy image --format table $IMAGE_TAG
# JSON voor programmatische verwerking
trivy image --format json --output results.json $IMAGE_TAG
# SARIF voor GitHub/GitLab security dashboards
trivy image --format sarif --output trivy.sarif $IMAGE_TAG
# HTML rapport voor stakeholders
trivy image --format template --template "@contrib/html.tpl" --output report.html $IMAGE_TAG
Voor GitLab’s security dashboard integratie:
scan:
stage: scan
image: aquasec/trivy:latest
script:
- trivy image --format template --template "@/contrib/gitlab.tpl" --output gl-container-scanning-report.json $IMAGE_TAG
- trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_TAG
artifacts:
reports:
container_scanning: gl-container-scanning-report.json
Nu verschijnen vulnerability findings direct in merge requests.
Meer Scannen dan Alleen Images
Trivy is niet beperkt tot container images. Het scant:
# Filesystem (je project directory)
trivy fs --exit-code 1 .
# Git repository
trivy repo https://github.com/your/repo
# Kubernetes manifests
trivy config ./k8s/
# Infrastructure as Code
trivy config ./terraform/
Een complete security stage kan er zo uitzien:
security-scan:
stage: scan
image: aquasec/trivy:latest
script:
# Scan source code dependencies
- trivy fs --exit-code 0 --severity HIGH,CRITICAL . --format table
# Scan Kubernetes manifests voor misconfiguraties
- trivy config ./k8s/ --exit-code 0 --severity HIGH,CRITICAL
# Scan container image (deze faalt de build)
- trivy image --input image.tar --exit-code 1 --severity HIGH,CRITICAL
Database Caching
Trivy downloadt vulnerability databases bij elke run. In CI voegt dit latency toe. Cache het:
variables:
TRIVY_CACHE_DIR: .trivy-cache
scan:
stage: scan
image: aquasec/trivy:latest
cache:
key: trivy-db
paths:
- .trivy-cache
script:
- trivy image --cache-dir $TRIVY_CACHE_DIR --input image.tar
Of gebruik een pre-populated image:
scan:
image: aquasec/trivy:latest
before_script:
- trivy image --download-db-only
script:
- trivy image --skip-db-update --input image.tar
Mijn Productie Configuratie
Dit is wat ik daadwerkelijk draai:
stages:
- build
- scan
- push
- deploy
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
TRIVY_CACHE_DIR: .trivy-cache
# Fail niet op ongefixte kwetsbaarheden
TRIVY_IGNORE_UNFIXED: "true"
.trivy-template: &trivy-template
image: aquasec/trivy:latest
cache:
key: trivy-db
paths:
- .trivy-cache
scan-image:
<<: *trivy-template
stage: scan
script:
# Genereer rapport voor GitLab dashboard
- trivy image
--cache-dir $TRIVY_CACHE_DIR
--format template
--template "@/contrib/gitlab.tpl"
--output gl-container-scanning-report.json
--input image.tar
# Fail op fixbare HIGH/CRITICAL
- trivy image
--cache-dir $TRIVY_CACHE_DIR
--exit-code 1
--severity HIGH,CRITICAL
--ignore-unfixed
--input image.tar
artifacts:
reports:
container_scanning: gl-container-scanning-report.json
needs:
- build
scan-config:
<<: *trivy-template
stage: scan
script:
- trivy config
--cache-dir $TRIVY_CACHE_DIR
--exit-code 1
--severity HIGH,CRITICAL
./k8s/
allow_failure: true # Alleen waarschuwing, niet blokkeren
Belangrijke beslissingen:
--ignore-unfixed— Geen zin om te falen op kwetsbaarheden zonder beschikbare fix- Config scan als waarschuwing — Misconfiguraties zijn belangrijk maar moeten niet elke deployment blokkeren
- Cached database — Snellere builds, minder netwerk afhankelijkheden
Shift Left vs. Gate Keeping
Er zijn twee filosofieën voor security scanning:
Gate keeping (wat we besproken hebben):
- Scan tijdens build time
- Blokkeer kwetsbare images van registry
- Handhaaf standaarden voor deployment
Shift left:
- Scan tijdens development
- IDE plugins en pre-commit hooks
- Snellere feedback loops
Ik raad beide aan. Gate keeping vangt wat er doorheen slipt, maar developers zouden issues moeten zien voordat ze committen:
# Developer workflow
trivy fs . # Scan voor commit
docker build -t myapp .
trivy image myapp # Scan voor push
De pipeline is het vangnet, niet het primaire feedback mechanisme.
Integratie met GitOps
Als je ArgoCD gebruikt voor deployments, voeg scanning toe aan je promotie workflow:
promote-to-prod:
stage: deploy
script:
# Herscan de specifieke image versie voor promotie
- trivy image --exit-code 1 --severity CRITICAL $IMAGE_TAG
# Update GitOps repo alleen als scan slaagt
- |
cd gitops-repo
kustomize edit set image myapp:${CI_COMMIT_TAG}
git commit -am "Promote ${CI_COMMIT_TAG} to production"
git push
rules:
- if: $CI_COMMIT_TAG =~ /^v[0-9]+/
Dit zorgt dat images opnieuw gescand worden voor productie promotie — kwetsbaarheden ontdekt na de initiële build slippen er niet doorheen.
Verder dan Kwetsbaarheden: SBOM
Een Software Bill of Materials (SBOM) is een inventaris van alles in je image. Trivy kan er een genereren:
trivy image --format spdx-json --output sbom.json $IMAGE_TAG
Waarom SBOMs belangrijk zijn:
- Compliance — Sommige industrieën vereisen ze
- Incident response — Wanneer een nieuwe CVE uitkomt, kun je snel alle images checken
- Supply chain zichtbaarheid — Ken je dependencies
Sla SBOMs op naast je images in je registry.
Veelvoorkomende Valkuilen
De verkeerde laag scannen
# Fout - scant de trivy image zelf
trivy image aquasec/trivy:latest
# Goed - scant je gebouwde image
trivy image --input image.tar
Exit code negeren
# Fout - slaagt altijd
trivy image $IMAGE_TAG || true
# Goed - fail bij findings
trivy image --exit-code 1 $IMAGE_TAG
Alert fatigue
Beginnen met MEDIUM severity en geen ignores leidt tot honderden findings. Teams geven op. Begin strikt op critical, breid geleidelijk uit.
Waarom Dit Ertoe Doet
Container scanning gaat niet over compliance checkboxes. Het gaat over begrijpen wat je draait.
Wanneer een nieuwe kritieke CVE wordt aangekondigd, wil je weten:
- Welke van mijn images zijn getroffen?
- Welke services draaien die images?
- Hoe snel kan ik patchen?
Zonder scanning vereisen deze vragen handmatig onderzoek door elke image. Met Trivy in je pipeline heb je continue zichtbaarheid.
Security door begrip, niet security door hoop.
Je kunt niet beschermen wat je niet kunt zien. Container scanning maakt de inhoud van je images zichtbaar, en verandert black boxes in gedocumenteerde, auditeerbare systemen.
