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

KCSA — Kubernetesセキュリティアソシエイト

Pod を作る権限はあるが、特権 Pod は止めなければならない

TT Labで続きを見る

目標

ValidatingAdmissionPolicy(VAP)で「特権コンテナの禁止」と「hostPathボリュームの禁止」のポリシーをCELで自分で書き、 バインディングで1つのネームスペースだけに有効にしたうえで、APIサーバーが返す拒否メッセージと、正常なPodの承認の両方を観察します。

なぜ重要なのか

Kubernetesのすべての書き込みリクエストは、認証 → 認可(RBAC) → アドミッションを経なければetcdに保存されません。RBACが答えるのは 「このユーザーがPodを作成してよいか」までで、「そのPodの内容が安全か」は問いません。 Podを作成する権限さえあれば、privileged: trueや、ノードの/をhostPathでマウントしたPodで、ノードをまるごと 乗っ取れます。その穴をふさぐ層が、アドミッション制御です。

Pod Security Admission(PSA)は、ネームスペースのラベル1つで、決まった基準(privileged/baseline/restricted)を有効にします。 便利ですが、組織独自のルール(許可するレジストリ、特定のラベルの必須化など)は表現できません。VAPはAPIサーバーの中で CEL式を直接評価するため、Webhookサーバーがなくても望むルールを書けますし、ポリシー(何を検査するか)と バインディング(どこに、Deny/Warn/Auditのどれで適用するか)が分離されているため、同じポリシーをネームスペースごとに異なる形で有効にできます。

拒否メッセージには、どのポリシーとどのバインディングが止めたのかが名前で出力されます。インシデント対応のとき、「なぜデプロイできないのか」を 最も早く解く手がかりが、まさにこの1行です。また、アドミッションは作成リクエストの時点でしか起きないため、ポリシーを有効にする前に すでに作成されていたオブジェクトはそのまま残るという点も、覚えておいてください。

ステップ

  1. ネームスペースkcsa-admを作成し、自動で付くラベルを確認します。
  2. 特権コンテナを拒否するポリシーdeny-privilegedを、CELで書きます。
  3. バインディングdeny-privileged-bindingで、kcsa-admだけにDenyで有効にします。
  4. 特権Podを作成してみて、拒否メッセージを/root/kcsa-adm/denied-privileged.txtに保存します。
  5. 平凡なPodgoodが承認されることを確認します。
  6. hostPathボリュームを拒否するポリシーdeny-hostpathとバインディングを作成します。
  7. hostPathのPodを作成してみて、拒否メッセージを/root/kcsa-adm/denied-hostpath.txtに保存します。
  8. 何が止められ、何が通過したかを/root/kcsa-adm/report.txtに残します。

参考

ポリシーを適用する分離区画を作る

ネームスペースkcsa-admを作成してください。このネームスペースには、APIサーバーが自動でkubernetes.io/metadata.name=kcsa-admラベルを付け、後のステップのバインディングが、このラベルで対象を選びます。

Kubernetesは、すべてのネームスペースに、自分の名前を値とするkubernetes.io/metadata.nameラベルを自動で付けます。作成したあとで、kubectl get ns <이름> --show-labelsで確認してみてください(プレースホルダーはネームスペース名です)。createを--dry-run=clientで生成してapplyすれば、何度実行しても安全です。

特権コンテナを止めるルールをCELで書く

ValidatingAdmissionPolicydeny-privilegedを作成してください。PodのCREATEリクエストで、securityContext.privilegedがtrueのコンテナが1つでもあれば拒否するCEL式を書き、拒否メッセージはprivileged containers are not allowedにします。failurePolicyはFailです。

ポリシーは、matchConstraints.resourceRulesでどのリクエスト(コアグループv1のpods、CREATE)を見るかを決め、validations[].expressionが真であれば通過します。object.spec.containers.all(c, ...)のように、すべてのコンテナが条件を満たすかを問いますが、securityContextやprivilegedフィールドがそもそもない場合も通過として扱う必要があるので、has()で先に確認してください。ポリシーだけでは、何も止まりません。

