🔗
概念 #セキュリティ #Kubernetes #サプライチェーン #cosign #SBOM #Admission Controller #Trivy 📚 Kubernetesセキュリティ

Kubernetes サプライチェーンセキュリティ

サプライチェーン攻撃とは

ソフトウェアのビルド・配布・デプロイのパイプラインそのものを攻撃し、成果物にバックドアを混入させる手法。

【通常の攻撃】
  攻撃者 → アプリケーションの脆弱性を狙う

【サプライチェーン攻撃】
  攻撃者 → ビルドパイプライン / ライブラリ / ベースイメージを汚染
         → 正規のデプロイフローに乗って全ユーザーに配布される
         → 検知が極めて困難

代表的な事例:

  • SolarWinds(2020) — ビルドサーバーに侵入し署名済みアップデートにバックドアを混入
  • CodeCov(2021) — CI スクリプトを改ざんし、実行した全社の環境変数(シークレット)を窃取
  • XZ Utils(2024) — OSS のメンテナーになりすまし圧縮ライブラリにバックドアを混入

SLSA フレームワーク

Supply-chain Levels for Software Artifacts。
Google が提唱したサプライチェーンのセキュリティ成熟度フレームワーク。4段階で評価する。

レベル要件内容
SLSA 0なし保証なし
SLSA 1Provenanceビルドのプロベナンス(出所情報)が存在する
SLSA 2Hosted Build認証されたホスト型ビルドサービスを使用
SLSA 3Hardened Buildビルド環境が改ざんから保護されている
SLSA 4(廃止→L3)Two-party Review全変更がレビューされ、密閉されたビルド
Provenance(出所情報)に含まれるもの:
  - どのソースコードから(コミットハッシュ)
  - どのビルドシステムで(GitHub Actions / Cloud Build)
  - どのビルダーが実行したか
  → これを改ざんできない形で署名・保管する

コンテナイメージのリスク

イメージのリスク源:
  ① ベースイメージの脆弱性(ubuntu:latest に含まれる CVE)
  ② アプリの依存ライブラリの脆弱性(Log4Shell 等)
  ③ Dockerfile の設定ミス(root 実行・不要なツールの組み込み)
  ④ 悪意ある公開イメージ(Docker Hub の typosquatting)
  ⑤ ビルドパイプラインへの侵入による混入

イメージスキャン

Trivy

最も広く使われているオープンソースの脆弱性スキャナー。
イメージ・ファイルシステム・Git リポジトリ・K8s クラスタを対象にスキャンできる。

# イメージのスキャン
trivy image my-app:v1.2.3

# 出力例
my-app:v1.2.3 (debian 11.6)
Total: 3 (CRITICAL: 1, HIGH: 2, MEDIUM: 0, LOW: 0)

┌─────────────────┬────────────────┬──────────┬───────────────┐
    Library Vulnerability Severity Fixed Version
├─────────────────┼────────────────┼──────────┼───────────────┤
 openssl CVE-2023-0286 CRITICAL 3.0.8-1
 libssl3 CVE-2022-4304 HIGH 3.0.8-1
└─────────────────┴────────────────┴──────────┴───────────────┘

# 重大度でフィルタ(CRITICAL / HIGH のみ)
trivy image --severity CRITICAL,HIGH my-app:v1.2.3

# 終了コードで CI を止める(CRITICAL があれば exit 1)
trivy image --exit-code 1 --severity CRITICAL my-app:v1.2.3

# Dockerfile のミス設定もスキャン
trivy config ./Dockerfile

# 実行中クラスタ全体をスキャン
trivy k8s --report=summary cluster

CI/CD への組み込み

# GitHub Actions での Trivy スキャン
name: Container Security Scan
on: [push, pull_request]

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

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

    - name: Run Trivy
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: my-app:${{ github.sha }}
        format: sarif                  # GitHub Security タブに結果を表示
        output: trivy-results.sarif
        severity: CRITICAL,HIGH
        exit-code: 1                   # CRITICAL/HIGH があればパイプラインを停止

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

Artifact Registry の脆弱性スキャン(GCP)

# Terraform: Artifact Registry でプッシュ時に自動スキャンを有効化
resource "google_artifact_registry_repository" "main" {
  location      = "asia-northeast1"
  repository_id = "production"
  format        = "DOCKER"

  cleanup_policies {
    id     = "delete-untagged"
    action = "DELETE"
    condition {
      tag_state = "UNTAGGED"
      older_than = "604800s"   # 1週間以上前の untagged イメージを削除
    }
  }
}

イメージの署名(cosign / Sigstore)

イメージをビルドしたのが誰か・どのパイプラインかを暗号的に証明する。
Sigstore はコード署名をパブリログ(Rekor)で透明化するオープンソースエコシステム。

cosign によるイメージ署名

# Keyless 署名(OIDC トークンを使用。鍵管理不要)
# CI 環境(GitHub Actions / Cloud Build)で実行

# インストール
brew install cosign

# Keyless 署名(GitHub Actions 上で実行する想定)
cosign sign \
  --yes \
  asia-northeast1-docker.pkg.dev/my-project/production/my-app@sha256:abc123...

# 署名の検証
cosign verify \
  --certificate-identity-regexp="https://github.com/my-org/my-repo" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  asia-northeast1-docker.pkg.dev/my-project/production/my-app@sha256:abc123...

GitHub Actions での署名ワークフロー

name: Build, Scan, Sign
on:
  push:
    branches: [main]

permissions:
  id-token: write    # Keyless 署名に必要
  contents: read

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

    - name: Authenticate to GCP
      uses: google-github-actions/auth@v2
      with:
        workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
        service_account: ${{ secrets.WIF_SA }}

    - name: Build and Push
      run: |
        IMAGE="asia-northeast1-docker.pkg.dev/my-project/production/my-app"
        docker build -t $IMAGE:${{ github.sha }} .
        docker push $IMAGE:${{ github.sha }}
        # ダイジェストを取得
        echo "DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $IMAGE:${{ github.sha }})" >> $GITHUB_ENV

    - name: Scan with Trivy
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: ${{ env.DIGEST }}
        severity: CRITICAL
        exit-code: 1

    - name: Sign Image with cosign
      uses: sigstore/cosign-installer@v3
      
    - run: |
        cosign sign --yes ${{ env.DIGEST }}
      # スキャンをパスしたイメージだけ署名される

SBOM(Software Bill of Materials)

ソフトウェアに含まれる全コンポーネントの一覧表。
新しい CVE が公開されたとき、影響を受けるシステムをすばやく特定できる。

# Trivy で SBOM を生成(CycloneDX 形式)
trivy image --format cyclonedx --output sbom.json my-app:v1.2.3

# Syft でも生成できる
syft my-app:v1.2.3 -o cyclonedx-json=sbom.json

# SBOM をイメージに添付(cosign でアタッチ)
cosign attach sbom --sbom sbom.json \
  asia-northeast1-docker.pkg.dev/my-project/production/my-app@sha256:abc123...

# 添付された SBOM を取得
cosign download sbom \
  asia-northeast1-docker.pkg.dev/my-project/production/my-app@sha256:abc123...

Admission Controller による強制

スキャン済み・署名済みのイメージだけをデプロイできるように、
クラスタ側でゲートを設ける。

Binary Authorization(GKE)

# Terraform: Binary Authorization ポリシーの設定
resource "google_binary_authorization_policy" "policy" {
  admission_whitelist_patterns {
    name_pattern = "gcr.io/google_containers/*"   # GKE システムイメージは除外
  }

  default_admission_rule {
    evaluation_mode  = "REQUIRE_ATTESTATION"
    enforcement_mode = "ENFORCED_BLOCK_AND_AUDIT_LOG"

    require_attestations_by = [
      google_binary_authorization_attestor.prod_attestor.name
    ]
  }
}

