TT Lab
はじめる
学ぶ 学習パス コース

ポリシーをコードで

Webhook を一つ Fail にしただけでクラスタが止まった

TT Labで続きを見る

目標

アドミッションWebhookの適用範囲を、3つの層で自分で絞ってみて、ポリシーサーバーが落ちた状態を本当に作り、failurePolicyとtimeoutSecondsが何を引き換えにするのかを、時間を計って確認し、クラスターのすべてのWebhookについて「これが落ちると何が止まるのか」に答えるレポートと検査ツールを作ります。

なぜ重要なのか

ポリシー導入の事故の記録を集めてみると、ルールが間違っていて起きた事故は珍しいです。ほとんどは、次のどちらかです。広すぎる範囲にかけたか、故障したときの動作を決めていなかったかです。広くかけるコストは、ポリシー1回の実行時間ではなく、入ってくるすべての書き込みリクエストに付くレイテンシ税であり、クラスターには、人が送るリクエストよりも、コントローラーが送るリクエストのほうが、ずっと多いです。もっと恐ろしいのは、2つ目です。ポリシーがkube-systemまで見るようにかけておいて、ポリシーサーバーが落ちると、failurePolicy: Failのもとで、クラスターは自分自身を復旧できなくなります。サーバーを蘇生させるためのデプロイも、そのポリシーを通る必要があるからです。範囲と故障モードは、別々に選ぶ値ではなく、セットで選ぶ値であり、このラボは、そのセットを手で回してみます。最後に作る爆発半径のレポートは、ポリシーをオンにする前ではなく、すでにオンにしてあるクラスターで、今すぐ作ってみるべきものです。