ルールをkcsa-admに結び付けて実際に有効にする

ValidatingAdmissionPolicyBindingdeny-privileged-bindingを作成してください。policyNameはdeny-privileged、validationActionsは["Deny"]、対象はmatchResources.namespaceSelectorのmatchLabelsでkubernetes.io/metadata.name: kcsa-admのネームスペースだけを選びます。

VAPは、ポリシー(何を検査するか)とバインディング(どこに、どんな動作で適用するか)に分かれています。バインディングがなければ、ポリシーは評価すらされません。validationActionsにはDeny以外にWarn・Auditもありますが、Denyであって初めてリクエストが実際に拒否されます。ネームスペースを選ぶときは、ステップ1で確認した自動ラベルを使ってください。

特権Podが入口で追い返される

ネームスペースkcsa-admに、securityContext.privileged: trueのコンテナを持つPod(イメージはnginx:1.27-alpine)を作成してみて、APIサーバーが返した拒否メッセージ全体を/root/kcsa-adm/denied-privileged.txtに保存してください。

拒否はPodが作成される前に起きるため、kubectlが0以外の終了コードとともにエラーを標準エラー出力に出力します。標準エラー出力までファイルに受け取る必要があります(2>&1)。メッセージには、どのポリシーとどのバインディングが拒否したのかが名前で入っています。バインディングの直後は、反映に1–2秒かかることがあるので、Podが作成されてしまったら削除して、もう一度試してください。

平凡なPodはつかえずに入る

ネームスペースkcsa-admに、特権の設定がない平凡なPodgood(イメージはnginx:1.27-alpine)を作成してください。ポリシーが有効になっていても、このPodは承認される必要があります。

よいポリシーは、悪いリクエストだけを止めて、正常なリクエストはそのまま通します。CEL式でsecurityContextのないコンテナを通過として処理できているかが、ここで明らかになります。作成されていればkubectl get pod good -n kcsa-admで見え、止められていれば、ステップ4と同じ形のエラーが出ます。

ノードのディスクに通じるhostPathも止める

ValidatingAdmissionPolicydeny-hostpathとバインディングdeny-hostpath-bindingを作成してください。ポリシーは、PodのCREATEでhostPathボリュームが1つでもあれば拒否し(メッセージはhostPath volumes are not allowed、failurePolicy: Fail)、バインディングはvalidationActionsを["Deny"]にして、kubernetes.io/metadata.name: kcsa-admのネームスペースにだけ適用します。

構造はステップ2・3と同じで、検査の対象だけがvolumesに変わります。volumesフィールドがそもそもないPodもあるため、has(object.spec.volumes)で先に確認し、各ボリュームにhostPathフィールドがあるかをhas()で問いかけてください。ポリシーとバインディングを---でつないで、一度にapplyしてもかまいません。

ノードの/etcをマウントしようとしたPodが拒否される

ネームスペースkcsa-admに、ノードの/etcをhostPathボリュームとしてマウントするPod(イメージはnginx:1.27-alpine)を作成してみて、拒否メッセージ全体を/root/kcsa-adm/denied-hostpath.txtに保存してください。

PodのspecのvolumesにhostPathボリュームを置き、コンテナのvolumeMountsでマウントする形です。ステップ4と同じように、標準エラー出力をファイルに受け取ってください。今回のメッセージに出力されるポリシーとバインディングの名前が、ステップ4と違うことを確認してください。

何が止められ何が通過したかを帳簿として残す

観察結果を/root/kcsa-adm/report.txtに4行で書いてください。privileged=denied、hostpath=denied、plain=allowed、enforcement=validatingadmissionpolicyです。採点ツールは、各行を実際のクラスターにPodを作成して照合します。

値はdeniedかallowedのどちらかで、最後の行は、この拒否を誰が実行したか(PSAラベルではなく、どのアドミッションの仕組みか)を小文字の1単語で書きます。推測ではなく、ステップ4・5・7で自分の目で見た結果を書き写してください。