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:

  1. Enkele binary — Geen database server, geen complexe setup
  2. Snelheid — Scans zijn klaar in seconden, niet minuten
  3. Uitgebreid — OS packages, language dependencies, IaC misconfiguraties
  4. CI-vriendelijk — Exit codes en meerdere output formaten
  5. 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.