跳转至

Pod 安全策略

Pod 安全策略使得对 Pod 创建和更新进行细粒度的权限控制成为可能。默认情况下,Kubernetes 允许创建一个有特权容器的 Pod,这些容器很可能会危机系统安全,而 Pod 安全策略(PSP)则通过确保请求者有权限按配置来创建 Pod,从而来保护集群免受特权 Pod 的影响。

PodSecurityPolicy 是 Kubernetes API 对象,它能够控制 Pod 规范中与安全性相关的各个方面,PodSecurityPolicy 对象定义了一组 Pod 运行时必须遵循的条件及相关字段的默认值,只有 Pod 满足这些条件才会被系统接受,Pod 安全策略允许管理员控制如下方面:

Pod 安全策略由设置和策略组成,它们能够控制 Pod 访问的安全特征,这些设置分为如下三类:

  • 基于布尔值控制:这种类型的字段默认为最严格限制的值
  • 基于被允许的值集合控制:这种类型的字段会与这组值进行对比,以确认值被允许
  • 基于策略控制:设置项通过一种策略提供的机制来生成该值,这种机制能够确保指定的值落在被允许的这组值中

Pod 安全策略实现为一种可选(但是建议启用)的准入控制器,启用了准入控制器即可强制实施 Pod 安全策略,不过如果没有授权认可策略之前即启用准入控制器将导致集群中无法创建任何 Pod。

Admission Controller

Admission Controller(准入控制器)拦截对 kube-apiserver 的请求,拦截发生在请求的对象被持久化之前,但是在请求被验证和授权之后。这样我们就可以查看请求对象的来源,并验证需要的内容是否正确。通过将它们添加到 kube-apiserver 的 --enable-admission-plugins 参数中来启用准入控制器。另外需要注意准入控制器的顺序很重要。

PodSecurityPolicy 添加到 kube-apiserver 上的 --enabled-admission-plugins 参数中,然后重启 kube-apiserver:

--enable-admission-plugins=NodeRestriction,PodPreset,PodSecurityPolicy

我们这里使用的是 kubeadm 搭建的集群,当我们开启 PodSecurityPolicy 后你会发现一个奇怪的现象,就是我们在 kube-system 命名空间下面看不到 apiserver 的 Pod 了:

$ kubectl get pods -n kube-system
NAME                              READY   STATUS    RESTARTS   AGE
coredns-6d56c8448f-cjmds          1/1     Running   0          39d
coredns-6d56c8448f-szdpn          1/1     Running   0          39d
etcd-master1                      1/1     Running   1          39d
kube-controller-manager-master1   1/1     Running   12         39d
kube-flannel-ds-6jq5j             1/1     Running   0          36d
kube-flannel-ds-jxz42             1/1     Running   0          39d
kube-flannel-ds-qvmpz             1/1     Running   0          39d
kube-proxy-f7bkn                  1/1     Running   0          39d
kube-proxy-rb6sb                  1/1     Running   0          36d
kube-proxy-rh4s5                  1/1     Running   0          39d
kube-scheduler-master1            1/1     Running   14         39d
metrics-server-7db76cfc8f-9ggb5   1/1     Running   17         15d

大家看到这里可能觉得很奇怪,其实这是因为我们开启了 Pod 的安全策略了,而 apiserver 这个 Pod 是一个静态 Pod,他需要 kubelet 创建一个镜像的 Pod 我们才能看到,但是现在我们并没有针对 kubelet 的任何安全策略声明,所以这个镜像 Pod 是创建不成功的,所以我们也就看不到了。同样我们去创建新的 Pod 也会失败。

