--- id: devsec-container-scanning title: Container Scanning — Trivy / Grype / 정책 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [devsecops, container, security, vibe-coding] tech_stack: { language: "Docker / CI", applicable_to: ["DevOps"] } applied_in: [] aliases: [Trivy, Grype, Snyk Container, Docker Scout, base image, distroless] --- # Container Scanning > Image 안 vulnerable lib → exploit. **Trivy / Grype / Docker Scout / Snyk** 가 CVE 검사. **Distroless / Chainguard / Alpine 작은 base** + multi-stage = 작은 attack surface. ## 📖 핵심 개념 - CVE: 알려진 취약점. - Base image: 작을수록 좋음. - Multi-stage: build 에 사용한 도구 prod 안 안 옴. - Distroless: shell / package manager 없는 image. ## 💻 코드 패턴 ### Trivy (가장 인기) ```bash # Image 검사 trivy image myapp:latest # Severity 필터 trivy image --severity HIGH,CRITICAL myapp:latest # JSON 출력 (CI) trivy image --format json --output report.json myapp:latest # IaC 검사 trivy config terraform/ # Filesystem (Dockerfile 빌드 전) trivy fs --scanners vuln,secret,misconfig . ``` ### CI 통합 ```yaml # .github/workflows/security.yml - name: Build image run: docker build -t myapp:${{ github.sha }} . - name: Trivy scan uses: aquasecurity/trivy-action@master with: image-ref: myapp:${{ github.sha }} format: 'sarif' output: 'trivy-results.sarif' severity: 'HIGH,CRITICAL' exit-code: '1' - uses: github/codeql-action/upload-sarif@v3 if: always() with: { sarif_file: 'trivy-results.sarif' } ``` ### Grype (alternative) ```bash grype myapp:latest grype dir:./ grype --output json --file report.json myapp:latest ``` ### Docker Scout ```bash docker scout cves myapp:latest docker scout recommendations myapp:latest # base image 권장 ``` ### Multi-stage Dockerfile ```dockerfile # Build stage — 큰 toolchain FROM node:20 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Runtime stage — 작고 안전 FROM node:20-alpine RUN addgroup -g 1001 -S nodejs && adduser -S app -u 1001 WORKDIR /app COPY --from=builder --chown=app:nodejs /app/dist ./dist COPY --from=builder --chown=app:nodejs /app/node_modules ./node_modules USER app EXPOSE 3000 CMD ["node", "dist/index.js"] ``` ### Distroless (가장 작고 안전) ```dockerfile FROM node:20 AS builder # build 작업 FROM gcr.io/distroless/nodejs20-debian12 COPY --from=builder /app/dist /app/dist COPY --from=builder /app/node_modules /app/node_modules WORKDIR /app USER nonroot CMD ["dist/index.js"] ``` → Shell 없음. Exploit 어려움. ### Chainguard images (modern) ```dockerfile FROM cgr.dev/chainguard/node:latest # 매일 패치, 최소 CVE ``` ### Alpine vs Debian ``` Alpine: ~5MB, musl libc — 일부 native module 호환 X Debian slim: ~80MB, glibc — 호환성 좋음 Distroless: ~50MB, 매우 안전 Chainguard: 0 CVE 목표 ``` ### .dockerignore ``` node_modules .git .env *.log coverage .next .cache dist ``` → 빌드 context 작게 + secret leak 방지. ### Secrets in build (안전) ```dockerfile # ❌ ENV 에 secret ENV API_KEY=secret # ❌ ARG 에 secret 도 layer 에 보임 ARG API_KEY RUN echo $API_KEY > /tmp/key # ✅ Build secret (BuildKit) RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \ npm install ``` ```bash docker build --secret id=npmrc,src=$HOME/.npmrc . ``` ### SBOM 생성 (Software Bill of Materials) ```bash # Trivy trivy image --format spdx-json --output sbom.json myapp:latest # Syft syft myapp:latest -o spdx-json > sbom.json # 또는 Docker scout docker scout sbom myapp:latest ``` → Compliance / supply chain. ### Vulnerability database 업데이트 ```bash trivy image --download-db-only ``` CI 매번 자동 download. ### 정책 (OPA / Kyverno) ```yaml # Kubernetes admission — 위험 image 거부 apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: { name: disallow-critical-cves } spec: validationFailureAction: Enforce rules: - name: scan match: { resources: { kinds: [Pod] } } validate: message: "Image has CRITICAL CVEs" pattern: spec: containers: - image: "!*:latest" # 또는 trivy + admission webhook ``` ### Daily rebuild (security patch) ```yaml # Schedule — base image 패치 적용 on: schedule: [{ cron: '0 6 * * *' }] jobs: rebuild: steps: - run: docker pull node:20-alpine # latest base - run: docker build -t myapp:nightly . - run: trivy image --exit-code 1 --severity CRITICAL myapp:nightly - if: success() run: docker push registry/myapp:nightly ``` ### Image signing (Sigstore / cosign) ```bash cosign sign --key cosign.key registry/myapp:v1 cosign verify --key cosign.pub registry/myapp:v1 ``` → Supply chain attack 방어. ### Allowed registries (admission) ``` prod 만 trusted registry: company.io/... public docker.io 차단 / proxy ``` ## 🤔 의사결정 기준 | 작업 | 추천 | |---|---| | OSS / 무료 | Trivy / Grype | | Docker 친화 | Docker Scout | | Enterprise | Snyk / Aqua / Sysdig | | 최소 attack surface | Distroless / Chainguard | | 규제 / SBOM | Syft + cosign | | Quick start | Alpine multi-stage | ## ❌ 안티패턴 - **Latest tag prod**: 추적 불가. semver. - **Root user**: container escape 시 host 권한. - **모든 거 한 stage**: 큰 image + build 도구 노출. - **Secret in image layer**: leak. - **Scan 안 함**: CVE 모름. - **CRITICAL ignore**: scan 의미 없음. - **Base 안 update**: 옛 patch 가 누적. - **Daily rebuild 안**: 새 CVE 패치 안 됨. ## 🤖 LLM 활용 힌트 - Trivy + multi-stage + non-root + distroless 4종. - Daily rebuild + signing. - SBOM 가 compliance 표준. ## 🔗 관련 문서 - [[DevOps_Kubernetes_Basics]] - [[DevSec_Supply_Chain]] - [[DevOps_CI_CD_Pipeline_Patterns]]