ラベルのルールが緩められたのに誰も気づかなかった
目標
Kyverno CLIのapply・test・jpで、ポリシーが何を拒否し、何を書き換え、何を作るのかを、クラスターに適用する前に判定し、その判定をテストスイートとCIスクリプトで固定します。
なぜ重要なのか
アドミッションポリシーは、間違っても静かです。検証ルールを緩く直してしまったミスは、どのリクエストも止めなくなるだけで、エラーは出ません。mutateルールのアンカー1つで、すでに値のあるリソースに触れるかどうかが変わります。本番クラスターでその違いを見つけたときには、もう手遅れです。kyverno testは、「このリソースは通過、あのリソースは失敗、このリソースはこう変わる」という期待を先に書いておき、エンジンの実際の判定と比べます。そのため、ポリシーを直した人が意図せず緩和してしまったときに、テストが先に赤信号を出します。クラスターにないConfigMapやAPI呼び出しの結果はvaluesファイルで埋め、条件式のJMESPathはkyverno jpで別に動かしてみながら、ポリシーのリポジトリをコードのように検証します。
ステップ
/root/kca-cli/validate/policy.yamlにClusterPolicyrequire-teamを書いてください。ルールcheck-teamはPodを選び、metadata.labels.teamが空でない値であることを要求し、failureActionはEnforceです。/root/kca-cli/validate/resources.yamlには、Podgood(ラベルteam=pay)、ラベルのないPodbad、PodテンプレートにteamラベルがないDeploymentwebを置きます(すべてnamespaceはdefaultです)。kyverno applyに--policy-reportを付けて実行し、標準出力を/root/kca-cli/apply-report.yamlに保存してください。/root/kca-cli/validate/kyverno-test.yamlにTestを書いてください(apiVersionはcli.kyverno.io/v1alpha1です)。policiesはpolicy.yaml、resourcesはresources.yamlで、resultsには、goodはpass、badはfail、Deploymentwebはステップ1のレポートに出た自動生成ルール名でfailを期待として書きます。kyverno test /root/kca-cli/validateが、3件すべて通過する必要があります。/root/kca-cli/validateを/root/kca-cli/regressedに丸ごとコピーし、コピーのpolicy.yamlでteamキーだけを等価アンカー=(team)に変えてください(ラベルがなければ通過してしまう、よくあるミスです)。kyverno test /root/kca-cli/regressed --remove-colorの出力全体を/root/kca-cli/regression.txtに保存し、終了コードも確認してください。サマリー行と終了コードだけを見ずに、表のRESULT・REASON列で、期待と実際が食い違っている行を探します。元のvalidateは、そのまま通過する必要があります。/root/kca-cli/mutate/に、ClusterPolicyadd-managed-by(ルールadd-label、Podにmanaged-by: kyvernoラベルをない場合だけ追加する追加アンカーを使うもの)、resources.yaml(ラベルのないPodplain、managed-by: helmラベルがあるPodowned)、mutate後に期待される姿のpatched.yaml(plainにラベルが付いたPod)、kyverno-test.yamlを置いてください。テストは、plainをpatchedResourcesと比較してpass、ownedはskipを期待し、kyverno test /root/kca-cli/mutateが通過する必要があります。/root/kca-cli/generate/に、ClusterPolicyns-quota(ルールgen-quota、Namespaceができたら、そのネームスペースにResourceQuotadefault-quotaをspec.hard.pods: "10"で生成し、synchronizeはfalse)、resources.yaml(Namespaceteam-a)、期待される生成物generated.yaml、kyverno-test.yaml(team-aについてgeneratedResourceを比較し、pass)を置いて、kyverno test /root/kca-cli/generateを通過させてください。/root/kca-cli/context/にClusterPolicyallowed-registriesを置いてください。ルールcheck-registryは、Podについて、コンテキストregでConfigMapplatform/registry-configを読み取り、foreachで、すべてのコンテナイメージがreg.data.allowed(カンマ区切りのパターンのリスト)のどれにも合わなければ拒否します(Enforce)。resources.yamlには、イメージがregistry.lab/web:1.01つだけのPodinternalと、そこにdocker.io/busybox:1.36のコンテナsidecarを加えたPodexternalを置きます。クラスターがないので、values.yaml(kindはValues)でreg.data.allowedをregistry.lab/*で埋め、kyverno-test.yamlのvariablesでつないで、internalはpass、externalはfailで通過させてください。/root/kca-cli/jp/query.txtにJMESPath式を1つ書いてください。Podオブジェクトを入力として受け取り、イメージがregistry.lab/で始まらないコンテナの名前のリストを返す必要があります。/root/kca-cli/jp/external.yamlにはステップ6のPodexternalを1つだけ置き、kyverno jp query -i jp/external.yaml -q jp/query.txtの出力を/root/kca-cli/jp/result.jsonに保存してください。/root/kca-cli/ci.shを実行可能なスクリプトとして作成してください。最初の引数でルートディレクトリ(なければ/root/kca-cli)を受け取り、その下のvalidate・mutate・generate・contextの4つのディレクトリでそれぞれkyverno testを実行し、1つでも失敗したら0以外の値で終了する必要があります。ステップ3で見たように、終了コードが0でも、出力の表に期待と実際が食い違った行(REASONがWant ...のもの)があれば、失敗として扱う必要があります。regressedは実行しません。採点ツールは、コピーしたポリシーとテストをいろいろな方法で壊して、スクリプトが失敗するかを確認します。
参考
- このイメージには、kyverno CLI 1.13.2が入っています。
kyverno versionで確認してください。このラボはクラスターを使いません。 - テスト結果だけを見るには
kyverno test <디렉터리> --fail-only(プレースホルダーはディレクトリです)、詳しい理由は--detailed-resultsです。 - よくある間違い: Deploymentに対する期待を、Podルールの名前で書くこと。自動生成ルールは名前が違います。
- よくある間違い:
Test Summary行と終了コードだけを信じること。このバージョンは、failを期待したのにpassに反転した行を、表にだけ残します(ステップ3で直接確認します)。 - よくある間違い: JMESPathの文字列をバッククォートで囲むこと。バッククォートの中はJSONなので、引用符のない文字は構文エラーになります。
- Kyverno CLI test・Kyverno CLI apply・Kyverno CLI jp・自動生成ルール
ポリシーを適用する前に、エンジンに先に聞いてみる
/root/kca-cli/validate/policy.yamlにClusterPolicy require-teamを書いてください。ルールcheck-teamはPodを選び、metadata.labels.teamが空でない値であることを要求し、failureActionはEnforceです。/root/kca-cli/validate/resources.yamlには、Pod good(ラベルteam=pay)、ラベルのないPod bad、PodテンプレートにteamラベルがないDeployment webを置きます(すべてnamespaceはdefaultです)。kyverno applyに--policy-reportを付けて実行し、標準出力を/root/kca-cli/apply-report.yamlに保存してください。
kyverno apply <정책> --resource <리소스>(プレースホルダーはポリシーとリソースです)の形です。Pod対象のルールがDeploymentにも適用されるか、適用されるならルール名が何として報告されるかを、レポートで確認してください。違反があれば、終了コードは0以外です。
期待する結果を先に書いておくテスト
/root/kca-cli/validate/kyverno-test.yamlにTestを書いてください(apiVersionはcli.kyverno.io/v1alpha1です)。policiesはpolicy.yaml、resourcesはresources.yamlで、resultsには、goodはpass、badはfail、Deployment webはステップ1のレポートに出た自動生成ルール名でfailを期待として書きます。kyverno test /root/kca-cli/validateが、3件すべて通過する必要があります。
resultsの項目ごとに、policy・rule・resources・kind・resultを書きます。Podルールから自動生成されたルールは、名前の前に接頭辞が付きます。
ラベルのルールを緩めたら、テストが先に気づいた
/root/kca-cli/validateを/root/kca-cli/regressedに丸ごとコピーし、コピーのpolicy.yamlでteamキーだけを等価アンカー=(team)に変えてください(ラベルがなければ通過してしまう、よくあるミスです)。kyverno test /root/kca-cli/regressed --remove-colorの出力全体を/root/kca-cli/regression.txtに保存し、終了コードも確認してください。サマリー行と終了コードだけを見ずに、表のRESULT・REASON列で、期待と実際が食い違っている行を探します。元のvalidateは、そのまま通過する必要があります。
=(key)は、キーがあるときだけ値を検査します。ラベルのマップはあるのにteamがないリソースがどれかを見てください。このイメージのCLI(1.13.2)は、failを期待したリソースがpassと判定されると、REASONにその食い違いを書いたうえで、サマリーと終了コードは通過として出力します(実測)。テストツールがすべての食い違いを失敗として数えるかどうかは、このようにわざと壊してみてはじめてわかります。
書き換え後の結果まで比べるmutateテスト
/root/kca-cli/mutate/に、ClusterPolicy add-managed-by(ルールadd-label、Podにmanaged-by: kyvernoラベルをない場合だけ追加する追加アンカーを使うもの)、resources.yaml(ラベルのないPod plain、managed-by: helmラベルがあるPod owned)、mutate後に期待される姿のpatched.yaml(plainにラベルが付いたPod)、kyverno-test.yamlを置いてください。テストは、plainをpatchedResourcesと比較してpass、ownedはskipを期待し、kyverno test /root/kca-cli/mutateが通過する必要があります。
+(key): valueは、キーがないときだけ入れます。すでにラベルがあるリソースでは、ルールは何も変えないので、結果がpassにならないことがあります。patchedResourcesはファイル名です。
作られるオブジェクトを事前に比べるgenerateテスト
/root/kca-cli/generate/に、ClusterPolicy ns-quota(ルールgen-quota、Namespaceができたら、そのネームスペースにResourceQuota default-quotaをspec.hard.pods: "10"で生成し、synchronizeはfalse)、resources.yaml(Namespace team-a)、期待される生成物generated.yaml、kyverno-test.yaml(team-aについてgeneratedResourceを比較し、pass)を置いて、kyverno test /root/kca-cli/generateを通過させてください。
生成するオブジェクトのnamespaceには、リクエストオブジェクトの名前を変数として使います。期待される生成物にはmetadata.namespaceまで入っていてはじめて、比較が合います。
クラスターなしでConfigMapコンテキストを埋める
/root/kca-cli/context/にClusterPolicy allowed-registriesを置いてください。ルールcheck-registryは、Podについて、コンテキストregでConfigMap platform/registry-configを読み取り、foreachで、すべてのコンテナイメージがreg.data.allowed(カンマ区切りのパターンのリスト)のどれにも合わなければ拒否します(Enforce)。resources.yamlには、イメージがregistry.lab/web:1.01つだけのPod internalと、そこにdocker.io/busybox:1.36のコンテナsidecarを加えたPod externalを置きます。クラスターがないので、values.yaml(kindはValues)でreg.data.allowedをregistry.lab/*で埋め、kyverno-test.yamlのvariablesでつないで、internalはpass、externalはfailで通過させてください。
CLIはコンテキストのconfigMapを参照できないので、ルール単位のvaluesで変数の値を渡します。カンマ区切りの文字列をリストに変えるJMESPath関数があります。AnyNotInは、値のリストのワイルドカードを理解します。
条件式をポリシーの外で先に動かしてみる
/root/kca-cli/jp/query.txtにJMESPath式を1つ書いてください。Podオブジェクトを入力として受け取り、イメージがregistry.lab/で始まらないコンテナの名前のリストを返す必要があります。/root/kca-cli/jp/external.yamlにはステップ6のPod externalを1つだけ置き、kyverno jp query -i jp/external.yaml -q jp/query.txtの出力を/root/kca-cli/jp/result.jsonに保存してください。
フィルター式[?조건](プレースホルダーは条件です)と、文字列関数starts_withを使います。JMESPathでは、文字列リテラルはシングルクォートです(バッククォートはJSONリテラルです)。採点ツールは、ほかのPodでも式を動かしてみます。
ポリシーリポジトリのCIゲートキーパー
/root/kca-cli/ci.shを実行可能なスクリプトとして作成してください。最初の引数でルートディレクトリ(なければ/root/kca-cli)を受け取り、その下のvalidate・mutate・generate・contextの4つのディレクトリでそれぞれkyverno testを実行し、1つでも失敗したら0以外の値で終了する必要があります。ステップ3で見たように、終了コードが0でも、出力の表に期待と実際が食い違った行(REASONがWant ...のもの)があれば、失敗として扱う必要があります。regressedは実行しません。採点ツールは、コピーしたポリシーとテストをいろいろな方法で壊して、スクリプトが失敗するかを確認します。
出力を変数やファイルに受け取り、終了コードと食い違いの文言の両方を検査します。ループで最初の失敗で終わらせても、すべて実行してからまとめて終わらせても、結果のコードさえ正確であればかまいません。色のコードが混ざると文字列検索が外れるので、--remove-colorを使ってください。