PodSecurityPolicy 限制 Pod 使用安全上下文中的相关特性

PodSecurityPolicy 是一种 集群级别资源,用来限制用户能够在 Pod 的 安全上下文 中使用的各种安全相关的特性。通俗的说,用来限制 Pod 的定义中的安全上下文字段可以指定的相关配置。

🪐 PodSecurityPolicy 是由 API 服务器的 准入插件 完成的。

当 API 服务器收到 Pod 创建请求时,该准入插件会将 Pod 的定义与已配置的 PodSecurityPolicy 进行校验,集群中可以有多个该资源。若 Pod 符合其中一个资源,插件会根据匹配到的 PodSecurityPolicy 中定义的默认值对 Pod 进行修改,然后将该 Pod 存入 etcd;否则会被立刻拒绝。

🔥 其默认是没有被开启的,需要在启动 API 服务器时指定开启。一旦被开启,集群中的 PodSecurityPolicy 就会起作用,若当前没有任何该资源的话,则意味着任何用户都无法创建 Pod(因为 Pod 无法找到符合其需求的 PodSecurityPolicy 资源,所以通常集群中会存在一条最宽松的 PodSecurityPolicy 资源,保证系统自身可以正常运行、集群管理员可以创建任意安全级别的 Pod,然后再根据需求创建不同安全级别的 PodSecurityPolicy 资源,并将该资源的使用权交由用户,从而实现对用户创建的 Pod 的安全特性的控制)。

再次强调!!只要用户能够使用 PodSecurityPolicy 资源,那么该资源就会对该用户创建的 Pod 生效!!!

🥑 PodSecurityPolicy 可以定义的安全特性如下:

  • 是否允许 Pod 使用宿主机节点的 PID、IPC 以及网络命名空间;

  • 限制 Pod 可以绑定的宿主机节点端口;

  • 容器运行时使用的用户 ID;

  • 是否允许创建拥有特权模式的容器的 Pod;

  • 允许添加哪些内核功能、默认添加哪些内核功能、禁用哪些内核功能;

  • 允许容器使用哪些 SELinux 选项;

  • 容器是否允许使用可写的根文件系统;

  • 允许容器在哪些文件系统组下运行;

  • 允许 Pod 使用哪些类型的存储卷;

🌝 修改 PodSecurityPolicy 对已存在的 Pod 无效,因为该资源是被准入插件使用的,因此仅在创建和更新 Pod 时才会起作用。

🤖️ Minikube 官方提供了开启该准入插件的方式,见该网址

理解一下,为了保证集群能够顺利的运行,首先创建两条 PodSecurityPolicy 资源,一个名为 psp-restricted,即具有一定安全限制,并将其赋予给 system:authenticated 组,即限制了该组的 Pod 的安全特性;另一个名为 psp-privileged,即安全级别最低,权限最大,并将其赋予给 system:masters, system:nodes, system:serviceaccounts:kube-system 组,即给予集群管理层面最大权限,从而保证集群的正常运行。配置文件如下:

---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: privileged
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: "*"
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
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'
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: restricted
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
spec:
  privileged: false
  allowPrivilegeEscalation: false
  requiredDropCapabilities:
    - ALL
  volumes:
    - 'configMap'
    - 'emptyDir'
    - 'projected'
    - 'secret'
    - 'downwardAPI'
    - 'persistentVolumeClaim'
  hostNetwork: false
  hostIPC: false
  hostPID: false
  runAsUser:
    rule: 'MustRunAsNonRoot'
  seLinux:
    rule: 'RunAsAny'
  supplementalGroups:
    rule: 'MustRunAs'
    ranges:
      # Forbid adding the root group.
      - min: 1
        max: 65535
  fsGroup:
    rule: 'MustRunAs'
    ranges:
      # Forbid adding the root group.
      - min: 1
        max: 65535
  readOnlyRootFilesystem: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: psp:privileged
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
rules:
- apiGroups: ['policy']
  resources: ['podsecuritypolicies']
  verbs:     ['use']
  resourceNames:
  - privileged
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: psp:restricted
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
rules:
- apiGroups: ['policy']
  resources: ['podsecuritypolicies']
  verbs:     ['use']
  resourceNames:
  - restricted
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: default:restricted
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: psp:restricted
subjects:
- kind: Group
  name: system:authenticated
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: default:privileged
  namespace: kube-system
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: psp:privileged
subjects:
- kind: Group
  name: system:masters
  apiGroup: rbac.authorization.k8s.io
- kind: Group
  name: system:nodes
  apiGroup: rbac.authorization.k8s.io
- kind: Group
  name: system:serviceaccounts:kube-system
  apiGroup: rbac.authorization.k8s.io