ステップ

  1. /root/polscopeで作業します(export KUBECONFIG=/root/.kube/config、kubectl config use-context kwok-lab)。まず、ネームスペースscope-app・scope-ops・scope-builtinを作成し、3か所すべてに、kubectl create sa default -n <네임스페이스>(プレースホルダーはネームスペースです)でdefaultサービスアカウントを自分で作成してください(このクラスターにはコントローラーマネージャーがないため、自然には作られません)。/root/polscope/webhook-wide.yamlに、ValidatingWebhookConfiguration scope-wide-auditを書いてください。Webhook名はwide.polscope.local、admissionReviewVersionsは["v1"]、sideEffectsはNone、failurePolicyはIgnore、timeoutSecondsは2、clientConfig.urlはhttps://192.0.2.77:8443/validateです。rulesは最も広く、apiGroups: ["*"]、apiVersions: ["*"]、operations: ["CREATE", "UPDATE"]、resources: ["*"]、scope: "*"にして、セレクターは1つも置きません。テスト用のマニフェストも3つ作成してください。/root/polscope/pod.yaml(Pod probe-app、コンテナweb、イメージregistry.internal/app:1.0)、/root/polscope/pod-guard.yaml(Pod probe-guard、ラベルpolscope.io/guard: "on"、同じコンテナ)、/root/polscope/cm.yaml(ConfigMap probe-cm、データa: b)です。適用した後、4つのリクエストの所要時間を計ってください。kubectl create -n scope-app -f pod.yaml --dry-run=server、kubectl create -n scope-app -f cm.yaml --dry-run=server、kubectl create ns probe-ns --dry-run=server、kubectl get pods -n scope-appです。1秒以上かかったらSLOW、そうでなければFASTとして、/root/polscope/01-reach.txtに、pod-create・configmap-create・namespace-create・pod-listの4行を、<이름> <낱말>(プレースホルダーは名前と語です)の形で書いてください。
  2. /root/polscope/webhook-strict.yamlに、2つ目の設定scope-strictを書いてください。Webhook名はstrict.polscope.local、rulesはscope-wide-auditとまったく同じく最も広く、違うのは3つだけです。failurePolicyはFail、timeoutSecondsは3、clientConfig.urlは、接続が拒否されるアドレスhttps://127.0.0.1:19443/validateです。scope-wide-auditは、削除せずにそのままにしておきます。同じ範囲で、故障モードだけが違う2つが並んで立っているのが、このラボの対照群です。適用した後、4つを試してブロックされるか見て、ブロックされたらBLOCK、通過したらPASSとして、/root/polscope/02-blocked.txtに、<이름> <낱말>(プレースホルダーは名前と語です)の4行を書いてください。pod-create-scope-app(kubectl create -n scope-app -f pod.yaml --dry-run=server)、configmap-kube-system(kubectl create -n kube-system -f cm.yaml --dry-run=server)、namespace-create(kubectl create ns probe-ns --dry-run=server)、webhookconfig-edit(kubectl apply -f webhook-strict.yamlをもう一度)です。
  3. まず、適用除外ラベルで抜け出してみてください。kubectl label ns kube-system polscope.io/admission=exempt --overwriteを実行して、その出力を標準エラー出力まで一緒に/root/polscope/03-lockout.txtに保存してください(ブロックされます)。そのあと、/root/polscope/webhook-strict.yamlのscope-strictに、namespaceSelector.matchExpressionsを2つ追加して、もう一度適用してください。(1)キーkubernetes.io/metadata.name、演算子NotIn、値["kube-system", "kube-node-lease", "kube-public"]、(2)キーpolscope.io/admission、演算子NotIn、値["exempt"]です。これでラベルが付けられます。kube-systemとscope-opsに、polscope.io/admission=exemptをかけてください。最後に、/root/polscope/pod-guard.yamlを3か所に--dry-run=serverで送って、/root/polscope/03-escape.txtに、kube-system・scope-ops・scope-appの3行を、<네임스페이스> <BLOCK|PASS>(プレースホルダーはネームスペースと、BLOCKまたはPASSです)の形で書いてください。
  4. /root/polscope/webhook-strict.yamlのscope-strictを、2つの層で絞ってください。rulesは、コアグループ("")のv1のpodsに対するCREATE・UPDATEだけ、scopeはNamespacedに変え、objectSelector.matchLabelsで、polscope.io/guard: "on"ラベルが付いたオブジェクトだけを見るようにします。ステップ3のnamespaceSelectorは、そのままにしておきます。適用した後、4つのリクエストをもう一度送って、/root/polscope/04-narrow.txtに、<이름> <좁히기전> <좁힌뒤>(プレースホルダーは順に、名前、絞る前、絞った後です)の4行を書いてください(絞る前の値は、ステップ2で見たものです)。pod-guard-scope-app(pod-guard.yamlをscope-appに)、pod-plain-scope-app(pod.yamlをscope-appに)、configmap-scope-app(cm.yamlをscope-appに)、namespace-create(kubectl create ns probe-ns --dry-run=server)です。判定はBLOCKまたはPASSです。
  5. /root/polscope/webhook-strict.yamlのscope-strictに、matchConditionsを2つ追加して、もう一度適用してください。skip-kube-system-saは、リクエスト元がsystem:serviceaccount:kube-system:で始まるサービスアカウントなら、Webhookを呼び出さないようにし、skip-break-glassは、リクエスト元のグループにpolscope:break-glassがあれば、呼び出さないようにします。適用した後、同じ/root/polscope/pod-guard.yamlをscope-appに、3つのサブジェクトで送って、/root/polscope/05-conditions.txtに、kube-system-sa・break-glass・normalの3行を、<이름> <BLOCK|PASS>(プレースホルダーは名前と、BLOCKまたはPASSです)の形で書いてください。kubectl --as=system:serviceaccount:kube-system:replicaset-controller create -n scope-app -f pod-guard.yaml --dry-run=server、kubectl --as=oncall --as-group=polscope:break-glass --as-group=system:masters create -n scope-app -f pod-guard.yaml --dry-run=server、そして、今のアカウントのまま1回です。
  6. /root/polscope/webhook-slow.yamlに、3つ目の設定scope-slowを書いてください。Webhook名slow.polscope.local、clientConfig.urlは、応答がまったく来ないアドレスhttps://192.0.2.77:8443/validate、sideEffectsはNone、namespaceSelector.matchExpressionsは、キーkubernetes.io/metadata.nameをIn ["scope-ops"]に、objectSelector.matchLabelsはpolscope.io/slow: "on"、rulesは、コアグループのv1のpodsのCREATE・UPDATE(scope: "Namespaced")で、最終状態はfailurePolicy: FailとtimeoutSeconds: 3です。/root/polscope/pod-slow.yamlに、Pod probe-slow(ラベルpolscope.io/slow: "on"、コンテナweb、イメージregistry.internal/app:1.0)を作成してください。そして、/root/polscope/timeout-probe.sh <Fail|Ignore> <초>(プレースホルダーはFailまたはIgnoreと、秒数です)を作成してください。scope-slowをその2つの値に一時的に変更し、scope-opsにpod-slow.yamlを--dry-run=serverで1回送って、かかった時間を計り、元の値に必ず戻した後、BLOCK <초>またはPASS <초>(プレースホルダーは秒数です)を1行だけ出力します(秒は、四捨五入した整数)。このスクリプトで3回計って、/root/polscope/06-timeout.txtに、<failurePolicy> <타임아웃> <BLOCK|PASS> <걸린초>(プレースホルダーは順に、failurePolicy、タイムアウト、BLOCKまたはPASS、かかった秒数です)の3行を書いてください。Fail 6、Ignore 6、Fail 4で、それぞれ1回ずつです。
  7. Webhookサーバーがない今のまま、同じ場所に組み込みポリシーを立てて、比較します。ネームスペースscope-builtinに、ラベルpod-security.kubernetes.io/enforce=baselineをかけてください。/root/polscope/vap-owner.yamlに、ValidatingAdmissionPolicy polscope-require-owner(コアグループv1のpodsのCREATE・UPDATEを捉え、Podのメタデータにownerラベルがあることを要求)と、同じ名前のValidatingAdmissionPolicyBinding(validationActionsは["Deny"]、matchResources.namespaceSelectorはkubernetes.io/metadata.nameがIn ["scope-builtin"])を、一緒に書いて適用してください。テスト用のPodをさらに3つ作成してください。/root/polscope/pod-owned.yaml(Pod probe-owned、ラベルowner: platform)、/root/polscope/pod-host.yaml(Pod probe-host、同じラベルにspec.hostNetwork: true)、/root/polscope/pod-both.yaml(Pod probe-both、ラベルowner: platformとpolscope.io/guard: "on")です。コンテナは、3つともweb/registry.internal/app:1.0です。4つのマニフェストをscope-builtinに--dry-run=serverで送って、何が判定したかを、/root/polscope/07-builtin.txtに、<파일이름> <낱말>(プレースホルダーはファイル名と語です)の4行で書いてください。語は、VAP(ValidatingAdmissionPolicyが拒否)、PSA(PodSecurityが拒否)、WEBHOOK(Webhookの呼び出しが失敗)、PASS(通過)のうちの1つで、対象はpod.yaml・pod-host.yaml・pod-both.yaml・pod-owned.yamlです。
  8. /root/polscope/blast-radius.shを作成してください。クラスターのすべてのValidatingWebhookConfigurationを走査して、Webhook1つごとにオブジェクト1つを入れたJSONの配列を、標準出力に出力します。キーは、正確に8つです。config(設定の名前)・webhook(Webhookの名前)・failurePolicy(なければ"Fail")・timeoutSeconds(なければ10)・resources(そのWebhookのすべてのrules[].resourcesを、重複なしでソートした配列)・wildcard(resourcesに"*"があれば真)・excludesKubeSystem(namespaceSelector.matchExpressionsに、キーがkubernetes.io/metadata.name、演算子がNotIn、値にkube-systemが入った項目があれば真)・riskです。riskは、wildcardが真で、failurePolicyがFailで、excludesKubeSystemが偽なら"high"、それ以外でfailurePolicyがFailなら"medium"、残りは"low"です。配列は、config・webhookの順にソートします。その出力を/root/polscope/blast-radius.jsonに保存してください。そして、/root/polscope/risky.shを作成してください。riskがhighのものだけを、<config>/<webhook>の形で1行ずつ出力し、1つでもあれば終了コード1、1つもなければ何も出力せずに0で終了します。2つのスクリプトとも、保存されたファイルではなく、今クラスターに存在するオブジェクトを読む必要があります。

