iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Kubernetes

資安這條路:從攻擊者視角看 Kubernetes系列 第 23 篇

Day 23|防禦供應鏈:映像簽章 + 掃描 + ImagePolicyWebhook

  • 分享至 

  • xImage
  •  

資安這條路:從攻擊者視角看 Kubernetes

從 Build 到 Deploy 的信任鏈——確保進入叢集的每一個映像都是安全且已驗證的。

前置準備

所有指令都在 koad 專案目錄下執行。如果還沒 clone,請先參考 Day 1 的 Step 0。

開始前請確認以下環境就緒。

1. 安裝 Cosign 和 Trivy

# 主機終端
cosign version       # 需要 v2.0+
trivy --version      # 需要 v0.50+

如果未安裝:

> # Cosign
> go install github.com/sigstore/cosign/v2/cmd/cosign@latest
> # Trivy
> curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh

2. 確認 Kyverno 運行中

# 主機終端
kubectl get pods -n kyverno | head -3

3. 確認有可用的 Container Registry

本日實作需要一個 registry 來推送已簽章的映像。可使用 Docker Hub、GitHub Container Registry,或本地 registry。

學習目標

完成本日實作後,你將能夠:

  • 用 Cosign 簽署和驗證容器映像(keypair 和 keyless 模式)
  • 用 Trivy 掃描映像漏洞並設定 CI/CD 品質門檻
  • 配置 Kyverno verifyImages 策略阻擋未簽章映像
  • 理解 ImagePolicyWebhook 的 API Server 層級控制
  • 建立完整的 Build → Sign → Scan → Verify → Deploy 信任鏈

防禦架構

Day 22 展示了供應鏈攻擊的四個攻擊面。今天建立完整的防禦體系——從 CI/CD 到 K8s Admission,每一步都有安全檢查。

Source Code → Build → Sign → Scan → Registry → Verify → Deploy

防禦矩陣

攻擊面 防禦措施 阻擋時機
映像竄改 Cosign 簽章 + Kyverno 驗簽 Admission
已知漏洞 Trivy 掃描 + CI/CD gate Build
非信任 Registry Kyverno require-trusted-registry Admission
映像策略 ImagePolicyWebhook API Server
依賴漏洞 SBOM + grype Build

Cosign 映像簽章

什麼是 Cosign

Cosign 是 Sigstore 專案的映像簽章工具,讓你可以用密碼學方式證明映像「確實是由信任的人/流程建構的」。

Cosign 簽章流程

傳統金鑰對模式

# 1. 產生金鑰對
$ cosign generate-key-pair
Enter password for private key: ********
Private key written to cosign.key
Public key written to cosign.pub

# 2. 簽署映像(對映像的 digest 簽章)
$ cosign sign --key cosign.key myregistry.io/myapp:v1.0
Pushing signature to: myregistry.io/myapp:sha256-abc123.sig

# 3. 驗證簽章
$ cosign verify --key cosign.pub myregistry.io/myapp:v1.0
Verification for myregistry.io/myapp:v1.0 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key

[{"critical":{"identity":{"docker-reference":"myregistry.io/myapp"},
"image":{"docker-manifest-digest":"sha256:abc123..."},...}]

原理深入:簽章存在哪裡

# Cosign 將簽章作為 OCI artifact 推送到 Registry
# 與映像放在同一個 Repository,但用特殊的 tag

# 映像:myregistry.io/myapp:v1.0
# 簽章:myregistry.io/myapp:sha256-<digest>.sig

# 這意味著:
# 1. 簽章跟映像在一起,不需要額外的儲存
# 2. push/pull 映像時,簽章一起移動
# 3. 任何支援 OCI 的 Registry 都能存放簽章

Keyless 簽章(推薦)

Cosign 支援 OIDC-based keyless 簽章——不需要管理金鑰,使用 CI/CD 的身份(GitHub Actions OIDC token)直接簽章:

# .github/workflows/build-and-sign.yaml
name: Build and Sign
on:
  push:
    tags: ['v*']

permissions:
  contents: read
  packages: write
  id-token: write         # ← 需要 OIDC token 權限

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Cosign
        uses: sigstore/cosign-installer@v3

      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        id: build
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}

      - name: Sign image (keyless)
        run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
        env:
          COSIGN_EXPERIMENTAL: "true"

Keyless vs 金鑰對

項目 金鑰對模式 Keyless 模式
金鑰管理 需要保管私鑰 不需要
身份證明 金鑰持有者 OIDC identity(GitHub/Google)
離線使用 可以 不行(需要 Sigstore 服務)
透明度 無公開記錄 Rekor 透明度日誌
適用場景 離線/企業內部 CI/CD 自動化
CKS 考試 常考 了解即可

Cosign Attestations(SLSA 合規)

Cosign 不只能簽章,還能附加 attestations(證明)到映像:

# 附加 SBOM attestation
$ cosign attest --key cosign.key \
    --predicate sbom.spdx.json \
    --type spdxjson \
    myregistry.io/myapp:v1.0

# 附加 Trivy 掃描結果
$ trivy image --format cosign-vuln myregistry.io/myapp:v1.0 > vuln.json
$ cosign attest --key cosign.key \
    --predicate vuln.json \
    --type vuln \
    myregistry.io/myapp:v1.0

# 驗證 attestation
$ cosign verify-attestation --key cosign.pub \
    --type spdxjson \
    myregistry.io/myapp:v1.0

SLSA(Supply-chain Levels for Software Artifacts)是 Google 提出的供應鏈安全框架。Cosign attestation 是達到 SLSA Level 2+ 的關鍵工具。


Kyverno 驗簽策略

只允許已簽章的映像

# defense/kyverno-verify-images.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce    # ← 阻擋未簽章映像
  background: false
  rules:
    - name: verify-cosign
      match:
        any:
          - resources:
              kinds: ["Pod"]
      verifyImages:
        - imageReferences:
            - "myregistry.io/*"
          attestors:
            - entries:
                - keys:
                    publicKeys: |
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
                      -----END PUBLIC KEY-----

驗證效果

# 未簽章的映像 → 被阻擋
$ kubectl run test --image=myregistry.io/unsigned-app:latest
Error from server: admission webhook "mutate.kyverno.svc-fail" denied the request:
  image verification failed for myregistry.io/unsigned-app:latest:
  signature not found

# 已簽章的映像 → 正常部署
$ kubectl run test --image=myregistry.io/signed-app:v1.0
pod/test created

Kyverno 映像白名單

# 只允許來自信任 Registry 的映像
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-trusted-registry
spec:
  validationFailureAction: Enforce
  rules:
    - name: trusted-registry-only
      match:
        any:
          - resources:
              kinds: ["Pod"]
      validate:
        message: "Only images from trusted registries are allowed"
        pattern:
          spec:
            containers:
              - image: "gcr.io/myproject/* | ghcr.io/myorg/* | myregistry.io/*"
            =(initContainers):
              - image: "gcr.io/myproject/* | ghcr.io/myorg/* | myregistry.io/*"

Kyverno 強制使用 digest

# 禁止使用 :latest tag,要求使用 digest
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-image-digest
spec:
  validationFailureAction: Enforce
  rules:
    - name: no-latest-tag
      match:
        any:
          - resources:
              kinds: ["Pod"]
      validate:
        message: "Images must use digest (@sha256:), not :latest"
        deny:
          conditions:
            any:
              - key: "{{ images.containers.*.tag || '' }}"
                operator: AnyIn
                value: ["latest", ""]

Trivy 映像漏洞掃描

CLI 掃描

# 掃描映像漏洞
$ trivy image nginx:1.25
nginx:1.25 (debian 12.4)

Total: 142 (UNKNOWN: 0, LOW: 85, MEDIUM: 40, HIGH: 15, CRITICAL: 2)

Library Vulnerability Severity Fixed Version
libssl3 CVE-2024-XXX CRITICAL 3.0.13-1~deb12u1
openssl CVE-2024-XXX CRITICAL 3.0.13-1~deb12u1
curl CVE-2024-XXX HIGH 7.88.1-10+deb12u5

# 只顯示 HIGH + CRITICAL
$ trivy image --severity HIGH,CRITICAL nginx:1.25

# 掃描 KOAD 自訂映像
$ trivy image koad/vulnerable-webapp:latest

# 掃描 K8s YAML 的安全配置
$ trivy config scenarios/privilege-escalation/

Trivy 進階用法

# 掃描 Dockerfile(靜態分析)
$ trivy config --policy my-policies/ Dockerfile

# 掃描 Helm chart
$ trivy config --helm-values values.yaml mychart/

# 掃描本機檔案系統
$ trivy fs --scanners vuln,secret .

# 產生 SARIF 格式(GitHub Code Scanning)
$ trivy image --format sarif -o results.sarif nginx:1.25

# 用自訂 .trivyignore 忽略特定 CVE
$ cat .trivyignore
CVE-2024-XXXX    # 已確認不影響我們的使用場景
CVE-2024-YYYY    # 上游尚未修復,暫時忽略

CI/CD 整合

# GitHub Actions
- name: Trivy scan
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.IMAGE }}
    severity: 'CRITICAL,HIGH'
    exit-code: '1'              # 發現漏洞時 CI 失敗
    format: 'sarif'
    output: 'trivy-results.sarif'

- name: Upload Trivy results
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: 'trivy-results.sarif'

KubeSec 靜態分析

# 掃描 K8s YAML 的安全性
$ kubesec scan scenarios/privilege-escalation/S13-privileged-escape.yaml

[
  {
    "object": "Pod/privileged-pod.koad",
    "valid": true,
    "score": -30,
    "scoring": {
      "critical": [
        {"id": "Privileged", "reason": "Privileged pods can escalate to root"},
        {"id": "HostPID", "reason": "Sharing host PID namespace"}
      ]
    }
  }
]

Score -30 表示安全配置極差——KOAD 的攻擊場景當然如此。


ImagePolicyWebhook

原理

ImagePolicyWebhook 是 K8s 原生的映像准入控制,在 API Server 層級驗證映像。與 Kyverno 不同,它是 API Server 內建的 admission plugin。

Pod 建立請求

配置步驟

# 1. /etc/kubernetes/admission-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
  - name: ImagePolicyWebhook
    configuration:
      imagePolicy:
        kubeConfigFile: /etc/kubernetes/image-policy-webhook.kubeconfig
        allowTTL: 50          # 通過的快取時間(秒)
        denyTTL: 50           # 拒絕的快取時間(秒)
        retryBackoff: 500     # 重試間隔(毫秒)
        defaultAllow: false   # ← 預設拒絕(安全)
# 2. /etc/kubernetes/image-policy-webhook.kubeconfig
apiVersion: v1
kind: Config
clusters:
  - name: image-policy
    cluster:
      server: https://image-policy-webhook.svc:443
      certificate-authority: /etc/kubernetes/pki/webhook-ca.crt
users:
  - name: api-server
    user:
      client-certificate: /etc/kubernetes/pki/webhook-client.crt
      client-key: /etc/kubernetes/pki/webhook-client.key
contexts:
  - name: default
    context:
      cluster: image-policy
      user: api-server
current-context: default
# 3. API Server flag
--admission-plugins=...,ImagePolicyWebhook
--admission-control-config-file=/etc/kubernetes/admission-config.yaml

defaultAllow 的選擇

設定 行為 風險
defaultAllow: true Webhook 不可用時允許所有映像 安全風險:Webhook 故障 = 無防禦
defaultAllow: false Webhook 不可用時拒絕所有映像 可用性風險:Webhook 故障 = 無法部署

CKS 考試通常要求設 defaultAllow: false(安全優先)。

ImagePolicyWebhook vs Kyverno

項目 ImagePolicyWebhook Kyverno
類型 K8s 內建 第三方 CRD
配置位置 API Server flag + 外部檔案 ClusterPolicy CRD
需要外部服務 是(Webhook server) 否(自帶)
功能 只驗證映像 驗證 + 變異 + 產生
修改 API Server 需要 不需要
CKS 考試 常考 也常考
生產環境推薦 適合已有 webhook 基礎設施 更靈活,推薦

完整的供應鏈安全流程

開發者 push code


VEX 與 in-toto 供應鏈證明

Cosign 簽章和 SBOM 確保了「映像來自信任來源」和「映像包含哪些元件」,但還有兩個問題需要解決:

  1. 掃描出的 CVE 是否真的影響我的映像?(VEX)
  2. 建構過程中的每一步是否都可驗證?(in-toto)

VEX (Vulnerability Exploitability eXchange)

VEX 是一種機器可讀的聲明格式,用來表達「某個 CVE 是否真的影響特定產品」。漏洞掃描工具常產生大量誤報——例如映像中有 OpenSSL 的漏洞,但該漏洞只影響 DTLS,而你的應用根本不用 DTLS。VEX 讓你正式聲明這種判斷:

# 產生 VEX attestation 並附加到映像
cosign attest --key cosign.key \
    --predicate vex-statement.json \
    --type vuln \
    myregistry.io/myapp:v1.0

VEX 狀態值:

狀態 意義
not_affected CVE 存在於元件中,但不影響此產品
affected CVE 確實影響此產品
fixed 已修復
under_investigation 調查中

in-toto 供應鏈框架

in-toto 為軟體供應鏈的每一步(source、build、test、package、deploy)產生簽章的 attestation,確保沒有步驟被跳過或竄改:

Source Code → [attestation] → Build → [attestation] → Test → [attestation] → Package → [attestation] → Deploy

每個步驟由指定的角色執行,產生的 attestation 包含輸入/輸出的 hash 和執行者的簽章。最終驗證時,比對所有 attestation 是否符合預定義的 layout(供應鏈規格)。

Notary v2 (notation)

Notary v2(CLI 工具名為 notation)是另一個映像簽章工具,由 CNCF Notary 專案維護。與 Cosign 的差異:

面向 Cosign Notary v2 (notation)
簽章儲存 OCI artifact(tag-based) OCI artifact(referrers API)
標準 Sigstore 生態系 OCI 1.1 Referrers 標準
Cloud 支援 廣泛 AWS ECR、Azure ACR 原生支援
Keyless 支援(Fulcio + Rekor) 不支援
適用場景 開源 / CI/CD 自動化 企業 / 合規要求

