Skip to main content

Як сканувати Docker образи на вразливості?

Vulnerability-сканування відповідає на «чи мої image містять відомі security-діри?» Сучасний tooling робить це near-zero-effort кроком у CI. Скіпання означає шиппінг відомих CVE у прод.

Теорія

TL;DR

  • Scanner інспектує встановлені пакети image (OS + мова) і матчить їх проти CVE-баз (NVD, vendor-advisory, GitHub Security Advisories).
  • Три популярні tools: Trivy (Aqua), Grype (Anchore), Docker Scout (Docker Inc).
  • Severities: LOW, MEDIUM, HIGH, CRITICAL. Більшість команд gate на HIGH+ або CRITICAL.
  • Три моменти сканування: build-time (CI gate), admission-time (cluster gate), continuously (ловити нові CVE у вже-running image).
  • Дві CVE-категорії: OS-level (фікс через base-image update) і app-level (фікс через package-update).
  • Завжди парь з .trivyignore або еквівалентом для задокументованих винятків.

Trivy (найпопулярніший)

bash
# Scan локального image trivy image myorg/api:1.0 # Завали на HIGH або CRITICAL trivy image --severity HIGH,CRITICAL --exit-code 1 myorg/api:1.0 # Ігнорувати unfixed CVE (немає patch, чекаємо upstream) trivy image --ignore-unfixed myorg/api:1.0 # Формати output trivy image --format json myorg/api:1.0 trivy image --format sarif --output report.sarif myorg/api:1.0

Trivy opinionated і швидкий. Вбудовані CI-флаги. Дефолт severity це усе; прод-CI зазвичай фільтрує на HIGH+.

Grype

bash
grype myorg/api:1.0 grype --fail-on high myorg/api:1.0

Від Anchore. Трохи інша DB; іноді ловить, що Trivy пропускає (і навпаки). Хороший для second-opinion-сканів.

Docker Scout

bash
docker scout cves myorg/api:1.0 docker scout recommendations myorg/api:1.0 # пропонує base-image-upgrade для фіксу CVE

Scanner Docker Inc. Тісно інтегровано з Docker CLI і Docker Hub. Команда recommendations унікальна і корисна: каже точно, який base-image bump фіксить які CVE.

CI-інтеграція

yaml
# GitHub Actions з Trivy - name: Build image run: docker build -t myorg/api:${{ github.sha }} . - name: Scan with Trivy uses: aquasecurity/trivy-action@master with: image-ref: myorg/api:${{ github.sha }} severity: 'HIGH,CRITICAL' exit-code: '1' ignore-unfixed: true - name: Upload SARIF to GitHub if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif'

Заливає у GitHub Code Scanning-tab, вразливості з'являються поряд з рештою code-quality сигналів.

Multi-stage scan під час білду

dockerfile
# syntax=docker/dockerfile:1.7 FROM golang:1.23-alpine AS build # ... build app ... FROM aquasec/trivy:0.59.0 AS scan COPY --from=build / /scan-target RUN trivy filesystem --severity HIGH,CRITICAL --exit-code 1 /scan-target FROM alpine:3.21 COPY --from=build /out/server /server ENTRYPOINT ["/server"]

Стейдж scan крутиться як частина білду. Білд падає, якщо HIGH/CRITICAL CVE знайдено. Не може бути скіпнуто careless CI-config.

Admission-time-сканування

Kubernetes admission-controllers можуть відмовити деплою unscanned або vulnerable image:

  • OPA Gatekeeper / Kyverno з політиками, що перевіряють Cosign-attestation scan-результатів.
  • Trivy Operator для K8s — continuously сканує running-image.
  • Cosign + sigstore-policy-controller — верифікує, що image було підписано і attested як scanned.

Pipeline: CI підписує attestation («це image було scanned at time X, found N HIGH CVE»); admission верифікує attestation і перевіряє policy.

Continuous re-scanning

CVE, розкритий сьогодні, впливає на image, що ти зібрав минулого тижня. Байти ті самі; threat-landscape змінився.

bash
# Cron-driven re-scan у проді 0 6 * * * trivy image --severity HIGH,CRITICAL myorg/api:current 2>&1 | mail security@example.com

Або continuous-scanner (Trivy Operator, Snyk Container, Sysdig Secure), що стежить за running-image і re-scan'ить періодично.

OS vs app CVE

Результати зазвичай падають у дві групи:

OS-level (alpine:3.20 → CVE у libssl):

  • Фікс: bump base-image (alpine:3.21), rebuild.
  • Або чекай, поки OS-vendor опублікує patched-version.

App-level (requests==2.30.0 → CVE у requests):

  • Фікс: update залежності у твоєму requirements.txt / package.json / go.mod.
  • Часто бере довше, якщо нова версія має breaking changes.

Sканери виявляють обидва. Шлях фіксу різний.

Типові помилки

Сканування без gating

yaml
- run: trivy image myorg/api:${{ github.sha }} || true # ← завжди проходить

