Kubernetes: Pod Security Policy によるセキュリティの設定

この記事は [Pod Security Policy](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) (`PodSecurityPolicy`)によるセキュリティの設定について Kubernetes v1.9 で確認した内容になります。v1.9 未満では RBAC 周りで大きな違いがあるのでご注意ください。

## PodSecurityPolicy とは

[PodSecurityPolicy](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) とはクラスタ全体のセキュリティ上のポリシーを定義する機能です。ホストに影響を与える可能性がある 特権 (privileged) や HostIPC などの機能を制限し Pod に脆弱性があった場合にクラスタを守ることができます。ポリシーの制限は [Admission Control として実装されており](https://kubernetes.io/docs/admin/admission-controllers/#podsecuritypolicy)、ポリシーを満たしていない Pod の実行を拒否することができるようになります。[Design Proposal](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/pod-security-policy.md) を見るとマルチテナントでの利用を想定した機能のようです。v1.9 時点では beta の機能になります。

ポリシーは RBAC (Role-Based Access Control) を使ってユーザごとに分けることができます。これにより柔軟なポリシーの設定が可能です。

なお PodSecurityPolicy も含め、Kubernetes の Pod に関するセキュリティに関連する設定には以下の 3 つがあります。(名前が似通っていて若干わかりづらいですね)

- [PodSecurityContext v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#podsecuritycontext-v1-core) **Pod 単位** のセキュリティ定義
  - `pod.spec.securityContext` で Pod 単位で設定
- [SecurityContext v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#securitycontext-v1-core) **コンテナ単位** のセキュリティ定義
  - `pod.spec.containers.securityContext` でコンテナ単位に設定
- [PodSecurityPolicy v1beta1 extensions](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#podsecuritypolicy-v1beta1-extensions) **クラスタ単位のセキュリティポリシー**の定義
  - `podsecuritypolicy` というオブジェクトでクラスタ管理者がクラスタ全体のポリシーとして定義する

PodSecurityPolicy は他の 2 つと比べてクラスタ全体のポリシーである点が大きく異なっています。

## PodSecurityPolicy で設定できるポリシー

PodSecurityPolicy は下記のようなセキュリティのポリシーを定義することができます。どれもがホストに影響を与える可能性のある設定のポリシーになります。ポリシーは複数の定義が可能で RBAC でユーザごとにポリシーを分けることができます。

フィールド名 | 型 | 内容
--- | --- | ---
allowPrivilegeEscalation | bool | Pod に allowPrivilegeEscalation の設定を許可するかどうか
allowedCapabilities  | string array | Pod に設定を許可する Linux Capabilities のリスト
allowedHostPaths | [AllowedHostPath](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#allowedhostpath-v1beta1-extensions) array | 利用を許可する hostPath のホワイトリスト
defaultAddCapabilities | string array | デフォルトで追加する Linux Capabilities のリスト
defaultAllowPrivilegeEscalation | boolean | デフォルトで allowPrivilegeEscalation を設定するかどうか
fsGroup | [FSGroupStrategyOptions](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#fsgroupstrategyoptions-v1beta1-extensions) | Pod の fsGroup での設定を制限するかどうか。MustRunAs, RunAsAny が設定でき、MustRunAs の場合は GID の範囲を指定する。
hostIPC | boolean | Pod に HostIPC の利用を許可するかどうか
hostNetwork | boolean | Pod に HostNetwork の利用を許可するかどうか
hostPID | boolean | Pod に HostPID の利用を許可するかどうか
hostPorts | [HostPortRange](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#hostportrange-v1beta1-extensions) array | Pod が hostPort として利用可能なポートのレンジを設定する
privileged | boolean | Pod に privileged の設定を許可するかどうか
readOnlyRootFilesystem | boolean | true を設定した場合、readOnlyRootFilesystem が true に設定されていないものを拒否する
requiredDropCapabilities | string array | Pod に設定を必須とする drop する Linux Capabilities のリスト
runAsUser | [RunAsUserStrategyOptions](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#runasuserstrategyoptions-v1beta1-extensions) | Pod の runAsUser での設定を制限するかどうか。MustRunAs, MustRunAsNonRoot, RunAsAny が設定でき、MustRunAs の場合は UID の範囲を指定する。
seLinux | [SELinuxStrategyOptions](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#selinuxstrategyoptions-v1beta1-extensions) | Pod の seLinux での設定を制限するかどうか。MustRunAs, , RunAsAny が設定でき、MustRunAs の場合は seLinux のラベルを設定する。
supplementalGroups | [SupplementalGroupsStrategyOptions](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#supplementalgroupsstrategyoptions-v1beta1-extensions) | Pod の SupplementalGroup での設定を制限するかどうか。MustRunAs, RunAsAny が設定でき、MustRunAs の場合は GID の範囲を指定する。
volumes | string array | ボリュームとして利用を許可する volume plugins のホワイトリスト

## PodSecurityPolicy を利用する

PodSecurityPolicy を利用するには下記の設定が必要になります。

1. Admisson Control で [`PodSecurityPolicy`](https://kubernetes.io/docs/admin/admission-controllers/#podsecuritypolicy) を有効にする
2. PodSecurityPolicy を定義する
3. RBAC で PodSecurityPolicy をユーザに紐付ける

## Admisson Control で PodSecurityPolicy を有効にする

API サーバーの `--admission-control` 引数に `PodSecurityPolicy` を追加することで、PodSecurityPolicy の Admission Control を有効にすることができます。

:warning: Admission Control を設定しない場合は PodSecurityPolicy を定義してもポリシーは有効になりません。また Admisson Control で PodSecurityPolicy を有効にしポリシーをなにも設定しない状態では全ての Pod が作成できない状態になっています。

```bash
# Admisson Control で PodSecurityPolicy を有効にし、何も設定していない状態
$ kubectl run nginx --image nginx:1.13 --restart=Never
Error from server (Forbidden): pods "nginx" is forbidden: no providers available to validate pod request
```

### minikube での Admissin Control の設定

minikube で利用するには `--extra-config` を利用して、API サーバーの`--admission-control` 引数を設定します。この記事の記載時点では minikube で v1.9.0 の localkube のイメージが出ていないため、下記のように kubeadm bootstrapper を使って v1.9.0 の環境を作りました。なお、kubeadm を使った場合とデフォルトの localkube では[`--extra-config` の指定の仕方が違う](https://github.com/kubernetes/minikube/blob/master/docs/configuring_kubernetes.md)のでご注意ください。

```bash
# extra-config を使って API サーバーの引数を設定
$ minikube start --kubernetes-version v1.9.0 --bootstrapper kubeadm \
  --extra-config=apiserver.admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ValidatingAdmissionWebhook,ResourceQuota,DefaultTolerationSeconds,MutatingAdmissionWebhook,PodSecurityPolicy
```

## PodSecurityPolicy を定義する

PodSecurityPolicy は Pod や Deployment と同じく API オブジェクトとして定義をします。ここでは制限のないポリシーと、制限をしたポリシーの 2 つを定義してみます。

### 制限の無いポリシー

PodSecurityPolicy を利用しない状態と同等の制限の無いポリシーを定義してみます。[以下の設定は公式ドキュメントの例](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#example-policies) に記載されているものになります。

```yaml:privileged.yaml
# 制限がないポリシー
# https://kubernetes.io/docs/concepts/policy/pod-security-policy/#example-policies より
apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: privileged
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
spec:
  privileged: true
  allowPrivilegeEscalation: true
  allowedCapabilities:
  - '*'
  volumes:
  - '*'
  hostNetwork: true
  hostPorts:
  - min: 0
    max: 65535
  hostIPC: true
  hostPID: true
  runAsUser:
    rule: 'RunAsAny'
  seLinux:
    rule: 'RunAsAny'
  supplementalGroups:
    rule: 'RunAsAny'
  fsGroup:
    rule: 'RunAsAny'
```

### 制限したポリシー

次に制限したポリシーを定義してみます。この例では特権 (privileged) やホストのネットワーク、IPC, PID、コンテナの root 動作などを制限しています。

```yaml:restricted.yaml
# 制限したポリシー
apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: restricted
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
spec:
  privileged: false  # privileged は利用させない
  allowPrivilegeEscalation: false # 特権昇格は利用させない
  allowedCapabilities:
  - '*'
  volumes:
  - '*'
  hostNetwork: false # ホストのネットワークは利用させない
  hostPorts:
  - min: 0
    max: 65535
  hostIPC: true # ホスト IPC は利用させない
  hostPID: true # ホスト PID は利用させない
  runAsUser:
    rule: 'MustRunAsNonRoot' # コンテナは root で実行させない
  seLinux:
    rule: 'RunAsAny'
  supplementalGroups:
    rule: 'RunAsAny'
  fsGroup:
    rule: 'RunAsAny'
```

## RBAC で PodSecurityPolicy をユーザに紐付ける

ここでは[公式ドキュメントの例を元に](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) RBAC でユーザに紐付けをしてみます。まずは紐付けるユーザとして新規の ServiceAccount を用意します。 kubectl の操作には cluster-admin 権限を持つ minikube のデフォルトユーザを利用します。なお cluster-admin 権限はすべての PodSecurityPolicy に対して権限を持っているので、全てのポリシーを利用可能です(いずれかのポリシーで許可されていれば許可される)。

```bash
# default ネームスペースに psp-user という ServiceAccount を作成
$ kubectl create serviceaccount psp-user
serviceaccount "psp-user" created

# psp-user にクラスタ全体の edit 権限を与える
$ kubectl create clusterrolebinding psp-user --clusterrole edit --serviceaccount default:psp-user
clusterrolebinding "user-edit" created
```

下記の 2 つにユーザを使って RBAC を使った PodSecurityPolicy の紐付けを確認してきます。

ユーザ | クラスタ権限 | PodSecurityPolicy
--- | --- | ---
minikube-user (minikube のデフォルトユーザ)| cluster-admin | cluster-admin なので全てのポリシーが利用できる
`default:psp-user` (ServiceAccount) | edit | 制限したポリシーを紐付ける

公式の例を参考にわかりやすい用に以下のエイリアスを設定しておきます。

```bash
$ alias kubectl-admin='kubectl'
$ alias kubectl-user='kubectl --as=system:serviceaccount:default:psp-user'
```

### PodSecurityPolicy の適用

まずは定義した PodSecurityPolicy を適用してみます。

```bash
# PodSecurityPolicy 定義前は Pod が作成できない
$ kubectl-admin run nginx --image nginx:1.13 --restart=Never
Error from server (Forbidden): pods "nginx" is forbidden: no providers available to validate pod request

# 制限のない PodSecurityPolicy を設定
$ kubectl-admin apply -f privileged-psp.yaml
podsecuritypolicy "privileged" created

$ kubectl-admin get psp
NAME         DATA      CAPS      SELINUX    RUNASUSER   FSGROUP    SUPGROUP   READONLYROOTFS   VOLUMES
privileged   true      [*]       RunAsAny   RunAsAny    RunAsAny   RunAsAny   false            [*]
```

cluster-admin 権限を持つユーザは全ての PodSecurityPolicy に対して権限を持つため、制限のない PodSecurityPolicy を設定した時点で Pod を作成できるようになります。一方 edit 権限では PodSecurityPolicy への権限を持っていないため、まだ Pod を作成することはできません。

```bash
# cluster-admin 権限では Pod が作れるようになった
$ kubectl-admin run nginx --image nginx:1.13 --restart=Never
pod "nginx" created

# edit 権限では Pod は作れない
$ kubectl-user run nginx2 --image nginx:1.13 --restart=Never
Error from server (Forbidden): pods "nginx2" is forbidden: unable to validate against any pod security policy: []
```

次に制限を入れた PodSecurityPolicy を設定します。

```bash
$ kubectl-admin apply -f restricted-psp.yaml
podsecuritypolicy "restricted" created

$ kubectl-admin get psp
NAME         DATA      CAPS      SELINUX    RUNASUSER          FSGROUP    SUPGROUP   READONLYROOTFS   VOLUMES
privileged   true      [*]       RunAsAny   RunAsAny           RunAsAny   RunAsAny   false            [*]
restricted   false     [*]       RunAsAny   MustRunAsNonRoot   RunAsAny   RunAsAny   false            [*]
```

制限されたポリシー (`restricted`) ではコンテナの root 実行が禁止されているため、root 権限で実行する `nginx:1.13` は利用できません。しかし、PodSecurityPolicy では許可されるポリシーが優先されるため、cluster-admin 権限を持つユーザは制限の無いポリシー (`privileged`)を使って引き続き Pod を作成することができます。

```bash
# 引き続き Pod を作成できる
$ kubectl-admin run nginx3 --image nginx:1.13 --restart=Never
pod "nginx3" created

# privileged, restricted 両方の権限を持っている
$ kubectl-admin auth can-i use podsecuritypolicy/privileged
yes
$ kubectl-admin auth can-i use podsecuritypolicy/restricted
yes
```


### RBAC による紐付け

次に制限されたポリシーを ServiceAccount `default:psp-user` に紐付けます。PodSecurityPolicy の紐付けには `use` という verb を利用します。

```bash
# clusterrole の定義。制限されたポリシーの利用 (use) を許可している
$ kubectl-admin create clusterrole psp-restricted \
    --verb=use \
    --resource=podsecuritypolicy \
    --resource-name=restricted
clusterrole "psp-restricted" created

# clusterrolebinding でロールを紐付ける
$ kubectl-admin create clusterrolebinding psp-restricted \
    --clusterrole=psp-restricted \
    --serviceaccount=default:psp-user
clusterrolebinding "psp-restricted" created

# psp-user で権限を確認する
$ kubectl-user auth can-i use podsecuritypolicy/restricted
yes
```

制限されたポリシーを紐付けた ServiceAccount を使って nginx をデプロイします。nginx はコンテナが root 権限で実行されるためroot の実行を制限されたポリシーによって実行が拒否されます。

```bash
# pod 自体は作成できる
$ kubectl-user run nginx4 --image nginx:1.13 --restart=Never
pod "nginx4" created

# しかし実行に失敗する
$ kubectl-user get pods nginx4
NAME      READY     STATUS                       RESTARTS   AGE
nginx4    0/1       CreateContainerConfigError   0          52s

# 失敗の理由を見てみる
$ kubectl-user describe pods nginx4
# 中略
Events:
  Type     Reason                 Age              From               Message
  ----     ------                 ----             ----               -------
  Normal   Scheduled              1m               default-scheduler  Successfully assigned nginx4 to minikube
  Normal   SuccessfulMountVolume  1m               kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-mshgx"
  Normal   Pulled                 3s (x8 over 1m)  kubelet, minikube  Container image "nginx:1.13" already present on machine
  Warning  Failed                 3s (x8 over 1m)  kubelet, minikube  Error: container has runAsNonRoot and image will run as root
                                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

次にポリシーを満たす root で動作しないコンテナをデプロイしてみます。ここでは [tomaskral/nonroot-nginx](https://hub.docker.com/r/tomaskral/nonroot-nginx/) という非 root で動作する nginx コンテナを利用しました。

```bash
$ kubectl-user run nonroot-nginx --image tomaskral/nonroot-nginx --restart=Never
pod "nonroot-nginx" created

$ kubectl-user get pods nonroot-nginx
NAME            READY     STATUS    RESTARTS   AGE
nonroot-nginx   1/1       Running   0          1m
#                         ^^^^^^^ 動作した
```

ポリシーを満たしたコンテナがデプロイできることが確認できました :tada:

# まとめ

PodSecurityPolicy と RBAC を組み合わせることでユーザごとに異なるセキュリティポリシーが設定できます。複数のポリシーを定義できるため、一部のユーザには特権を与えつつその他のユーザの権限は制限するといったセキュリティ上の設定が可能です。クラスタのマルチテナントを実現する上では必須な機能といえるでしょう。