然后通过命令在集群运行之前将这些配置导入集群:

mkdir -p ~/.minikube/files/etc/kubernetes/addons
vim ~/.minikube/files/etc/kubernetes/addons/psp.yaml

psp.yaml 中的配置即上面的配置,复制进去即可

Minikube 启用 PodSecurityPolicy 准入插件的命令如下:

🌈   ~ minikube start --docker-env HTTP_PROXY=$HTTP_PROXY --docker-env HTTPS_PROXY=$HTTPS_PROXY --extra-config=apiserver.enable-admission-plugins=PodSecurityPolicy
😄  Darwin 10.15.3 上的 minikube v1.8.2
  根据现有的配置文件使用 virtualbox 驱动程序
  重新配置现有主机
🏃  Using the running virtualbox "minikube" VM ...
🌐  找到的网络选项:
     HTTP_PROXY=http://192.168.99.1:1087
  您似乎正在使用代理,但您的 NO_PROXY 环境不包含 minikube IP (192.168.99.111)。如需了解详情,请参阅 https://minikube.sigs.k8s.io/docs/reference/networking/proxy/
     HTTPS_PROXY=http://192.168.99.1:1087
🐳  正在 Docker 19.03.6 中准备 Kubernetes v1.17.3…
     env HTTP_PROXY=http://192.168.99.1:1087
     env HTTPS_PROXY=http://192.168.99.1:1087
     apiserver.enable-admission-plugins=PodSecurityPolicy
🚀  正在启动 Kubernetes ... 
🌟  Enabling addons: default-storageclass, storage-provisioner
🏄  完成!kubectl 已经配置至 "minikube"

若网络情况比较好,则可以不使用代理

PodSecurityPolicy 定义案例

PodSecurityPolicy 是集群资源,因此同样可以通过定义 YAML 清单,然后通过 kubectl apply -f 命令生成。下面列举一个案例,创建一个名为 default 的 PodSecurityPolicy 资源,不允许 Pod 使用宿主机的 IPC、PID 以及网络命名空间,Pod 能够使用的宿主机的端口范围为 [1000, 11000] 和 [13000, 14000],不允许 Pod 中包含拥有特权模式的容器,Pod 中容器的根文件系统必须是只读的:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy        # 资源类型为 PodSecurityPolicy
metadata:
  name: default
spec:
  hostIPC: false                # 不允许使用宿主机的 IPC 命名空间
  hostPID: false                # 不允许使用宿主机的 PID 命名空间
  hostNetwork: false        # 不允许使用宿主机的网络命名空间
  hostPorts:    # 限制 Pod 中的容器只能绑定(映射)宿主机的 1000~11000 和 13000~14000 端口(包含端点)
  - min: 10000
    max: 11000
  - min: 13000
    max: 14000
  privileged: false    # 不能创建特权模式的容器(若为 true,表示可以运行特权模式的容器,但并不是必须的)
  readOnlyRootFilesystem: true    # Pod 中的容器只能使用只读的根文件系统(若为 false,则用户可以通过安全上下文配置,自行定义哪些容器使用只读根文件系统,哪些不使用)
  runAsUser:    # 容器可以以任意用户 ID 运行
    rule: RunAsAny
  fsGroup:    # 容器可以以任意用户组 ID 运行
    rule: RunAsAny
  supplementalGroups:    # 容器可以以任意用户组 ID 运行
    rule: RunAsAny
  seLinux:    # 容器可以使用任何 SELinux 选项
    rule: RunAsAny
  volumes:    # 容器可以使用任何类型的存储卷
  - '*'

✨ 最好在创建资源时将 privileged, readOnlyRootFilesystem, fsGroup, supplementalGroups, seLinux, volumes 都进行指定;

💥 volumes 若不指定,则表示容器不能使用任何类型的存储卷,而由于每个 Pod 默认会挂载命名空间中的默认 Secret 对象,因此会导致 Pod 创建不成功;

Pod 安全性限制

限制用户 ID 和 用户组 ID

在上面的案例中可以了解到,可以通过 podsecuritypolicy.spec.runAsUser 限制用户 ID,通过 podsecuritypolicy.spec.fsGrouppodsecuritypolicy.spec.supplementalGroups 限制用户组 ID。

可以通过不同的 rule 字段进行限制,包括:

  • RunAsAny:可以使用任何 ID;

  • MustRunAs:限制可以使用的 ID 范围;

  • MustRunAsNonRoot (仅适用于 runAsUser ):限制使用 root 用户,即可以使用 ID 为 0 以为的任何 ID;

MustRunAs 规则

