18.6 容器映象安全掃描與供應鏈安全
在 DevOps 流程中,容器映象安全已經成為不容忽視的關鍵環節。從開發、建立、儲存到部署,映象的整個生命週期都需要安全防護。本節深入討論映象漏洞掃描、軟體物料清單(SBOM)、映象簽名驗證等供應鏈安全實踐。
18.6.1 容器映象漏洞掃描工具對比
Trivy - 輕量級通用掃描器
Trivy 是由 Aqua Security 開發的開源漏洞掃描器,以其輕量級、快速、準確而聞名,已成為業界標準。
優點:
- 零依賴,單個二進位檔案
- 掃描速度快(秒級)
- 支援映象、檔案系統、Git 倉庫多種掃描源
- 資料庫每日自動更新
- 支援多種輸出格式(JSON、表格、SBOM 等)
安裝與基本使用:
# 安裝 Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh -o install-trivy.sh
less install-trivy.sh # 先審閱指令碼內容
sudo sh install-trivy.sh -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 中整合:
# 設定嚴重程度過濾
trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
myregistry.com/myapp:v1.0.0
Grype - 支援多種軟體套件的掃描器
Grype 由 Anchore 開發,支援更廣泛的軟體套件管理器和語言。
優點:
- 支援 Java、Python、Go、Ruby、JavaScript 等多種語言的依賴檢測
- 與 Syft(SBOM 產生器)配合效果好
- 可自定義漏洞資料庫源
- 支援離線掃描模式
安裝與使用:
# 安裝 Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh -o install-grype.sh
less install-grype.sh
sudo sh install-grype.sh -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 部署後安全監控
基本使用:
# 安裝 Snyk CLI
npm install -g snyk
# 認證
snyk auth
# 掃描映象
snyk container test docker-archive://image.tar
# 監控倉庫
snyk monitor --docker
此外,Docker 官方提供的 Docker Scout(docker scout CLI)可以直接在 Docker Desktop 或指令行中對映象進行漏洞掃描和依賴分析,適合日常開發流程中快速檢查:
# 掃描本地映象
$ docker scout cves myapp:latest
# 檢視映象的依賴和漏洞摘要
$ docker scout quickview myapp:latest
工具對比表:
| 屬性 | Docker Scout | Trivy | Grype | Snyk |
|---|---|---|---|---|
| Docker 整合 | ✓(原生) | 需安裝 | 需安裝 | 需安裝 |
| 零依賴 | ✗ | ✓ | ✗ | ✗ |
| 離線模式 | ✗ | ✓ | ✓ | ✗ |
| 許可證掃描 | ✓ | ✓ | ✓ | ✓ |
| 自動修復建議 | ✓ | ✗ | ✗ | ✓ |
| 開源免費 | 部分 | ✓ | ✓ | 部分 |
| IDE 整合 | ✓ | ✓ | ✓ | ✓ |
18.6.2 SBOM(軟體物料清單)生成與管理
SBOM(Software Bill of Materials)是一份詳細清單,記錄了軟體中使用的所有元件、依賴庫及其版本訊息。SBOM 在供應鏈安全中至關重要,特別是在發現新的安全漏洞時,能快速定位受影響的應用。
Syft - SBOM 生成工具
Syft 是 Anchore 推出的專業 SBOM 生成工具。
安裝:
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh -o install-syft.sh
less install-syft.sh
sudo sh install-syft.sh -b /usr/local/bin
生成 SBOM:
# 從映象生成 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 version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" version="1">
<components>
<component type="library">
<name>openssl</name>
<version>1.1.1k</version>
<purl>pkg:deb/debian/openssl@1.1.1k-1+deb11u5</purl>
</component>
<component type="library">
<name>curl</name>
<version>7.74.0-1.3+deb11u1</version>
<purl>pkg:deb/debian/curl@7.74.0-1.3+deb11u1</purl>
</component>
</components>
</bom>
SPDX 格式範例:
{
"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 被發現時,可快速查詢受影響的應用:
# 使用 Grype 針對 SBOM 進行漏洞掃描
grype sbom:sbom.json --add-cpes-if-none
合規性報告:
將 SBOM 儲存為建立產物,用於審計和合規性檢查。
依賴升級決策:
透過分析 SBOM 中的依賴版本,制定安全升級計劃。
18.6.3 映象簽名與驗證
映象簽名確保映象的來源可信且未被篡改。新專案通常優先評估 Cosign (Sigstore) 或 Notary Project 的 Notation;Docker Content Trust / Notary v1 屬於歷史路徑。
Cosign - 現代簽名解決方案
Cosign 是 Sigstore 專案的核心工具,支援無金鑰簽名,適合現代 CI/CD 流程。
安裝:
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
生成金鑰對(傳統方式):
cosign generate-key-pair
# 生成 cosign.key 和 cosign.pub
簽名映象:
# 先推送映象,再使用不可變 digest 簽名
IMAGE_DIGEST="myregistry.com/myapp@sha256:<digest>"
cosign sign --key cosign.key "$IMAGE_DIGEST"
# 系統會提示輸入私鑰密碼
驗證簽名:
# 使用公鑰驗證
cosign verify --key cosign.pub "$IMAGE_DIGEST"
# 輸出結果範例
# Verification successful!
# {
# "critical": {
# "identity": {...},
# "image": {...},
# "type": "cosign container image signature"
# },
# "optional": {...}
# }
Keyless 簽名(推薦用於 CI/CD):
# 在 GitHub Actions 等 CI 中無需儲存金鑰
cosign sign --yes "$IMAGE_DIGEST"
# 驗證時檢查簽名證書中的 OIDC 身份宣告是否對應預期 workflow 與 issuer
cosign verify "$IMAGE_DIGEST" \
--certificate-identity https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Notary Project / Notation
Notation 是 Notary Project 的 OCI 簽名工具,適合希望採用 CNCF Notary Project 規範、或使用 registry/雲廠商原生整合的團隊。與 Cosign 類似,生產環境應圍繞不可變 digest 做簽名和策略校驗。
# 簽名已推送的映象 digest
notation sign myregistry.com/myapp@sha256:<digest>
# 驗證簽名
notation verify myregistry.com/myapp@sha256:<digest>
Docker Content Trust 與 Notary v1
注意:DCT 退役時間線
Docker 已宣佈退役 Content Trust,Docker Official Images 的部分 DCT 簽名證書自 2025 年 8 月起已陸續過期;完整退役節奏應以 Docker 官方公告和你所使用的 registry 服務商文件為準。不要把雲廠商特定日期直接當作 Docker 全域時間線。
新專案應優先使用上文介紹的 Cosign (Sigstore) 或 registry 原生簽名/證明能力;現有 DCT 使用者應先盤點依賴、驗證替代方案,再製定遷移計劃。
Docker Content Trust 使用 Notary v1 實現映象簽名,是 Docker 官方的傳統簽名解決方案,不建議新專案採用。
歷史用法範例(不建議新專案採用):
# 在環境中啟用 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
簽名金鑰管理:
# 首次推送時會提示建立 Delegation Key
# 金鑰儲存在 ~/.docker/trust/private/root_keys/ 和 ~/.docker/trust/private/tuf_keys/
# 檢視 DCT/Notary 信任資料;RepoDigests 只是內容摘要,不等於簽名驗證
docker trust inspect --pretty myregistry.com/myapp:v1.0.0
18.6.4 供應鏈安全最佳實踐
1. 基礎映象安全
# ❌ 不推薦:使用 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 --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
2. 建立時掃描
在 CI/CD 建立階段整合安全掃描,避免在 Dockerfile 裡從分支 URL 下載並執行遠端安裝指令碼:
docker run --rm \
-v "$PWD:/work" \
-w /work \
aquasec/trivy:latest \
fs . --exit-code 1 --severity HIGH,CRITICAL
3. 執行時映象掃描策略
# 映象建立完成後立即掃描
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(私有映象倉庫)的安全掃描:
Harbor 的掃描器應在管理介面的 Interrogation Services / Scanner 中設定;推送自動掃描通常在專案級 Configuration 中啟用。不要把掃描策略誤寫成通用 harbor.yml 頂層欄位。實際部署時應按所用 Harbor 版本文件設定 scanner、專案策略和漏洞阻斷閾值。
5. 政策執行
在 Kubernetes 環境中使用 Admission Webhook 強制映象簽名和掃描:
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 工作流範例
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
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Install Cosign
if: github.event_name == 'push'
uses: sigstore/cosign-installer@v3
- name: Login to Registry
if: github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
if: github.event_name == 'push'
id: build-push
uses: docker/build-push-action@v7
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
provenance: mode=max
sbom: true
- name: Build Docker image for pull request scan
if: github.event_name == 'pull_request'
uses: docker/build-push-action@v7
with:
context: .
push: false
load: true
tags: local/${{ env.IMAGE_NAME }}:pr
- name: Run Trivy vulnerability scan
# 安全提醒:2026 年 3 月 19 日 Trivy GitHub Actions 遭受供應鏈攻擊,
# 76 個版本標籤被劫持。務必使用不可變的 commit SHA 引用,而非可變標籤。
# 使用前請到 https://github.com/aquasecurity/trivy-action/releases 核實 SHA 對應正確版本。
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: ${{ github.event_name == 'push' && format('{0}/{1}@{2}', env.REGISTRY, env.IMAGE_NAME, steps.build-push.outputs.digest) || format('local/{0}:pr', env.IMAGE_NAME) }}
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: ${{ github.event_name == 'push' && format('{0}/{1}@{2}', env.REGISTRY, env.IMAGE_NAME, steps.build-push.outputs.digest) || format('local/{0}:pr', env.IMAGE_NAME) }}
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 }}@${{ steps.build-push.outputs.digest }}
GitLab CI 工作流範例
stages:
- build
- scan
- push
- sign
variables:
REGISTRY: registry.gitlab.com
IMAGE_NAME: $REGISTRY/$CI_PROJECT_PATH
build:
stage: build
image: docker:<version>-cli@sha256:<docker-cli-digest>
services:
- name: docker:<version>-dind@sha256:<docker-dind-digest>
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:<version>@sha256:<trivy-digest>
script:
- trivy image --severity HIGH,CRITICAL --exit-code 1 docker-archive://image.tar
allow_failure: false
scan:grype:
stage: scan
image: anchore/grype:<version>@sha256:<grype-digest>
script:
- grype docker-archive://image.tar
generate:sbom:
stage: scan
image: anchore/syft:<version>@sha256:<syft-digest>
script:
- syft docker-archive://image.tar -o cyclonedx > sbom.xml
artifacts:
reports:
cyclonedx: sbom.xml
# 如需讓 GitLab 安全面板攝取容器掃描結果,應輸出 GitLab 相容的 container_scanning 報告。
push:
stage: push
image: docker:<version>-cli@sha256:<docker-cli-digest>
services:
- name: docker:<version>-dind@sha256:<docker-dind-digest>
script:
- docker load < image.tar
- echo "$REGISTRY_PASSWORD" | docker login --username "$REGISTRY_USER" --password-stdin "$REGISTRY"
- docker push $IMAGE_NAME:$CI_COMMIT_SHA
- DIGEST=$(docker buildx imagetools inspect "$IMAGE_NAME:$CI_COMMIT_SHA" --format '{{.Manifest.Digest}}')
- echo "$IMAGE_NAME@$DIGEST" > image-digest.txt
artifacts:
paths:
- image-digest.txt
only:
- main
sign:
stage: sign
image: gcr.io/projectsigstore/cosign:<version>@sha256:<cosign-digest>
script:
- cosign sign --key $COSIGN_KEY "$(cat image-digest.txt)"
only:
- main
18.6.6 常見問題與最佳實踐
Q: 掃描報告中有過時的 CVE,如何處理?
A: 某些 CVE 可能已經被修復但資料庫未更新,可以:
- 手動驗證安全補丁是否已應用
- 使用工具的忽略清單功能(如 Trivy 的
.trivyignore) - 定期更新掃描工具和漏洞資料庫
Q: 如何平衡映象大小和安全性?
A:
- 使用多階段建立減少最終映象大小
- 使用精簡基礎映象(Alpine、Distroless)
- 定期更新依賴而不是一味求小
- 優先安全性,體積次之
Q: 如何管理和輪換簽名金鑰?
A:
- 在金鑰管理系統(如 HashiCorp Vault)中儲存金鑰
- 定期輪換金鑰(建議每 90 天)
- 使用 Keyless 簽名消除金鑰管理複雜性
- 保留金鑰輪換的審計日誌