この記事は Kubernetes v1.37 時点の Alpha(フィーチャーゲート APIServerWebhookAuthenticationToken)に基づいています。Beta / GA への昇格時に API フィールド名・JWT クレーム構造・RBAC リソース名などが変更される可能性があります。
TL;DR
Kubernetes v1.37 Alpha で導入された APIServerWebhookAuthenticationToken フィーチャーゲートは、kube-apiserver が Admission Webhook を呼び出す際に自分自身の身元を証明するトークンを送信する仕組みを導入します。
KEP: https://github.com/kubernetes/enhancements/issues/6060
背景
Admission Webhook の認証の現状
kube-apiserver がリソースの作成・更新・削除をリクエストされた際に、登録された webhook に送信するリクエストを AdmissionReview と呼ぶ。webhook はこのリクエストを受け取り、許可・拒否・変更を返す。kube-apiserver だけが送ることを想定した設計だが、フォーマット自体は公開されており、webhook エンドポイントへのネットワークアクセスさえあれば誰でも同じ形式のリクエストを直接送れてしまう。
kube-apiserver と Admission Webhook 間の通信は以下のような非対称な状態になっている。
| 方向 | 状態 |
|---|---|
| webhook → kube-apiserver(サーバー認証) | TLS で保証。caBundle で webhook サーバーの証明書を検証 |
| kube-apiserver → webhook(クライアント認証) | デフォルトなし |
つまり kube-apiserver は正しい webhook サーバーに繋いでいることは保証できるが、webhook 側は「このリクエストが本当に kube-apiserver から来たのか」を確認できない。
既存の opt-in 手段として --admission-control-config-file で AdmissionConfiguration を指定し、kubeconfig ファイル経由でクライアント証明書や Bearer トークンを設定できる。
# AdmissionConfiguration で kubeconfig を参照
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionWebhook
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: WebhookAdmissionConfiguration
kubeConfigFile: /etc/k8s/webhook-auth-kubeconfig.yaml
# kubeconfig でトークンを指定
apiVersion: v1
kind: Config
users:
- name: apiserver-client
user:
token: "eyJhbGciOiJSUzI1NiIs..." # 静的な Bearer トークン
しかしこの仕組みには以下の問題がある。
- トークンはファイルに書かれた固定値であり、ローテーションには kube-apiserver の再起動が必要
- 全 webhook に同じ認証情報が使われるため、webhook ごとのスコープ制御ができない
- トークンに「どの webhook 向けか」「どの API グループについてか」という情報が含まれない
- 認証方式が統一されておらず webhook メンテナが全方式に対応する必要があり普及しにくい
CVE-2025-1974(IngressNightmare)
この構造的な問題が実害につながった事例が CVE-2025-1974(IngressNightmare、CVSS 9.8)。2025年3月に Wiz 社が公開。
IngressNightmare は単一の CVE ではなく、複数の CVE を連鎖させた攻撃として成立する。
| CVE | 場所 | 内容 |
|---|---|---|
| CVE-2025-24514 |
auth-url アノテーション |
入力サニタイズ不足による NGINX 設定インジェクション |
| CVE-2025-1097 |
auth-tls-match-cn アノテーション |
正規表現バイパスによる設定インジェクション |
| CVE-2025-1098 | mirror UID | フィルタリング不足による設定インジェクション |
| CVE-2025-1974 | admission webhook 全体 | 上記3つを組み合わせた RCE(CVSS 9.8) |
CVE-2025-24514 / CVE-2025-1097 / CVE-2025-1098 はいずれも単体では NGINX 設定を注入できるにとどまるが、CVE-2025-1974 はこれらを組み合わせることで任意コード実行まで到達する。
Admission Webhook の種類
Kubernetes の Admission Webhook には2種類ある。
MutatingAdmissionWebhook
リクエストの内容を変更できる。変更内容を JSON Patch で返す。サイドカー自動注入(Istio など)やデフォルト値の自動設定などに使われる。
ValidatingAdmissionWebhook
許可・拒否のみを返す。内容の変更はできない。ingress-nginx が Ingress オブジェクトを nginx -t で事前検証するのもこの仕組み。
実行順序は Mutating → Validating の順。変更後の状態に対して検証をかけるため。
リクエスト
↓
MutatingAdmissionWebhook(変更)
↓
スキーマバリデーション
↓
ValidatingAdmissionWebhook(検証)
↓
etcd に保存
KEP-6060 では MutatingWebhookConfiguration / ValidatingWebhookConfiguration の両方を boundObjectRef に指定できるため、どちらの webhook に対しても認証トークンを提示できる。
なぜ ingress-nginx が AdmissionReview を受け取るのか: ingress-nginx コントローラーには Ingress オブジェクトの設定を事前検証するための Admission Webhook が組み込まれており、コントローラー自身が webhook サーバーとしても動作している。
kubectl apply -f ingress.yaml
↓
kube-apiserver
↓(ValidatingWebhookConfiguration に従い)
ingress-nginx コントローラーの admission webhook エンドポイント
↓
受け取った Ingress オブジェクトから nginx.conf を生成
↓
nginx -t で設定の妥当性を検証
↓
kube-apiserver に allow / deny を返す
CVE-2025-1974 はこの nginx -t の実行タイミングを悪用した攻撃で、nginx 本体の脆弱性ではなく ingress-nginx の admission webhook の設計上の問題(認証なし+アノテーションのサニタイズ不足)が根本原因。クラスター内から上記3つの CVE を連鎖させてアノテーション経由で NGINX 設定をインジェクションし、RCE へつなげた。ingress-nginx の Pod には全 namespace の Secret にアクセスできる高権限 SA がマウントされており、クラスター全体の侵害につながる。
クラウド環境の約 43%、6,500 以上のクラスターが影響を受けたとされる。
このKEPの位置付け
KEP-6060 は上記の構造的な問題(kube-apiserver がデフォルトで身元を証明しない)を解決するもの。
従来の SA トークンは sub(system:serviceaccount:kube-system:some-sa)で「誰か」を伝えるだけで、以下を区別できなかった。
- そのトークンが「kube-apiserver が admission 処理の一環として発行したもの」なのか「誰かが SA のトークンを取得して直接 webhook に送りつけたもの」なのか
- どの webhook に対して発行されたトークンなのか
- どの API グループについての AdmissionReview なのか
sub だけの「誰か」から、「誰が・どの webhook に対して・どの API グループについて」を証明できるトークンに拡張された。
仕組み
TokenRequest API の拡張
このKEPでは既存の TokenRequestSpec に対して以下の2点を追加した。
| フィールド | 導入時期 | 内容 |
|---|---|---|
audiences |
v1.12 | トークンの対象オーディエンス |
expirationSeconds |
v1.12 | 有効期限 |
boundObjectRef |
v1.13 | Pod / Secret へのバインド |
boundObjectRef(拡張) |
v1.37(このKEP) | ValidatingWebhookConfiguration / MutatingWebhookConfiguration を指定可能に |
attestations |
v1.37(このKEP) | JWT に埋め込むクレームを宣言するフィールド(新規追加) |
attestations は map[string]AttestationValue 型で、AttestationValue は []string(文字列の配列)。キーに admissionReviewAPIGroups を指定することで JWT の attestations クレームにどの API グループの AdmissionReview に使えるかを埋め込む。これはこのKEPで初めて導入された概念。
現在サポートされているキーは admissionReviewAPIGroups のみ。マップ型にしているのは将来の拡張のためで、Alpha の時点では1種類だけ。値には有効な DNS サブドメイン形式の API グループ名か "*" を配列で指定する。webhook にバインドするトークンには admissionReviewAPIGroups を必ず1つ指定する制約があり、0個・2個以上はエラーになる。
{ "admissionReviewAPIGroups": ["apps"] } // apps グループのみ(Aggregated API サーバー向け)
{ "admissionReviewAPIGroups": ["networking.k8s.io"] } // networking.k8s.io グループのみ
{ "admissionReviewAPIGroups": ["*"] } // 全 API グループ(kube-apiserver 向け)
webhook 側はこのクレームを見て「このトークンが送ってきた AdmissionReview は宣言された API グループのリソースについてのものか」を検証する。これによりAggregated API サーバーが自分の管轄外のリソースについて webhook を問い合わせることを防ぐ。
トークンの特徴
-
boundObjectRefに webhook configuration を指定することで webhook 設定の name/UID が JWT に埋め込まれる -
attestations.admissionReviewAPIGroupsでこのトークンが使える API グループを宣言する - 有効期限は最大 10 分(自動制限)
JWT ペイロード例
{
"sub": "system:serviceaccount:kube-system:webhook-auth",
"aud": ["https://mutagen-capsule.default.svc/admission/review"],
"kubernetes.io": {
"namespace": "kube-system",
"serviceaccount": {
"name": "webhook-auth",
"uid": "<sa-uid>"
},
"mutatingwebhookconfiguration": {
"name": "mutagen-capsule",
"uid": "<uid>"
},
"attestations": {
"admissionReviewAPIGroups": ["*"]
}
}
}
フロー
kube-apiserver
│
├─① TokenRequest(kube-system:webhook-auth SA)
│ BoundObjectRef: MutatingWebhookConfiguration "my-webhook"
│ Attestation: admissionReviewAPIGroups=apps
│ Audience: "https://my-webhook.default.svc/..."
│
├─② API サーバーが認可チェック
│ - TokenRequest クライアントが webhook-auth SA のトークンを作れるか
│ - webhook config が存在するか
│ - webhook-auth SA が admissionReviewAPIGroups への "attest" 権限を持つか
│
├─③ JWT 発行(max 10 分、キャッシュあり)
│
└─④ Webhook 呼び出し時に Authorization: Bearer <token> を付与
↓
webhook 側の検証
1. OIDC discovery でシグネチャ検証
2. audience 確認
3. allowedAPIGroup が AdmissionReview のリソースを含むか確認
Alpha (v1.37) での実装範囲
- TokenRequest API の拡張(新フィールドの追加)
- トークン発行ロジック(認可チェック含む)
- TokenReview でのトークン検証(Extra フィールドへの attestation 値の伝搬)
- kube-apiserver から webhook への自動送信(後続 PR / マイルストーン)
- webhook 検証ライブラリの提供
動作確認 with KinD
1. KinD クラスタ作成
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
feature-gates: "APIServerWebhookAuthenticationToken=true"
$ kind create cluster --name webhook-auth-test --config kind-config.yaml --image kindest/node:v1.37.0
$ kubectl cluster-info --context kind-webhook-auth-test
2. テスト用 Webhook 設定と SA の作成
$ kubectl create namespace webhook-test
$ kubectl create serviceaccount webhook-auth -n kube-system
ValidatingWebhookConfiguration を作成する(後でトークンをバインドするため)。
# validating-webhook.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: test-webhook
webhooks:
- name: test.example.com
admissionReviewVersions: ["v1"]
clientConfig:
# テスト用のため url を直接指定
url: "https://test.example.com/validate"
caBundle: "" # TODO: 実際のテストでは設定が必要
rules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["deployments"]
sideEffects: None
failurePolicy: Ignore
$ kubectl apply -f validating-webhook.yaml
3. RBAC 設定
webhook-auth SA に admissionReviewAPIGroups への attest 権限を付与する。
# webhook-auth-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: webhook-auth-attest
rules:
- apiGroups: ["authentication.k8s.io"]
resources: ["admissionReviewAPIGroups"]
verbs: ["attest"]
resourceNames: ["*"] # 全 APIGroup を許可(kube-apiserver 相当)
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: webhook-auth-attest-binding
subjects:
- kind: ServiceAccount
name: webhook-auth
namespace: kube-system
roleRef:
kind: ClusterRole
name: webhook-auth-attest
apiGroup: rbac.authorization.k8s.io
$ kubectl apply -f webhook-auth-rbac.yaml
4. attestation 付きトークンの発行
attestations フィールドは kubectl create token コマンドではまだサポートされていないため、TokenRequest サブリソースに直接 POST する。kubectl proxy を使うのが最も簡単。
4-1. kubectl proxy を起動
$ kubectl proxy &
# Starting to serve on 127.0.0.1:8001
4-2. TokenRequest を POST してトークン取得
TokenRequest は ServiceAccount のサブリソース(/token)として発行する。
POST /api/v1/namespaces/{namespace}/serviceaccounts/{name}/token
# webhook 設定の UID を取得(BoundObjectRef に必要)
$ WEBHOOK_UID=$(kubectl get validatingwebhookconfiguration test-webhook \
-o jsonpath='{.metadata.uid}')
# attestation 付きトークンを発行
$ TOKEN=$(curl -s -X POST \
http://localhost:8001/api/v1/namespaces/kube-system/serviceaccounts/webhook-auth/token \
-H "Content-Type: application/json" \
-d "{
\"apiVersion\": \"authentication.k8s.io/v1\",
\"kind\": \"TokenRequest\",
\"spec\": {
\"audiences\": [\"https://test.example.com/validate\"],
\"expirationSeconds\": 600,
\"boundObjectRef\": {
\"apiVersion\": \"admissionregistration.k8s.io/v1\",
\"kind\": \"ValidatingWebhookConfiguration\",
\"name\": \"test-webhook\",
\"uid\": \"${WEBHOOK_UID}\"
},
\"attestations\": {
\"admissionReviewAPIGroups\": [\"apps\"]
}
}
}" | jq -r '.status.token')
$ echo $TOKEN
ポイントは以下のとおり。
-
boundObjectRef.kindにValidatingWebhookConfiguration/MutatingWebhookConfigurationを指定することでトークンがその webhook にバインドされる(webhook 設定の name/UID が JWT に埋め込まれる) -
attestations.admissionReviewAPIGroupsを指定することで attestation 付きトークンになり、使える API グループが JWT に宣言される(["*"]で全グループ) -
attestations.admissionReviewAPIGroupsでこのトークンが使える API グループを宣言する("*"で全グループ) -
audiencesは webhook の clientConfig.url または service から導出されたものと一致する必要がある - 有効期限を 600 秒(10 分)以上に設定しても自動的に 600 秒に切り詰められる
4-3. エラーケースの確認
RBAC が不足している場合(attest 権限なし)は以下のようなエラーになる。
{
"kind": "Status",
"status": "Failure",
"message": "admissionReviewAPIGroups.authentication.k8s.io \"apps\" is forbidden: User \"system:serviceaccount:kube-system:webhook-auth\" cannot attest resource \"admissionReviewAPIGroups\" in API group \"authentication.k8s.io\" at the cluster scope",
"reason": "Forbidden",
"code": 403
}
boundObjectRef の UID が実際と異なる場合はエラーになる。
{
"message": "boundObjectRef uid does not match: expected ..., got ..."
}
5. JWT クレームの確認
取得したトークンをデコードして attestation クレームが埋め込まれていることを確認する。
# JWT header
$ echo $TOKEN | cut -d. -f1 | base64 -d 2>/dev/null | jq .
# JWT payload
$ echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq .
期待されるペイロードは以下のとおり。
{
"iss": "https://kubernetes.default.svc.cluster.local",
"sub": "system:serviceaccount:kube-system:webhook-auth",
"aud": ["https://test.example.com/validate"],
"exp": 1234567890,
"iat": 1234567290,
"kubernetes.io": {
"namespace": "kube-system",
"serviceaccount": {
"name": "webhook-auth",
"uid": "<sa-uid>"
},
"validatingwebhookconfiguration": {
"name": "test-webhook",
"uid": "<uid>"
},
"attestations": {
"admissionReviewAPIGroups": ["apps"]
}
}
}
通常のサービスアカウントトークン(kubectl create token)と比べて kubernetes.io 以下に validatingwebhookconfiguration と attestations が追加されているのが確認できる。
6. TokenReview での検証
webhook がトークンを検証する際は以下のように TokenReview リクエストを送る。
$ TOKEN=<上で取得したトークン>
$ cat <<EOF | kubectl create -f -
apiVersion: authentication.k8s.io/v1
kind: TokenReview
spec:
token: "${TOKEN}"
audiences:
- "https://test.example.com/validate"
EOF
成功時のレスポンスには attestation 値が user.extra フィールドに含まれる。
{
"status": {
"authenticated": true,
"user": {
"username": "system:serviceaccount:kube-system:webhook-auth",
"uid": "<sa-uid>",
"groups": [
"system:serviceaccounts",
"system:serviceaccounts:kube-system",
"system:authenticated"
],
"extra": {
"attestation.authentication.kubernetes.io/admissionReviewAPIGroups": ["apps"],
"authentication.kubernetes.io/credential-id": ["JTI=<jti>"],
"authentication.kubernetes.io/validatingwebhookconfiguration-name": ["test-webhook"],
"authentication.kubernetes.io/validatingwebhookconfiguration-uid": ["<uid>"]
}
},
"audiences": ["https://test.example.com/validate"]
}
}
7. クリーンアップ
$ kind delete cluster --name webhook-auth-test
補足
attest 権限のスコープ
RBAC の ClusterRole で attest verb を付与する際、resourceNames に指定した値が「どの API グループへの attestation を要求できるか」を制限する。TokenRequest 発行時に kube-apiserver がこの認可チェックを行う。
# kube-apiserver 用 SA に付与する ClusterRole
rules:
- apiGroups: ["authentication.k8s.io"]
resources: ["admissionReviewAPIGroups"]
verbs: ["attest"]
resourceNames: ["*"] # 全グループへの attestation を許可
# Aggregated API サーバー用 SA に付与する ClusterRole
rules:
- apiGroups: ["authentication.k8s.io"]
resources: ["admissionReviewAPIGroups"]
verbs: ["attest"]
resourceNames: ["apps.mycompany.io"] # 自分の管轄グループのみ
resourceNames |
発行できるトークンの attestation | 用途 |
|---|---|---|
["*"] |
admissionReviewAPIGroups: ["*"] |
kube-apiserver(全グループを扱うため) |
["apps"] |
admissionReviewAPIGroups: ["apps"] のみ |
Aggregated API サーバー等 |
"*" は技術的に制限されているわけではなく、KEP が「kube-apiserver にだけ付与することを強く推奨」としている。Aggregated API サーバーに "*" を付与すると、侵害された場合に自分の管轄外のリソースについて webhook を問い合わせて情報を引き出せてしまうリスクがある。
トークンが無効化されるケース
- バインドされた webhook configuration が削除された場合
- UID が変わった場合(再作成など)
TokenReview vs OIDC 検証
webhook ライブラリでは TokenReview を呼ばずに OIDC discovery エンドポイント経由でシグネチャを検証することが推奨される(不要な round trip を避けるため)。ただし TokenReview を使っても検証可能。