参考

Webhook1つを広くかけたら、すべての書き込みが2秒ずつ遅くなった

/root/polscopeで作業します(export KUBECONFIG=/root/.kube/config、kubectl config use-context kwok-lab)。まず、ネームスペースscope-app・scope-ops・scope-builtinを作成し、3か所すべてに、kubectl create sa default -n <네임스페이스>(プレースホルダーはネームスペースです)でdefaultサービスアカウントを自分で作成してください(このクラスターにはコントローラーマネージャーがないため、自然には作られません)。/root/polscope/webhook-wide.yamlに、ValidatingWebhookConfiguration scope-wide-auditを書いてください。Webhook名はwide.polscope.local、admissionReviewVersionsは["v1"]、sideEffectsはNone、failurePolicyはIgnore、timeoutSecondsは2、clientConfig.urlはhttps://192.0.2.77:8443/validateです。rulesは最も広く、apiGroups: ["*"]、apiVersions: ["*"]、operations: ["CREATE", "UPDATE"]、resources: ["*"]、scope: "*"にして、セレクターは1つも置きません。テスト用のマニフェストも3つ作成してください。/root/polscope/pod.yaml(Pod probe-app、コンテナweb、イメージregistry.internal/app:1.0)、/root/polscope/pod-guard.yaml(Pod probe-guard、ラベルpolscope.io/guard: "on"、同じコンテナ)、/root/polscope/cm.yaml(ConfigMap probe-cm、データa: b)です。適用した後、4つのリクエストの所要時間を計ってください。kubectl create -n scope-app -f pod.yaml --dry-run=server、kubectl create -n scope-app -f cm.yaml --dry-run=server、kubectl create ns probe-ns --dry-run=server、kubectl get pods -n scope-appです。1秒以上かかったらSLOW、そうでなければFASTとして、/root/polscope/01-reach.txtに、pod-create・configmap-create・namespace-create・pod-listの4行を、<이름> <낱말>(プレースホルダーは名前と語です)の形で書いてください。

