2021/04/07追記
Pod Security PolicyはKubernetes v1.25で廃止されます。
PSPに代わる新たな機能が検討されており、今後は複雑な制御はOpen Policy Agentのような外部オープンソースツールが推奨され、よりシンプルで明確な機能としてPSP Replacement Policy(仮)がKubernetesには実装される予定です。
詳細は以下のブログをご確認ください。
Pod Security Policy(ポッドセキュリティポリシー)
Pod Security Policy(ポッドセキュリティポリシー、PSP)とは、Kubernetesクラスタ全体のPodのセキュリティポリシーを定義する機能で、Podのセキュリティレベルによって稼働可否を制御できる機能です。
Pod Security Policy自体の詳細については他に譲ります。
Pod Security Policyはマルチテナントのクラスターを運用する上で非常に有効な設定ですが、実際にクラスターに適用する際には、ユーザーだけでなくPodを作成するKubernetes Controllerについても考慮が必要です。
例えば、Aユーザーには特権Podを作成させるが、Bユーザーには作成させないといった設定は比較的簡単ですが、実際はクラスター内にユーザーが直接Podリソースを作成することは通常少なく、おそらくDeploymentなどでPodを起動するかと思います。
Deploymentを作成した際に、実際にPodを作成するのはReplicaSet Controllerです。
この場合、Deploymentを作成したユーザーにポリシーが設定されているだけでは不十分で、ReplicaSet Controllerにも適切にPolicyが適用されなければ、特権Podを実行することができてしまいます。
他にもDaemonSet ControllerやPodを作成するOperator等を使用している場合は、それらに適切にも設定する必要があります。
Controllerに対するPSPの設定
DeploymentでPodを作成する場合、Template内のPodSpecでServiceAccountを指定している場合は、その指定したServiceAccountに対するPSPが使用されます。他にもDaemonSetやStatefulSetの場合も同樣です。
また、PodSpecでServiceAccountを指定していない場合は、Podを起動するNamespaceにデフォルトで存在するdefault
というServiceAccountが使用されます。
そのため、一般的にはServiceAccountに対して適切なPSPが有効になるように設定する必要があります。
例えば自前でValidation Webhookを実装したり、Open Policy Agent等を使用することで、DeploymentのSpecに特権を必要とするPodSpecを記載できないようにすることも実現できるかと思います。
しかし、これらはあくまでもSpecに対する静的な解析に留まりますが、Pod Security Policyはkubeletと連携し、コンテナ実行時にrootユーザーでの実行をブロックするなどの強力なセキュリティ機能を持っており、これはPSP以外で実現するのは困難かと思います。
PSPの優先順位
複数のPSPが有効な場合に使用されるポリシーの優先度については、少しクセがあり注意が必要です。
公式ドキュメントには以下の記載があります。
- PodSecurityPolicies which allow the pod as-is, without changing defaults or mutating the pod, are preferred. The order of these non-mutating PodSecurityPolicies doesn't matter.
Podのas-isを許可する(つまり、デフォルト値を設定したりPodを変更することがない)PSPが優先される。変更しないPSPの優先度は重要ではない。
- If the pod must be defaulted or mutated, the first PodSecurityPolicy (ordered by name) to allow the pod is selected.
Podにデフォルト値を設定したり変更を強制する必要があるとき、そのPodを許可する最初のPSP(名前順)が使用される。
AWS IAM Policyなどは拒否ポリシーが優先されますが、PSPの場合はそのままを許可するポリシーが優先されます。
そのため、基本的には大きな範囲で拒否ポリシーを適用し、特権が必要な対象にのみ許可ポリシーを当てていくというやり方がいいかと思います。複数合致すると適用範囲の理解が複雑になりますので、ポリシーを組み合わせて複雑な権限制御を行うのは諦めたほうが良さそうです。
Pod Security Standard
KubernetesではPod Security Standard
個人的ベストプラクティス
上記を踏まえて以下の考え方をベースとするのがいいかと思っています。
- まずは特権を許可するPSPと制限付きPSPの2つを作成する。
- クラスターの全範囲に制限付きPSPを適用する。
- ServiceAccount一つ一つにPSPの設定をしていくのは困難であるため、特権操作を許可するNamespaceを決め、そのNamespace単位に特権PSPが適用されるようにする。
- ただし
kube-system
Namespaceに対してはServiceAccountに個別にPSPを設定する。 - アプリの性質上、制限付きPSPで耐えられないものがあれば、適宜専用のPSPを追加し、そのアプリが稼働するNamespaceに適用する。
- PSPを操作するときは
psp-util
プラグインを使うと良い(重要)
設定手順
個人的ベストプラクティスに記載した内容で、具体的に設定する例を示します。
kubectl plugin psp-util
のインストール
Pod Security Policy(PSP)はkubernetesリソースPodSecurityPolicy
として定義し、ClusterRole
およびClusterRoleBinding
を用いてGroupやServiceAccountにバインドします。
Pod Security Policyの操作に関して、関連するRBACリソース含めていい感じに管理するためのpsp-utilというKubectl pluginを公開しています(宣伝)
https://github.com/jlandowner/psp-util
これによりクラスター内のポリシーの適用状況を確認したり、容易にポリシーをアタッチ・デタッチすることができます。
psp-util
プラグインは、Kubectlの公式プラグインマネージャーであるKrewで公開しています。Krew自体のインストールはこちらを参照してください。
Krewをインストールした後は、以下のコマンドでpsp-util
をインストールできます。
kubectl krew install psp-util
現状確認
自分が普段触る環境はEKSが多いので、EKSを例に進めていきます。
現在のPSPの一覧を確認します。
$ kubectl get psp
NAME PRIV CAPS SELINUX RUNASUSER FSGROUP SUPGROUP READONLYROOTFS VOLUMES
eks.privileged true * RunAsAny RunAsAny RunAsAny RunAsAny false *
EKSにはデフォルトでeks.privileged
という名前のPSPが存在します。これは全てのPodの稼働を許可するポリシーです。
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/pod-security-policy.html
次に適用範囲を確認します。
psp-utilのtree
コマンドを実行すると、このPSPがsystem:authenticated
Groupに適用されていることが分かります。
$ kubectl psp-util tree
📙 PSP eks.privileged
└── 📕 ClusterRole eks:podsecuritypolicy:privileged
└── 📘 ClusterRoleBinding eks:podsecuritypolicy:authenticated
└── 📗 Subject{Kind: Group, Name: system:authenticated, Namespace:}
system:authenticated
Groupは、API Serverに認証された全てのアクセスが属するグループですので、実質クラスター内の全てにこのポリシーが適用されています。
PSPの作成
特権を許可するPSPの作成
上記の通り、EKSではデフォルトで全てを許可するPSPがすでに存在するので、特権を許可するPSPとしてはこちらをそのまま利用すれば良いかと思います。
他の環境においても一般的には同樣の設定で作成すれば良いかと思いますが、特権Namespaceや管理者ユーザーが作成するPodもしっかり制限したい場合は、環境に合ったPSPを作成してください。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
annotations:
kubernetes.io/description: privileged allows full unrestricted access to pod features,
as if the PodSecurityPolicy controller was not enabled.
seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
labels:
eks.amazonaws.com/component: pod-security-policy
kubernetes.io/cluster-service: "true"
name: eks.privileged
spec:
allowPrivilegeEscalation: true
allowedCapabilities:
- '*'
fsGroup:
rule: RunAsAny
hostIPC: true
hostNetwork: true
hostPID: true
hostPorts:
- max: 65535
min: 0
privileged: true
runAsUser:
rule: RunAsAny
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
volumes:
- '*'
特権PSPが存在しない場合、上記の内容の設定ファイルを作成し、適用します。
kubectl apply -f psp-privileged.yaml
制限付きPSPの作成
制限付きPSPについては、AWSが公開しているEKS Best Practices内のrecommendationsに記載のPSPをまずは適用するのが良いかと思います。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default' #seccompが有効でない場合は外す
apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' #apparmorが有効でない場合は外す
seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default' #seccompが有効でない場合は外す
apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' #apparmorが有効でない場合は外す
spec:
privileged: false
# Required to prevent escalations to root.
allowPrivilegeEscalation: false
# This is redundant with non-root + disallow privilege escalation,
# but we can provide it for defense in depth.
requiredDropCapabilities:
- ALL
# Allow core volume types.
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
# Assume that persistentVolumes set up by the cluster admin are safe to use.
- 'persistentVolumeClaim'
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
上記の内容の設定ファイルを作成し、適用します。
kubectl apply -f psp-restricted.yaml
もしすでにアプリケーションがクラスター内で稼働中であり、現在動いているアプリで許されている範囲で制限ポリシーを設定したい場合は、sysdigが公開しているkube-psp-advisorを使用すると、適切なPSPを自動作成してくれます。
こちらもKrewで公開されていますので以下のコマンドでインストールできます。
kubectl krew install advise-psp
現在アプリケーションが動いているNamespaceを指定してinspect
コマンドを実行することでPSPを自動生成してくれます。
以下のコマンドはinspect結果を元にPSPの作成を行います。
kubectl advise-psp inspect --namespace=YOUR_NAMESPACE | kubectl apply -f -
PSPの適用
ここまでで特権PSPと制限付きPSPが作成できましたので、それぞれを実際に適用していきます。
(EKSの場合)
EKSの場合は、まずはsystem:authenticated
Groupに特権PSPが適用されているため、このPSPをデタッチします。
psp-utilを使用すると自動でClusterRoleBindingを作成するので、一旦はeks:podsecuritypolicy:authenticated
というClusterRoleBindingを削除します。
kubectl delete ClusterRoleBinding eks:podsecuritypolicy:authenticated
制限付きPSPの適用
制限付きPSPを全クラスターに適用するために、system:authenticated
Groupおよびsystem:unauthenticated
Groupに適用します。
今回は前述の制限付きPSPの内容でrestricted
という名前のPSPを作成している前提で、
以下の2つのコマンドを実行します。
kubectl psp-util attach restricted --group system:authenticated
kubectl psp-util attach restricted --group system:unauthenticated
設定内容を確認します。これにより自動でClusterRoleとClusterRoleBindingが作成されています。
$ kubectl psp-util tree
📙 PSP eks.privileged
└── 📕 ClusterRole eks:podsecuritypolicy:privileged
📙 PSP restricted
└── 📕 ClusterRole psp-util.restricted
└── 📘 ClusterRoleBinding psp-util.restricted
└── 📗 Subject{Kind: Group, Name: system:authenticated, Namespace:}
└── 📗 Subject{Kind: Group, Name: system:unauthenticated, Namespace: }
特権PSPの適用
次に特権PSPを管理者ユーザーグループにアタッチします。
例えばsystem:master
に特権PSPをアタッチするには以下のように設定します。
kubectl psp-util attach eks.privileged --group system:master
EKSはeksというGroupもあるようなので、一応アタッチしておきます。
kubectl psp-util attach eks.privileged --group eks
続いて、特権Namespace内のServiceAccountをアタッチします。
Namespaceの全てのServiceAccountはsystem:serviceaccounts:<Namespace名>
というGroupに所属するため、このグループを特権PSPにアタッチすれば良いです。
例えばdefault
Namespaceには以下のコマンドでアタッチできます。
kubectl psp-util attach eks.privileged --group system:serviceaccounts:default
kube-systemへのPSPの適用
ただしkube-system
への特権PSPの適用は注意が必要です。
kube-system
にはreplicaset-controller
などのcontrollerが使用するServiceAccountが存在しています。
$ kubectl get sa -n kube-system
NAME SECRETS AGE
alb-ingress-controller 1 277d
attachdetach-controller 1 380d
aws-cloud-provider 1 380d
aws-node 1 380d
certificate-controller 1 380d
clusterrole-aggregation-controller 1 380d
coredns 1 380d
cronjob-controller 1 380d
daemon-set-controller 1 380d
default 1 380d
deployment-controller 1 380d
disruption-controller 1 380d
eks-vpc-resource-controller 1 12d
endpoint-controller 1 380d
expand-controller 1 380d
generic-garbage-collector 1 380d
horizontal-pod-autoscaler 1 380d
job-controller 1 380d
kube-proxy 1 380d
kubernetes-route53-sync 1 87d
namespace-controller 1 380d
node-controller 1 380d
node-problem-detector 1 177d
persistent-volume-binder 1 380d
pod-garbage-collector 1 380d
pv-protection-controller 1 380d
pvc-protection-controller 1 380d
replicaset-controller 1 380d
replication-controller 1 380d
resourcequota-controller 1 380d
service-account-controller 1 380d
service-controller 1 380d
statefulset-controller 1 380d
ttl-controller 1 380d
vpc-resource-controller 1 12d
例えば、replicaset-controller
というServiceAccountに特権PSPをアタッチすると、Deploymentで作成したPodが全て特権PSPで作成されてしまいます。そのため、kube-system
Namespaceには必要なSerivceAccountに個別に適用していく必要があります。
実際にPodとしてデプロイされているリソースが使用しているServiceAccountに直接アタッチするしかないかと思います。
自分のクラスターにはDaemonSetとDeploymentでしかPodが展開されていないので、以下のコマンドで確認します。
# DaemonSetが使用しているSerivceAccount
$ kubectl -n kube-system get ds -o jsonpath='{.items[*].spec.template.spec.serviceAccountName}'
aws-node kube-proxy
# Deploymentが使用しているSerivceAccount
$ kubectl -n kube-system get deploy -o jsonpath='{.items[*].spec.template.spec.serviceAccountName}'
alb-ingress-controller coredns
ワンライナーでアタッチします。
# DaemonSetが使用しているSAに特権PSPをアタッチ
for i in $(kubectl -n kube-system get ds -o jsonpath='{.items[*].spec.template.spec.serviceAccountName}'); do kubectl psp-util attach eks.privileged --sa $i -n kube-system; done
# Deploymentが使用しているSAに特権PSPをアタッチ
for i in $(kubectl -n kube-system get deploy -o jsonpath='{.items[*].spec.template.spec.serviceAccountName}'); do kubectl psp-util attach eks.privileged --sa $i -n kube-system; done
ここまでの設定結果を確認すると以下の通りです。
$ kubectl psp-util tree
📙 PSP eks.privileged
└── 📕 ClusterRole eks:podsecuritypolicy:privileged
└── 📕 ClusterRole psp-util.eks.privileged
└── 📘 ClusterRoleBinding psp-util.eks.privileged
└── 📗 Subject{Kind: Group, Name: eks, Namespace: }
└── 📗 Subject{Kind: Group, Name: system:master, Namespace: }
└── 📗 Subject{Kind: ServiceAccount, Name: aws-node, Namespace: kube-system}
└── 📗 Subject{Kind: ServiceAccount, Name: kube-proxy, Namespace: kube-system}
└── 📗 Subject{Kind: ServiceAccount, Name: alb-ingress-controller, Namespace: kube-system}
└── 📗 Subject{Kind: ServiceAccount, Name: coredns, Namespace: kube-system}
📙 PSP restricted
└── 📕 ClusterRole psp-util.restricted
└── 📘 ClusterRoleBinding psp-util.restricted
└── 📗 Subject{Kind: Group, Name: system:authenticated, Namespace: }
└── 📗 Subject{Kind: Group, Name: system:unauthenticated, Namespace: }
設定は以上となります。
特権PSPと制限付きPSPの中間のPSPが欲しい場合は、適宜専用のPSPを作成し、特権PSPを適用するのと同じ手順で適用するといいと思います。
ただし特権PSPが適用されている対象に別のPSPを適用しても特権PSPが優先されてしまうため、特権PSPと合わせて使用することがないように注意する必要があります。
最後に
PSPはマルチテナントのクラスターにおいて有効かつ強力なポリシー制御を可能にする一方で、設定や適用範囲の理解が難しいです。どなたかのご参考になれば幸いです。