KubernetesのSigstore活用

Shun Kobayashi

2025.10.1

本記事では、KubernetesにおけるSigstoreプロジェクトの活用方法を解説します。

Sigstoreの概要や導入事例、キーレス署名については、前回の記事[Cosignによる署名検証とSigstoreの全体像]をあわせてご覧ください。

Cosignのインストール

Sigstoreエコシステムにおける署名アーティファクトの検証には、Cosignのみが必要です。インストール方法はいくつかあリますが、詳細は公式ドキュメントを参照してください。ここでは、例としてGoを用いたインストール方法を紹介します。

$ go install github.com/sigstore/cosign/v2/cmd/cosign@latest
$ cosign --help

Kubernetesアーティファクトの検証

Kubernetesアーティファクトの署名と検証は、KEP-3031: Signing release artifacts で進められています。v.1.24 で α、v.1.26 で β になっています。β では、Kubernetes標準のアーティファクト(バイナリやコンテナイメージ)に署名されており、全てのアーティファクトが署名されるようになった段階で、GA に到達する予定です。

この機能により、Kubernetesユーザはリリースプロセスの一部として署名検証を組み込み、サプライチェーン攻撃のリスクを軽減できます。ここでいう「Kubernetesアーティファクト」とは、PodやDeploymentなどのリソースではなく、Kubernetes本体を構成する主要コンポーネントであり、kube-apiserverkubectlといったバイナリ、公式のコンテナイメージ、リリースに含まれる関連ファイルなどです(KEP-3031では「リリースアーティファクト」と呼ばれています)。

バイナリ署名の検証

Kubernetesのリリースでは、すべてのバイナリに対して Cosign によるキーレス署名が付与されています。

ここでは、Kubernetesのダウンロードページ から kubectl をダウンロードし、Cosignを用いて正規のバイナリであるかを検証します。

kubectl のバイナリ・署名・証明書をダウンロード

$ curl -OL https://dl.k8s.io/v1.33.3/bin/linux/amd64/kubectl
$ curl -OL https://dl.k8s.io/v1.33.3/bin/linux/amd64/kubectl.sig
$ curl -OL https://dl.k8s.io/v1.33.3/bin/linux/amd64/kubectl.cert

バイナリの署名検証 バイナリ署名の署名情報は以下の通りです。

  • Identity: krel-staging@k8s-releng-prod.iam.gserviceaccount.com
  • OIDC Issuer: https://accounts.google.com
$ cosign verify-blob kubectl \
  --signature kubectl.sig \
  --certificate kubectl.cert \
  --certificate-identity krel-staging@k8s-releng-prod.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com
Verified OK

KuberentesコンポーネントのバイナリはすべてSHA256チェックサムを公開されています。そのため、ハッシュ値による検証も可能です。

$ curl -L https://dl.k8s.io/v1.33.3/bin/linux/amd64/kubectl.sha256

$ curl -OL https://dl.k8s.io/v1.33.3/bin/linux/amd64/kubectl
$ sha256sum kubectl

ハッシュ値による検証はファイルの完全性(改竄されていないこと)は確認できますが、誰がビルドしたかまでは証明できません。もし、ダウンロードページに置かれたバイナリとハッシュが両方とも改竄されてしまえば、攻撃を防ぐことはできません。

一方で Cosign による検証では、完全性に加えて署名者の正当性も確認できます。Fulcio と Rekor により、どこでビルドされたのかまで追跡することが可能になります。

コンテナイメージの署名検証

コンテナイメージにも Cosign のキーレス署名が付与されています。Kubernetesアーティファクトのコンテナイメージは、registry.k8s.io というコンテナレジストリに保存されています。

コンテナイメージの場合には、コンテナレジストリに保存したコンテナイメージと署名、証明書を使用するため、バイナリや署名、証明書といったファイルをダウンロードする必要はありません。仕組みについては、前回の記事を参照してください。

cosign verifyでコンテナイメージの検証 コンテナイメージの署名情報は以下の通りです。

  • Identity: krel-trust@k8s-releng-prod.iam.gserviceaccount.com
  • OIDC Issuer: https://accounts.google.com
$ cosign verify registry.k8s.io/kube-apiserver-amd64:v1.33.3 \
  --certificate-identity krel-trust@k8s-releng-prod.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com \
  | jq .
