PodSecurityPolicyの廃止とPodSecurity
Kubernetesのv1.21でPodSecurityPolicy(以下PSP)が非推奨となり、v1.25では削除される予定となっています。
PSPが廃止されてしまいますが、代わりにPodSecurityという新しい組み込みのAdmission Controllerが追加されるようです。
順調に開発が進めばv1.22からalpha機能として利用可能となるようですが、待ちきれずコア機能がmergeされた段階で今できることを検証してみました。
以下はドキュメントもない状態なので、コードを軽く確認しながら動作てみたため記事にしてみました。
2020/09/02 追記
Kubernetes Meetup Tokyo #44でこの内容を若干アップデートした内容を発表させていただきました。スライドはこちらにあります。
検証バージョンについて
PodSecurityは現在(2021/7/1)時点でこちらはalphaとしてすら公開されているものではないため、急速に変更が加わっています。そのためalpha機能提供時にはこちらの内容と大きな差がある可能性があります。
こちらのコミット時点のKubernetesで検証しています。PodSecurityのコア機能が追加され、多少のポリシーのチェックが追加されたような状態です。
PSPを軽くおさらいする
PodSecurityの前に、PSPの利用について振り返ってみます。PSPについてはこちらの記事が参考になります。
KubernetesのRBACを利用するとPodを作成できるかどうかの制御ができますが、Podの特性を確認した上で作成してよいかどうかという制御ができません。
それに対してPSPを利用することで、PodのsecurityContextやマウント可能なvolume、ネットワークの機能を制限することができ、制限された機能を利用しようとしたPodは作成できなくなります。これにより必要以上の権限を持ったPodを作成せずに済んだり、意図しない権限昇格を防ぐことができるようになります。
PSPは以下のような専用のリソースを作成し、そのリソースに対するRBACをユーザ・サービスアカウントに付与することで利用可能となります。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default'
apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default'
apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default'
spec:
privileged: false
# Required to prevent escalations to root.
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
# Allow core volume types.
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
# Assume that ephemeral CSI drivers & persistentVolumes set up by the cluster admin are safe to use.
- 'csi'
- 'persistentVolumeClaim'
- 'ephemeral'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
# Require the container to run without root privileges.
rule: 'MustRunAsNonRoot'
seLinux:
# This policy assumes the nodes are using AppArmor rather than SELinux.
rule: 'RunAsAny'
supplementalGroups:
rule: 'MustRunAs'
ranges:
# Forbid adding the root group.
- min: 1
max: 65535
fsGroup:
rule: 'MustRunAs'
ranges:
# Forbid adding the root group.
- min: 1
max: 65535
readOnlyRootFilesystem: false
上記リソースの権限付与は以下のようなCLusterRole/ClusterRoleBindingで実現します。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: restricted-psp
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames:
- restricted
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: restricted-psp
roleRef:
kind: ClusterRole
name: restricted-psp
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:authenticated
利用可能なPSPがない場合、Podが利用できないため、既存のクラスタを有効にするときは注意が必要です。
利用可能なPSPがない場合のデフォルトのPSPを実現するには、上記のマニフェストのように認証済みのユーザ(サービスアカウントも含む)の属性であるsystem:authenticated
グループに対して利用可能なPSPを定義しておくことで実現することができます。
PSPの問題点
PSPが廃止される理由は以下の記事やPodSecurityのKEPのMotivationを参照ください。
- PodSecurityPolicy Deprecation: Past, Present, and Future
- KEP-2579: Pod Security Admission Control - Motivation
公式の意見としては上記のリンク先の通りなのですが、個人的にPSPを触り始めて大変だなと感じていたのはPSPはPod作成時のリクエストユーザ、またはPodに指定されたServiceAccountの権限でどのPSPが適用されるか決定される
ということです。
単純にPodを作成するだけであれば理解しやすいと思います。問題となるのはDeploymentやStatefulSetのようにPodTemplate
を持つものを動かした場合です。これらは実際にPod作成のリクエストを投げるのはユーザではなくそれぞれのコントローラとなります。
そのためDeploymentを作成しようとした時に、利用可能なPSPが存在せずPodが起動しないということとなり得ます。「自分はPrivilegedなPodを作成できるはずなのに、なぜPodが作成できないのか?」という経験をした事がある方もいらっしゃるのではないでしょうか。
これを回避するためにはコントローラに強めのPSPを利用できるようにするか、以下のようにPodのspecにPod作成に必要なPSPが利用できるServiceAccountを指定することになります。
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
serviceAccountName: my-sa
コントローラに強めのPSPを利用できるようにするのは、実質制限がなくなると同義になってしまうため、ServiceAccountを利用することとなるはずです。
さて上記を踏まえた上で、あるNamespace Aにとても強いPSP(例えばPrivilegedなPodが作成できるなど)が利用可能なServiceAccount Aが存在している場合を考えてみましょう。
あるユーザAはNamespace AでPodを作成することができるとします。しかしこのユーザAは制限されたPSPしか利用できないように設定されていたとしましょう。このユーザがPod作成時にServiceAccount Aを指定してPrivilegedなPodを作成したらどうなるでしょうか?
この場合ユーザはPrivilegedなPodが作成できてしまいます。ユーザが許可されていなくとも、Namespace内のサービスアカウントで許可されているPSPをユーザは利用できるということです。
個人的にこれは非常にわかりづらい挙動でした。また、あるユーザがあるNamespaceで許されているPSPはどうなっているのか?という確認が、この挙動により非常に面倒となっていました。PSPの柔軟さを残したまま、このような部分がPodSecurityで解決していると嬉しいなと思いながら調査をしました。
PodSecurity
やっと本題のPodSecurityです。
現時点でこれについて一番詳細なドキュメントは、おそらくKEPかと思われます。公式のドキュメントには執筆時点でPodSecurityについて使い方等の説明はありません。そのためコードやKEPを適宜確認しながら挙動を確認していきました。
PodSecurityを有効化する
まずPodSecurityを有効化する方法ですが、FeatureGatesにてPodSecurity
を有効化することで利用可能となります。PodSecurityはAdmission Controllerとして実装されるのですが、PSPと異なりデフォルトで有効となっています。そのためFeatureGatesを有効化するだけで利用可能です。
PodSecurityについて
PodSecurityはPod Security Standardsで定義されているポリシーを適用し、作成しようとしているPodがそれを満たさない場合、作成を拒否したり、警告に表示したり、audit logへ記録するといったことを制御します。(検証時点ではPodSecurityStandardの全ては実装されていません)
PodSecurityはValidationは実行しますが、ポリシーを満たさないPodをポリシーを満たすようにリソースを編集したりはしません。
Pod Security Standardsにはenforce
,audit
,warn
の3つのモードがあります。それらに対してそれぞれPod Security Standardsで定義されているrestricted
, baseline
, privileged
をポリシーとして設定します。privileged
は制限がなくbaseline
, restricted
の順に制限が厳しいものとなっています。それぞれのモードでポリシーに違反するPodの作成が検知された場合モード毎に挙動が異なります。またそれぞれのモードは別々のポリシーを設定可能です。
- enforce: Podの作成を拒否します。
- audit: audit logのアノテーションとして記録します。これを満たさない場合でもPodの作成の可否には影響しません。
- warn: warningとして表示します。これを満たさない場合でもPodの作成の可否には影響しません。
上記の実装部分はこちらです。
PodSecurityを適用する方法
PodSecurityによるポリシーの強制はNamespaceのラベルにより制御します。そのためPSPのような専用のリソースはありません。
それぞれのモード毎に設定が可能であり、pod-security.kubernetes.io/{enforce,audit,warn}
のようなラベルに対して、値としてrestricted
, baseline
, privileged
を指定します。
例えばbaseline
を違反した場合には警告を表示するが、Podの作成を拒まない場合には以下のように設定します。
apiVersion: v1
kind: Namespace
metadata:
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: baseline
name: test
このようにNamespaceのラベルで制御するため、Namespaceの作成・編集権限があるということは、ユーザへPSPを利用できるようにClusterRole/ClusterRolebindingを作成するのと同じような権限があるということになります。
PSPを利用しているクラスタで運用者と利用者が分かれている場合は、PSPからPodSecurityへ移行する上で気をつけなければいけない点になるかと思います。
そしてPSPと異なり、PodSecuirtyにはデフォルト値が存在します。設定がないモードはprivileged
つまり制限がないものとして扱われます。
KEPによると今後PodSecurityはデフォルト有効化されるようなので、デフォルト有効化されたバージョンになってもこれらの設定がなければ「PodSecurityは機能しているが、制限がない状態」ということになり、既存のクラスタへの影響はなさそうです。
PodSecurityの設定を変更する方法
他の組み込みのAdmission Controllerと同様にPodSecurityも設定が変更できコードを確認した限り、以下のような設定が可能のようです(動作未検証)。
-
pod-security.kubernetes.io/{enforce,audit,warn}
ラベルが付与されていないときのデフォルト値 - 設定がないモードのデフォルト値やポリシーのバージョン
- PodSecurityの制約を受けないユーザ
- PodSecurityの制約を受けないNamespace
- PodSecurityの制約を受けないRuntimeClass
組み込みのAdmission Controllerはkube-apiserverの--admission-control-config-file
により設定できそうです。
KEPの段階ではPodSecurityのためのコンフィグはapiserver.config.k8s.io
だったようですが議論の末、pod-security.admission.config.k8s.io
となったようです。確認時点で設定ファイルのVersionはv1alpha1
のみが存在する状態でした。
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
path: podsecurity.yaml
apiVersion: pod-security.admission.config.k8s.io/v1alpha1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
defaults: # pod-security.kubernetes.io/{enforce,audit,warn} ラベルがセットされていないとき
enforce: <default enforce policy level>
enforce-version: <default enforce policy version>
audit: <default audit policy level>
audit-version: <default audit policy version>
warn: <default warn policy level>
warn-version: <default warn policy version>
exemptions: # PodSecurityの対象外の設定
usernames: [ <array of authenticated usernames to exempt> ]
runtimeClassNames: [ <array of runtime class names to exempt> ]
namespaces: [ <array of namespaces to exempt> ]
...
上記設定を見てお気づきかもしれませんが、ポリシーにはバージョンの概念があります。
まだポリシーの実装が完全には終わっていなかったため、どのようにバージョンが利用されていくか確認できておりませんが、PodのSpecやPod Security Standardsが更新がありPodSecurity内のポリシーの実装も更新の必要があった時にバージョンが上がっていくのでしょうか? 指定しない場合は最新のバージョンであるlatest
が指定されるようです。
PodSecurityを利用する
dry-runで実行する
今後PodSecurityがデフォルト有効となっても、上記の通りnamespaceにPodSecurityのラベルがなければ制限はされません。
既にクラスタを利用していて、Podが存在するnamespaceにPodSecurityのenforceを設定したくなることもあるでしょう。
特にPodSecurityでenforce
を適用する場合、満たさないPodがいた場合、次回Pod作成時に起動しなくなってしまいます(admission controllerなので起動中のPodには影響がないため)。
PodSecurityでは実際にenforce
の設定を適用する前にdry-runが実行できるようになっています。
ここではdefault
ネームスペースにpod-security.kubernetes.io/enforce: baseline
を適用したいが、それを満たさないPodが存在していたという状況を考えてみます。
以下のPrivilegedなPodをdefault
ネームスペースへ作成しておきます。このPodはbaseline
では違反となるPodです。
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
securityContext:
privileged: true
PodSecurityのdry-runはNamespaceのラベルの更新リクエストをserver-sideなdry-runで実行することで実行できます。
$ kubectl label --dry-run=server --overwrite ns default pod-security.kubernetes.io/enforce=baseline
Warning: nginx: privileged == true
namespace/default labeled
上記のようにdry-runでコマンドを実行すると、nginxがprivilegedなコンテナを含むという警告が表示されていることが確認できます。
enforceのようにPodの起動に影響してしまう設定を、事前にdry-runで確認できるのは素敵ですね。
PodSecurityを適用する
さて今度は別のnamespaceでテストします。ここではtest-ps
というnamespaceを作成しました。
このnamespaceはenforce
とwarn
がbaseline
に設定してあります。そのため先ほどのprivileged: true
なコンテナを持つPodは起動できないようになっているはずです(そして作成しようとすると警告も表示されるはず)。
先ほどと同じspecのPodを、そのnamespaceへ作成を試みます。
apiVersion: v1
kind: Namespace
metadata:
name: test-ps
labels:
kubernetes.io/metadata.name: test-ps
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: baseline
$ kubectl apply -f privileged-pod.yaml
Warning: Pod violates PodSecurity profile baseline:latest: privileged == true (spec.containers[0].securityContext.privileged)
Error from server (Forbidden): error when creating "privileged-pod.yaml": pods "nginx" is forbidden: privileged == true (spec.containers[0].securityContext.privileged)
正常にPodの作成がrejectされました
そしてKEPによると、DeploymentやStatefulSetのようなPodTemplateの領域もwarnやauditは機能してくれそうです。
enforceは実際のPod作成時のみなので、それらのリソースが作成できなくなるわけではありませんが、Deployment作成直後やdry-runによって事前にPodが作成できるかどうかが確認できます。PSPの時のようにリソース作成後にReplicaSetやStatefulSetのイベントを確認しなくても、事前にチェックできるのは嬉しいですね!PodTemplateはbuilt-inなリソースであればValidationしてくれそうです。KEPを見る限りカスタムリソースについても今後何かしら対応策がでてきそうです。
早速試してみましょう。上記のnamespaceに以下のようなPrivilegedなコンテナを含むDeploymentの作成をdry-runで試みます。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
namespace: test-ps
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
securityContext:
privileged: true
$ kubectl apply --dry-run=server -f privileged-deployment.yaml
Warning: Pod violates PodSecurity profile baseline:latest: privileged == true (spec.containers[0].securityContext.privileged)
deployment.apps/nginx created (server dry run)
Deploymentの作成リクエストで警告が確認できました
PodSecurityでカスタムポリシーを利用する
PodSecurityはPod Security Standardsで定義されているprivileged
, baseline
, restricted
を適用します。それ以外のポリシーを適用したい場合、PSPのように簡単には用意できなさそうです。そのためFlexible Extension SupportとCustom Profilesを読む限り、APIServerのフラグでカスタムポリシーを設定し、そのポリシーを処理するPodSecurityのようなWebhookを自前で実装する必要があるようにみえます。baseline
でhostPathの一部だけ許可したようなポリシーを適用したい場合でも、別途実装が必要となると...多少大変そうですね。
最後に
PSPより柔軟性はなくなっているものの、デフォルトで有効になっているセキュリティの機能としては非常に楽しみですね。
v1.22でPodSecurityがalphaとしてリリースされたらこの記事からの差分をまとめたいと考えています。
またカスタムポリシーのための実装がまとまってきたら、それも調査していこうと思います。