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

KCA — Kyverno認定アソシエイト

四つのアンカーと、ポリシーを割らずに段階導入する方法

TT Labで続きを見る

一言でいうと

validateルールには3つの文法があります。オブジェクトの形をそのまま描くpattern、条件式で拒否するdeny.conditions、そしてCEL式です。これに、強制レベルをルール単位で決めるfailureActionが加わったことで、ポリシーを2つに分割しなくても、新しいルールだけを観察モードで上乗せする流れが可能になりました。

なぜ必要なのか

新しいポリシー言語を覚えさせないというのが、Kyvernoの出発点です。OPA/GatekeeperはRegoを習得しなければならず、ConstraintTemplateとConstraintの2つのオブジェクトを組み合わせる必要があります。Kyvernoは、検査したいオブジェクトをそのままYAMLで描き、値の位置に演算子を入れます。Podのコンテナにメモリリミットが必要なら、Podスペックの形のまま書いて、値の位置に「空でない」ことを表す演算子を入れる、という具合です。

ただし、形をそのまま描く方式には限界があります。「このフィールドがあればあの条件も見なければならない」や「このキーはそもそも存在してはいけない」といった関係は、値では表現できません。そこで、キー名に付けるアンカーが生まれました。

どう動くのか

値の位置の演算子は次のとおりです。?*は空でない値、*はnullを含むすべての値、X|Yはどちらか一方、!XはXではない値を表し、>=256Miのように数値の比較もできます。

キーの位置のアンカーは4種類で、ここで最も事故が起きます。

アンカー 名前 意味
() 条件アンカー このキーがこの値と一致するときだけ、残りを検査します
=() 等価アンカー このキーが存在する場合、値が条件を満たす必要があります
^() 追加アンカー 配列の中に、条件を満たす要素が最低1つ必要です
X() 否定アンカー このキーが存在してはいけません

最もよくある誤用が否定アンカーです。X(privileged)を「privilegedの値が違わなければならない」と読む人が多いのですが、正確には「privilegedというキー自体が存在してはいけない」という意味です。値がfalseと明示されていても、キーがあれば引っかかります。値を比較したいなら、アンカーではなく値の演算子かdenyの条件を使う必要があります。

foreachはコレクションの各要素を検査します。ここにも落とし穴が1つあり、foreach.listはJMESPath式そのものを受け取るので、波括弧で囲んではいけません。そして、initContainersまで一緒に見たいときに使う定型句がrequest.object.spec.[initContainers, containers][]です。initContainersを見落としたポリシーは実際に非常に多く、攻撃者ではなく、ただ初期化コンテナを使う普通のチームがポリシーを回避することになります。

deny.conditionsは、条件が真のときに拒否します。allとanyで束ね、各条件はkey、operator、valueの3つの欄からなります。patternでは表現しづらいもののほとんどが、ここに来ます。CELはKubernetes 1.25以降で標準となった式言語で、Kyvernoにもvalidate.celとして取り入れられています。ただし、これはすでにKyvernoを運用しているときに便利な選択肢であって、Kyvernoを導入する理由にはなりません。フィールド1つを検査するだけなら、Kubernetes組み込みのValidatingAdmissionPolicyで十分で、コンポーネントを1つ少なく運用できるということは、アップグレード対象が1つ減り、Webhook証明書の更新を気にする場面が1つ減るということです。

最後が強制レベルです。以前はspec.validationFailureActionがポリシー全体の強制レベルを決めていました。そのため、1つのポリシーの中で、あるルールは確信があるので止めたいが、別のルールはまだ観察だけにしたい場合は、ポリシーを2つに分割しなければならず、matchブロックが複製されて管理対象が増えました。現在はspec.rules[*].validate[*].failureActionに下りてきており、値はEnforceとAuditの2つです。指定しなければ、デフォルトはAuditです。新しいルールを既存のポリシーにAuditで上乗せし、数日間レポートを見てから、そのルールだけをEnforceに上げる流れが、ポリシーを分割せずに可能になったことが、この変更の価値です。一緒に動いたフィールドにwebhookTimeoutSecondsとfailurePolicyがあり、それぞれwebhookConfiguration.timeoutSeconds、webhookConfiguration.failurePolicyへ移され、1.13からdeprecatedと表示されます。

現場での姿

著者のイメージスキャンの経験が、このテーマにぴったり重なります。あるイメージを初めてスキャンしたとき、全部で1,247件が出て、そのうちCriticalが9件でした。このレポートをそのままチームのチャンネルに貼ると、結果は2つのどちらかです。全員が無視するか、誰も手を付けられないままリリースが止まるかです。修正版があるものだけを残すオプションを1つ付けたところ4件になり、実際に悪用が確認されたリストと突き合わせると、今日対処すべきものは1件でした。

ポリシーもまったく同じです。最初からEnforceで全面適用すると、デプロイがまるごと止まり、結局、ボトルネックになるのはポリシーではなくポリシーを作った人です。かといって、すべてをAuditにしておくと、誰もレポートを見ません。答えは、ルール単位のfailureActionです。確実なルールをいくつかだけEnforceに上げておき、残りはAuditで観察すれば、今対処できる項目だけが人の目の前に残ります。スキャンレポートで学んだことと同じ原理です。

次のラボですること

/root/kca-validate/にClusterPolicyを書きながら、match・pattern・deny・excludeを1つずつ埋め、ルールごとに異なるfailureActionを指定します。そして、同じルールをKubernetes組み込みのValidatingAdmissionPolicyとBindingで実際のクラスターに載せ、ポリシーエンジンなしでどこまでできるかを目で見て比較します。