Pod Security Standards と Admission — レベルとモードは別の軸
一言でいうと
Pod Security Standardsは、何を禁止するか(privileged・baseline・restricted)を決め、Pod Security Admissionは、違反をどう扱うか(enforce・audit・warn)を決めます。この2つは別々の軸なので、ネームスペースのラベルで自由に組み合わせられます。
なぜ必要なのか
Podスペックには、ホストをまるごと開くスイッチがいくつもあります。hostNetwork、hostPID、privileged: true、hostPathボリューム、任意のcapabilityの追加です。1つでもオンにすると、コンテナの分離が事実上なくなります。ところが、これらのフィールドは、正常なインフラのワークロード(CNIエージェント、ログコレクター、ノードエクスポーター)が、実際に使うものでもあります。「禁止」ではなく「どこまで許可するか」を決めなければならない問題なので、単純な禁止リストでは解決しませんでした。
PodSecurityPolicyがその役目を担っていましたが、v1.25で削除されました。PSPは、ユーザーではなくPodを作る主体(たいていコントローラーのサービスアカウント)の権限を見ており、Podに複数のPSPがかかるとき、どれが適用されるかを予測しにくかったのです。代わりに入ったのが、Pod Security Admissionです。ルールはプロジェクトが決めて、3つのレベルに固定し、適用の単位は、ネームスペースのラベル1つに単純化しました。
どう動くのか
1つ目の軸は、レベル(Pod Security Standards)です。
| レベル | 性格 |
|---|---|
privileged |
制約なし。システム・インフラのワークロード向け |
baseline |
既知の権限昇格を防ぐ最小限の制約。デフォルトのPodスペックはそのまま通過する |
restricted |
Podのハードニングのベストプラクティスを強く要求する |
baselineがブロックするのは、ホストのネームスペースの共有、privileged、許可リスト外のcapability、hostPathボリューム、ホストポート、AppArmor・SELinux・seccompの回避のようなものです。restrictedは、ここにボリュームの種類の制限、allowPrivilegeEscalation: false、runAsNonRoot: true、runAsUserを0にしないこと、seccompプロファイルの明示的な指定、すべてのcapabilityをdropしてNET_BIND_SERVICEだけを許可することを追加します。ここで、重要な違いがあります。baselineは、「変わったものを使わなければ」通過しますが、restrictedは、ごく普通のマニフェストも通過できません。何も書いていないコンテナは、seccompプロファイルがなく、capabilityをdropしていないからです。
2つ目の軸は、モード(Pod Security Admission)です。
| モード | 違反のとき |
|---|---|
enforce |
Podを拒否する |
audit |
監査ログのイベントにアノテーションを追加して、通過させる |
warn |
ユーザーに警告を見せて、通過させる |
2つの軸は、ラベルで出会います。モードごとに、ラベルが2つずつあります。
apiVersion: v1
kind: Namespace
metadata:
name: my-baseline-namespace
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: v1.30
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
この組み合わせが、導入手順そのものです。今はbaselineだけを強制し、restricted違反は警告と監査で数えてみます。1つのネームスペースが、3つのモードすべてを設定でき、モードごとに異なるレベルを与えられるので、「次の目標を先にオンにして観察する」ことが、ラベル2行でできます。
-versionの接尾辞を抜かすと、アップグレードの日にデプロイが止まります。バージョンのラベルの値は、有効なKubernetesのマイナーバージョンかlatestで、書かなければlatestとして動作します。レベルの内容は、バージョンごとに変わります。実際に、v1.25でrestrictedが変わりました。latestにしておくと、クラスターを上げた日に、マニフェストを何も変えていないのに、昨日まで通っていたデプロイがブロックされます。逆に、バージョンを固定しておけば、レベルが強くなっても、その日は何も起きず、バージョンを上げる日程は、人が決めます。ポリシーの変更とクラスターのアップグレードを、分離する仕組みです。
アドミッションはリクエストを見る仕組みです。すでに起動しているPodは、ブロックできません。ラベルを付けた後も、違反しているPodは動き続け、それが再起動されたり、再びスケジュールされたりするときにはじめて、拒否されます。そのため、切り替えの前に、違反を先に取り出してみる手順が必要です。ラベルのコマンドをサーバーのdry-runで実行すると、そのネームスペースにすでにあるPodの違反が、警告として出ます。
kubectl label --dry-run=server --overwrite ns payments \
pod-security.kubernetes.io/enforce=restricted
ドキュメントに書かれている、見落としやすい事実がもう1つあります。enforceはワークロードのリソースには適用されず、その結果として作られるPodにだけ適用されます。auditとwarnは、Deploymentのようなワークロードにも適用されます。そのため、違反するテンプレートを持つDeploymentは、kubectl applyが成功し(警告は出ます)、Podが作られないことは、数秒後のReplicaSetのイベントではじめて表に出ます。「デプロイはされたのにPodがない」という問い合わせの、よくある原因です。
適用除外(exemption)は、ラベルではなく、アドミッションコントローラーの設定ファイルに書き、軸は3つあります。ユーザー名、RuntimeClass名、ネームスペースです。ドキュメントは、コントローラーのサービスアカウントを適用除外しないようにと警告しています。replicaset-controllerを適用除外すると、ワークロードを作れるすべての人が、暗黙的に適用除外されます。
現場での姿
1つ目は、restrictedをいきなりenforceでオンにしたチームです。ごく普通のマニフェストが、すべてブロックされます。securityContextを一度も書いたことのないチームにとっては、「すべてのDeploymentを直せ」という意味なので、たいてい1日でラベルが下げられます。順序は、warn・auditでrestrictedをオンにしておき、enforceはbaselineに置くことです。
2つ目は、システムのネームスペースにrestrictedをかけた場合です。CNIエージェントやノードエクスポーターが、再起動される瞬間に起動できません。クラスターが自分自身を復旧できない状態になりますが、原因がラベルだとわかるのは、Podのイベントを開いてみてからです。
3つ目は、クラスターのアップグレードの日の障害です。-versionなしでオンにしておいたネームスペースでだけ、デプロイがブロックされます。マニフェストもポリシーも変わっていないので、原因を探すのに時間がかかります。バージョンの固定は怠慢ではなく、変更管理です。
4つ目は、メトリクスで見る方法です。kube-apiserverが、pod_security_evaluations_total、pod_security_errors_total、pod_security_exemptions_totalを出力します。auditモードでオンにしておいた期間に、これらの値をダッシュボードに載せておけば、「enforceに上げてもよいか」に、数字で答えられます。
参考ドキュメント
- Pod Security Admission: https://kubernetes.io/docs/concepts/security/pod-security-admission/
- Pod Security Standards: https://kubernetes.io/docs/concepts/security/pod-security-standards/
- ネームスペースのラベルで標準を適用する: https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels/
- PodSecurityPolicyから移行する: https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/
次のラボですること
kwokクラスターのネームスペースに、ラベルを直接付けながら、切り替えの手順を一周します。違反するPodを先に起動しておき、--dry-run=serverでラベルを試して、既存の違反が警告として出るのを確認し、warnとauditだけをオンにした状態で、同じPodが通過するのを見た後、enforceに上げて拒否されるのを確認します。-versionを固定したネームスペースと、latestのネームスペースを並べて置き、違反するテンプレートを持つDeploymentが、applyには成功しながらPodが作られない場面も、自分で作ります。