Результати показано, але не валять білд. Деви ігнорують. Через місяці у тебе сотні HIGH CVE у проді. Завжди постав --exit-code 1 на реальний severity-поріг.

Сканування лише при build-time

HIGH CVE, розкритий минулої ночі, зараз у твоєму прод-image. Твій CI пройшов учора; реальність змінилася за ніч. Додай nightly re-scan running-image.

Не підтримувати .trivyignore

Реальні CVE іноді не мають patch («won't fix»). Або толеруєш їх, або замінюєш пакет. Документуй рішення у .trivyignore:

# .trivyignore CVE-2023-12345 # Нема patch, mitigated через network-policy. Re-evaluate quarterly.

Довіряти одному scanner

Різні scanner мають різні DB. CVE у твоїй image-базі може бути відсутній в одного scanner. Для high-stakes роботи крути два scanner і union результати.

Ігнорувати SBOM

Scanner-результат без SBOM це просто «ці CVE існують». З SBOM (Software Bill of Materials) у тебе точний список того, що в image, queryable пізніше: «які image містять log4j?». Генеруй SBOM під час білду (docker buildx build --sbom=true), зберігай з image.

Реальний setup

Мінімум

  • Trivy у CI, gate на HIGH/CRITICAL.
  • .trivyignore для задокументованих винятків.
  • Готово.

Зрілий

  • Trivy у CI + scan-стейдж у Dockerfile.
  • SBOM-генерація + зберігання.
  • Cosign-attestation scan-результатів.
  • Admission-controller верифікує attestation.
  • Nightly re-scan running-image.
  • Trivy Operator на K8s.
  • Findings → Slack-канал + ticket-створення.

Питання для поглиблення

Q: Яка різниця між CVE і vulnerability?


A: CVE (Common Vulnerabilities and Exposures) це публічний ID для відомого security-issue. Vulnerability це лежачий баг. Більшість сучасних scanner репортять по CVE-ID, з severity, scored через CVSS.

Q: Чи scanner може знайти zero-day?


A: Ні, вони знають лише про публічно розкриті вразливості. Zero-day за визначенням ще не відомі. Сканування це один шар; доповни runtime-monitoring, sandbox-testing і code-review.

Q: Що таке SBOM і навіщо генерувати?


A: Software Bill of Materials — список кожного пакета і версії у твоєму image. SPDX або CycloneDX формат. З SBOM можеш пізніше відповісти «які з моїх image містять $vulnerable_package» без re-pull і re-scan.

Q: Як обробляти CVE без доступного фіксу?


A: Документуй у .trivyignore з обґрунтуванням і re-evaluation-датою. Mitigate через runtime-control (network-policy, capability-drops). Re-check quarterly.

Q: (Senior) Як побудувати scanning-pipeline, що запобігає регресіям?


A: Три gate: (1) CI-build падає на новий HIGH/CRITICAL — навіть fixing-an-unrelated-bug PR не може merge, якщо вводить новий CVE. (2) Admission-control відкидає image без свіжого підписаного scan-attestation. (3) Прод runtime-scanner alert'ить на нові CVE проти running-image і тригерить remediation-workflow (auto-PR до bump base-image, ticket до dev-команди). Результат: будь-який CVE, що дістав прод, був або свідомо проігнорований (у .trivyignore з sign-off), або розкритий після deploy з tracked-response-window.

Приклади

Trivy у GitHub Actions

yaml
name: ci on: [push] jobs: build-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: docker build -t myorg/api:${{ github.sha }} . - name: Trivy scan (gate) uses: aquasecurity/trivy-action@master with: image-ref: myorg/api:${{ github.sha }} severity: 'HIGH,CRITICAL' ignore-unfixed: true exit-code: '1' - name: Trivy scan (full report, ніколи не падає) if: always() uses: aquasecurity/trivy-action@master with: image-ref: myorg/api:${{ github.sha }} format: 'sarif' output: 'trivy-results.sarif' severity: 'LOW,MEDIUM,HIGH,CRITICAL' - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif'

Два проходи: gating на HIGH+, повний звіт залито для видимості.

Multi-stage з вбудованим scan

dockerfile
# syntax=docker/dockerfile:1.7 FROM golang:1.23-alpine AS build WORKDIR /src COPY . . RUN CGO_ENABLED=0 go build -o /out/server ./cmd/server FROM aquasec/trivy:0.59.0 AS scan COPY --from=build /out /scan RUN trivy filesystem --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed /scan FROM scratch COPY --from=build /out/server /server USER 65532:65532 ENTRYPOINT ["/server"]

docker build падає, якщо HIGH/CRITICAL CVE знайдено. Можна обійти через --target для runtime-стейджу при потребі, але дефолтний build-шлях enforce'ить gate.

Порівняти два scanner

bash
#!/bin/bash IMG=$1 echo "=== Trivy ===" trivy image --severity HIGH,CRITICAL $IMG --no-progress --quiet echo "=== Grype ===" grype $IMG --output table --quiet

Різні scanner flag'ують різні речі. Для high-stakes image union це твоя true risk-картина.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів