Go容器安全掃描實戰:Trivy SBOM供應鏈安全的6個關鍵實踐

云原生

當供應鏈攻擊遇上鏡像漏洞:容器安全的至暗時刻

凌晨3點,告警狂響。線上容器被植入挖礦程式,CPU飆到99%。排查發現:基礎鏡像golang:1.22-alpine存在未修復的CVE-2024-1234,攻擊者通過依賴傳遞鏈注入惡意代碼,而你的CI/CD沒有任何安全門禁。更糟的是,你根本不知道鏡像裡到底裝了哪些依賴——沒有SBOM,沒有簽名驗證,合規審計一問三不知。

這不是假設。2024年XZ Utils後門事件、Log4Shell漏洞、SolarWinds供應鏈攻擊,都在證明容器安全不是可選項,而是生存底線。你需要漏洞掃描發現風險、SBOM讓依賴透明、鏡像簽名保證可信、CI/CD門禁阻斷威脅。本文將從6個關鍵實踐出發,幫你構建Go容器供應鏈安全防線。


核心概念速查

概念 核心思想 關鍵工具 典型場景
容器安全(Container Security) 鏡像構建到運行全生命週期防護 Trivy, Clair 鏡像漏洞掃描、運行時防護
SBOM(軟體物料清單) 列出軟體所有組件及依賴關係 syft, trivy sbom 依賴透明化、合規審計
CVE(通用漏洞揭露) 標準化漏洞標識與評級 NVD, OSV 漏洞追蹤與修復優先級
Trivy 全能型容器安全掃描器 trivy 鏡像/檔案系統/Git倉庫掃描
Cosign 容器鏡像簽名與驗證 cosign 鏡像來源可信驗證
SLSA(供應鏈層級框架) 軟體供應鏈完整性保障框架 SLSA規範 構建過程可信保障
Sigstore 免費開源的程式碼簽名平台 cosign, rekor 鏡像/製品簽名與審計
鏡像簽名(Image Signing) 用密鑰對鏡像摘要簽名 cosign sign 防止鏡像篡改
漏洞嚴重級別 CRITICAL > HIGH > MEDIUM > LOW CVSS評分 修復優先級決策
CIS基準 容器/編排安全配置標準 trivy misconfig Docker/K8s安全加固

目錄

  1. 問題分析:5大容器安全挑戰
  2. 實踐1:Trivy鏡像漏洞掃描整合
  3. 實踐2:SBOM生成與驗證
  4. 實踐3:Cosign鏡像簽名與驗證
  5. 實踐4:CI/CD安全門禁配置
  6. 實踐5:Go模組依賴審計
  7. 實踐6:運行時安全監控
  8. 5大常見陷阱
  9. 10大錯誤排查
  10. 進階優化技巧
  11. 掃描工具對比
  12. 在線工具推薦
  13. 總結與展望

問題分析:5大容器安全挑戰

挑戰1:鏡像漏洞修復滯後 — Go基礎鏡像golang:1.22包含數百個系統套件,CVE從揭露到修復平均耗時97天,你的鏡像可能帶著已知漏洞運行數月。

挑戰2:依賴傳遞風險go mod tidy拉取的間接依賴可能包含惡意程式碼。XZ Utils事件證明,即使是廣泛使用的函式庫也可能被植入後門,而go.sum只驗證完整性不驗證安全性。

挑戰3:鏡像簽名驗證缺失 — 沒有簽名驗證,任何人都可以推送篡改後的鏡像到倉庫。K8s預設不驗證鏡像來源,拉到什麼就跑什麼。

挑戰4:SBOM生成與管理 — 沒有SBOM,你無法回答「這個鏡像裡有什麼」。合規審計要求SBOM,但多數團隊從未生成過。

挑戰5:合規審計自動化 — SOC2、等保2.0要求持續安全掃描記錄,手動審計效率低下且容易遺漏。


實踐1:Trivy鏡像漏洞掃描整合

安裝與基礎掃描

# 安裝Trivy
brew install trivy
# 或使用Docker
docker run aquasec/trivy:latest version

掃描Go容器鏡像

# 掃描本地鏡像
trivy image myapp:latest

# 只顯示CRITICAL和HIGH級別漏洞
trivy image --severity CRITICAL,HIGH myapp:latest