resource "google_binary_authorization_attestor" "prod_attestor" {
  name = "prod-attestor"
  attestation_authority_note {
    note_reference = google_container_analysis_note.attestation_note.name
    public_keys {
      id = data.google_kms_crypto_key_version.signing_key.id
      pkix_public_key {
        public_key_pem      = data.google_kms_crypto_key_version.signing_key.public_key[0].pem
        signature_algorithm = "ECDSA_P256_SHA256"
      }
    }
  }
}

Kyverno でイメージ署名を検証

# cosign で署名されたイメージのみデプロイを許可
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-image-signature
    match:
      any:
      - resources:
          kinds: ["Pod"]
    verifyImages:
    - imageReferences:
      - "asia-northeast1-docker.pkg.dev/my-project/production/*"
      attestors:
      - count: 1
        entries:
        - keyless:
            subject: "https://github.com/my-org/my-repo/.github/workflows/build.yml@refs/heads/main"
            issuer: "https://token.actions.githubusercontent.com"
            rekor:
              url: https://rekor.sigstore.dev

ベースイメージのセキュリティ

# 悪い例
FROM ubuntu:latest           # 毎回内容が変わる・不要なパッケージが多い
FROM python:3.11             # 同上

# 良い例1: Distroless(Google 製)
FROM gcr.io/distroless/python3-debian12
# シェルなし・パッケージマネージャなし・最小限のライブラリのみ
# → 攻撃者がシェルを取得しても操作できるツールがない

# 良い例2: Alpine(軽量だが musl libc に注意)
FROM python:3.12-alpine

# 良い例3: マルチステージビルド(ビルド環境を本番イメージに含めない)
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM gcr.io/distroless/python3-debian12
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY app.py .
USER nonroot
CMD ["app.py"]

ベースイメージのピン留め

# タグは可変(latest は特に危険)
FROM ubuntu:22.04        # 内容が変わる可能性がある

# ダイジェストでピン留め(推奨)
FROM ubuntu@sha256:27cb6e6ccef575a4698b66f5de06c7ecd61589132d5a91d098f7f3f9285415a9

タグ vs ダイジェスト

# 危険: タグは上書き可能
image: my-app:latest        # 攻撃者がレジストリを侵害してタグを上書きできる
image: my-app:v1.2.3        # タグは mutable(変更可能)

# 安全: ダイジェストは不変
image: my-app@sha256:abc123def456...   # ハッシュ値は変更不可能

チェックリスト

□ Trivy を CI/CD に組み込み CRITICAL 検出でパイプラインを止めている
□ SBOM を生成し、イメージに添付している
□ cosign(Keyless)でイメージに署名している
□ Kyverno / Binary Authorization で署名検証を Admission Control している
□ ベースイメージをダイジェスト指定でピン留めしている
□ Distroless / Alpine など最小ベースイメージを使用している
□ マルチステージビルドでビルドツールを本番イメージから除いている
□ Docker Hub 等の公開イメージを直接使わず、承認済みレジストリにミラーしている
□ Artifact Registry / ECR の脆弱性スキャンを有効化している
□ ベースイメージを定期的に更新して CVE をパッチしている

参考文献

  • SLSA Framework(slsa.dev)— サプライチェーンの成熟度モデルと Provenance の仕様
  • Sigstore / cosign 公式ドキュメント(docs.sigstore.dev)— Keyless 署名と Rekor ログの仕組み
  • Trivy 公式ドキュメント(aquasecurity.github.io/trivy)— スキャン対象・出力フォーマット・CI 統合
  • NIST SP 800-161r1『Cybersecurity Supply Chain Risk Management』(2022)— サプライチェーンリスク管理の公式ガイドライン
  • Google Cloud『Binary Authorization』公式ドキュメント — GKE での署名検証と Terraform 設定

出典: SLSA Framework(slsa.dev)/ Sigstore Documentation(docs.sigstore.dev)/ Trivy Documentation(aquasecurity.github.io/trivy)/ NIST SP 800-161r1(Cybersecurity Supply Chain Risk Management)/ Google Cloud『Binary Authorization』ドキュメント