與現有防禦的整合

VEX + in-toto 補完了 Day 23 已覆蓋的供應鏈安全體系:

層級 工具 解決的問題
映像完整性 Cosign 簽章 映像是否被竄改
元件清單 SBOM (Syft/Trivy) 映像包含哪些套件
漏洞掃描 Trivy 有哪些已知 CVE
漏洞評估 VEX CVE 是否真的影響此產品
建構驗證 in-toto 建構過程是否可信
合規等級 SLSA 達到哪個供應鏈安全等級

踩坑提醒

踩坑 1:Kyverno verifyImages 在 Audit 模式不會阻擋

# Audit 模式只記錄違規,不阻擋
validationFailureAction: Audit    # ← 不會阻擋

# 要真的阻擋必須用 Enforce
validationFailureAction: Enforce  # ← 會阻擋

# 建議:先 Audit 觀察一段時間,確認沒有誤報後再切 Enforce

踩坑 2:ImagePolicyWebhook 需要重啟 API Server

# 修改 API Server 參數後需要重啟
# 在 kubeadm 叢集中,修改 /etc/kubernetes/manifests/kube-apiserver.yaml
# kubelet 會自動偵測變更並重啟 API Server

# 如果 API Server 沒有啟動,檢查日誌:
$ crictl logs $(crictl ps -a | grep kube-apiserver | head -1 | awk '{print $1}')

踩坑 3:Cosign 公鑰必須安全傳遞

# 如果攻擊者能替換 cosign.pub,就能用自己的私鑰簽章惡意映像
# 公鑰應該:
# 1. 存在安全的 Secret Management(HashiCorp Vault)
# 2. 透過 ConfigMap 掛載(受 RBAC 保護)
# 3. 或直接寫在 Kyverno ClusterPolicy 中

踩坑 4:Trivy 需要網路下載漏洞資料庫

# 首次執行 Trivy 需要下載 ~30MB 的漏洞資料庫
# 在離線環境需要預先下載
$ trivy image --download-db-only
$ trivy image --skip-db-update nginx:1.25    # 使用本地 DB

CKS 考點

考點 本日內容 權重
映像簽章 Cosign 簽署和驗證 Supply Chain 20%
映像掃描 Trivy 漏洞掃描 Supply Chain 20%
ImagePolicyWebhook API Server 配置 Supply Chain 20%
准入控制 Kyverno verifyImages Supply Chain 20%

供應鏈安全佔 CKS 考試 20% 的權重,是最重要的領域之一。


清理與重設

# 主機終端 — 移除測試用的映像驗簽策略
kubectl delete clusterpolicy koad-verify-images 2>/dev/null

# 如果配置了 ImagePolicyWebhook,還原 API Server 設定
# 移除 --admission-control-config-file 和 --enable-admission-plugins 中的 ImagePolicyWebhook

Kyverno 的映像白名單策略(koad-require-trusted-registry)是 Audit 模式,不影響攻擊場景,不需要移除。


完成度自我檢查

  • [ ] 成功用 Cosign 產生 keypair 並簽署映像
  • [ ] 能用 Trivy 掃描映像並解讀漏洞報告
  • [ ] 成功配置 Kyverno verifyImages 策略
  • [ ] 理解 keyless signing(無金鑰簽章)的運作原理
  • [ ] 能解釋 Build → Sign → Scan → Verify → Deploy 完整流程
  • [ ] 知道 ImagePolicyWebhook 與 Kyverno 的差異和適用場景

本日小結

你完成了什麼

  • [x] 用 Cosign 產生 keypair 並簽署映像
  • [x] 用 Trivy 掃描映像漏洞(--severity CRITICAL --exit-code 1)
  • [x] 配置 Kyverno verifyImages 策略驗證映像簽章
  • [x] 理解 ImagePolicyWebhook 的 API Server 層級控制
  • [x] 建立完整的 Build → Sign → Scan → Verify → Deploy 流程

關鍵帶走

  1. Cosign:映像簽章建立信任鏈——keyless 模式 + CI/CD 自動簽章
  2. Trivy:CI/CD 中自動掃描漏洞——有 CRITICAL 漏洞就擋住
  3. Kyverno:Admission 層驗簽 + 映像白名單 + 禁止 :latest——三重保障
  4. ImagePolicyWebhook:K8s 原生映像策略——API Server 層級控制

下一步

明天 Day 24 是全系列的高潮——KOAD Kill Chain:35 場景端到端全自動攻擊演練。



上一篇
Day 22|供應鏈攻擊:映像分層藏密 + Registry 滲透 + CI/CD 投毒
下一篇
Day 24|KOAD Kill Chain:35 場景端到端全自動攻擊鏈演練
系列文
資安這條路:從攻擊者視角看 Kubernetes 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言