Sigstore Cosignでコンテナイメージ署名とKyverno admission検証:2026年サプライチェーン防御実践ガイド

Sigstore Cosign 2.xのkeyless署名からKyverno admission検証まで、コンテナサプライチェーン攻撃を塞ぐ2026年最新の実装手順を、攻撃シナリオと防御策を対で解説する実践ガイド。

Cosign×Kyvernoでコンテナ署名検証

更新日: 2026年8月8日

Sigstore Cosign は、コンテナイメージや任意のアーティファクトを短命な OIDC トークンで keyless 署名し、その署名を Rekor 透明性ログに刻み込むことで「誰が・いつ・どのソースから」ビルドしたかを検証可能にする OSS ツールチェーンだ。 私はレッドチーム業務でイメージレジストリを侵害してマルウェア入りタグを差し込む攻撃シナリオを何度も再現してきたが、Cosign の署名検証を Kyverno などの admission controller で強制するだけで、その攻撃経路の 9 割は即死する。本稿では Cosign 2.x の keyless 署名フロー、Fulcio と Rekor の役割、GitHub Actions からの自動署名、そして Kyverno による Kubernetes admission 検証までを、2026 年時点の最新仕様で実装レベルまで踏み込んで解説する。

  • Cosign 2.x の keyless 署名は OIDC ID トークンから短命な X.509 証明書を Fulcio が発行し、鍵管理を不要にする。
  • すべての署名は Rekor の Merkle tree ベース透明性ログに刻まれ、事後の改ざん・削除が暗号学的に検出できる。
  • Kyverno または Sigstore Policy Controller の verifyImages ルールで、署名済み・特定 issuer・特定リポジトリ以外のイメージを admission 段階で拒否できる。
  • SLSA Provenance と SBOM を attestation として同じ Cosign で添付すれば、脆弱性スキャンとサプライチェーン監査を統一データモデルで扱える。
  • GitHub Actions の OIDC トークンを Fulcio に渡すだけで、ワークフロー内から鍵ファイル無しで署名が完結する。
  • 2026 年時点で Cosign は v2.4 系が安定版、Kyverno は 1.13、Sigstore Policy Controller は 0.11 系が最新。

なぜコンテナイメージ署名が必要か:攻撃者視点のサプライチェーン侵害

ペンテストの現場でコンテナ環境を狙うとき、私が真っ先に評価するのは「クラスタが引いてくるイメージが本当にそのビルドパイプラインから出てきたものか、検証されているか」だ。答えが No なら、勝負は始まる前に決まっている。典型的な攻撃経路は 3 つある。

  1. レジストリの認証情報奪取:CI で漏れた DOCKER_PASSWORD や、開発者ノート PC から抜いた ~/.docker/config.json を使い、正規タグに悪性レイヤーを push し直す。
  2. タグの上書きlatest や環境タグに古い脆弱なイメージへのポインタを差し替え、既知 CVE を持つバージョンを再デプロイさせて再侵入経路を確保する。
  3. 依存レジストリのタイポスクワッティング:base image を nginx の代わりに攻撃者所有の nqinx に置換した Dockerfile を PR に紛れ込ませる。

共通するのは、いずれもイメージの「出自」を検証していないから通ってしまう点だ。SLSA v1.0 サプライチェーン脅威モデルで分類されているように、Build から Distribution までのどこか一箇所で改ざんできる状態を放置しているだけで、後段の SELinux や AppArmor をどれだけ固めても意味が薄れる。前工程の Trivy と Grype による脆弱性スキャンで CVE を潰しても、実際に本番に落ちてくるイメージが別物なら検出はすり抜ける。Cosign による署名と admission 段階の検証は、この経路そのものを塞ぐ最小コストの対策になる。

Sigstore アーキテクチャ:Cosign・Fulcio・Rekor の役割分担

Sigstore は Linux Foundation 傘下の OSS プロジェクトで、以下 3 つのコンポーネントで構成される。攻撃面と防御効果を理解するには、この分担を最初に押さえる必要がある。

  • Cosign:クライアント CLI。ローカルの docker/OCI イメージ、Helm チャート、任意のバイナリに対して署名と検証を行う。
  • Fulcio:短命 X.509 証明書を発行する CA。OIDC ID トークン(GitHub Actions、GitLab CI、Google、Microsoft など)と引き換えに、通常 10 分だけ有効な証明書を返す。この短命性が鍵漏洩リスクを構造的に消す。
  • Rekor:Merkle tree ベースの透明性ログ。すべての署名エントリを追記専用で記録し、事後の改ざんや削除を暗号学的に検出可能にする。Sigstore 公式の Rekor 概要ドキュメントに内部構造の詳細がある。

2026 年時点では、これらのパブリックインスタンスは Sigstore コミュニティが運用しており、rekor.sigstore.devfulcio.sigstore.dev がデフォルトエンドポイントとなる。エンタープライズ用途で外部依存を嫌う場合は Rekor と Fulcio をオンプレ運用することも可能で、後述する Policy Controller の trust root 設定で自組織インスタンスへ切り替えられる。

keyless 署名の内部フローは次の通りだ。① Cosign が OIDC プロバイダから ID トークンを取得、② そのトークンを Fulcio に提示し証明書を発行、③ 発行された短命証明書で署名生成、④ 署名・証明書・タイムスタンプを Rekor に投稿しログ ID を取得、⑤ すべてをまとめて OCI レジストリの signature tag(例: sha256-abcd.sig)として push する。検証側はこれを逆順にたどるだけで、鍵配布問題を回避しながら「どの Identity がこの blob に署名したか」を検証できる。

Cosign 2.x のインストールと初期セットアップ

2026 年 8 月時点の安定版は Cosign 2.4 系だ。macOS / Linux の主要な導入方法を示す。

# Linux (amd64) 直接バイナリ
COSIGN_VERSION="v2.4.1"
curl -sSLo /tmp/cosign "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64"
curl -sSLo /tmp/cosign.pem "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64.pem"
curl -sSLo /tmp/cosign.sig "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64.sig"

# 既存 cosign があるなら self-verify で入れ替え、無ければ GitHub の checksum を厳密照合
sha256sum /tmp/cosign  # リリースページの checksums.txt と突合
install -m 0755 /tmp/cosign /usr/local/bin/cosign
cosign version

# Debian/Ubuntu では以下も使える(Sigstore APT リポジトリ)
# curl -fsSL https://packages.sigstore.dev/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/sigstore.gpg
# echo "deb [signed-by=/etc/apt/keyrings/sigstore.gpg] https://packages.sigstore.dev/apt stable main" | sudo tee /etc/apt/sources.list.d/sigstore.list
# sudo apt update && sudo apt install -y cosign

インストール直後にやるべきなのは、Cosign 自身の再帰的な自己検証だ。攻撃者は cosign バイナリそのものを差し替えることも狙う。

# 公式リリース署名の検証(cosign-linux-amd64 の署名は Sigstore keyless で行われている)
cosign verify-blob \
  --certificate /tmp/cosign.pem \
  --signature   /tmp/cosign.sig \
  --certificate-identity-regexp 'https://github.com/sigstore/cosign/.*' \
  --certificate-oidc-issuer     'https://token.actions.githubusercontent.com' \
  /tmp/cosign

keyless 署名でイメージに署名する(ローカルとGitHub Actions)

keyless 署名は「自分のマシンから OIDC でログインする」パターンと「CI ワークフローの OIDC トークンで自動署名する」パターンの二通りある。ローカルで試すには以下だけで十分だ。

# ビルドと push
IMAGE="ghcr.io/example/webapp:1.4.0"
docker build -t "$IMAGE" .
docker push "$IMAGE"

# ダイジェスト付きで固定してから署名(タグ再割当てで検証をすり抜けられないようにする)
DIGEST=$(cosign triangulate --type digest "$IMAGE")
echo "signing $DIGEST"

# OIDC ブラウザフローで Fulcio に認証、短命証明書で署名、Rekor へ投稿
COSIGN_EXPERIMENTAL=1 cosign sign "$DIGEST"

本番運用で価値があるのは CI 側だ。GitHub Actions では OIDC トークンが自動発行されるため、鍵ファイルを一切保持せず、ワークフロー ID をそのまま署名者アイデンティティにできる。

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

permissions:
  contents: read
  id-token: write   # OIDC トークン発行に必須
  packages: write   # GHCR push に必要

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - 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@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}

      - uses: sigstore/cosign-installer@v3
        with:
          cosign-release: 'v2.4.1'

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

ここで発行される Fulcio 証明書の Subject は https://github.com/<org>/<repo>/.github/workflows/build-sign.yml@refs/tags/v1.4.0 の形式になる。Issuer は https://token.actions.githubusercontent.com だ。この 2 値が admission ポリシーの一次判定材料になる。

署名の検証と Rekor 透明性ログの照合

検証は cosign verify で完結するが、必ず --certificate-identity(あるいは正規表現版)と --certificate-oidc-issuer の両方を明示する。これを省くと「なんらかの Fulcio 証明書で署名されたイメージ」なら全部通ってしまい、防御としてほぼ意味を失う。

# 例: 特定リポジトリの特定タグワークフローからの署名のみ許可
cosign verify \
  --certificate-identity-regexp \
    '^https://github\.com/example/webapp/\.github/workflows/build-sign\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' \
  --certificate-oidc-issuer \
    'https://token.actions.githubusercontent.com' \
  ghcr.io/example/webapp:1.4.0

返ってくる JSON には Rekor のログインデックスと UUID が含まれる。運用監査では、これを Rekor の CLI(rekor-cli)で引き当てて改ざん検出用の inclusion proof を追加取得しておくのが望ましい。

# 直近 24 時間で自組織リポジトリを Subject に持つ署名を列挙(不審な署名の発見)
rekor-cli search --email '[email protected]' 2>/dev/null || \
rekor-cli search --pki-format=x509 --public-key '/tmp/fulcio-root.pem' \
  --operator OR --artifact ghcr.io/example/webapp

Kyverno で署名済みイメージのみ Kubernetes に許可する

Kyverno は Kubernetes 用のポリシーエンジンで、admission webhook として動く。Cosign と統合された verifyImages ルールを書けば、署名検証に失敗した Pod は API server が受け付けなくなる。Kyverno 公式ポリシーカタログにサンプルが揃っている。まずインストールする。

# Kyverno 1.13 系 (2026-08 時点の安定版)
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno -n kyverno --create-namespace \
  --version 3.3.0 \
  --set admissionController.replicas=3 \
  --set backgroundController.replicas=2

続いて、production ネームスペースに対して「自組織 GitHub Actions ワークフローで署名されたイメージのみ許可」ポリシーを適用する。

apiVersion: kyverno.io/v2
kind: ClusterPolicy
metadata:
  name: require-cosign-signature
spec:
  validationFailureAction: Enforce
  webhookConfiguration:
    failurePolicy: Fail
  rules:
    - name: verify-webapp-signature
      match:
        any:
          - resources:
              kinds: [Pod]
              namespaces: [production]
      verifyImages:
        - imageReferences:
            - "ghcr.io/example/*"
          failureAction: Enforce
          attestors:
            - entries:
                - keyless:
                    subjectRegExp: "^https://github\\.com/example/[^/]+/\\.github/workflows/build-sign\\.yml@refs/tags/v[0-9]+\\.[0-9]+\\.[0-9]+$"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev
          mutateDigest: true    # タグを sha256 ダイジェストに書き換えて再割当てを防ぐ
          verifyDigest: true
          required: true

ポイントは 3 つある。第一に failureAction: Enforce(旧 validationFailureAction)で、これを Audit のまま本番投入するのは事故のもとだ。第二に mutateDigest: trueghcr.io/example/webapp:1.4.0 のようなタグ参照は、後からタグ張替えで別イメージに差し替え可能なので、admission 段階でダイジェストに書き換えて Pod spec を固定する。第三に subjectRegExp の厳密さ。自組織のリポジトリだけでなく、ビルド用ワークフローファイルとタグ形式まで縛る。SELinux や AppArmor による強制アクセス制御と組み合わせれば、たとえ改ざんイメージが押し込まれてもコンテナ内部からの権限昇格を二重に防げる。

Kyverno 以外の選択肢としては、Sigstore 公式の Policy Controller(Knative ベース)や、より柔軟な Rego ポリシーが書ける OPA Gatekeeper もある。私見では Cosign の verify セマンティクスを最も低摩擦で表現できるのは Kyverno だが、既に OPA を採用している組織なら Gatekeeper + cosign verify のサイドカーで統一する構成も筋がいい。

SLSA Provenance と SBOM を attestation として添付する

Cosign の真価は「署名だけでなく attestation を統一フォーマット(in-toto v1)で添付できる」点にある。SBOM や SLSA Provenance を同じ Rekor 上に刻めば、admission 段階で「署名済み・SBOM 添付済み・SLSA level 3 でビルドされたもの」を単一ポリシーで要求できる。

# Syft で SBOM を生成
syft ghcr.io/example/webapp:1.4.0 -o cyclonedx-json > sbom.cdx.json

# SBOM を attestation として添付
cosign attest --yes \
  --predicate sbom.cdx.json \
  --type cyclonedx \
  ghcr.io/example/webapp:1.4.0

# SLSA Provenance (GitHub Actions が自動生成するもの) を添付
cosign attest --yes \
  --predicate provenance.json \
  --type slsaprovenance \
  ghcr.io/example/webapp:1.4.0

# 検証: SBOM attestation が正当な CI から署名されているか
cosign verify-attestation \
  --type cyclonedx \
  --certificate-identity-regexp '^https://github\.com/example/webapp/.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/example/webapp:1.4.0

Kyverno の verifyImages は attestations フィールドで attestation 種別まで縛れる。実運用では、脆弱性スキャンで既知 CVE ゼロが確認された SBOM を admission 条件に組み込むと、Trivy と Grype のパイプラインと一気通貫の防御になる。Falco や Tetragon による eBPF ランタイム検知と重ねれば、ビルド前・ビルド時・ビルド後・実行時の 4 層すべてで独立した検出面を確保できる。