比如现在我们创建一个 Nginx 的 Deployment,YAML 文件内容如下所示:(nginx.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  namespace: default
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx

然后直接创建上面的 Deployment:

$ kubectl apply -f nginx.yaml
deployment.apps/nginx-deploy created

我们可以看到 Deployment 已经创建成功了,现在检查下 default 命名空间下面的 pod、replicaset、deployment:

$ kubectl get po,rs,deploy -l app=nginx
NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deploy-6799fc88d8   1         0         0       28s

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deploy   0/1     0            0           28s

可以看到 replicaset 和 deployment 都创建成功了,但是 replicaset 控制器却并没有创建 Pod,我们可以去描述下 replicaset 查看下事件信息:

$ kubectl describe rs nginx-deploy-6799fc88d8                                  
Name:           nginx-deploy-6799fc88d8
Namespace:      default
......
Conditions:
  Type             Status  Reason
  ----             ------  ------
  ReplicaFailure   True    FailedCreate
Events:
  Type     Reason        Age                 From                   Message
  ----     ------        ----                ----                   -------
  Warning  FailedCreate  31s (x14 over 73s)  replicaset-controller  Error creating: pods "nginx-deploy-6799fc88d8-" is forbidden: PodSecurityPolicy: unable to admit pod: []

可以看到有明显的 forbidden: PodSecurityPolicy: unable to admit pod: [] 错误信息,这个时候就需要使用 ServiceAccount 了。

ServiceAccount Controller Manager

一般来说用户很少会直接创建 Pod,通常是通过 Deployment、StatefulSet、Job 或者 DasemonSet 这些控制器来创建 Pod 的,我们这里需要配置 kube-controller-manager 来为其包含的每个控制器使用单独的 ServiceAccount,我们可以通过在其命令启动参数中添加如下标志来实现:

--use-service-account-credentials=true

一般情况下上面这个标志在大多数安装工具(如 kubeadm)中都是默认开启的,所以不需要单独配置了。

当 kube-controller-manager 开启上面的标志后,它将使用由 Kubernetes 自动生成的以下 ServiceAccount:

$ kubectl get serviceaccount -n kube-system | egrep -o '[A-Za-z0-9-]+-controller'

attachdetach-controller
calico-kube-controller
certificate-controller
clusterrole-aggregation-controller
cronjob-controller
daemon-set-controller
deployment-controller
disruption-controller
endpoint-controller
expand-controller
job-controller
namespace-controller
node-controller
pv-protection-controller
pvc-protection-controller
replicaset-controller
replication-controller
resourcequota-controller
service-account-controller
service-controller
statefulset-controller
ttl-controller

这些 ServiceAccount 可以用来指定哪个控制器可以解析哪些策略的配置。

策略

PodSecurityPolicy 对象提供了一种声明式的方式,用于表达我们运行用户和 ServiceAccount 在我们的集群中创建的内容。我们可以查看策略文档来了解如何设置。在我们当前示例中,我们将创建2个策略,第一个是提供限制访问的“默认”策略,保证使用一些特权设置(例如使用 hostNetwork)无法创建 Pod。第二种是一个“提升”的许可策略,允许将特权设置用于某些 Pod,例如在 kube-system 命名空间下面创建的 Pod 有权限。

首先,创建一个限制性策略,作为默认策略:(psp-restrictive.yaml)

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: restrictive
spec:
  privileged: false
  hostNetwork: false
  allowPrivilegeEscalation: false
  defaultAllowPrivilegeEscalation: false
  hostPID: false
  hostIPC: false
  runAsUser:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  volumes:  # TODO?为什么普通 Pod 会使用到 hostPath 的权限策略
  - '*'
  allowedCapabilities:
  - '*'

直接创建上面的 psp 对象:

$ kubectl apply -f psp-restrictive.yaml
podsecuritypolicy.policy/restrictive configured

虽然限制性的访问对于大多数 Pod 创建是足够的了,但是对于需要提升访问权限的 Pod 来说,就需要一些允许策略了,例如,kube-proxy 就需要启用 hostNetwork:

$ kubectl get pods -n kube-system -l k8s-app=kube-proxy
NAME               READY   STATUS    RESTARTS   AGE
kube-proxy-4z4vf   1/1     Running   0          18d
$ kubectl get pods -n kube-system kube-proxy-4z4vf -o yaml |grep hostNetwork
  hostNetwork: true

这就需要创建一个用于提升创建权限的许可策略了:(psp-permissive.yaml)

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: permissive
spec:
  privileged: true
  hostNetwork: true
  hostIPC: true
  hostPID: true
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  runAsUser:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  hostPorts:
  - min: 0
    max: 65535
  volumes:
  - '*'

同样直接创建上面的 psp 对象:

$ kubectl apply -f psp-permissive.yaml
podsecuritypolicy.policy/permissive configured
$ kubectl get psp
NAME               PRIV    CAPS   SELINUX    RUNASUSER   FSGROUP     SUPGROUP    READONLYROOTFS   VOLUMES
permissive         true           RunAsAny   RunAsAny    RunAsAny    RunAsAny    false            *
restrictive        false   *      RunAsAny   RunAsAny    RunAsAny    RunAsAny    false            configMap,downwardAPI,emptyDir,persistentVolumeClaim,secret,projected

现在配置都已经就绪了,但是我们需要引入到 Kubernetes 授权,这样才可以确定请求 Pod 创建的用户或者 ServiceAccount 是否解决了限制性或许可性策略,这就需要用到 RBAC 了。

RBAC

在我们启用 Pod 安全策略的时候,可能会对 RBAC 引起混淆。它确定了一个账户可以使用的策略,使用集群范围的 ClusterRoleBinding 可以为 ServiceAccount(例如 replicaset-controller)提供对限制性策略的访问权限。使用命名空间范围的 RoleBinding,可以启用对许可策略的访问,这样可以在特定的命名空间(如 kube-system)中进行操作。下面演示了 daemonset-controller 创建 kube-proxy Pod 的解析路径:

rbac flow

上面的流程图可以帮助我们从概念上去了解策略解决方案,当然实际上的代码执行路径不一定就完全就是这样的,这只是一个简单的演示。

首先创建允许使用 restrictive 策略的 ClusterRole。然后创建一个 ClusterRoleBinding,将 restrictive 策略和系统中所有的控制器 ServiceAccount 进行绑定:(psp-restrictive-rbac.yaml)

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: psp-restrictive
rules:
- apiGroups:
  - policy
  resources:
  - podsecuritypolicies
  resourceNames:
  - restrictive
  verbs:
  - use

---

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: psp-default
subjects:
- kind: Group  # 授权给 kube-system 下面的 serviceaccount
  name: system:serviceaccounts
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: psp-restrictive
  apiGroup: rbac.authorization.k8s.io

直接创建上面的 RBAC 相关的资源对象:

$ kubectl apply -f psp-restrictive-rbac.yaml
clusterrole.rbac.authorization.k8s.io/psp-restrictive created
clusterrolebinding.rbac.authorization.k8s.io/psp-default created

然后现在我们再重新创建上面我们的定义的 Deployment:

$ kubectl delete -f nginx.yaml
deployment.apps "nginx-deploy" deleted
$ kubectl apply -f nginx.yaml
deployment.apps/nginx-deploy created

创建完成后同样查看下 default 命名空间下面我们创建的一些资源对象:

$ kubectl get po,rs,deploy -l app=nginx
NAME                                       READY   STATUS      RESTARTS   AGE
pod/nginx-deploy-77f7d4c6b4-njfdl          1/1     Running     0          13s

NAME                                            DESIRED   CURRENT   READY   AGE
replicaset.extensions/nginx-deploy-77f7d4c6b4   1         1         1       13s

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/nginx-deploy   1/1     1            1           13s

我们可以看到 Pods 被成功创建了,但是,如果我们尝试做一些策略不允许的事情,正常来说就应该被拒绝了。首先删除上面的这个 Deployment:

$ kubectl delete -f nginx.yaml
deployment.apps "nginx-deploy" deleted

现在我们在 nginx-deploy 基础上添加 hostNetwork: true 来使用 hostNetwork 这个特权:(nginx-hostnetwork.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-hostnetwork-deploy
  namespace: default
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      hostNetwork: true  # 注意添加hostNetwork
      containers:
      - name: nginx
        image: nginx

然后直接创建上面的 Deployment 这个资源对象:

$ kubectl apply -f nginx-hostnetwork.yaml
deployment.apps/nginx-hostnetwork-deploy created

创建完成后同样查看 default 这个命名空间下面的一些资源对象:

$ kubectl get po,rs,deploy -l app=nginx

NAME                                       READY   STATUS      RESTARTS   AGE

NAME                                                        DESIRED   CURRENT   READY   AGE
replicaset.extensions/nginx-hostnetwork-deploy-74c8fbd687   1         0         0       44s

NAME                                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/nginx-hostnetwork-deploy   0/1     0            0           44s

现在我们发现 ReplicaSet 又没有创建 Pod 了,可以使用 kubectl describe 命令去查看这里我们创建的 ReplicaSet 资源对象来了解更多的信息:

$ kubectl describe rs nginx-hostnetwork-deploy-74c8fbd687
Name:           nginx-hostnetwork-deploy-74c8fbd687
......
Events:
  Type     Reason        Age                   From                   Message
  ----     ------        ----                  ----                   -------
  Warning  FailedCreate  1s (x14 over 42s)  replicaset-controller  Error creating: pods "nginx-deploy-db7654df-" is forbidden: PodSecurityPolicy: unable to admit pod: [spec.securityContext.hostNetwork: Invalid value: true: Host network is not allowed to be used]

我们可以看到很明显 hostNetwork 不被允许使用,但是在某些情况下,我们的确有在某个命名空间(比如 kube-system)下面创建使用 hostNetwork 的 Pod,这里就需要我们创建一个允许执行的 ClusterRole,然后为特定的命名空间创建一个 RoleBinding,将这里的 ClusterRole 和相关的控制器 ServiceAccount 进行绑定:(psp-permissive-rbac.yaml)

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: psp-permissive
rules:
- apiGroups:
  - policy
  resources:
  - podsecuritypolicies
  resourceNames:
  - permissive
  verbs:
  - use

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: psp-permissive
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: psp-permissive
subjects:
- kind: ServiceAccount
  name: replicaset-controller
  namespace: kube-system

然后直接创建上面的 RBAC 相关的资源对象:

$ kubectl apply -f psp-permissive-rbac.yaml
clusterrole.rbac.authorization.k8s.io/psp-permissive created
rolebinding.rbac.authorization.k8s.io/psp-permissive created

现在,我们就可以在 kube-system 这个命名空间下面使用 hostNetwork 来创建 Pod 了,将上面的 nginx 资源清单更改成 kube-system 命名空间下面:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-hostnetwork-deploy
  namespace: kube-system
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      hostNetwork: true
      containers:
      - name: nginx
        image: nginx

重新创建这个 Deployment:

$ kubectl apply -f nginx-hostnetwork.yaml
deployment.apps/nginx-hostnetwork-deploy created

创建完成后同样查看下对应的资源对象创建情况:

$ kubectl get po,rs,deploy -l app=nginx
NAME                                            READY   STATUS    RESTARTS   AGE
pod/nginx-hostnetwork-deploy-74c8fbd687-7x8px   1/1     Running   0          2m1s

NAME                                                        DESIRED   CURRENT   READY   AGE
replicaset.extensions/nginx-hostnetwork-deploy-74c8fbd687   1         1         1       2m1s

NAME                                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/nginx-hostnetwork-deploy   1/1     1            1           2m1s

现在我们可以看到 Pod 在 kube-system 这个命名空间下面创建成功了。

特定应用的 ServiceAccount

如果我们现在有这样的一个需求,在某个命名空间下面要强制执行我们创建的 restrictive(限制性)策略,但是这个命名空间下面的某个应用需要使用 permissive(许可)策略,那么应该怎么办呢?在当前模型中,我们只有集群级别和命名空间级别的解析。为了给某个应用提供单独的许可策略,我们可以为应用的 ServiceAccount 提供使用 permissive 这个 ClusterRole 的能力。

比如,还是在默认的命名空间下面创建一个名为 specialsa 的 ServiceAccount:

$ kubectl create serviceaccount specialsa
serviceaccount/specialsa created

然后创建一个 RoleBinding 将 specialsa 绑定到上面的 psp-permissive 这个 CluterRole 上:(specialsa-psp.yaml)

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: specialsa-psp-permissive
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: psp-permissive
subjects:
- kind: ServiceAccount
  name: specialsa
  namespace: default

创建上面的 RoleBinding 对象:

$ kubectl apply -f specialsa-psp.yaml
rolebinding.rbac.authorization.k8s.io/specialsa-psp-permissive created

然后为我们上面的 Deployment 添加上 serviceAccount 属性:(nginx-hostnetwork-sa.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-hostnetwork-deploy
  namespace: default
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
      hostNetwork: true
      serviceAccount: specialsa  # 注意这里使用的sa的权限绑定

然后直接创建即可:

$ kubectl apply -f nginx-hostnetwork-sa.yaml
deployment.apps/nginx-hostnetwork-deploy configured

这个时候我们查看 default 这个命名空间下面带有 hostNetwork 的 Pod 也创建成功了:

$ kubectl get po,rs,deploy -l app=nginx
NAME                                            READY   STATUS    RESTARTS   AGE
pod/nginx-hostnetwork-deploy-6c85dfbf95-hqt8j   1/1     Running   0          65s

NAME                                                        DESIRED   CURRENT   READY   AGE
replicaset.extensions/nginx-hostnetwork-deploy-6c85dfbf95   1         1         1       65s
replicaset.extensions/nginx-hostnetwork-deploy-74c8fbd687   0         0         0       31m

NAME                                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/nginx-hostnetwork-deploy   1/1     1            1           31m

上面我们描述了 Pod 安全策略是一种通过使用 PSP 授权策略来保护 k8s 集群中的 Pod 的创建过程的方法。

静态 Pod

同样要解决上面静态 Pod 不出现的问题,我们也只需要给 kubelet 的用户添加一些策略即可:

# psp-node.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: node-psp
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:
  - '*'

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: node:psp:privileged
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: node:psp:privileged
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: system:nodes
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: kubelet

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node:psp:privileged
rules:
- apiGroups:
  - policy
  resourceNames:
  - node-psp
  resources:
  - podsecuritypolicies
  verbs:
  - use

我们声明一个 psp-node 的 PSP 对象,然后绑定到 system:nodes 这个 Group 和 kubelet 这个 User 上,创建完成后,就可以正常显示静态 Pod 了:

$ kubectl apply -f psp-node.yaml
$ kubectl get pods -n kube-system
NAME                              READY   STATUS    RESTARTS   AGE
coredns-6d56c8448f-cjmds          1/1     Running   0          39d
coredns-6d56c8448f-szdpn          1/1     Running   0          39d
etcd-master1                      1/1     Running   4          39d
kube-apiserver-master1            1/1     Running   0          4m58s
kube-controller-manager-master1   1/1     Running   15         5m24s
kube-flannel-ds-6jq5j             1/1     Running   0          36d
kube-flannel-ds-jxz42             1/1     Running   0          39d
kube-flannel-ds-qvmpz             1/1     Running   0          39d
kube-proxy-f7bkn                  1/1     Running   0          39d
kube-proxy-rb6sb                  1/1     Running   0          36d
kube-proxy-rh4s5                  1/1     Running   0          39d
kube-scheduler-master1            1/1     Running   16         39d
metrics-server-7db76cfc8f-9ggb5   1/1     Running   17         15d