[
  {
    "critical": {
      "identity": {
        "docker-reference": "registry.k8s.io/kube-apiserver-amd64"
      },
      "image": {
        "docker-manifest-digest": "sha256:cc86c18b1a7282d2640dbe74fb65aa9ddfcc677190379d39fa4b91057405ae66"
      },
    },
    "optional": {
      "Issuer": "https://accounts.google.com",
      "Subject": "krel-trust@k8s-releng-prod.iam.gserviceaccount.com",
    }
  }
]

この結果から、registry.k8s.io/kube-apiserver-amd64:v1.33.3krel-trust@k8s-releng-prod.iam.gserviceaccount.com によって署名されていることを確認できます。

コンテナイメージを利用する際のベストプラクティスとして、コンテナイメージのハッシュ値を指定することが推奨されています。Podマニフェストで、 registry.k8s.io/kube-apiserver-amd64@sha256:cc86c18b1a7282d2640dbe74fb65aa9ddfcc677190379d39fa4b91057405ae66 のように指定することで、確実に署名されたコンテナを使用することができます。

Kubernetesでは、署名済みのコンテナイメージ一覧を SPDX 2.3 形式の SBOM として公開しています。これを利用することで、全てのコアコンポーネントをまとめて検証することが可能です。

以下のように SBOM からイメージ一覧を抽出し、一括検証することができます。

# 全てのコンテナイメージを取得
$ curl -Ls "https://sbom.k8s.io/$(curl -Ls https://dl.k8s.io/release/stable.txt)/release" \
  | grep "SPDXID: SPDXRef-Package-registry.k8s.io" \
  | grep -v sha256 | cut -d- -f3- | sed 's/-/\//' | sed 's/-v1/:v1/' \
  | sort > images.txt

# 各イメージを Cosign で検証
$ while IFS= read -r image
do
  cosign verify "$image" \
    --certificate-identity krel-trust@k8s-releng-prod.iam.gserviceaccount.com \
    --certificate-oidc-issuer https://accounts.google.com \
    | jq .
done < images.txt

ただし注意点として、署名の検証が常に成功するとは限りません。

Kubernetes の公式レジストリ registry.k8s.io はマルチベンダー・マルチリージョン構成をとっており、環境によっては同一のイメージでも署名情報が一致しないことがあります。

実際に、リージョンによる不一致や一部パッチリリースで署名が付与されていないといった問題が報告されています。

アドミッションコントローラーによる署名検証

Sigstore には、Kubernetesのアドミッションコントローラーとして policy-controller が用意されています。これは、ValidatingAdmissionWebhook を利用して動作し、Podの作成や更新時に cosign verifycosign verify-attestation を実行し、その結果に基づいてリソースの許可/拒否を判断します。

policy-controller を導入することで、Cosign から取得したサプライチェーンメタデータに基づいてクラスター内でポリシーを強制できます。具体的には、コンテナイメージの署名やアテステーションを検証し、アテステーションに対してcueまたはregoで定義したポリシーを適用します。

ポリシーはネームスペース単位で設定でき、複数のポリシーを同時に適用することも可能です。

policy-controller はコンテナイメージのタグをdigestに解決することで、署名検証後にタグが差し替えられて別のイメージが実行されることを防ぎます。

policy-controllerのインストール

既存のKubernetesクラスターを利用する場合はこの手順は不要です。ここでは検証用として、kindを使い、コントロールプレーン1台・ワーカー1台の2ノード構成でクラスターを作成します。

$ kind create cluster --config - <<EOF
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
- role: worker
EOF

policy-controller は Helmチャート で配布されています。インストール方法は、Artifact HUBのpolicy-controller が参考になります。

$ helm repo add sigstore https://sigstore.github.io/helm-charts
$ helm repo update

$ kubectl create namespace cosign-system
$ helm install policy-controller -n cosign-system sigstore/policy-controller

インストール後、policy.sigstore.devというValidatingWebhookConfigurationが作られており、policy-controller-webhookというPodがRunningになっていれば、準備完了です。

policy-controllerの有効化

デフォルトでは、opt-in 形式になっています。これは、既存環境に影響を与えないようにするためです。