Webhookのアドレスが192.0.2.0/24(TEST-NET-1)だと、接続が拒否されることも、応答が来ることもありません。APIサーバーは、timeoutSecondsの分だけ待ってから、failurePolicyに渡すので、Ignoreにしておくと、リクエストはすべて通過しますが、かかった時間が、そのリクエストが範囲内にあったという証拠になります。範囲外のリクエストは、待たずに終わります。アドミッションは書き込みにだけかかります。読み取りのリクエストは、Webhookをそもそも通りません。時間は、s=$(date +%s%N)とe=$(date +%s%N)の間を引けば、ナノ秒で計れます。kubectl create -f <파일> --dry-run=server(プレースホルダーはファイルです)は、アドミッションをそのまま通しつつ、オブジェクトは残しません。Webhookの呼び出しも拒否メッセージも、本物のリクエストとまったく同じように出ます。

故障モードだけをFailに変えたら、その範囲の書き込みがすべて止まった

/root/polscope/webhook-strict.yamlに、2つ目の設定scope-strictを書いてください。Webhook名はstrict.polscope.local、rulesはscope-wide-auditとまったく同じく最も広く、違うのは3つだけです。failurePolicyはFail、timeoutSecondsは3、clientConfig.urlは、接続が拒否されるアドレスhttps://127.0.0.1:19443/validateです。scope-wide-auditは、削除せずにそのままにしておきます。同じ範囲で、故障モードだけが違う2つが並んで立っているのが、このラボの対照群です。適用した後、4つを試してブロックされるか見て、ブロックされたらBLOCK、通過したらPASSとして、/root/polscope/02-blocked.txtに、<이름> <낱말>(プレースホルダーは名前と語です)の4行を書いてください。pod-create-scope-app(kubectl create -n scope-app -f pod.yaml --dry-run=server)、configmap-kube-system(kubectl create -n kube-system -f cm.yaml --dry-run=server)、namespace-create(kubectl create ns probe-ns --dry-run=server)、webhookconfig-edit(kubectl apply -f webhook-strict.yamlをもう一度)です。

接続が拒否されるアドレスは、待たずに即座に失敗として返ってきます。Failはその失敗を拒否に移し、Ignoreは通過に移します。同じ範囲、同じ死んだサーバーなのに、答えが正反対です。これが、failurePolicyが引き換えにするものです。4行目が、このステップの核心です。リソース1つが、アドミッションWebhookを通らないなら、何がブロックされても、それだけは直せます。Webhookの設定を直すリクエストが、そのWebhookに尋ねに行くなら、誰もクラスターを蘇生させられないからです。結果を予測せずに、4つを自分で実行してみてください。

ラベルで外そうとしたら、ラベルを付けることまでブロックされていた

