Pod Security Admissionをラベルで掛ける
目標
ネームスペースのラベルだけでPodのセキュリティポリシーを適用し、restrictedに違反するPodが実際に拒否されることと その拒否メッセージを確認し、最後に等級を上げても、すでに動いているPodは追い出されないという PSAの核心的な性質を、自分で観察します。
なぜ重要なのか
PSPが失敗した理由は、機能が足りなかったからではなく、デバッグが不可能だったからです。 複数のPSPが一致したときにどれが適用されたのかがわかりにくく、mutatingだったために、自分が書いたスペックと 実行されるスペックが違っていました。使われないセキュリティ機能は、セキュリティではありません。
PSAは、その教訓から作られました。ラベル1つでポリシーになり、拒否メッセージに違反したフィールドがすべて出ます。 表現力を失った代わりに運用しやすさを得たトレードオフであり、このトレードオフがなぜ正しかったのかを理解することが、 KCSAでよく問われるポイントです。
ステップ7が、このラボのハイライトです。PSAはアドミッション時にしか動作しないため、
enforceをrestrictedに上げても、すでに動いているPodはそのまま生き残ります。何も起きていないように
見えますが、次のロールアウトやノードの入れ替えのときにPodが起動せず、問題が表面化します。
ポリシーの変更と事故の間には時間差があることを手で確認しておくと、移行を設計するときに違いが出ます。
ステップ
- ネームスペース
kcsa-psa-baselineを作成し、ラベルを4つ付けます。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です。 - ネームスペース
kcsa-psa-restrictedを作成し、ラベルを4つ付けます。pod-security.kubernetes.io/enforce=restricted、pod-security.kubernetes.io/enforce-version=v1.30、pod-security.kubernetes.io/audit=restricted、pod-security.kubernetes.io/warn=restrictedです。 kcsa-psa-restrictedに、privilegedコンテナを持つPodbadの作成を試みます。拒否されるはずなので、その拒否メッセージ全体を/root/kcsa-psa/reject.txtに保存します。Podbadは、最後まで作成されていない状態である必要があります。kcsa-psa-baselineにPodapp-baselineを作成します。イメージはnginx:1.27-alpineで、securityContextは何も指定しません。Runningまで進めます。kcsa-psa-restrictedにPodapp-restrictedを作成します。イメージはnginx:1.27-alpine、PodレベルにsecurityContext.runAsNonRoot: trueとsecurityContext.seccompProfile.type: RuntimeDefault、コンテナレベルにallowPrivilegeEscalation: false、capabilities.drop: ["ALL"]、runAsUser: 1000を設定します。/root/kcsa-psa/exempt.yamlにAdmissionConfigurationを書きます。plugins[0].nameはPodSecurity、configuration.kindはPodSecurityConfiguration、defaults.enforceはbaseline、exemptions.namespacesの最初の項目はkube-systemにします。そして/root/kcsa-psa/exempt-note.txtに、PSAの適用除外の3つの軸を1行ずつ、正確にusernames、runtimeClasses、namespacesと書きます。kcsa-psa-baselineのenforceラベルだけをrestrictedに変更します。そのうえで(a)既存のPodapp-baselineの現在の状態を確認して、/root/kcsa-psa/upgrade.txtにexisting-pod=<상태>の形で書き(プレースホルダーは状態です)、(b)ステップ4と同じスペックのPodapp-baseline2を作成してみて拒否されることを確認したうえで、同じファイルにnew-pod=rejectedの行を追加します。Podapp-baseline2は、作成されていない状態である必要があります。
参考
- ラベルは
kubectl label namespace <이름> <키>=<값>で付けます(プレースホルダーは名前、キー、値です)。すでにある値を変更するときは--overwriteが必要です。 - 拒否メッセージは標準エラー出力に出るため、
kubectl apply -f bad.yaml 2> /root/kcsa-psa/reject.txtのようにして保存する必要があります。 - Podの状態は
kubectl get pod app-baseline -n kcsa-psa-baseline -o jsonpath='{.status.phase}'で確認します。 - よくあるミス1: ステップ4のPodに、セキュリティ設定をあらかじめ入れておくことです。そうするとステップ7で対比がなくなり、採点も通りません。
- よくあるミス2: ステップ3やステップ7で、拒否されたPodをなんとか作成しようとして、ラベルを一時的に下げることです。採点は、そのPodが存在しないことを確認します。
baselineのネームスペースを作成する
ネームスペースkcsa-psa-baselineを作成し、ラベルを4つ付けてください。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です。
PSAは、ネームスペースのラベルだけで動作します。モードは3つ(enforce/audit/warn)で、これにバージョンを固定するラベルが1つ加わります。enforceは実際に遮断する等級、audit/warnは「上げると何が壊れるか」をあらかじめ見る等級であることを考えながら、値を決めてください。
restrictedのネームスペースを作成する
ネームスペースkcsa-psa-restrictedを作成し、ラベルを4つ付けてください。pod-security.kubernetes.io/enforce=restricted、pod-security.kubernetes.io/enforce-version=v1.30、pod-security.kubernetes.io/audit=restricted、pod-security.kubernetes.io/warn=restrictedです。
前のステップと同じ4つのラベルですが、値はすべて最も厳しい等級です。バージョンのラベルを抜かすと、クラスターをアップグレードするときにポリシーの内容が変わる可能性があります。
restrictedに違反するPodが拒否されるか確認する
kcsa-psa-restrictedに、privilegedコンテナを持つPodbadの作成を試みてください。拒否されるはずなので、その拒否メッセージ全体を/root/kcsa-psa/reject.txtに保存します。Podbadは、最後まで作成されていない状態である必要があります。
このステップの目標は、失敗を作り出すことです。コマンドが失敗するとメッセージは標準エラー出力に出るので、リダイレクトに注意してください。拒否メッセージの中に、どのプロファイルのどのフィールドが問題なのかがすべて書かれています。
baselineだけを通過する平凡なPod
kcsa-psa-baselineにPodapp-baselineを作成してください。イメージはnginx:1.27-alpineで、securityContextは何も指定しません。Runningまで進めます。
特別なセキュリティ設定を1つも入れていない、平凡なPodです。baselineは通過しますが、restrictedの要件は1つも満たしていない状態である必要があります。次のステップと比較するための対照群なので、わざわざ強化しないでください。
restrictedを通過するPod
kcsa-psa-restrictedにPodapp-restrictedを作成してください。イメージはnginx:1.27-alpine、PodレベルにsecurityContext.runAsNonRoot: trueとsecurityContext.seccompProfile.type: RuntimeDefault、コンテナレベルにallowPrivilegeEscalation: false、capabilities.drop: ["ALL"]、runAsUser: 1000を設定します。
4つの要件が必要で、そのうち2つはPodレベルのsecurityContextに、2つはコンテナレベルに入ります。どれがどこに入るのか迷ったら、拒否メッセージが教えてくれます。runAsUserも明示する必要があります。
適用除外の設定と適用除外の軸を整理する
/root/kcsa-psa/exempt.yamlにAdmissionConfigurationを書いてください。plugins[0].nameはPodSecurity、configuration.kindはPodSecurityConfiguration、defaults.enforceはbaseline、exemptions.namespacesの最初の項目はkube-systemにします。そして/root/kcsa-psa/exempt-note.txtに、PSAの適用除外の3つの軸を1行ずつ、正確にusernames、runtimeClasses、namespacesと書きます。
適用除外はネームスペースのラベルでは表現できないため、apiserverのアドミッション設定ファイルに書きます。このラボでは、そのファイルを作成するだけです。適用除外の軸は3つあり、3つの軸がそれぞれ何を基準に検査をスキップするのかを考えてみてください。
ラベルだけを上げたとき、既存のPodはどうなるか
kcsa-psa-baselineのenforceラベルだけをrestrictedに変更してください。そのうえで(a)既存のPodapp-baselineの現在の状態を確認して、/root/kcsa-psa/upgrade.txtにexisting-pod=<상태>の形で書き(プレースホルダーは状態です)、(b)ステップ4と同じスペックのPodapp-baseline2を作成してみて拒否されることを確認したうえで、同じファイルにnew-pod=rejectedの行を追加します。Podapp-baseline2は、作成されていない状態である必要があります。
PSAは、アドミッション時にしか判断しません。この事実が、等級を上げたときの結果を決めます。既存のPodの状態を自分で確認してそのまま書き、同じスペックの新しいPodを作成してみると、対比がはっきりします。