運用でハマるポイントとトラブルシュート

ここまで書いた通り実装は 1 日仕事だが、運用に乗せると必ずぶつかる問題がいくつかある。私が実際にレッドチーム演習後の防御側で相談を受けたケースを中心に整理する。

1. Rekor がダウンした / 到達不能になった

Fulcio と Rekor はパブリックインスタンスなので、まれに応答遅延が発生する。cosign sign--rekor-url で別インスタンスを指定できる。エンタープライズ用途では自組織 Rekor を tuf-repo ごと運用するのが確実だ。CI 側でリトライロジックを持つと同時に、admission 側ではオフラインバンドル(cosign sign-blob --bundle で生成できる Rekor 応答同梱形式)を使えばオンライン検証依存を減らせる。

2. subject regex がゆるすぎて Enforce にできない

初期導入では大抵 --certificate-identity-regexp '^https://github\.com/example/.*' のように広めに書いてしまい、監査ログを見ると全然関係ないリポジトリでも通ってしまう。私が推奨するのは、ワークフロー名までフルパスで縛る形(本記事の Kyverno サンプル参照)。organization polytype で他組織へのフォークからのビルドが混ざる場合は、runnerEnvironmentref クレームの追加検証を ctlogAny ではなく --certificate-github-workflow-repository のような専用フラグで書く。

3. 既存イメージが未署名で admission が全落ちする

Enforce に切り替えた瞬間、レガシー DaemonSet が起動不能になる事故は本当に多い。対策は 2 段階だ。まず failureAction: Audit で 2 週間ほど並走させ、Kyverno の PolicyReport で未署名リソースを洗い出す。次に match.any.resources.namespaces を production から順次拡大する形で段階適用する。

4. 開発者ローカルビルドを本番に載せたい

そもそも本番に載せるビルドは CI 経由に強制するのが正解だが、ホットフィックスなどで開発者マシンから直接署名したいケースはある。その場合、--identity-token を経由した Google/Microsoft/GitLab OIDC を使い、admission ポリシー側で「CI 発行の identity または特定メンバの email identity」の OR 条件で許可する。email identity は Fulcio 発行時に SAN として証明書に埋まる。

よくある質問

Cosign と Notary v2(Notation)はどちらを選ぶべきですか?

keyless 署名と透明性ログを重視するなら Cosign、伝統的な X.509 鍵管理と OCI Distribution Spec v1.1 準拠を優先するなら Notation という棲み分けが 2026 年時点の実情だ。Kubernetes admission への統合ツール(Kyverno、Policy Controller)は Cosign 前提のものが依然として多く、実装ロードマップも Sigstore 側の方が活発。特別な理由がなければ Cosign から始めて問題ない。

Sigstore は無料で使えますか?企業利用でも問題ないですか?

パブリックの Fulcio / Rekor インスタンスは Linux Foundation 傘下の Sigstore プロジェクトが無償で提供しており、商用利用も可能だ。ただし本番運用の依存としては、SLA を求めるならエンタープライズ SaaS(Chainguard など)を契約するか、自社内で Fulcio と Rekor を運用するのが安全だ。

keyless 署名のセキュリティは秘密鍵署名より劣りますか?

むしろ多くの脅威モデルで優れている。10 分で失効する短命証明書は盗んでも再利用がほぼ不可能で、鍵管理オペレーションの人為ミス(鍵の GitHub 誤コミット、退職者権限剥奪漏れ)を構造的に消せる。Rekor 透明性ログにより、後付けで署名を偽装しても発覚するのも大きい。

Cosign で署名したイメージのタグを後から張り替えたらどうなりますか?

署名は Manifest のダイジェストに紐づいているので、タグ張替えで別イメージを指しても署名は無効になる。ただし検証コマンドや Kyverno ポリシーでダイジェスト検証(verifyDigest)を有効にしていない場合、タグだけを見て通してしまう実装があり得るので、必ず mutateDigestverifyDigest を併用すること。

Kyverno と OPA Gatekeeper のどちらで Cosign 検証を実装すべきですか?

Cosign 検証のためだけなら Kyverno の方が学習コストが圧倒的に低い。verifyImages ルールが標準機能として組み込まれており、YAML 数十行で始められる。既に OPA/Rego 資産があり、他のポリシー(ラベル必須、リソース上限など)と統合したいなら Gatekeeper + cosign サイドカーの構成も合理的だ。

Felix Lindqvist
著者について Felix Lindqvist

Penetration tester and OSCP holder. Reverse engineers misconfigured servers for a living and writes about what he finds.