まず、適用除外ラベルで抜け出してみてください。kubectl label ns kube-system polscope.io/admission=exempt --overwriteを実行して、その出力を標準エラー出力まで一緒に/root/polscope/03-lockout.txtに保存してください(ブロックされます)。そのあと、/root/polscope/webhook-strict.yamlのscope-strictに、namespaceSelector.matchExpressionsを2つ追加して、もう一度適用してください。(1)キーkubernetes.io/metadata.name、演算子NotIn、値["kube-system", "kube-node-lease", "kube-public"]、(2)キーpolscope.io/admission、演算子NotIn、値["exempt"]です。これでラベルが付けられます。kube-systemとscope-opsに、polscope.io/admission=exemptをかけてください。最後に、/root/polscope/pod-guard.yamlを3か所に--dry-run=serverで送って、/root/polscope/03-escape.txtに、kube-system・scope-ops・scope-appの3行を、<네임스페이스> <BLOCK|PASS>(プレースホルダーはネームスペースと、BLOCKまたはPASSです)の形で書いてください。

2つの条件は、同じことをする2つの方法です。1つ目は、APIサーバーが1.21から、すべてのネームスペースに自動的に付けてくれるkubernetes.io/metadata.nameラベルを使う方法で、何の準備も必要なく、2つ目は、適用除外の一覧を、自分で付けたラベルで管理する方法で、あとでネームスペースを増やすときに、Webhookの設定を直さなくてもすみます。ところが、すでにブロックされたクラスターでは、2つ目は使えません。ラベルを付けるのは、ネームスペースのUPDATEであり、そのリクエストがまさにブロックされているからです。そのため、順序が決まります。まず、1つ目の方法でエスケープハッチを作り、そのあとで、2つ目の方法を載せます。namespaceSelectorの複数のmatchExpressionsは、すべてを満たしてはじめて範囲内です。

範囲を絞ったら、引っかかっていた4つのうち3つが、Webhookを通らなくなった

/root/polscope/webhook-strict.yamlのscope-strictを、2つの層で絞ってください。rulesは、コアグループ("")のv1のpodsに対するCREATE・UPDATEだけ、scopeはNamespacedに変え、objectSelector.matchLabelsで、polscope.io/guard: "on"ラベルが付いたオブジェクトだけを見るようにします。ステップ3のnamespaceSelectorは、そのままにしておきます。適用した後、4つのリクエストをもう一度送って、/root/polscope/04-narrow.txtに、<이름> <좁히기전> <좁힌뒤>(プレースホルダーは順に、名前、絞る前、絞った後です)の4行を書いてください(絞る前の値は、ステップ2で見たものです)。pod-guard-scope-app(pod-guard.yamlをscope-appに)、pod-plain-scope-app(pod.yamlをscope-appに)、configmap-scope-app(cm.yamlをscope-appに)、namespace-create(kubectl create ns probe-ns --dry-run=server)です。判定はBLOCKまたはPASSです。

範囲を絞ることは、セキュリティを減らすことではなく、尋ねる回数を減らすことです。rulesでふるい落とされたリクエストは、APIサーバーがこのWebhookを思い出しもせず、namespaceSelectorとobjectSelectorがその次です。ワイルドカードで受けておいて、Webhookサーバーの中でふるい分けると、結果は同じですが、コストはすべて払うことになります。scopeは、Namespaced・Cluster・*のうちの1つで、ネームスペーススコープのリソースだけを見ると書けば、クラスタースコープのリソースのリクエストは、そもそも来ません。objectSelectorは、リクエストの中のオブジェクトのラベルを見て、namespaceSelectorは、そのオブジェクトが入っているネームスペースのラベルを見ます。別々の層です。

コントローラーとオンコール担当者だけが通る扉を作る

/root/polscope/webhook-strict.yamlのscope-strictに、matchConditionsを2つ追加して、もう一度適用してください。skip-kube-system-saは、リクエスト元がsystem:serviceaccount:kube-system:で始まるサービスアカウントなら、Webhookを呼び出さないようにし、skip-break-glassは、リクエスト元のグループにpolscope:break-glassがあれば、呼び出さないようにします。適用した後、同じ/root/polscope/pod-guard.yamlをscope-appに、3つのサブジェクトで送って、/root/polscope/05-conditions.txtに、kube-system-sa・break-glass・normalの3行を、<이름> <BLOCK|PASS>(プレースホルダーは名前と、BLOCKまたはPASSです)の形で書いてください。kubectl --as=system:serviceaccount:kube-system:replicaset-controller create -n scope-app -f pod-guard.yaml --dry-run=server、kubectl --as=oncall --as-group=polscope:break-glass --as-group=system:masters create -n scope-app -f pod-guard.yaml --dry-run=server、そして、今のアカウントのまま1回です。

