はじめに
この記事では、Kubernetesで利用できるセキュリティの設定(Pod Security Context, Security Context)、セキュリティポリシー (Pod Security Policy) に関する説明と検証結果をまとめています。
Kubernetesにおけるセキュリティの設定 / ポリシーについて
セキュリティ設定 / ポリシーの全体像
Kubernetesで適用可能なセキュリティの設定 / ポリシーとして、下記の3種類の機能が提供されています。PodSecurityPolicyに関してはv1.17時点で beta 機能として提供されています。
PodSecurityPolicyはポリシーを定義したオブジェクト(podSecurityPolicy
)を作成する必要があります。
Pod Security ContextとSecurity Contextに関しては、podのマニュフェストファイルの中でsecurityContext
を指定して設定します。
(指定する名前は同じですがマニュフェストファイルの中で指定する箇所が異なるので、注意が必要です。)
- Pod Security Policy:クラスタ単位のセキュリティポリシー
-
Pod Security Context:pod単位のセキュリティの設定
-
pod.spec.securityContext
でを定義 - https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod (example)
-
-
Security Context:コンテナ単位のセキュリティの設定
-
pod.spec.containers.securityContext
で定義 - https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container (example)
-
Admission Controlについて
PodSecurityPolicyの利用においては、Admission Controllが利用できることが前提となります。
(Kubernetesのドキュメントでは、Pod security policy control is implemented as an optional (but recommended) admission controller.と説明されています。)
Admission Controllとは、リクエストの内容をチェックした上で制御する機能です。
任意のプラグインを有効化することで、特定の条件に該当するリクエストに対する処理 (リクエストの拒否・リクエストの変更 etc.) を有効化します。
podSecurityPolicyを利用する場合は、クラスタ作成時にAdmission ControllerのプラグインとしてpodSecurityPolicy
を指定した上で有効化します。
Role / Rolebindingについて
KubernetesのRBACの機能を利用してpodSecurityPolicyをユーザに紐づけることも可能です。
KubernetesのDocsで提供されている例では、SA(ServiceAccount)を作成した上でそのユーザーにRoleを紐づけています。
下記の例ではSAに対してedit
の権限のみを付与しています。
$ kubectl create namespace psp-example
$ kubectl create serviceaccount -n psp-example fake-user
$ kubectl create rolebinding -n psp-example fake-editor \
--clusterrole=edit \
--serviceaccount=psp-example:fake-user
ユーザーのリクエストにポリシーを適用する際には、指定したリソース(ここでは作成したpodSecurityPolicyに)のuse
権限をユーザーに付与する必要があります。
(use
権限が付与されていないユーザーは、podを作成することができません。)
$ kubectl create role psp:unprivileged \
--verb=use \
--resource=podsecuritypolicy \
--resource-name=example \
--as=system:serviceaccount:psp-example:fake-user \
-n psp-example
$ kubectl create rolebinding fake-user:psp:unprivileged \
--role=psp:unprivileged \
--serviceaccount=psp-example:fake-user
use
権限を付与したRoleをSAに紐付けた後は「ユーザーがポリシーを利用できるかどうか」を下記のコマンドで確認できます。
$ kubectl auth can-i use podsecuritypolicy/example \
--as=system:serviceaccount:psp-example:fake-user \
-n psp-example
各マネージドサービスの対応状況
各マネージドサービスでは、基本的に上記のいずれのポリシーも利用可能となっています。
podSecurityPolicyに関しては、デフォルトでポリシーが定義されていたり、kubectl以外のCLI経由で有効/無効を設定できるサービスもあるため、各サービスの仕様を確認する必要があります。
(確認できていないサービスもあるので、必要に応じてこの部分は追記していきます)
- pod Security Policy
- Amazon EKS:デフォルトでpodSecurityPolicyが作成済
- Google Kubernetes Engine:CLIを利用してpodSecurityPolicyのプラグインを有効化 / 無効化することが可能
各機能の検証
ここからは、各ポリシーの検証を行っていきます。
バージョン情報は下記の通りです。今回はIBM Cloud Kubernetes Serviceの無料クラスタ (Master: 1、Worker: 1) を利用して検証を行います。
(EKS・GKEなど、その他のマネージドサービスを利用して検証することも可能です。)
$ k version
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.2", GitCommit:"59603c6e503c87169aea6106f57b9f242f64df89", GitTreeState:"clean", BuildDate:"2020-01-18T23:30:10Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.8+IKS", GitCommit:"0a5a9d717cf2cedaf9b7ab8ce758fd3ab2638d90", GitTreeState:"clean", BuildDate:"2020-01-16T19:49:53Z", GoVersion:"go1.12.12", Compiler:"gc", Platform:"linux/amd64"}
Pod Security Policy検証
事前準備として、Namespace, SA, Rolebindingを作成します。
- Namespaceに紐付けたSA(fake-user)を作成する
- SAに対して
edit
の権限を有するClusterRoleを紐づけるkubectl create rolebinding -n psp-example fake-editor --clusterrole=edit --serviceaccount=psp-example:fake-user
続いて、PodSecurityPolicyを作成します。
ここでは、privileged
の部分をfalse
にすることで特権コンテナの作成を許可しないというポリシーを定義します。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: example
spec:
privileged: false # Don't allow privileged pods!
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
runAsUser:
rule: RunAsAny
fsGroup:
rule: RunAsAny
volumes:
- '*'
また、クラスタ作成時に作成されているPodSecurityPolicy(ibm-privileged-psp
)では、PRIV=true
となっているので、検証のために削除しておきます。
PodSecurityPolicyに関しては、複数のポリシーの重ねがけの場合に明示的なAllowが優先されるため、このようなデフォルトのポリシーが存在する場合は注意が必要です。
# デフォルトで作成されている複数のPodSecurityPolicyが作成されている
# PodSecurityPolicy(ibm-privileged-psp)では、PRIV=trueとなっている
$ k get psp
NAME PRIV CAPS SELINUX RUNASUSER FSGROUP SUPGROUP READONLYROOTFS VOLUMES
example false RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-anyuid-hostaccess-psp false SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-anyuid-hostpath-psp false SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-anyuid-psp false SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP RunAsAny RunAsAny RunAsAny RunAsAny false configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
ibm-privileged-psp true * RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-restricted-psp false RunAsAny MustRunAsNonRoot MustRunAs MustRunAs false configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
# 検証のためにibm-privileged-pspを削除する
$ k delete psp ibm-privileged-psp
podsecuritypolicy.extensions "ibm-privileged-psp" deleted
$ k get psp
NAME PRIV CAPS SELINUX RUNASUSER FSGROUP SUPGROUP READONLYROOTFS VOLUMES
example false RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-anyuid-hostaccess-psp false SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-anyuid-hostpath-psp false SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-anyuid-psp false SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP RunAsAny RunAsAny RunAsAny RunAsAny false configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
ibm-restricted-psp false RunAsAny MustRunAsNonRoot MustRunAs MustRunAs false configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
この状態では、ユーザー(fake-user
)に対してedit
の権限のみを付与しているため、特権コンテナ以外のコンテナも作成することができない状態になります。
# Adminユーザーはuse権限を有しているのでコンテナは作成できる
$ kubectl apply -n psp-example -f- <<EOF
> apiVersion: v1
> kind: Pod
> metadata:
> name: privileged
> spec:
> containers:
> - name: pause
> image: k8s.gcr.io/pause
> securityContext:
> privileged: true
> EOF
pod/pause created
# edit権限しか有していないユーザーではコンテナを作成できない
$ kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example create -f- <<EOF
> apiVersion: v1
> kind: Pod
> metadata:
> name: privileged
> spec:
> containers:
> - name: pause
> image: k8s.gcr.io/pause
> securityContext:
> privileged: true
> EOF
Error from server (Forbidden): error when creating "STDIN": pods "privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]
次に、use
の権限を有したロールをユーザー(fake-user
)に紐づけます。
$ kubectl -n psp-example create role psp:unprivileged \
> --verb=use \
> --resource=podsecuritypolicy \
> --resource-name=example
role.rbac.authorization.k8s.io/psp:unprivileged created
$ kubectl -n psp-example create rolebinding fake-user:psp:unprivileged \
> --role=psp:unprivileged \
> --serviceaccount=psp-example:fake-user
rolebinding.rbac.authorization.k8s.io/fake-user:psp:unprivileged created
use
の権限を持つユーザーでprivileged: true
の設定をしたpodを作成しようとすると、作成時にエラーとなります。
# リクエストの検証はされているが、ポリシーに準拠しているので拒否はされない
$ kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example create -f- <<EOF
> apiVersion: v1
> kind: Pod
> metadata:
> name: pause
> spec:
> containers:
> - name: pause
> image: k8s.gcr.io/pause
> EOF
pod/pause created
# 特権コンテナを作成しようとしているのでリクエストが拒否される
$ kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example create -f- <<EOF
> apiVersion: v1
> kind: Pod
> metadata:
> name: privileged
> spec:
> containers:
> - name: pause
> image: k8s.gcr.io/pause
> securityContext:
> privileged: true
> EOF
Error from server (Forbidden): error when creating "STDIN": pods "privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]
Pod Security Context検証
RunAsUser / fsGroup
下記の2つの設定を用いて、Pod Security Contextを検証します。
fsGroup
と似た設定項目としてrunAsGroup
もありますが、runAsGroup
はプロセスに対してGIDを指定するという設定なのでGIDを設定する対象が異なります。
-
RunAsUser
:UIDを指定して特定のユーザーでコンテナ内のプロセスを実行する -
fsGroup
:GIDを指定してコンテナのボリュームやボリューム内で作成されたファイルにGIDをセットする
今回は下記のマニュフェストファイルを利用します。
pod起動時に、sleep 1h
のコマンドを実行することで、プロセスのUIDを確認します。
また、Pod用の一時的なディスク領域として/data/demo
のパスでemptyDir
を作成して、作成されたボリュームのGIDを確認します。
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: sec-ctx-vol
mountPath: /data/demo
podを作成して、コンテナのシェルを取得してプロセスのUIDを確認します。
全てのプロセスに対して、UID=1000
がセットされていることがわかります。
$ kubectl apply -f https://k8s.io/examples/pods/security/security-context.yaml
pod/security-context-demo created
$ kubectl exec -it security-context-demo -- sh
/ $ ps aux
PID USER TIME COMMAND
1 1000 0:00 sleep 1h
7 1000 0:00 sh
14 1000 0:00 ps aux
続いて、作成されたボリュームのGIDを確認します。
作成したdata/demo
に関して、所有グループの箇所にGID=2000
がセットされていることがわかります。(ボリュームの所有者はroot
のままです。)
/ $ ls -al
total 48
drwxr-xr-x 1 root root 4096 Feb 11 05:19 .
drwxr-xr-x 1 root root 4096 Feb 11 05:19 ..
drwxr-xr-x 2 root root 12288 Dec 23 19:21 bin
drwxr-xr-x 3 root root 4096 Feb 11 05:19 data
drwxr-xr-x 5 root root 360 Feb 11 05:19 dev
drwxr-xr-x 1 root root 4096 Feb 11 05:19 etc
drwxr-xr-x 2 nobody nogroup 4096 Dec 23 19:21 home
dr-xr-xr-x 218 root root 0 Feb 11 05:19 proc
drwx------ 2 root root 4096 Dec 23 19:21 root
dr-xr-xr-x 13 root root 0 Feb 9 06:13 sys
drwxrwxrwt 2 root root 4096 Dec 23 19:21 tmp
drwxr-xr-x 3 root root 4096 Dec 23 19:21 usr
drwxr-xr-x 1 root root 4096 Feb 11 05:19 var
/ $ ls -al /data
total 12
drwxr-xr-x 3 root root 4096 Feb 11 05:19 .
drwxr-xr-x 1 root root 4096 Feb 11 05:19 ..
drwxrwsrwx 2 root 2000 4096 Feb 11 05:18 demo
また、作成したボリュームでファイルを作成して確認すると、所有者としてUID=1000
、所有グループとしてGID=2000
がセットされていることがわかります。
/ $ cd data/demo
/data/demo $ echo hello > demofile
/data/demo $ ls -al
total 12
drwxrwsrwx 2 root 2000 4096 Feb 11 05:22 .
drwxr-xr-x 3 root root 4096 Feb 11 05:19 ..
-rw-r--r-- 1 1000 2000 6 Feb 11 05:22 demofile
Security Context検証
Linux Capabilities
今回はSecurity Contextとして設定できる項目の中でも、Linux Capabilitiesを設定します。
Linux Capabilitiesは、rootに紐づいた権限を分割・グループ化して必要な権限を付与する仕組みのことを指します。
参考: https://gihyo.jp/admin/serial/01/linux_containers/0042
マニュフェストファイルの中では、pod.spec.containers.securityContext
の部分でCapabilitiesを設定します。
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo-4
spec:
containers:
- name: sec-ctx-4
image: gcr.io/google-samples/node-hello:1.0
securityContext:
capabilities:
add: ["NET_ADMIN", "SYS_TIME"]
コンテナに紐づく設定としてLinux Capabilitiesを設定しているため、コンテナ作成後にコンテナのシェルを起動して確認します。
コンテナ内のプロセスが持つCapabilitiesはcat proc/1/status | grep Cap*
で確認することができます。
Kubernetesが提供しているExamples内のsecurity-context-3.yaml
では、Capabilitiesを設定していないため下記のようになります。
$ k apply -f https://k8s.io/examples/pods/security/security-context-3.yaml
pod/security-context-demo-3 created
$ k exec security-context-demo-3 -it bash
root@security-context-demo-3:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4332 764 ? Ss 09:51 0:00 /bin/sh -c node server.js
root 6 0.1 0.5 772120 22796 ? Sl 09:51 0:00 node server.js
root 11 0.0 0.0 20240 3220 pts/0 Ss 09:52 0:00 bash
root 16 0.0 0.0 17496 2120 pts/0 R+ 09:52 0:00 ps aux
root@security-context-demo-3:/# cat proc/1/status | grep Cap*
CapInh: 00000000a80425fb
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
CapBnd: 00000000a80425fb
CapAmb: 0000000000000000
root@security-context-demo-3:/# getpcaps 1
Capabilities for `1': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip
注意点として、IKSでデフォルトで作成されているPodSecurityPolicyの中で今回検証したいCapabilitiesが許可されていません。
Admin権限のユーザーで検証を行う際にはデフォルトのポリシーを削除することをお勧めします。
# CAPSの箇所でCapabilitiesが設定されている
$ k get psp
NAME PRIV CAPS SELINUX RUNASUSER FSGROUP SUPGROUP READONLYROOTFS VOLUMES
example false RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-anyuid-hostaccess-psp false SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-anyuid-hostpath-psp false SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP RunAsAny RunAsAny RunAsAny RunAsAny false *
ibm-anyuid-psp false SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP RunAsAny RunAsAny RunAsAny RunAsAny false configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
ibm-restricted-psp false RunAsAny MustRunAsNonRoot MustRunAs MustRunAs false configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
# PodSecurityPolicyの中でCapabilitiesが設定されていないのでpodの作成自体ができない
$ k apply -f security-context-pod4.yaml
Error from server (Forbidden): error when creating "security-context-pod4.yaml": pods "security-context-demo-4" is forbidden: unable to validate against any pod security policy:
[`spec.containers[0].securityContext.capabilities.add: Invalid value: "NET_ADMIN": capability may not be added
spec.containers[0].securityContext.capabilities.add: Invalid value: "SYS_TIME": capability may not be added
spec.containers[0].securityContext.capabilities.add: Invalid value: "NET_ADMIN": capability may not be added
spec.containers[0].securityContext.capabilities.add: Invalid value: "SYS_TIME": capability may not be added
spec.containers[0].securityContext.capabilities.add: Invalid value: "NET_ADMIN": capability may not be added
spec.containers[0].securityContext.capabilities.add: Invalid value: "SYS_TIME": capability may not be added
spec.containers[0].securityContext.capabilities.add: Invalid value: "NET_ADMIN": capability may not be added
spec.containers[0].securityContext.capabilities.add: Invali`d value: "SYS_TIME": capability may not be added]
PodSecurityPolicy削除後に、security-context-4.yaml
を利用してpodを作成した上で、先ほどと同様に、proc/1/status
を確認します。
ビットが変化していることがわかりますが、どのようなCapabilitiesが付与されているかは不明です。
確認する際には、getpcaps <PID>
コマンドを利用することでプロセスに付与されているCapabilitiesを確認できます。
これにより、プロセスにcap_net_admin
とcap_sys_time
がCapabilitiesとして付与されていることがわかります。
$ k apply -f https://k8s.io/examples/pods/security/security-context-4.yaml
pod/security-context-demo-4 created
$ k exec security-context-demo-4 -it bash
$ k get po
NAME READY STATUS RESTARTS AGE
security-context-demo-4 1/1 Running 0 9s
root@security-context-demo-4:/# cat /proc/1/status | grep Cap
CapInh: 00000000aa0435fb
CapPrm: 00000000aa0435fb
CapEff: 00000000aa0435fb
CapBnd: 00000000aa0435fb
CapAmb: 0000000000000000
root@security-context-demo-4:/# getpcaps 1
Capabilities for `1': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_admin,cap_net_raw,cap_sys_chroot,cap_sys_time,cap_mknod,cap_audit_write,cap_setfcap+eip
PodSecurityPolicyのマニュフェストファイル内でCapabilitiesの許可設定をすることも可能です。
allowedCapabilities
で指定したCapabilitiesを許可したり、RequiredDropCapabilities
で任意のCapabilitiesを拒否することも可能です。
https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: example
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.
allowedCapabilities:
- '*'
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
runAsUser:
rule: RunAsAny
fsGroup:
rule: RunAsAny
volumes:
- '*'
Pod Security Policyに関する注意点
Pod Security Policy (PSP) は、v1.17時点では beta 機能として提供されています。
ただし、2020年1月末時点で、Pod Security Policy(PSP)はGA化されない方向で進んでいます。
https://github.com/kubernetes/enhancements/issues/5
代替策として Gatekeeper が挙がっており、現在も議論が続いています。
特に、ポリシーの方式の違いを踏まえてどのように移行をスムーズに進めるかという部分が議論されています。
(許可設定を追加するPSPと違い、Gatekeeper は拒否設定を追加する方式のようです)