# 輸出JSON格式用於整合
trivy image --format json --output result.json myapp:latest

# 掃描特定架構
trivy image --platform linux/amd64 myapp:latest

Go專案整合掃描

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
)

type TrivyReport struct {
	Results []TrivyResult `json:"Results"`
}

type TrivyResult struct {
	Target    string        `json:"Target"`
	Type      string        `json:"Type"`
	Vulnerabilities []Vuln `json:"Vulnerabilities"`
}

type Vuln struct {
	VulnerabilityID string `json:"VulnerabilityID"`
	Severity        string `json:"Severity"`
	PkgName         string `json:"PkgName"`
	InstalledVersion string `json:"InstalledVersion"`
	FixedVersion    string `json:"FixedVersion"`
	Title           string `json:"Title"`
}

func scanImage(imageRef string) (*TrivyReport, error) {
	cmd := exec.Command("trivy", "image",
		"--format", "json",
		"--severity", "CRITICAL,HIGH",
		imageRef,
	)
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("trivy scan failed: %w", err)
	}

	var report TrivyReport
	if err := json.Unmarshal(output, &report); err != nil {
		return nil, fmt.Errorf("parse report failed: %w", err)
	}
	return &report, nil
}

func main() {
	imageRef := os.Getenv("SCAN_IMAGE")
	if imageRef == "" {
		imageRef = "myapp:latest"
	}

	report, err := scanImage(imageRef)
	if err != nil {
		fmt.Fprintf(os.Stderr, "scan error: %v\n", err)
		os.Exit(1)
	}

	criticalCount := 0
	for _, result := range report.Results {
		for _, v := range result.Vulnerabilities {
			if v.Severity == "CRITICAL" {
				criticalCount++
				fmt.Printf("[CRITICAL] %s: %s (%s -> %s)\n",
					v.VulnerabilityID, v.Title,
					v.InstalledVersion, v.FixedVersion)
			}
		}
	}

	if criticalCount > 0 {
		fmt.Printf("\nFound %d CRITICAL vulnerabilities. Blocking deploy.\n", criticalCount)
		os.Exit(1)
	}
	fmt.Println("No CRITICAL vulnerabilities found. Safe to deploy.")
}

Trivy配置檔案

# trivy.yaml
severity:
  - CRITICAL
  - HIGH

skipDirs:
  - /tmp
  - /var/cache

ignorefile: .trivyignore

db:
  skipUpdate: false

vulnerability:
  type:
    - os
    - library

misconfiguration:
  type:
    - dockerfile
    - kubernetes

實踐2:SBOM生成與驗證

使用Trivy生成SBOM

# 生成SPDX格式SBOM
trivy image --format spdx-json --output sbom.spdx.json myapp:latest

# 生成CycloneDX格式SBOM
trivy image --format cyclonedx --output sbom.cyclonedx.json myapp:latest

使用Syft生成SBOM

# 安裝Syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

# 生成SBOM
syft myapp:latest -o spdx-json > sbom.spdx.json
syft myapp:latest -o cyclonedx-json > sbom.cyclonedx.json

Go程式化SBOM管理

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"time"
)

type SBOMMetadata struct {
	ImageRef      string    `json:"imageRef"`
	GeneratedAt   time.Time `json:"generatedAt"`
	Tool          string    `json:"tool"`
	Format        string    `json:"format"`
	ComponentCount int     `json:"componentCount"`
}

