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 はコンテナがノードを掌握する既知の経路を遮断します。主な禁止項目は次のとおりです。

  • hostNetworkhostPIDhostIPC などの host 名前空間共有
  • privileged: true コンテナ
  • hostPath ボリューム (一部例外を除く)
  • NET_RAW を除いた危険な追加 capabilities
  • 既定の seccomp プロファイルを Unconfined に明示すること

restricted が追加で要求するもの #

restricted は baseline の禁止をすべて含みつつ、さらに次を 必ず設定 するよう要求します。試験で restricted を通過する Pod を書くなら、このリストがそのままチェックリストになります。

  • runAsNonRoot: true (root での実行禁止)
  • allowPrivilegeEscalation: false
  • seccompProfile.type: RuntimeDefault (または Localhost)
  • capabilities.dropALL を含む。追加で許されるのは NET_BIND_SERVICE のみ
  • hostPath のようなボリュームタイプを除いた安全なボリュームのみ使用

これらの設定は Pod レベルの securityContext とコンテナレベルの securityContext の両方に適切に入る必要があります。特に runAsNonRootseccompProfile は Pod レベルに、allowPrivilegeEscalationcapabilities はコンテナレベルに置くのが一般的です。

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>enforceauditwarn のいずれかです。
  • <level>privilegedbaselinerestricted のいずれかです。
  • -version label は適用する基準の 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 レベル securityContextrunAsNonRoot: trueseccompProfile.type: RuntimeDefault を置きます。
  • コンテナレベル securityContextallowPrivilegeEscalation: falsecapabilities.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: trueallowPrivilegeEscalation: falseseccompProfile.type: RuntimeDefaultcapabilities.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 は runAsNonRootallowPrivilegeEscalation: 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 パターンまで、試験の観点で整理します。

X