そのため、Namespace に policy.sigstore.dev/include=true というラベルを付与した場合のみ、その Namespace 内の Pod が検証対象になります。 今回は、ns1ns2というNamespaceを作成し、ns1 のみ policy-controller を有効化します。

$ kubectl create namespace ns1
$ kubectl create namespace ns2
$ kubectl label namespace ns1 policy.sigstore.dev/include=true

この後、Keyless署名の検証用に、Cosignで署名したコンテナイメージ mozsec/hello を使います。Cosign によるコンテナイメージ署名の手順については、前回の記事を参照してください。

それでは、ns1ns2 にそれぞれ mozsec/hello を使った Pod を作成してみます。ns2 では Pod が作られるのに対して、ns1 では、”policy.sigstore.dev” でリクエストが拒否されていることが確認できます。

$ kubectl run -n ns1 --image mozsec/hello:latest hello
Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: no matching policies: spec.containers[0].image
index.docker.io/mozsec/hello:latest@sha256:714afd8d69161a880ca9695dfa9843784e0ffe550d56e3c9c66af4db04ad8bae

$ kubectl run -n ns2 --image mozsec/hello:latest hello
pod/hello created

ClusterImagePolicyの定義

ns1 でリクエストが拒否されているのは、署名を検証するポリシーが未定義のためです。policy-controllerはポリシーが存在しない場合、デフォルトで拒否します。

ポリシーは、ClusterImagePolicy リソースで定義します。

images.glob で検証対象のコンテナイメージを指定します。コンテナイメージの指定には、GolangのMatchが使われており、ホストが省略されると index.docker.io がデフォルト値として使用されます。

キーレス署名の場合、authorities.keylessで誰の署名を許可するか指定します。issuerで OIDC IdP を指定し、subject で署名者を指定します。subject は完全一致であるのに対して、subjectRegExpでは正規表現で一致するか判定します。少なくとも1つの authority に一致すれば、そのイメージは許可されます。

$ kubectl apply -f - <<EOF
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: mozsec-policy
spec:
  images:
  - glob: "index.docker.io/mozsec/*" # 全てのコンテナイメージを指定するには "**"
  authorities:
    - keyless:
        identities:
          - issuer: https://github.com/login/oauth
            subject: m0253c@gmail.com # 署名者(完全一致)
          - issuer: https://accounts.google.com
            subjectRegExp: ^[a-z]+@3-shake\.com$ # 組織のメールだけ許可
          - issuer: https://token.actions.githubusercontent.com
            subjectRegExp: ^https://github\.com/your-org/.+/.github/workflows/.+@refs/heads/main$
            # your-orgのリポジトリのmainブランチで実行されたWorkflowの署名のみ許可
EOF

それでは、再度 ns1 で Pod を起動してみます。mozsec/hello イメージは GitHubアカウントm0253c@gmail.com によって署名されているため、Pod は正常に作成されます。

$ kubectl run -n ns1 --image mozsec/hello:latest hello
pod/hello created

一方で、nginx の Pod を起動しようとすると、リクエストが拒否されます。これは、nginx イメージに m0253c@gmail.com の署名が存在しないためです。

$ kubectl run -n ns1 --image nginx:latest nginx
Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: failed policy: mozsec-policy: spec.containers[0].image
index.docker.io/library/nginx:latest@sha256:33e0bbc7ca9ecf108140af6288c7c9d1ecc77548cbfd3952fd8466a75edefe57 signature keyless validation failed for authority authority-0 for index.docker.io/library/nginx@sha256:33e0bbc7ca9ecf108140af6288c7c9d1ecc77548cbfd3952fd8466a75edefe57: no signatures found

どのポリシーにもマッチしなかったとき

policy-controller では、コンテナイメージに一致するポリシーが存在しない場合の挙動を制御できます。

config-policy-controller ConfigMapで no-match-policy を設定し、その値を以下から選びます。

  • deny(デフォルト): ポリシーに一致しないイメージは拒否
  • warn: Pod は作成されるが、警告が出力
  • allow: ポリシーに一致しなくても許可

これにより、先ほど ClusterImagePolicy が未設定の状態でリクエストが拒否されたケースも、no-match-policy を設定することで、「警告だけを出す」または「許可する」といった挙動に変更可能です。

opt-out 形式に変更

デフォルトでは opt-in 形式 であり、Namespace にラベル policy.sigstore.dev/include=true を付けた場合のみ、コンテナイメージの検証が有効になります。