func generateSBOM(imageRef, format, outputPath string) error {
	args := []string{"image", "--format", format, "--output", outputPath, imageRef}
	cmd := exec.Command("trivy", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func validateSBOM(sbomPath string) error {
	data, err := os.ReadFile(sbomPath)
	if err != nil {
		return fmt.Errorf("read sbom failed: %w", err)
	}

	if !json.Valid(data) {
		return fmt.Errorf("invalid SBOM JSON format")
	}

	var sbom map[string]interface{}
	if err := json.Unmarshal(data, &sbom); err != nil {
		return fmt.Errorf("parse SBOM failed: %w", err)
	}

	if _, ok := sbom["components"]; !ok {
		if _, ok2 := sbom["packages"]; !ok2 {
			return fmt.Errorf("SBOM missing components/packages field")
		}
	}

	fmt.Printf("SBOM validation passed: %s\n", sbomPath)
	return nil
}

func main() {
	imageRef := "myapp:latest"
	sbomPath := fmt.Sprintf("sbom-%s.json", time.Now().Format("20060102"))

	fmt.Printf("Generating SBOM for %s...\n", imageRef)
	if err := generateSBOM(imageRef, "cyclonedx", sbomPath); err != nil {
		fmt.Fprintf(os.Stderr, "generate SBOM failed: %v\n", err)
		os.Exit(1)
	}

	if err := validateSBOM(sbomPath); err != nil {
		fmt.Fprintf(os.Stderr, "validate SBOM failed: %v\n", err)
		os.Exit(1)
	}

	meta := SBOMMetadata{
		ImageRef:      imageRef,
		GeneratedAt:   time.Now(),
		Tool:          "trivy",
		Format:        "cyclonedx",
		ComponentCount: 0,
	}
	metaData, _ := json.MarshalIndent(meta, "", "  ")
	fmt.Println(string(metaData))
}

實踐3:Cosign鏡像簽名與驗證

密鑰對生成與簽名

# 生成密鑰對
cosign generate-key-pair

# 簽名鏡像(使用私鑰)
cosign sign --key cosign.key myregistry/myapp:latest

# 驗證簽名(使用公鑰)
cosign verify --key cosign.pub myregistry/myapp:latest

# 使用Keyless簽名(Sigstore)
cosign sign myregistry/myapp:latest

# Keyless驗證
cosign verify myregistry/myapp:latest

Go整合鏡像驗證

package main

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
)

func verifyImageSignature(imageRef, publicKey string) error {
	args := []string{"verify", "--key", publicKey, imageRef}
	cmd := exec.Command("cosign", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("signature verification failed: %s", string(output))
	}

	if !strings.Contains(string(output), "Verified OK") {
		return fmt.Errorf("signature not verified for %s", imageRef)
	}

	fmt.Printf("Image verified: %s\n", imageRef)
	return nil
}

func signImage(imageRef, keyPath string) error {
	args := []string{"sign", "--key", keyPath, imageRef}
	cmd := exec.Command("cosign", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func main() {
	action := os.Args[1]
	imageRef := os.Args[2]

	switch action {
	case "sign":
		if err := signImage(imageRef, "cosign.key"); err != nil {
			fmt.Fprintf(os.Stderr, "sign failed: %v\n", err)
			os.Exit(1)
		}
	case "verify":
		if err := verifyImageSignature(imageRef, "cosign.pub"); err != nil {
			fmt.Fprintf(os.Stderr, "verify failed: %v\n", err)
			os.Exit(1)
		}
	default:
		fmt.Fprintf(os.Stderr, "unknown action: %s\n", action)
		os.Exit(1)
	}
}

K8s Admission Controller驗證策略

apiVersion: policies.kubewarden.io/v1
kind: ClusterAdmissionPolicy
metadata:
  name: verify-image-signatures
spec:
  module: registry://ghcr.io/kubewarden/policies/verify-image-signatures:v0.2.5
  rules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      resources: ["pods"]
      operations: ["CREATE", "UPDATE"]
  settings:
    signatures:
      - image: "myregistry.io/*"
        pubKeys:
          - |
            -----BEGIN PUBLIC KEY-----
            YOUR_PUBLIC_KEY_HERE
            -----END PUBLIC KEY-----

實踐4:CI/CD安全門禁配置

GitHub Actions安全流水線

name: Security Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

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

      - name: Build Image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Trivy Vulnerability Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: "myapp:${{ github.sha }}"
          format: "sarif"
          output: "trivy-results.sarif"
          severity: "CRITICAL,HIGH"
          exit-code: "1"

      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: "myapp:${{ github.sha }}"
          format: cyclonedx-json
          output-file: sbom.json

      - name: Sign Image
        run: |
          cosign sign --key env://COSIGN_PRIVATE_KEY myapp:${{ github.sha }}
        env:
          COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
          COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}

      - name: Go Module Audit
        run: |
          go install golang.org/x/vuln/cmd/govulncheck@latest
          govulncheck ./...

      - name: Upload Trivy Results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

GitLab CI安全門禁

stages:
  - build
  - scan
  - sign
  - deploy

build-image:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .

trivy-scan:
  stage: scan
  image: aquasec/trivy:latest
  script:
    - trivy image --exit-code 1 --severity CRITICAL,HIGH $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - trivy image --format cyclonedx --output sbom.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  artifacts:
    paths:
      - sbom.json

cosign-verify:
  stage: sign
  script:
    - cosign sign --key cosign.key $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  only:
    - main

deploy:
  stage: deploy
  script:
    - cosign verify --key cosign.pub $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  only:
    - main

實踐5:Go模組依賴審計

govulncheck深度掃描

# 安裝govulncheck
go install golang.org/x/vuln/cmd/govulncheck@latest

# 掃描當前專案
govulncheck ./...

# 輸出JSON格式
govulncheck -json ./...

# 只顯示已調用的漏洞(更精準)
govulncheck -mode binary ./...

Go依賴審計程式

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"strings"
)

type VulnEntry struct {
	OSV      string `json:"osv"`
	Module   string `json:"module"`
	Version  string `json:"version"`
	Package  string `json:"package"`
	Call     string `json:"call"`
	Severity string `json:"severity"`
}

func auditGoModules(projectPath string) ([]VulnEntry, error) {
	cmd := exec.Command("govulncheck", "-json", "./...")
	cmd.Dir = projectPath
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("govulncheck failed: %w", err)
	}

	var vulns []VulnEntry
	decoder := json.NewDecoder(strings.NewReader(string(output)))
	for decoder.More() {
		var entry map[string]interface{}
		if err := decoder.Decode(&entry); err != nil {
			continue
		}
		if vulnData, ok := entry["vulnerability"]; ok {
			v := VulnEntry{}
			if m, ok := vulnData.(map[string]interface{}); ok {
				if id, ok := m["id"].(string); ok {
					v.OSV = id
				}
				if mod, ok := m["module"].(string); ok {
					v.Module = mod
				}
			}
			vulns = append(vulns, v)
		}
	}
	return vulns, nil
}

