Kubernetes での Pod のセキュリティ対策についてまとめる。
ここでは次の3点について書く。

いずれも securityContext / security policy による AttackSurface の制御方法について書いている。

securityContext

Pod の Manifest で securityContext を指定することで、その Pod の Attack Surface を制御できる。

詳しくはドキュメントを見れば良いので割愛するが、「privileged なコンテナを許容するか」「どのユーザーがコンテナでプロセスを動かすか」「root ファイルシステムを read only とするか」といった設定が可能。

securityContext を適用する

次のような Pod を作成する。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:alpine
  terminationGracePeriodSeconds: 0
  securityContext:
    runAsNonRoot: true # root での動作を禁止
$ kubectl apply -f nginx.yaml
pod/nginx/created
$ kubectl get pods
NAME    READY   STATUS                       RESTARTS   AGE
nginx   0/1     CreateContainerConfigError   0          3s
$ kubectl describe pods nginx
...
Events:
  Type     Reason     Age               From                 Message
  ----     ------     ----              ----                 -------
  Normal   Scheduled  12s               default-scheduler    Successfully assigned default/nginx to k8s-node-1
  Normal   Pulled     9s (x3 over 11s)  kubelet, k8s-node-1  Container image "nginx:alpine" already present on machine
  Warning  Failed     9s (x3 over 11s)  kubelet, k8s-node-1  Error: container has runAsNonRoot and image will run as root

nginx:alpine は root で動作するため、ポリシー違反で Pod の作成に失敗する。

SecurityContextDeny

SecurityContextDeny をとは Admission Controller Plugin の1つで、これを設定することで Pod の securityContext を設定できなくなる。
ただし、securityContext の中でも runAsUser や FsGroup などのみに対して有効である。
詳しくは https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/securitycontext/scdeny/admission.go を参照。

SecurityContextDeny を有効化する

デフォルトで有効になっていない場合もあるので有効化されているか確認する。
有効化されているかの確認は kubeapi-server の引数に enable-admission-plugins=SecurityContextDeny が含まれているかを確認する。

$ cat /etc/kubernetes/manifests/kube-apiserver.yaml
...
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=172.16.20.100
    ...
    - --enable-admission-plugins=NodeRestriction,SecurityContextDeny # SecurityContextDeny を追加
    ...

securityContext が設定できないことを確認する

SecurityContextDeny が有効でない場合、 runAsUser: 0 は適用され、root なプロセスを作ることができる。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginxinc/nginx-unprivileged
    securityContext:
      runAsUser: 0
$ kubectl apply -f nginx.yaml
pod/nginx created
$ kubectl exec -it nginx sh
# id
uid=0(root) gid=0(root) groups=0(root)

次に SecurityContextDeny を適用した状態で試す。

$ kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": pods "nginx" is forbidden: SecurityContext.RunAsUser is forbidden

このように Pod が作成できないことが確認できる。

PodSecurityPolicy

PodSecurityPolicy とはクラスタから Pod の Attack Surface を制御するリソースである。
securityContext では Pod で制御したが、こちらはクラスタ側から制御する方法。

PodSecurityPolicy を有効にする

デフォルトで有効になっていない場合もあるので有効化されているか確認する。
有効化されているかの確認は kubeapi-server の引数に enable-admission-plugins=PodSecurityPolicy が含まれているかを確認する。
これにより Admission Controller で PodSecurityPolicy プラグインが有効化される。

$ cat /etc/kubernetes/manifests/kube-apiserver.yaml
...
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=172.16.20.100
    ...
    - --enable-admission-plugins=NodeRestriction,PodSecurityPolicy # PodSecurityPolicy を追加
    ...

PodSecurityPolicy を適用する

次のような PodSecurityPolicy を作る。

apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: restricted
  annotations:
    apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default'
    apparmor.security.beta.kubernetes.io/defaultProfileName:  'runtime/default'
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default'
    seccomp.security.alpha.kubernetes.io/defaultProfileName:  'docker/default'
spec:
  privileged: false
  allowPrivilegeEscalation: false
  allowedCapabilities: []
  volumes:
  - '*'
  hostNetwork: false
  hostPorts:
  - min: 0
    max: 65535
  hostIPC: true
  hostPID: true
  runAsUser:
    rule: 'MustRunAsNonRoot'
  seLinux:
    rule: 'RunAsAny'
  supplementalGroups:
    rule: 'RunAsAny'
  fsGroup:
    rule: 'RunAsAny'

runAsUserMustRunAsNonRoot となっているため root でプロセスを動かすことができない。
次にこの PodSecurityPolicy を ServiceAccount に紐づける。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: member
rules:
- apiGroups: ["policy"]
  resourceNames: ["restricted"]
  resources: ["podsecuritypolicies"]
  verbs: ["use"]

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: member
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: member
subjects:
- apiGroup: ""
  kind: ServiceAccount
  name: mrtc0
  namespace: "default"

この状態で pod を作成してみる。

$ kubectl run test --image nginx:latest --restart=Never
pod/test created
$ kubectl get pod
NAME   READY   STATUS                       RESTARTS   AGE
test   0/1     CreateContainerConfigError   0          18s
$ kubectl describe pod test
Events:
  Type     Reason     Age                From                 Message
  ----     ------     ----               ----                 -------
  Normal   Scheduled  34s                default-scheduler    Successfully assigned default/test to k8s-node-1
  Normal   Pulled     17s (x2 over 20s)  kubelet, k8s-node-1  Successfully pulled image "nginx:latest"
  Warning  Failed     17s (x2 over 20s)  kubelet, k8s-node-1  Error: container has runAsNonRoot and image will run as root

nginx:latest は root でプロセスが動くため、ポリシー違反で Pod 作成に失敗する。
では non root なプロセスで動かすようにしてみる。

$ kubectl run test --image nginxinc/nginx-unprivileged:latest --restart=Never
pod/test created
$ kubectl get pod
NAME   READY   STATUS    RESTARTS   AGE
test   1/1     Running   0          56s

今度は Pod が作成できた。