はじめに
PodSecurityPolicyはKubernetesクラスタに対するセキュリティ設定機能です。PodSecurityContextとSecurityContextと似ていますが、PodSecurityContextはPodに対して、SecurityContextはコンテナに対して設定しますので、制御する範囲が異なります。
また、PodSecurityPolicyを使用するにはAdmission Controllerで有効化する必要があります。
Using Admission Controllers
kube-apiserver
PodSecurityPolicyの設定
設定項目
PodSecurityPolicyで設定できる項目(抜粋)は以下になります。PodSecurityContext/SecurityContextと同じ項目も設定できます。
設定項目 | 概要 |
---|---|
privileged | 特権コンテナの実行可否 |
hostPID | コンテナがホストプロセスID Namespaceを共有できるかどうか |
hostIPC | コンテナがホストIPC Namespaceを共有できるかどうか |
hostNetwork | PodがノードネットワークNamespaceを使用できるかどうか |
hostPorts | ホストネットワークNamespace使用できるポートの範囲 |
volumes | 許可するボリュームタイプ |
fsGroup | 利用できるGIDの範囲 |
runAsUser | コンテナを実行するUID |
supplementalGroups | コンテナに追加するGID |
allowPrivilegeEscalation | SecurityContextの設定可否 |
allowedCapabilities | コンテナへの追加を許可するCapabilities |
seLinux | コンテナのSELinuxコンテキスト |
詳細およびその他の設定項目はこちらを参照
設定
PodSecurityPolicyはAdmissionControllerで有効化することで適用されますが、AdmissionControllerで先に有効化すると、Podが作成できなくなることがあります。そのため、PodSecurityPolicyを先に設定します。
Pod security policy control is implemented as an optional (but recommended) admission controller. PodSecurityPolicies are enforced by enabling the admission controller, but doing so without authorizing any policies will prevent any pods from being created in the cluster.
Since the pod security policy API (policy/v1beta1/podsecuritypolicy) is enabled independently of the admission controller, for existing clusters it is recommended that policies are added and authorized before enabling the admission controller.
以下のマニフェストのPodSecurityPolicyを設定します。
ここでは、privilegedのみを「false」に設定して、特権コンテナの実行を拒否します。その他の設定は全て許可します。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: privileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
spec:
privileged: false
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'
マニフェストをapplyして設定値を確認します。
$ kubectl apply -f podSecurityPolicy.yaml
podsecuritypolicy.policy/privileged created
$ kubectl get podsecuritypolicies.policy
NAME PRIV CAPS SELINUX RUNASUSER FSGROUP SUPGROUP READONLYROOTFS VOLUMES
controller false RunAsAny MustRunAs MustRunAs MustRunAs true configMap,secret,emptyDir
privileged false * RunAsAny RunAsAny RunAsAny RunAsAny false *
speaker true NET_ADMIN,NET_RAW,SYS_ADMIN RunAsAny RunAsAny RunAsAny RunAsAny true configMap,secret,emptyDir
ServiceAccountの作成
以下のServiceAccountを作成します。
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa
$ kubectl apply -f sa.yaml
serviceaccount/sa created
ClusterRole/CulusterRoleBindingsの作成
ClusterRole/CulusterRoleBindingsを作成して、PodSecurityPolicyとServiceAccountを紐付けます。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cr
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames:
- privileged
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: crb
roleRef:
kind: ClusterRole
name: cr
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: sa
namespace: default
$ kubectl apply -f crb.yaml
clusterrole.rbac.authorization.k8s.io/cr created
clusterrolebinding.rbac.authorization.k8s.io/crb created
動作確認
PodSecurityPolicyの設定は以上ですが、AdmissionControllerはまだ設定していませんので、この時点ではPodSecurityPolicyは動作しません。ここでは「動作しない」ことを確認します。
Podのデプロイ
特権コンテナがないPod(nginx-nomal-b)と、あるPod(nginx-privileged-b)をデプロイします。
apiVersion: v1
kind: Pod
metadata:
name: nginx-nomal-b
spec:
containers:
- name: nginx-nomal-b
image: nginx:latest
serviceAccountName: sa
automountServiceAccountToken: true
apiVersion: v1
kind: Pod
metadata:
name: nginx-privileged-b
spec:
containers:
- name: nginx-privileged-b
image: nginx:latest
securityContext:
privileged: true
serviceAccountName: sa
automountServiceAccountToken: true
$ kubectl apply -f nginx-nomal-b.yaml
pod/nginx-nomal-b created
$ kubectl apply -f nginx-privileged-b.yaml
pod/nginx-privileged-b created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-nomal-b 1/1 Running 0 77s
nginx-privileged-b 1/1 Running 0 12s
どちらも正常にデプロイできます。
AdmissionControllerの設定
今回の環境はkubeadmで構築したオンプレミスのクラスタ環境です。クラウドサービスのマネージドなKubernetes環境だと設定方法が異なるようです。
デフォルトプラグインの確認
Kubernetes1.10以降の場合、AdmissionControllerはデフォルトでいくつかのプラグインが有効になっています。有効になっているプラグインはこちらに記載されていますが、kube-apiserverにログインして確認してみます。
kube-apiserver名を確認します。
$ kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-77c4b7448-6prr9 1/1 Running 114 123d
calico-node-2hc9b 1/1 Running 115 123d
calico-node-cgdgk 1/1 Running 116 123d
calico-node-tkcz5 1/1 Running 109 123d
coredns-6955765f44-55wbn 1/1 Running 114 123d
coredns-6955765f44-bhdvr 1/1 Running 114 123d
etcd-k8s-master 1/1 Running 120 123d
kube-apiserver-k8s-master 1/1 Running 124 123d
kube-controller-manager-k8s-master 1/1 Running 122 123d
kube-proxy-8pngh 1/1 Running 185 123d
kube-proxy-gqt42 1/1 Running 184 123d
kube-proxy-pq2fb 1/1 Running 195 123d
kube-scheduler-k8s-master 1/1 Running 120 123d
metrics-server-fbc46dc5f-nlhvs 1/1 Running 114 79d
ログインして確認します。
$ kubectl -n kube-system exec -it kube-apiserver-k8s-master /bin/sh
# kube-apiserver -h | grep enable-admission-plugins
--admission-control strings Admission is divided into two phases. In the first phase, only mutating admission plugins run. In the second phase, only validating admission plugins run. The names in the below list may represent a validating plugin, a mutating plugin, or both. The order of plugins in which they are passed to this flag does not matter. Comma-delimited list of: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, DefaultStorageClass, DefaultTolerationSeconds, DenyEscalatingExec, DenyExecOnPrivileged, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodPreset, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, RuntimeClass, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. (DEPRECATED: Use --enable-admission-plugins or --disable-admission-plugins instead. Will be removed in a future version.)
--enable-admission-plugins strings admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, RuntimeClass, ResourceQuota). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, DefaultStorageClass, DefaultTolerationSeconds, DenyEscalatingExec, DenyExecOnPrivileged, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodPreset, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, RuntimeClass, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter.
ログが長くてわかりにくいので、デフォルトで有効になっているものを以下に抜き出しました。今回の環境は1.17です。最新の1.18とは異なるようです。また、PodSecurityPolicyは含まれていません。
- NamespaceLifecycle
- LimitRanger
- ServiceAccount
- TaintNodesByCondition
- Priority
- DefaultTolerationSeconds
- DefaultStorageClass
- StorageObjectInUseProtection
- PersistentVolumeClaimResize
- MutatingAdmissionWebhook
- ValidatingAdmissionWebhook
- RuntimeClass
- ResourceQuota
PodSecurityPolicyプラグインの追加
/etc/kubernetes/manifests/kube-apiserver.yamlを編集して、--enable-admission-pluginsに「PodSecurityPolicy」を追記します。
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=10.20.30.10
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --enable-admission-plugins=NodeRestriction,PodSecurityPolicy #PodSecurityPolicyを追記
- --enable-bootstrap-token-auth=true
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
- --etcd-servers=https://127.0.0.1:2379
- --insecure-port=0
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
- --requestheader-allowed-names=front-proxy-client
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --requestheader-extra-headers-prefix=X-Remote-Extra-
- --requestheader-group-headers=X-Remote-Group
- --requestheader-username-headers=X-Remote-User
- --secure-port=6443
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-cluster-ip-range=10.96.0.0/12
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
image: k8s.gcr.io/kube-apiserver:v1.17.3
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 8
httpGet:
host: 10.20.30.10
path: /healthz
port: 6443
scheme: HTTPS
initialDelaySeconds: 15
timeoutSeconds: 15
name: kube-apiserver
resources:
requests:
cpu: 250m
volumeMounts:
- mountPath: /etc/ssl/certs
name: ca-certs
readOnly: true
- mountPath: /etc/pki
name: etc-pki
readOnly: true
- mountPath: /etc/kubernetes/pki
name: k8s-certs
readOnly: true
hostNetwork: true
priorityClassName: system-cluster-critical
volumes:
- hostPath:
path: /etc/ssl/certs
type: DirectoryOrCreate
name: ca-certs
- hostPath:
path: /etc/pki
type: DirectoryOrCreate
name: etc-pki
- hostPath:
path: /etc/kubernetes/pki
type: DirectoryOrCreate
name: k8s-certs
status: {}
/etc/kubernetes/manifests/kube-apiserver.yamlファイルを編集すると、kubeletがコンテナを再起動させます。
少し待ってPodを確認すると、kube-apiserverが確認できません。
$ kubectl -n kube-system get pod
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-77c4b7448-6prr9 1/1 Running 117 124d
calico-node-2hc9b 1/1 Running 118 124d
calico-node-cgdgk 1/1 Running 117 124d
calico-node-tkcz5 1/1 Running 110 124d
coredns-6955765f44-55wbn 1/1 Running 117 124d
coredns-6955765f44-bhdvr 1/1 Running 117 124d
etcd-k8s-master 1/1 Running 123 124d
kube-controller-manager-k8s-master 1/1 Running 128 124d
kube-proxy-8pngh 1/1 Running 187 124d
kube-proxy-gqt42 1/1 Running 186 124d
kube-proxy-pq2fb 1/1 Running 201 124d
kube-scheduler-k8s-master 1/1 Running 126 124d
metrics-server-fbc46dc5f-nlhvs 1/1 Running 116 80d
dockerコマンドで確認すると、コンテナが起動および再起動したのは確認できます。
$ docker ps | grep api
863b49a685f7 90d27391b780 "kube-apiserver --ad…" 9 minutes ago Up 2 minutes k8s_kube-apiserver_kube-apiserver-k8s-master_kube-system_db52c836d7613d8535c638ac67356d5e_0
4321d74c37b7 k8s.gcr.io/pause:3.1 "/pause" 9 minutes ago Up 9 minutes
動作確認
先ほどと同様にPodをデプロイして確認します。先ほどのPodと名前が異なるだけで設定が同じPodをデプロイします。
$ kubectl apply -f nginx-nomal-a.yaml
pod/nginx-nomal-a created
$ kubectl apply -f nginx-privileged-a.yaml
The Pod "nginx-privileged-a" is invalid: spec.containers[0].securityContext: Invalid value: core.SecurityContext{Capabilities:(*core.Capabilities)(0xc0072fe8a0), Privileged:(*bool)(0xc00617c63a), SELinuxOptions:(*core.SELinuxOptions)(nil), WindowsOptions:(*core.WindowsSecurityContextOptions)(nil), RunAsUser:(*int64)(nil), RunAsGroup:(*int64)(nil), RunAsNonRoot:(*bool)(nil), ReadOnlyRootFilesystem:(*bool)(0xc00617c64a), AllowPrivilegeEscalation:(*bool)(0xc0072ecce0), ProcMount:(*core.ProcMountType)(nil)}: cannot set `allowPrivilegeEscalation` to false and `privileged` to true
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-nomal-a 1/1 Running 0 33s
nginx-nomal-b 1/1 Running 3 2d
nginx-privileged-b 1/1 Running 3 2d
先ほど(AdmissionController設定前)とは異なり、特権がないPod(nginx-nomal-a)はデプロイできますが、特権を与えたPod(nginx-privileged-a)は失敗しています。
まとめ
PodSecurityPolicyはクラスタ全体のポリシーを定義できますので、適切に設定することでセキュリティを高めることができますね。逆に間違えるとPodのデプロイができなくなったりもしますので、設定には注意する必要があります。
また、今回はkube-apiserver.yamlファイルを編集することで、AdmissionControllerを設定しましたが、kubectlコマンドで表示されなくなってしまいましたので、この方法をProduction環境で使うのはちょっと怖いですね。PodSecurityPolicyはまだベータ版ですので、GA版となったときに再度設定方法を確認する必要があります。
参考資料
https://stackoverflow.com/questions/50352621/where-is-kube-apiserver-located
https://stackoverflow.com/questions/51489955/how-to-obtain-the-enable-admission-controller-list-in-kubernetes
https://tech.virtualtech.jp/entry/2020/03/23/152657
補足(AdmissionControllerの設定方法)
AdmissionControllerを有効にする方法として、マニュアルには以下が記載されています。
The Kubernetes API server flag enable-admission-plugins takes a comma-delimited list of admission control plugins to invoke prior to modifying objects in the cluster. For example, the following command line enables the NamespaceLifecycle and the LimitRanger admission control plugins:
kube-apiserver --enable-admission-plugins=NamespaceLifecycle,LimitRanger ...
この通りにプラグインを並べて指定すると、全てのプラグインが「unknown」となり、設定できませんでした。
色々試しましたがこれ以上やるとクラスタを壊してしまいそうでしたので、一旦先に進んで壊してもいいときになったら、もう少し調査したいと思います。
# kube-apiserver --enable-admission-plugins="NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, RuntimeClass, ResourceQuota, PodSecurityPolicy" --etcd-servers scheme://127.0.0.1:2379 --service-cluster-ip-range 192.168.0.0/16 --anonymous-auth=false
・・・enable-admission-plugins plugin " PodSecurityPolicy" is unknown]