一方で、opt-out 形式 に切り替えることも可能です。これは「すべての Namespace で検証を有効にし、特定の Namespace のみ除外する」方式です。この方法を採用すると、ポリシーの設定漏れを防ぎやすくなりますが、既存 Pod に影響を与えるリスクがあります。 特に kube-system Namespace を除外しない場合、コアコンポーネント(kube-apiserver や kube-scheduler など)が拒否され、クラスタが正常に動作しなくなる恐れがあります。

opt-out 形式への切り替えは、MutatingWebhookConfiguration の namespaceSelector を修正することで行えます。

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: policy.sigstore.dev
  namespaceSelector:
    matchExpressions:
    - key: policy.sigstore.dev/exclude
      operator: DoesNotExist

この設定では、デフォルトで全 Namespace が検証対象となり、policy.sigstore.dev/exclude=trueラベルを付与した Namespace のみ検証対象から除外されます。

YAMLマニフェストの署名・検証

Sigstoreには k8s-manifest-sigstore というkubectlのプラグインも用意されています。このプラグインを使用すると、Kubernetesの YAMLマニフェスト に署名を付与し、デプロイメントチームはその信頼性を検証できます。つまり「コンテナイメージの署名検証」に加えて、「マニフェスト自体の整合性検証」も可能にする試みです。

ただし、まだ開発中のプロジェクトであり、本番環境での利用は推奨されていません。

k8s-manifest-sigstoreのインストール

このプラグインはバイナリとして提供されており、PATH が通ったディレクトリに配置するだけで、kubectl サブコマンドとして利用できます。

$ go install github.com/sigstore/k8s-manifest-sigstore/cmd/kubectl-sigstore@latest
$ kubectl sigstore --help

マニフェストの署名

kubectl sigstore sign を実行すると、YAML マニフェストを OCI イメージに変換してレジストリにプッシュし、Cosign で署名します。

流れとしては「イメージ署名」と同様ですが、対象が YAML マニフェストになります。

ここでは、nginx-pod.yaml を Docker Hub にプッシュした後、署名しています。mozsec/bundle-bar を見ると、devタグがついたアーティファクトとその署名(.sigサフィックス)が確認できます。

# nginx Pod を起動するマニフェストの作成
$ kubectl run nginx --image=nginx --dry-run=client -o yaml > nginx-pod.yaml

# マニフェストの署名
$ kubectl sigstore sign -f nginx-pod.yaml --image docker.io/mozsec/bundle-bar:dev

マニフェストの検証

ローカルのマニフェストファイルを、OCIレジストリに保存された署名で検証できます。

$ kubectl sigstore verify -f nginx-pod.yaml -i docker.io/mozsec/bundle-bar:dev

さらに、実際にデプロイされたリソースから YAML を取得し、その内容を検証することも可能です。ただし、spec.nodeName などスケジューリング後に付与されるフィールドは差分として検出されてしまいます。この問題を回避するには、config.yaml のように無視するフィールドを定義する必要があります。

以下の例では、 ノード名やボリューム名などスケジューリング後に付与されるフィールドを無視する設定を加えて検証しています。

$ cat ignore-config.yaml 
ignoreFields: # 動的なフィールドを無視する
  - objects:
      - kind: Pod
        name: nginx
        namespace: ns2
    fields:
      - spec.nodeName
      - spec.volumes.*.name
      - spec.containers.*.volumeMounts.*.name

$ kubectl sigstore verify-resource pods nginx -i docker.io/mozsec/bundle-bar:dev \
    -c ignore-config.yaml

このように無視フィールドを設定すれば差分は解消できますが、対象ごとに個別設定が必要になるため、現時点では運用面でのハードルはやや高いといえます。

まとめ

本記事では、KEP-3031に基づく公式アーティファクト検証、Cosignによるイメージ署名検証、policy-controllerによるポリシー強制、そしてマニフェスト署名の試行までを解説しました。

前回に引き続き、本記事では Keyless 署名を中心に解説しましたが、Cosign は従来の PGP/GPG 鍵 や キーペア(x509)・KMS(AWS/GCP/Azure/HSM) を用いた署名・検証にも対応しています。

さらに詳しく知りたい方は、公式ドキュメントをご覧ください。

参考

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