範囲(rules・セレクター)と条件(matchConditions)は、別の層です。セレクターは、何に対するリクエストかだけを見ますが、条件は、CELでリクエスト全体を見ます。誰が送ったか(request.userInfo)、どのverbか、どのネームスペースかまでです。その代わり、条件はセレクターよりも後で評価されるので、セレクターでふるい落とせるものを条件に回すと、コストだけが増えます。条件がすべて真のときにWebhookを呼び出すので、「スキップしたい」は否定で書きます。--asで別のサブジェクトのふりができますが、真似をしたサブジェクトの権限しか持たなくなるため、認可まで通過するには、--as-group=system:mastersを一緒に与えます(認可はアドミッションよりも前に来ます)。コントローラーが送るリクエストまでブロックすると、クラスターが自分自身を復旧できなくなり、オンコール担当者のための扉がないと、障害のときに、Webhookの設定を削除するほかに道がありません。

タイムアウトを6秒にしたら、障害がリクエストごとに6秒ずつ長引いた

/root/polscope/webhook-slow.yamlに、3つ目の設定scope-slowを書いてください。Webhook名slow.polscope.local、clientConfig.urlは、応答がまったく来ないアドレスhttps://192.0.2.77:8443/validate、sideEffectsはNone、namespaceSelector.matchExpressionsは、キーkubernetes.io/metadata.nameをIn ["scope-ops"]に、objectSelector.matchLabelsはpolscope.io/slow: "on"、rulesは、コアグループのv1のpodsのCREATE・UPDATE(scope: "Namespaced")で、最終状態はfailurePolicy: FailとtimeoutSeconds: 3です。/root/polscope/pod-slow.yamlに、Pod probe-slow(ラベルpolscope.io/slow: "on"、コンテナweb、イメージregistry.internal/app:1.0)を作成してください。そして、/root/polscope/timeout-probe.sh <Fail|Ignore> <초>(プレースホルダーはFailまたはIgnoreと、秒数です)を作成してください。scope-slowをその2つの値に一時的に変更し、scope-opsにpod-slow.yamlを--dry-run=serverで1回送って、かかった時間を計り、元の値に必ず戻した後、BLOCK <초>またはPASS <초>(プレースホルダーは秒数です)を1行だけ出力します(秒は、四捨五入した整数)。このスクリプトで3回計って、/root/polscope/06-timeout.txtに、<failurePolicy> <타임아웃> <BLOCK|PASS> <걸린초>(プレースホルダーは順に、failurePolicy、タイムアウト、BLOCKまたはPASS、かかった秒数です)の3行を書いてください。Fail 6、Ignore 6、Fail 4で、それぞれ1回ずつです。

timeoutSecondsは、「Webhookサーバーが正常なときにかかる時間よりも、少し長く」決める値です。余裕を持たせて取ると、障害のときに、APIサーバーがリクエストごとにその分だけ拘束され、短く取ると、遅い応答が失敗として処理されて、failurePolicyに渡されます。そのため、2つのつまみは、別々に選ぶ値ではありません。Failは、タイムアウトが長いほど障害が長引き、Ignoreは、タイムアウトが長いほど、通過はするが遅くなります。どちらも無料ではありません。スクリプトには、trap ... EXITで元に戻す処理をかけておいてください。途中で死んでも、Webhookが変な値のまま残ってはいけません。設定を変更した後、1、2回の往復の分だけ反映が遅れるので、計る前に少し待ちます。このクラスターには、ステップ1の2秒の監査Webhookがずっと生きていて、Webhookは並列で呼び出されるため、かかった時間は、2つのうち大きいほうで出ます。

Webhookがすべて死んだクラスターで、組み込みポリシーだけが判定を出した