func checkGoModTidy(projectPath string) error {
	cmd := exec.Command("go", "mod", "tidy", "-diff")
	cmd.Dir = projectPath
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("go.mod needs tidy: %s", string(output))
	}
	return nil
}

func main() {
	projectPath := "."
	if len(os.Args) > 1 {
		projectPath = os.Args[1]
	}

	fmt.Println("Checking go.mod tidiness...")
	if err := checkGoModTidy(projectPath); err != nil {
		fmt.Fprintf(os.Stderr, "[WARN] %v\n", err)
	}

	fmt.Println("Running govulncheck...")
	vulns, err := auditGoModules(projectPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "audit failed: %v\n", err)
		os.Exit(1)
	}

	if len(vulns) > 0 {
		fmt.Printf("Found %d vulnerabilities:\n", len(vulns))
		for _, v := range vulns {
			fmt.Printf("  - %s: %s@%s\n", v.OSV, v.Module, v.Version)
		}
		os.Exit(1)
	}
	fmt.Println("No known vulnerabilities found.")
}

go.mod安全配置

// go.mod
module github.com/myorg/myapp

go 1.22.0

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/redis/go-redis/v9 v9.5.1
)

// 排除已知漏洞版本
exclude (
    github.com/example/vulnerable v1.0.0
)

// 指定安全版本替代
replace github.com/example/vulnerable => github.com/example/vulnerable v1.0.1

實踐6:運行時安全監控

Falco運行時檢測規則

# falco-rules/go-container.yaml
- rule: Unexpected Process in Go Container
  desc: Detect unexpected processes running in Go containers
  condition: >
    container and container.image contains "myapp" and
    proc.name not in (myapp, sh, cat, ls)
  output: >
    Unexpected process in Go container
    (user=%user.name container=%container.name
     image=%container.image.proc=%proc.name)
  priority: WARNING
  tags: [container, go, runtime]

- rule: Crypto Mining Detected
  desc: Detect crypto mining activity in containers
  condition: >
    container and
    (proc.name in (xmrig, minerd, cpuminer) or
     proc.cmdline contains "stratum+tcp")
  output: >
    Crypto mining detected
    (user=%user.name container=%container.name
     image=%container.image.proc=%proc.name)
  priority: CRITICAL
  tags: [crypto, container, runtime]

