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安全加固 |
目錄
- 問題分析:5大容器安全挑戰
- 實踐1:Trivy鏡像漏洞掃描整合
- 實踐2:SBOM生成與驗證
- 實踐3:Cosign鏡像簽名與驗證
- 實踐4:CI/CD安全門禁配置
- 實踐5:Go模組依賴審計
- 實踐6:運行時安全監控
- 5大常見陷阱
- 10大錯誤排查
- 進階優化技巧
- 掃描工具對比
- 在線工具推薦
- 總結與展望
問題分析: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/amd64和linux/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掃描開始,逐步構建你的容器安全防線。
本站提供瀏覽器本地工具,免註冊即可試用 →