Webhookサーバーがない今のまま、同じ場所に組み込みポリシーを立てて、比較します。ネームスペースscope-builtinに、ラベルpod-security.kubernetes.io/enforce=baselineをかけてください。/root/polscope/vap-owner.yamlに、ValidatingAdmissionPolicy polscope-require-owner(コアグループv1のpodsのCREATE・UPDATEを捉え、Podのメタデータにownerラベルがあることを要求)と、同じ名前のValidatingAdmissionPolicyBinding(validationActionsは["Deny"]、matchResources.namespaceSelectorはkubernetes.io/metadata.nameがIn ["scope-builtin"])を、一緒に書いて適用してください。テスト用のPodをさらに3つ作成してください。/root/polscope/pod-owned.yaml(Pod probe-owned、ラベルowner: platform)、/root/polscope/pod-host.yaml(Pod probe-host、同じラベルにspec.hostNetwork: true)、/root/polscope/pod-both.yaml(Pod probe-both、ラベルowner: platformとpolscope.io/guard: "on")です。コンテナは、3つともweb/registry.internal/app:1.0です。4つのマニフェストをscope-builtinに--dry-run=serverで送って、何が判定したかを、/root/polscope/07-builtin.txtに、<파일이름> <낱말>(プレースホルダーはファイル名と語です)の4行で書いてください。語は、VAP(ValidatingAdmissionPolicyが拒否)、PSA(PodSecurityが拒否)、WEBHOOK(Webhookの呼び出しが失敗)、PASS(通過)のうちの1つで、対象はpod.yaml・pod-host.yaml・pod-both.yaml・pod-owned.yamlです。

VAPは、APIサーバーのプロセスの中でCELを評価し、PSAは、組み込みのアドミッションプラグインです。どちらも、ネットワークの往復も証明書もエンジンのPodもないため、Webhookがすべて死んでいる今も、普段とまったく同じように判定します。故障は、「式の評価エラー」や「間違って付けたラベル」のような局所的な形でしか来ません。そのため、実務の配置は、エンジンなしで表現できる検証は、組み込みの仕組みに移して故障の表面積を減らし、本当に必要なものだけをWebhookに残すことになります。判定の順序にも、注目してください。組み込みのアドミッションが先に立つので、組み込みポリシーですでに拒否されたリクエストは、Webhookまで行きません。そのため、Webhookの故障を見るには、組み込みポリシーをすべて通過するPodを送る必要があります。baselineのレベルが何をブロックするかは、拒否メッセージが直接教えてくれます。

これが落ちると何が止まるのかを、1枚で答える

/root/polscope/blast-radius.shを作成してください。クラスターのすべてのValidatingWebhookConfigurationを走査して、Webhook1つごとにオブジェクト1つを入れたJSONの配列を、標準出力に出力します。キーは、正確に8つです。config(設定の名前)・webhook(Webhookの名前)・failurePolicy(なければ"Fail")・timeoutSeconds(なければ10)・resources(そのWebhookのすべてのrules[].resourcesを、重複なしでソートした配列)・wildcard(resourcesに"*"があれば真)・excludesKubeSystem(namespaceSelector.matchExpressionsに、キーがkubernetes.io/metadata.name、演算子がNotIn、値にkube-systemが入った項目があれば真)・riskです。riskは、wildcardが真で、failurePolicyがFailで、excludesKubeSystemが偽なら"high"、それ以外でfailurePolicyがFailなら"medium"、残りは"low"です。配列は、config・webhookの順にソートします。その出力を/root/polscope/blast-radius.jsonに保存してください。そして、/root/polscope/risky.shを作成してください。riskがhighのものだけを、<config>/<webhook>の形で1行ずつ出力し、1つでもあれば終了コード1、1つもなければ何も出力せずに0で終了します。2つのスクリプトとも、保存されたファイルではなく、今クラスターに存在するオブジェクトを読む必要があります。

このレポートが答える質問は、1つです。このWebhookサーバーが落ちたとき、クラスターで何が止まるのかです。failurePolicy: Failなら、その範囲の書き込みが止まり、その範囲が、resourcesとセレクターです。そのため、危険な組み合わせは、常に同じです。広い範囲 + Fail + システムのネームスペースを除外していないこと。3つが同時に当てはまると、エンジンを蘇生させようとするリクエストまでブロックされて、クラスターが自分自身を復旧できません。kubectl get validatingwebhookconfigurations -o jsonの.items[].webhooks[]をjqで展開すれば、一度に作れます。jqの//は、値がfalseのときも右側に漏れるので、デフォルト値は、has("키")(プレースホルダーはキーです)で、あるかどうかを先に尋ねて埋めてください。採点ツールは、危険な設定を1つ一時的に立てておいて、risky.shをもう一度呼び出します。名前を暗記しておいた答えは、そこで落ちます。