## 18.6 容器镜像安全扫描与供应链安全 在 DevOps 流程中,容器镜像安全已经成为不容忽视的关键环节。从开发、构建、存储到部署,镜像的整个生命周期都需要安全防护。本节深入讨论镜像漏洞扫描、软件物料清单(SBOM)、镜像签名验证等供应链安全实践。 ### 18.6.1 容器镜像漏洞扫描工具对比 #### Trivy - 轻量级通用扫描器 Trivy 是由 Aqua Security 开发的开源漏洞扫描器,以其轻量级、快速、准确而闻名,已成为业界标准。 **优点:** - 零依赖,单个二进制文件 - 扫描速度快(秒级) - 支持镜像、文件系统、Git 仓库多种扫描源 - 数据库每日自动更新 - 支持多种输出格式(JSON、表格、SBOM 等) **安装与基本使用:** ```bash # 安装 Trivy curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin # 扫描本地镜像 trivy image nginx:latest # 生成 JSON 格式报告 trivy image -f json -o report.json nginx:latest # 扫描文件系统 trivy fs /path/to/project # 扫描 Git 仓库 trivy repo https://github.com/aquasecurity/trivy ``` **在 CI/CD 中集成:** ```bash # 设置严重程度过滤 trivy image --severity HIGH,CRITICAL \ --exit-code 1 \ myregistry.com/myapp:v1.0.0 ``` #### Grype - 支持多种软件包的扫描器 Grype 由 Anchore 开发,支持更广泛的软件包管理器和语言。 **优点:** - 支持 Java、Python、Go、Ruby、JavaScript 等多种语言的依赖检测 - 与 Syft(SBOM 生成器)配合效果好 - 可自定义漏洞数据库源 - 支持离线扫描模式 **安装与使用:** ```bash # 安装 Grype curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin # 扫描镜像 grype docker:nginx:latest # 与 Syft 配合生成 SBOM syft docker:nginx:latest -o json > sbom.json grype sbom:sbom.json # 扫描特定目录 grype dir:/path/to/app ``` #### Snyk - 完整的安全平台 Snyk 提供了商业级的安全扫描服务,特别适合企业环境。 **特点:** - 支持开源漏洞和许可证扫描 - 与多个 Git 平台深度集成(GitHub、GitLab、Bitbucket) - 提供修复建议和自动化修复 PR - 支持 Kubernetes 部署后安全监控 **基本使用:** ```bash # 安装 Snyk CLI npm install -g snyk # 认证 snyk auth # 扫描镜像 snyk container test docker-archive://image.tar # 监控仓库 snyk monitor --docker ``` **工具对比表:** | 特性 | Trivy | Grype | Snyk | |------|-------|-------|------| | 零依赖 | ✓ | ✗ | ✗ | | 离线模式 | ✓ | ✓ | ✗ | | 许可证扫描 | ✗ | ✓ | ✓ | | 自动修复 | ✗ | ✗ | ✓ | | 开源免费 | ✓ | ✓ | 部分 | | IDE 集成 | ✓ | ✓ | ✓ | ### 18.6.2 SBOM(软件物料清单)生成与管理 SBOM(Software Bill of Materials)是一份详细列表,记录了软件中使用的所有组件、依赖库及其版本信息。SBOM 在供应链安全中至关重要,特别是在发现新的安全漏洞时,能快速定位受影响的应用。 #### Syft - SBOM 生成工具 Syft 是 Anchore 推出的专业 SBOM 生成工具。 **安装:** ```bash curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin ``` **生成 SBOM:** ```bash # 从镜像生成 SBOM(多种格式) syft docker:nginx:latest -o json > sbom.json syft docker:nginx:latest -o spdx > sbom.spdx syft docker:nginx:latest -o cyclonedx > sbom.xml # 从本地文件系统生成 syft dir:/path/to/app -o json > sbom.json # 从 OCI 镜像档案生成 syft oci-archive:image.tar -o json > sbom.json ``` #### CycloneDX 与 SPDX 格式 两种主流的 SBOM 格式: **CycloneDX 格式示例:** ```xml openssl 1.1.1k pkg:deb/debian/openssl@1.1.1k-1+deb11u5 curl 7.74.0-1.3+deb11u1 pkg:deb/debian/curl@7.74.0-1.3+deb11u1 ``` **SPDX 格式示例:** ```json { "SPDXID": "SPDXRef-DOCUMENT", "spdxVersion": "SPDX-2.2", "creationInfo": { "created": "2024-03-01T12:00:00Z", "creators": ["Tool: syft"] }, "packages": [ { "SPDXID": "SPDXRef-Package-openssl", "name": "openssl", "versionInfo": "1.1.1k", "downloadLocation": "NOASSERTION" } ] } ``` #### SBOM 的应用场景 **漏洞关联:** 当新的 CVE 被发现时,可快速查询受影响的应用: ```bash # 使用 Grype 针对 SBOM 进行漏洞扫描 grype sbom:sbom.json --add-cpes-if-none ``` **合规性报告:** 将 SBOM 保存为构建产物,用于审计和合规性检查。 **依赖升级决策:** 通过分析 SBOM 中的依赖版本,制定安全升级计划。 ### 18.6.3 镜像签名与验证(Cosign/Notary) 镜像签名确保镜像的来源可信且未被篡改。两种主流方案是 Cosign 和 Notary。 #### Cosign - 现代签名解决方案 Cosign 是 Sigstore 项目的核心工具,支持无密钥签名,适合现代 CI/CD 流程。 **安装:** ```bash wget https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 chmod +x cosign-linux-amd64 sudo mv cosign-linux-amd64 /usr/local/bin/cosign ``` **生成密钥对(传统方式):** ```bash cosign generate-key-pair # 生成 cosign.key 和 cosign.pub ``` **签名镜像:** ```bash # 使用私钥签名(推送到仓库前) cosign sign --key cosign.key myregistry.com/myapp:v1.0.0 # 系统会提示输入私钥密码 ``` **验证签名:** ```bash # 使用公钥验证 cosign verify --key cosign.pub myregistry.com/myapp:v1.0.0 # 输出结果示例 # Verification successful! # { # "critical": { # "identity": {...}, # "image": {...}, # "type": "cosign container image signature" # }, # "optional": {...} # } ``` **Keyless 签名(推荐用于 CI/CD):** ```bash # 在 GitHub Actions 等 CI 中无需存储密钥 cosign sign --yes myregistry.com/myapp:v1.0.0 # 验证时自动使用 OIDC 令牌验证身份 cosign verify myregistry.com/myapp:v1.0.0 \ --certificate-identity https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main \ --certificate-oidc-issuer https://token.actions.githubusercontent.com ``` #### Docker Content Trust(DCT)与 Notary Docker Content Trust 使用 Notary 实现镜像签名,是 Docker 官方的签名解决方案。 **启用 DCT:** ```bash # 在环境中启用 DCT export DOCKER_CONTENT_TRUST=1 # 此后所有 docker push/pull 都需要签名 docker push myregistry.com/myapp:v1.0.0 # 如果镜像未签名,操作会被拒绝 # 禁用 DCT(仅用于特定操作) docker push --disable-content-trust myregistry.com/myapp:v1.0.0 ``` **签名密钥管理:** ```bash # 首次推送时会提示创建 Delegation Key # 密钥存储在 ~/.docker/trust/private/root_keys/ 和 ~/.docker/trust/private/tuf_keys/ # 查看签名信息 docker inspect --format='{{.RepoDigests}}' myregistry.com/myapp:v1.0.0 ``` ### 18.6.4 供应链安全最佳实践 #### 1. 基础镜像安全 ```dockerfile # ❌ 不推荐:使用 latest 标签 FROM ubuntu:latest RUN apt-get update && apt-get install -y curl # ✓ 推荐:固定基础镜像版本和摘要 FROM ubuntu:22.04@sha256:a6d2b38300ce017add71440577d5b0a90460d0e6e0e14...(完整 64 位哈希) RUN apt-get update && apt-get install -y curl=7.68.0-1ubuntu1 ``` #### 2. 构建时扫描 在 Dockerfile 中集成安全扫描: ```dockerfile FROM golang:1.20-alpine AS builder WORKDIR /app COPY . . # 使用 Trivy 扫描源代码 RUN apk add --no-cache curl && \ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin && \ trivy fs . --exit-code 1 --severity HIGH,CRITICAL RUN go build -o app . FROM alpine:3.17@sha256:abcd1234...(请替换为实际完整的 64 位摘要哈希) COPY --from=builder /app/app /app ``` #### 3. 运行时镜像扫描策略 ```bash # 镜像构建完成后立即扫描 trivy image --severity HIGH,CRITICAL \ --exit-code 1 \ --timeout 30m \ $IMAGE_NAME:$IMAGE_TAG # 定期扫描已部署的镜像 trivy image --scanners vuln,misconfig registry:5000/myapp:latest ``` #### 4. 镜像仓库安全配置 **Harbor(私有镜像仓库)的安全扫描:** ```yaml # harbor.yml 配置示例 trivy: enabled: true # 启用镜像扫描 image_source: "Official" # 默认扫描配置 scan_on_push: true # 推送时自动扫描 scan_all: true # 扫描仓库中的所有镜像 ``` #### 5. 政策执行(Admission Controller) 在 Kubernetes 环境中使用 Admission Webhook 强制镜像签名和扫描: ```yaml apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: image-security-policy webhooks: - name: image-security.example.com clientConfig: service: name: image-security-webhook namespace: security path: "/validate" rules: - operations: ["CREATE", "UPDATE"] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] admissionReviewVersions: ["v1"] sideEffects: None ``` ### 18.6.5 CI/CD 中集成安全扫描 #### GitHub Actions 工作流示例 ```yaml name: Build and Scan Image on: push: branches: [main, develop] pull_request: branches: [main] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-scan: runs-on: ubuntu-latest permissions: contents: read packages: write security-events: write steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build Docker image uses: docker/build-push-action@v6 with: context: . push: false load: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - name: Run Trivy vulnerability scan uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest format: 'sarif' output: 'trivy-results.sarif' severity: 'HIGH,CRITICAL' - name: Upload Trivy results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif' - name: Generate SBOM uses: anchore/sbom-action@v0 with: image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest format: cyclonedx-json output-file: sbom-cyclonedx.json - name: Upload SBOM uses: actions/upload-artifact@v4 with: name: sbom path: sbom-cyclonedx.json - name: Sign image with Cosign if: github.event_name == 'push' run: | cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - name: Login to Registry and Push if: github.event_name == 'push' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Push image uses: docker/build-push-action@v6 with: context: . push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ``` #### GitLab CI 工作流示例 ```yaml stages: - build - scan - sign - push variables: REGISTRY: registry.gitlab.com IMAGE_NAME: $REGISTRY/$CI_PROJECT_PATH build: stage: build image: docker:latest services: - docker:dind script: - docker build -t $IMAGE_NAME:$CI_COMMIT_SHA . - docker save $IMAGE_NAME:$CI_COMMIT_SHA > image.tar scan:trivy: stage: scan image: aquasec/trivy:latest script: - trivy image --severity HIGH,CRITICAL --exit-code 1 docker-archive://image.tar allow_failure: false scan:grype: stage: scan image: anchore/grype:latest script: - grype docker-archive://image.tar generate:sbom: stage: scan image: anchore/syft:latest script: - syft docker-archive://image.tar -o cyclonedx > sbom.xml artifacts: reports: sbom: sbom.xml sign: stage: sign image: gcr.io/projectsigstore/cosign:latest script: - cosign sign --key $COSIGN_KEY $IMAGE_NAME:$CI_COMMIT_SHA only: - main push: stage: push image: docker:latest services: - docker:dind script: - docker load < image.tar - docker login -u $REGISTRY_USER -p $REGISTRY_PASSWORD $REGISTRY - docker push $IMAGE_NAME:$CI_COMMIT_SHA only: - main ``` ### 18.6.6 常见问题与最佳实践 **Q: 扫描报告中有过时的 CVE,如何处理?** A: 某些 CVE 可能已经被修复但数据库未更新,可以: - 手动验证安全补丁是否已应用 - 使用工具的忽略列表功能(如 Trivy 的 `.trivyignore`) - 定期更新扫描工具和漏洞数据库 **Q: 如何平衡镜像大小和安全性?** A: - 使用多阶段构建减少最终镜像大小 - 使用精简基础镜像(Alpine、Distroless) - 定期更新依赖而不是一味求小 - 优先安全性,体积次之 **Q: 如何管理和轮换签名密钥?** A: - 在密钥管理系统(如 HashiCorp Vault)中存储密钥 - 定期轮换密钥(建议每 90 天) - 使用 Keyless 签名消除密钥管理复杂性 - 保留密钥轮换的审计日志