当使用了 MustRunAs 规则,则必须使用 ranges 字段定义使用的范围(若范围的最小值和最大值为同一个,表示只允许使用该 ID)。

如果 Pod 的安全上下文中相关定义中的任一字段在该范围之外,则会被拒绝创建。

下面展示一个例子:

runAsUser:
  rule: MustRunAs
  ranges:    # 指定用户 ID 只能为 2
  - min: 2
    max: 2
fsGroup:
  rule: MustRunAs
  ranges:    # 指定用户组 ID 范围只能为 2~10 和 20~30
  - min: 2
    max: 10
  - min: 20
    max: 30
supplementalGroups:
  rule: MustRunAs
  ranges:    # 指定用户组 ID 范围只能为 2~10 和 20~30
  - min: 2
    max: 10
  - min: 20
    max: 30

配置允许、默认添加、禁止使用的内核功能

PodSecurityPolicy 允许在其定义中通过配置下面三个功能,分别配置 Pod 允许添加的内核功能、默认添加的内核功能以及禁止添加的内核功能:

  • allowedCapabilities : 允许容器添加的内核功能;

  • defaultAddCapabilities : 默认为容器添加的内核功能;

  • requiredDropCapabilities : 要求容器禁用的内核功能;

Pod 的安全上下文定义中的 capabilities.add 列表中只能添加 PodSecurityContext 定义中的 允许容器添加的内核功能 以及 容器默认添加的内核功能,否则就会请求被拒绝;如果该列表中添加了 PodSecurityContext 定义中的 禁止使用的内核功能,则同样会被拒绝请求;

下面展示一个例子:

spec:
  allowedCapabilities:    # 允许容器添加 SYS_TIME 功能。Pod 也可以不添加,要添加的话只能添加该列表以及 defaultAddCapabilities 列表中的
  - SYS_TIME
  defaultAddCapabilities:    # 为每个容器添加 CHOWN 功能(会默认在 Pod 的安全上下文的 capabilities.add 列表中加上该字段的值)
  - CHOWN
  requiredDropCapabilities:    # 要求容器禁用 SYS_ADMIN 和 SYS_MODULE 功能(会默认在 Pod 的安全上下文的 capabilities.drop 列表中加上该字段的值)
  - SYS_ADMIN
  - SYS_MODULE

限制 Pod 可使用的存储卷类型

在 PodSecurityPolicy 的定义的 spec.volumes 列表中可以指定 Pod 可使用的存储卷类型,若为 '*',则表示允许使用任意类型的存储卷,或者也可以通过指定卷类型名来单独制定。例如:

spec:
  volumes:
  - emptyDir
  - configMap
  - secret
  - downwardAPI
  - persistentVolumeClaim

为用户分配 PodSecurityPolicy

为指定用户分配 PodSecurityPolicy 资源需要配合 RBAC 机制 来实现。总共需要以下几步:

  1. 首先创建所需的 PodSecurityPolicy 资源;

  2. 然后创建 ClusterRole 资源指向该 PodSecurityPolicy 资源;

  3. 通过 ClusterRoleBinding 或 RoleBinding 将 ClusterRole 绑定至特定组或特定用户;

下面展示一个例子。

用户的创建可以参考 minikube创建用户 章节,这里创建的用户名为 alice

创建一个名为 psp-no-privileged不允许创建特权模式容器的 Pod 的 PodSecurityPolicy,其 YAML 定义如下:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: psp-no-privileged
spec:
  privileged: false    # 不允许使用特权模式的容器
  readOnlyRootFilesystem: false
  runAsUser:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  volumes:
  - '*'

创建一个名为 psp-no-privileged 的 ClusterRole,指向 psp-no-privileged 这个 PodSecurityPolicy,该 ClusterRole 使用能够执行的操作为 use,并且目前只能通过 YAML 文件定义,不能用命令行创建,定义如下:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: psp-no-privileged
rules:
- apiGroups: ["policy"]
  resources: ["podsecuritypolicies"]    # 限制的资源类型
  verbs: ["use"]    # 能够执行的操作为 use
  resourceNames: ["psp-no-privileged"]    # 精确到资源名

创建一个名为 psp-alice 的 ClusterRoleBinding,将名为 psp-no-privileged 的 ClusterRole 与 alice 这个用户进行绑定,该资源可以通过命令行创建,也可以通过 YAML 定义创建:

kubectl create clusterrolebinding psp-alice --clusterrole=psp-no-privileged --user=alice

上述流程执行完成后,用户 alice 目前可以使用该 PodSecurityPolicy 资源,即该用户创建的每个 Pod 请求会受该 PodSecurityPolicy 资源的限制。

Last updated

Was this helpful?