マルチテナントクラスターにおけるPod Security Policyの個人的ベストプラクティス

# 2021/04/07追記

Pod Security PolicyはKubernetes v1.25で廃止されます。

PSPに代わる新たな機能が検討されており、今後は複雑な制御はOpen Policy Agentのような外部オープンソースツールが推奨され、よりシンプルで明確な機能としてPSP Replacement Policy(仮)がKubernetesには実装される予定です。

詳細は以下のブログをご確認ください。

https://kubernetes.io/blog/2021/04/06/podsecuritypolicy-deprecation-past-present-and-future/

# Pod Security Policy（ポッドセキュリティポリシー）
[Pod Security Policy（ポッドセキュリティポリシー、PSP）](https://kubernetes.io/docs/concepts/policy/pod-security-policy/)とは、Kubernetesクラスタ全体のPodのセキュリティポリシーを定義する機能で、Podのセキュリティレベルによって稼働可否を制御できる機能です。

Pod Security Policy自体の詳細については他に譲ります。
- [公式ドキュメント](https://kubernetes.io/docs/concepts/policy/pod-security-policy/)
- [Kubernetes: Pod Security Policy によるセキュリティの設定](https://qiita.com/tkusumi/items/6692af743ae03dc0fdcc)

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](https://www.openpolicyagent.org/)等を使用することで、DeploymentのSpecに特権を必要とするPodSpecを記載できないようにすることも実現できるかと思います。
しかし、これらはあくまでもSpecに対する静的な解析に留まりますが、Pod Security Policyはkubeletと連携し、コンテナ実行時にrootユーザーでの実行をブロックするなどの強力なセキュリティ機能を持っており、これはPSP以外で実現するのは困難かと思います。

# PSPの優先順位
複数のPSPが有効な場合に使用されるポリシーの優先度については、少しクセがあり注意が必要です。

[公式ドキュメント](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#policy-order)には以下の記載があります。

>1. 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の優先度は重要ではない。

>2. 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

https://kubernetes.io/ja/docs/concepts/security/pod-security-standards/

# 個人的ベストプラクティス

上記を踏まえて以下の考え方をベースとするのがいいかと思っています。

- まずは特権を許可するPSPと制限付きPSPの２つを作成する。
- クラスターの全範囲に制限付き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](https://github.com/jlandowner/psp-util)というKubectl pluginを公開しています（宣伝）
https://github.com/jlandowner/psp-util

これによりクラスター内のポリシーの適用状況を確認したり、容易にポリシーをアタッチ・デタッチすることができます。

`psp-util`プラグインは、Kubectlの公式プラグインマネージャーである[Krew](https://github.com/kubernetes-sigs/krew)で公開しています。Krew自体のインストールは[こちら](https://krew.sigs.k8s.io/docs/user-guide/setup/install/)を参照してください。

Krewをインストールした後は、以下のコマンドで`psp-util`をインストールできます。

```shell
kubectl krew install psp-util
```

## 現状確認

自分が普段触る環境はEKSが多いので、EKSを例に進めていきます。

現在のPSPの一覧を確認します。

```shell
$ 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に適用されていることが分かります。

```shell
$ 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を作成してください。


```yaml:psp-privileged.yaml
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が存在しない場合、上記の内容の設定ファイルを作成し、適用します。

```shell
kubectl apply -f psp-privileged.yaml
```

### 制限付きPSPの作成
制限付きPSPについては、AWSが公開している[EKS Best Practices内のrecommendations](https://aws.github.io/aws-eks-best-practices/security/docs/pods/#recommendations)に記載のPSPをまずは適用するのが良いかと思います。

```yaml:psp-restricted.yaml
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
```

上記の内容の設定ファイルを作成し、適用します。

```shell
kubectl apply -f psp-restricted.yaml
```

もしすでにアプリケーションがクラスター内で稼働中であり、現在動いているアプリで許されている範囲で制限ポリシーを設定したい場合は、sysdigが公開している[kube-psp-advisor](https://github.com/sysdiglabs/kube-psp-advisor)を使用すると、適切なPSPを自動作成してくれます。

こちらもKrewで公開されていますので以下のコマンドでインストールできます。

```shell
kubectl krew install advise-psp
```

現在アプリケーションが動いているNamespaceを指定して`inspect`コマンドを実行することでPSPを自動生成してくれます。
以下のコマンドはinspect結果を元にPSPの作成を行います。

```shell
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を削除します。

```shell
kubectl delete ClusterRoleBinding eks:podsecuritypolicy:authenticated
```

### 制限付きPSPの適用
制限付きPSPを全クラスターに適用するために、`system:authenticated`Groupおよび`system:unauthenticated`Groupに適用します。

今回は前述の制限付きPSPの内容で`restricted`という名前のPSPを作成している前提で、
以下の2つのコマンドを実行します。

```shell
kubectl psp-util attach restricted --group system:authenticated
kubectl psp-util attach restricted --group system:unauthenticated
```

設定内容を確認します。これにより自動でClusterRoleとClusterRoleBindingが作成されています。

```shell
$ 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をアタッチするには以下のように設定します。

```shell
kubectl psp-util attach eks.privileged --group system:master
```

EKSはeksというGroupもあるようなので、一応アタッチしておきます。

```shell
kubectl psp-util attach eks.privileged --group eks
```

続いて、特権Namespace内のServiceAccountをアタッチします。
Namespaceの全てのServiceAccountは`system:serviceaccounts:<Namespace名>`というGroupに所属するため、このグループを特権PSPにアタッチすれば良いです。

例えば`default` Namespaceには以下のコマンドでアタッチできます。

```shell
kubectl psp-util attach eks.privileged --group system:serviceaccounts:default 
```

## kube-systemへのPSPの適用

ただし`kube-system`への特権PSPの適用は注意が必要です。
`kube-system`には`replicaset-controller`などのcontrollerが使用するServiceAccountが存在しています。

```sh
$ 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が展開されていないので、以下のコマンドで確認します。

```sh
# 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
```

ワンライナーでアタッチします。

```sh
# 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
```

ここまでの設定結果を確認すると以下の通りです。

```shell
$ 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はマルチテナントのクラスターにおいて有効かつ強力なポリシー制御を可能にする一方で、設定や適用範囲の理解が難しいです。どなたかのご参考になれば幸いです。