- rule: Container Drift Detected
  desc: Detect new files created in running container
  condition: >
    container and evt.type = openat and
    evt.arg.flags contains O_CREAT and
    not fd.name startswith /tmp and
    not fd.name startswith /var/log
  output: >
    Container drift detected
    (user=%user.name container=%container.name
     file=%fd.name)
  priority: WARNING
  tags: [drift, container, runtime]

Go運行時安全檢查

package main

import (
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

type SecurityCheck struct {
	Name   string
	Status string
	Detail string
}

func checkRunningAsRoot() SecurityCheck {
	if os.Getuid() == 0 {
		return SecurityCheck{
			Name:   "Root User Check",
			Status: "FAIL",
			Detail: "Container running as root",
		}
	}
	return SecurityCheck{
		Name:   "Root User Check",
		Status: "PASS",
		Detail: fmt.Sprintf("Running as UID %d", os.Getuid()),
	}
}

func checkReadOnlyFilesystem() SecurityCheck {
	if err := os.WriteFile("/tmp/.security-test", []byte("test"), 0644); err != nil {
		return SecurityCheck{
			Name:   "Read-Only FS Check",
			Status: "PASS",
			Detail: "Filesystem is read-only",
		}
	}
	os.Remove("/tmp/.security-test")
	return SecurityCheck{
		Name:   "Read-Only FS Check",
		Status: "WARN",
		Detail: "Filesystem is writable",
	}
}

func checkDistroless() SecurityCheck {
	_, err := exec.LookPath("sh")
	if err != nil {
		return SecurityCheck{
			Name:   "Distroless Check",
			Status: "PASS",
			Detail: "No shell available (distroless)",
		}
	}
	return SecurityCheck{
		Name:   "Distroless Check",
		Status: "WARN",
		Detail: "Shell available in image",
	}
}

func runSecurityAudit() []SecurityCheck {
	return []SecurityCheck{
		checkRunningAsRoot(),
		checkReadOnlyFilesystem(),
		checkDistroless(),
	}
}

func main() {
	fmt.Printf("Runtime Security Audit - %s\n", time.Now().Format(time.RFC3339))
	fmt.Printf("Platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
	fmt.Println(strings.Repeat("-", 60))

	checks := runSecurityAudit()
	failCount := 0
	for _, c := range checks {
		status := c.Status
		if status == "FAIL" {
			failCount++
		}
		fmt.Printf("[%s] %s: %s\n", status, c.Name, c.Detail)
	}

	fmt.Println(strings.Repeat("-", 60))
	if failCount > 0 {
		fmt.Printf("Audit FAILED: %d checks failed\n", failCount)
		os.Exit(1)
	}
	fmt.Println("Audit PASSED")
}

安全加固Dockerfile

FROM golang:1.22-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /myapp .

FROM gcr.io/distroless/static-debian12:nonroot

COPY --from=builder /myapp /myapp

USER nonroot:nonroot

HEALTHCHECK --interval=30s --timeout=3s \
  CMD ["/myapp", "healthcheck"]

ENTRYPOINT ["/myapp"]

5大常見陷阱

# 陷阱 錯誤做法 正確做法
1 ❌ 只掃描不阻斷 掃描結果存檔了事,CI/CD繼續部署 --exit-code 1阻斷含CRITICAL漏洞的部署
2 ❌ 忽略間接依賴 只檢查go.mod直接依賴 govulncheck ./...掃描完整依賴樹
3 ❌ 使用latest標籤 FROM golang:latest不可複現 ✅ 固定版本FROM golang:1.22.3-alpine3.19
4 ❌ 容器以root運行 Dockerfile預設root使用者 USER nonroot:nonroot + distroless基礎鏡像
5 ❌ 簽名與部署分離 簽完名不驗證就部署 ✅ K8s Admission Controller強制驗證簽名

10大錯誤排查

# 錯誤訊息 原因 解決方案
1 FATAL: error in DB download Trivy漏洞庫下載失敗 trivy image --download-db-only後重試;配置代理HTTP_PROXY
2 unsupported format: cyclonedx Trivy版本過低 升級到v0.40+:brew upgrade trivy
3 cosign: signing failed: KEY_REF 密鑰環境變數未設定 export COSIGN_PRIVATE_KEY=... 或使用--key cosign.key
4 verification failed: no signatures 鏡像未簽名 cosign sign簽名再驗證
5 govulncheck: module not found Go模組快取損壞 go clean -modcache && go mod download
6 trivy: permission denied Docker socket權限不足 sudo usermod -aG docker $USER或使用rootless模式
7 SBOM: empty components 多階段構建掃描了builder層 掃描最終鏡像而非builder階段
8 cosign verify: REKOR error Rekor透明日誌服務不可達 檢查網路;或使用--insecure-ignore-tlog(僅測試)
9 trivy: image not found locally 本地不存在該鏡像 docker pull或指定遠端倉庫位址
10 Falco: rule syntax error YAML縮排或欄位名錯誤 falco -V驗證規則檔案語法

進階優化技巧

1. Trivy Operator實現K8s集群級自動掃描 — 部署Trivy Operator到K8s集群,自動掃描所有Pod鏡像並生成VulnerabilityReport,配合Prometheus告警實現持續監控。

2. SBOM attestation附加到鏡像 — 使用cosign attest將SBOM作為attachment附加到鏡像manifest,實現SBOM與鏡像綁定分發,避免SBOM丟失。

3. VEX文件抑制誤報 — 生成VEX(Vulnerability Exploitability eXchange)文件標記已評估為不受影響的漏洞,減少掃描噪音,聚焦真實風險。

4. 多架構鏡像統一簽名 — 使用cosign sign對manifest list簽名而非單個manifest,確保linux/amd64linux/arm64架構鏡像統一驗證。

5. SLSA Level 3構建保障 — 在CI/CD中使用Sigstore簽名+Rekor透明日誌+SLSA provenance,達到SLSA Level 3,確保構建過程可審計、可驗證。


掃描工具對比

特性 Trivy Grype Clair Snyk
掃描速度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
漏洞庫覆蓋 全面(OS+語言) 良好 偏OS層 全面
SBOM生成 ✅ SPDX/CycloneDX ❌ 需Syft配合
鏡像簽名 ❌ 配合Cosign
IaC掃描
密鑰檢測
CI/CD整合 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
開源免費 ✅ Apache 2.0 ✅ Apache 2.0 ✅ Apache 2.0 ❌ 商業
K8s Operator
Go模組支援
適用場景 全能首選 輕量掃描 K8s集群級 企業合規

選型建議:中小團隊首選Trivy(開源免費、功能全面);大型K8s集群考慮Trivy Operator+Clair組合;企業合規場景評估Snyk。


在線工具推薦

工具 用途 連結
JSON格式化 SBOM/掃描結果格式化查看 /zh-TW/json/format
Hash計算 鏡像摘要/檔案完整性校驗 /zh-TW/encode/hash
Curl轉程式碼 安全掃描API調用生成 /zh-TW/dev/curl-to-code
Base64編解碼 密鑰/憑證編解碼 /zh-TW/encode/base64
正則測試 Falco規則/日誌匹配除錯 /zh-TW/dev/regex

總結與展望

容器安全掃描不是一次性動作,而是從構建到運行的全生命週期實踐。6個關鍵實踐的核心邏輯:Trivy掃描發現漏洞 → SBOM讓依賴透明 → Cosign保證鏡像可信 → CI/CD門禁自動阻斷 → govulncheck審計Go依賴 → Falco監控運行時異常

2026年趨勢:SLSA框架逐步成為供應鏈安全事實標準,Sigstore Keyless簽名降低簽名門檻,SBOM attestation實現物料清單與鏡像綁定,AI輔助漏洞修復優先級排序。容器安全正在從「事後補救」走向「左移內建」,越早整合安全實踐,修復成本越低。

記住:安全不是阻礙交付的牆,而是保障交付品質的門。從Trivy掃描開始,逐步構建你的容器安全防線。

本站提供瀏覽器本地工具,免註冊即可試用 →

#容器安全#Go#镜像扫描#Trivy#SBOM#供应链安全#2026#云原生