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コンテナサプライチェーンセキュリティの防衛線を構築する。


コア概念クイックリファレンス

概念 コア思想 主要ツール 典型的なシナリオ
コンテナセキュリティ ビルドからランタイムまでのライフサイクル全体の防護 Trivy, Clair イメージ脆弱性スキャン、ランタイム防護
SBOM(ソフトウェア部品表) ソフトウェアの全コンポーネントと依存関係を列挙 syft, trivy sbom 依存関係の透明化、コンプライアンス監査
CVE(共通脆弱性識別子) 標準化された脆弱性識別と評価 NVD, OSV 脆弱性追跡と修正優先度
Trivy オールインワンコンテナセキュリティスキャナー trivy イメージ/ファイルシステム/Gitリポジトリスキャン
Cosign コンテナイメージの署名と検証 cosign イメージの来歴信頼性検証
SLSA(サプライチェーンレベルフレームワーク) ソフトウェアサプライチェーンの完全性保障フレームワーク SLSA仕様 ビルドプロセスの信頼性保障
Sigstore 無料オープンソースのコード署名プラットフォーム cosign, rekor イメージ/アーティファクトの署名と監査
イメージ署名 鍵ペアでイメージダイジェストに署名 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や同等のフレームワークは継続的なセキュリティスキャン記録を要求する。手動監査は非効率で見落としが発生しやすい。


プラクティス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脆弱性DBのダウンロード失敗 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ソケットの権限不足 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をイメージマニフェストのアタッチメントとして付加。SBOMとイメージをバインドして配布し、SBOMの紛失を防止。

3. VEXドキュメントで誤検知を抑制 — VEX(Vulnerability Exploitability eXchange)ドキュメントを生成し、影響なしと評価された脆弱性をマーク。スキャンノイズを減らし、真のリスクに集中。

4. マルチアーキテクチャイメージの統一署名cosign signで個別マニフェストではなくマニフェストリストに署名し、linux/amd64linux/arm64の両アーキテクチャで統一的に検証。

5. SLSA Level 3のビルド保証 — CI/CDでSigstore署名 + Rekorトランスペアレンシーログ + SLSA Provenanceを使用し、SLSA Level 3を達成。監査可能で検証可能なビルドプロセスを確保。


スキャンツール比較

機能 Trivy Grype Clair Snyk
スキャン速度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
脆弱性DBカバレッジ 包括的(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/スキャン結果のフォーマット表示 /ja/json/format
ハッシュ計算 イメージダイジェスト/ファイル整合性検証 /ja/encode/hash
Curl→コード変換 セキュリティスキャンAPI呼び出し生成 /ja/dev/curl-to-code
Base64エンコード/デコード 鍵/証明書のエンコード/デコード /ja/encode/base64
正規表現テスター Falcoルール/ログマッチングのデバッグ /ja/dev/regex

まとめと展望

コンテナセキュリティスキャンは一度きりのアクションではなく、ビルドからランタイムまでのライフサイクル全体のプラクティスである。6つの重要プラクティスの中核ロジック:Trivyスキャンで脆弱性を発見 → SBOMで依存関係を透明化 → Cosignでイメージの信頼性を担保 → CI/CDゲートで自動ブロック → govulncheckでGo依存を監査 → Falcoでランタイム異常を監視

2026年のトレンド:SLSAフレームワークがサプライチェーンセキュリティのデファクトスタンダードになりつつあり、Sigstore Keyless署名が署名のハードルを下げ、SBOM Attestationが部品表とイメージをバインドし、AIによる脆弱性修正優先度付けが実用化している。コンテナセキュリティは「事後対応」から「シフトレフト組み込み」へと移行しており、セキュリティプラクティスを早く統合するほど修正コストは低くなる。

覚えておこう:セキュリティはデリバリーを阻む壁ではなく、デリバリーの品質を保証する門である。Trivyスキャンから始めて、段階的にコンテナセキュリティの防衛線を構築しよう。

ブラウザローカルツールを無料で試す →

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