Certified Kubernetes Security Specialist (CKS) #9: Pod Security Admission (PSA、Pod Security Standards)
#8 kernel hardening、capabilities、/proc 保護 までは、ノードとコンテナを Linux カーネルレベルで閉じ込める作業を扱いました。この記事からは Minimize Microservice Vulnerabilities ドメインに移り、危険な設定を持つ Pod そのものをクラスターが受け入れないようにする 方法を見ます。その最初のツールが Kubernetes に組み込まれた Pod Security Admission (PSA) です。
PSA は別途インストールなしで API サーバーに組み込まれた admission controller です。名前空間に label を 1 つ付けるだけで、その名前空間に入ってくるすべての Pod が定められたセキュリティ基準を満たすか検査され、違反すると作成そのものが拒否されます。試験では「この名前空間に restricted 基準を enforce で適用せよ」「拒否される Pod を基準に合わせて直せ」というタイプが定番で出ます。
PodSecurityPolicy は廃止された #
まず背景を 1 行で整理します。過去には PodSecurityPolicy (PSP) という admission controller が同じ役割をしていましたが、権限付与モデルが複雑で使いにくいという理由で v1.21 で deprecated になり、v1.25 で完全に削除 されました。その代替が v1.25 から stable になった Pod Security Admission です。現在の CKS 試験バージョンでは PSP が存在しないので、Pod 単位のセキュリティ基準はすべて PSA で扱うと考えればよいです。
PSA は PSP と違い、別途のポリシーリソースを作ったり RBAC を絡めたりする必要がありません。あらかじめ定義された Pod Security Standards という 3 段階の基準を、名前空間 label で選んで適用するだけです。
Pod Security Standards の 3 段階 #
Pod Security Standards はセキュリティ強度を 3 段階に分けた標準プロファイルです。段階が上がるほど Pod に許される危険な設定が減ります。
| 段階 | 意味 | 許容範囲 |
|---|---|---|
| privileged | 制限なし | すべての設定を許可。host 名前空間・privileged コンテナ・任意の capabilities まで全部可能 |
| baseline | 既知の権限昇格を防ぐ最低線 | privileged コンテナ・hostNetwork・hostPID・危険な capabilities などを禁止。それ以外の既定設定はおおむね許可 |
| restricted | 強く固めたベストプラクティス | baseline の禁止に加え、runAsNonRoot・seccomp RuntimeDefault・capabilities drop ALL などを積極的に要求 |
3 段階の違いを 1 行で要約するとこうです。privileged は何も防がず、baseline は明白な脱出経路だけを防ぎ、restricted は最小権限を強制します。 privileged は通常、システムコンポーネントやノードエージェントのようにホストアクセスが必須のワークロードにだけ使い、一般のアプリケーション名前空間には baseline または restricted を適用するのが推奨される方向です。
baseline が防ぐもの #
baseline はコンテナがノードを掌握する既知の経路を遮断します。主な禁止項目は次のとおりです。
hostNetwork、hostPID、hostIPCなどの host 名前空間共有privileged: trueコンテナhostPathボリューム (一部例外を除く)NET_RAWを除いた危険な追加 capabilities- 既定の seccomp プロファイルを Unconfined に明示すること
restricted が追加で要求するもの #
restricted は baseline の禁止をすべて含みつつ、さらに次を 必ず設定 するよう要求します。試験で restricted を通過する Pod を書くなら、このリストがそのままチェックリストになります。
runAsNonRoot: true(root での実行禁止)allowPrivilegeEscalation: falseseccompProfile.type: RuntimeDefault(または Localhost)capabilities.dropにALLを含む。追加で許されるのはNET_BIND_SERVICEのみhostPathのようなボリュームタイプを除いた安全なボリュームのみ使用
これらの設定は Pod レベルの securityContext とコンテナレベルの securityContext の両方に適切に入る必要があります。特に runAsNonRoot と seccompProfile は Pod レベルに、allowPrivilegeEscalation と capabilities はコンテナレベルに置くのが一般的です。
PSA モードの 3 種類 #
同じ基準でも 違反したとき何をするか をモードで選びます。PSA は 3 モードを提供し、1 つの名前空間に同時に異なる段階を掛けることもできます。
| モード | 動作 | 用途 |
|---|---|---|
| enforce | 基準を違反した Pod の作成を 拒否。Pod は作られない | 実際の遮断 |
| audit | 違反しても作成は許可するが、audit log に違反の事実を記録 | 影響分析 |
| warn | 違反しても作成は許可するが、kubectl の応答に警告メッセージを表示 | ユーザー案内 |
運用では通常、新しい基準をすぐ enforce で掛ける前に warn と audit でまず影響を確認したうえで、安全を確認してから enforce に上げます。ただし試験ではたいてい enforce 1 つだけを明確に要求するので、問題が「enforce で restricted を適用せよ」と言えば enforce label だけ正確に付ければよいです。
名前空間 label で適用する #
PSA の適用単位は 名前空間 です。名前空間に次の形式の label を付けると、その名前空間のすべての Pod に基準が適用されます。
pod-security.kubernetes.io/<mode>: <level>
pod-security.kubernetes.io/<mode>-version: <version><mode>はenforce、audit、warnのいずれかです。<level>はprivileged、baseline、restrictedのいずれかです。-versionlabel は適用する基準の Kubernetes バージョンを固定します。省略するとlatestとして動作し、明示するときはv1.30のように書きます。
たとえば app 名前空間に restricted を enforce で掛ける label は次のとおりです。
apiVersion: v1
kind: Namespace
metadata:
name: app
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest上のように enforce と warn を一緒に掛けると、違反 Pod は拒否されつつ同時に人が読みやすい警告も表示されます。試験で素早く処理するには、マニフェストの代わりに kubectl label で掛ける方法も覚えておくとよいです。
# すでに存在する名前空間に enforce restricted を適用
kubectl label namespace app \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest --overwrite--overwrite を付けて初めて、すでに別の値がある場合でも上書きできます。
違反 Pod はどう拒否されるか #
enforce モードが掛かった名前空間に基準を違反した Pod を作ろうとすると、API サーバーが admission 段階で止めてエラーメッセージを返します。たとえば restricted が enforce された名前空間に何の設定もない普通の nginx Pod を作ると、次のような拒否メッセージが出ます。
Error from server (Forbidden): error when creating "pod.yaml": pods "nginx" is forbidden:
violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (...),
unrestricted capabilities (...), runAsNonRoot != true (...),
seccompProfile (...) must be set to "RuntimeDefault" or "Localhost"このメッセージがそのまま修正ガイドです。違反項目がそのまま並ぶので、メッセージに書かれたフィールドを 1 つずつ埋めていけば通過します。試験で「拒否される Pod を直せ」が出たら、まずそのまま一度適用して拒否メッセージを受け取り、並んだ項目を securityContext に反映する流れが速いです。
すでに動いていた Deployment の Pod には影響がありません。PSA は admission 段階でのみ動作するので、label を掛けた後 新しく作成される Pod から 検査を受けます。したがって既存ワークロードに restricted を適用したら、Deployment を再デプロイして新しい Pod を作ってみて初めて実際の違反の有無が表面化します。
restricted を通過する Pod の例 #
restricted が enforce された名前空間でも正常に作成される Pod は次のように書きます。上のチェックリストがすべて反映されています。
apiVersion: v1
kind: Pod
metadata:
name: secure-nginx
namespace: app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginxinc/nginx-unprivileged:stable
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL要点をもう一度押さえます。
- Pod レベル
securityContextにrunAsNonRoot: trueとseccompProfile.type: RuntimeDefaultを置きます。 - コンテナレベル
securityContextにallowPrivilegeEscalation: falseとcapabilities.drop: [ALL]を置きます。 - イメージは root ではないユーザーで動くものを選ばなければなりません。一般の
nginxイメージは 80 番ポートを root でバインドするのでrunAsNonRootと衝突します。上の例のように非特権イメージを使うか、ポートを 1024 以上に変えます。
最後の項目が間違えやすい部分です。securityContext フィールドだけ埋めて root で動くイメージをそのまま使うと、作成は通過してもコンテナが起動段階で失敗します。基準を満たすマニフェストと、実際に非特権で動くイメージ の両方が合っていなければなりません。
クラスター全体の既定値 (AdmissionConfiguration) #
名前空間ごとに label を掛ける代わりに、クラスター全体に既定の基準を敷くこともできます。API サーバーに AdmissionConfiguration ファイルを渡して PodSecurity プラグインの既定値と例外を定めます。システム名前空間 (kube-system など) は通常、例外として外しておきます。
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1
kind: PodSecurityConfiguration
defaults:
enforce: baseline
enforce-version: latest
exemptions:
namespaces:
- kube-systemこのファイルは kube-apiserver の --admission-control-config-file フラグでつなぎます。ただし試験でよくある形はクラスター全域の設定より 個別の名前空間 label なので、優先順位は label 方式に置くのが安全です。
試験ポイント #
- PSP は v1.25 で削除 され、その代替が PSA です。PSP の文法を使ってはいけません。
- 3 段階は privileged → baseline → restricted の順で強くなります。restricted が最も厳格です。
- 3 モードは enforce (拒否)・audit (記録)・warn (警告) です。実際に遮断するのは enforce だけです。
- 適用単位は名前空間で、label キーは
pod-security.kubernetes.io/<mode>とpod-security.kubernetes.io/<mode>-versionです。 - restricted 通過チェックリストは
runAsNonRoot: true、allowPrivilegeEscalation: false、seccompProfile.type: RuntimeDefault、capabilities.drop: [ALL]です。 - 拒否メッセージに違反項目がそのまま出るので、まず適用してメッセージを受け取り、そのまま直す流れが速いです。
- root で動くイメージは securityContext だけ埋めても起動に失敗します。非特権イメージ まで一緒に揃える必要があります。
PSA のセキュリティ強化の方向は CKAD #15 securityContext と Pod セキュリティ で扱った Pod レベルのセキュリティ設定と同じフィールドを使い、PSA はその設定を名前空間単位で 強制 する点だけが違います。CKAD が推奨設定を手に覚える段階だったなら、CKS はその設定をクラスターが検査するようにする段階です。
まとめ #
この記事で押さえたこと:
- PodSecurityPolicy の廃止後、その座を Pod Security Admission が置き換えました。別途インストールなしで API サーバーに組み込まれています。
- Pod Security Standards の 3 段階 (privileged・baseline・restricted) は段階が上がるほど許容範囲が狭くなります。
- 3 モード (enforce・audit・warn) で違反時の動作を選び、実際の拒否は enforce です。
- 名前空間 label
pod-security.kubernetes.io/enforce: restrictedと enforce-version で適用します。 - restricted 通過 Pod は
runAsNonRoot・allowPrivilegeEscalation: false・seccomp RuntimeDefault・capabilities drop ALL を備え、非特権イメージ で動く必要があります。
次へ — Secrets 管理 #
Pod のセキュリティ基準は PSA で強制できるようになりました。ところが Pod が扱う機密データ、つまり Secret そのものはどう保護するのでしょうか。既定の Secret は etcd に平文に近い base64 で保存され、etcd にアクセスできる人はそのまま読めてしまいます。
#10 Secrets 管理: etcd 暗号化、External Secrets では、EncryptionConfiguration で etcd に保存される Secret を暗号化する方法、キーローテーション、そして外部の秘密ストアを連携する External Secrets パターンまで、試験の観点で整理します。