最近有客户反馈在开启了安全策略的集群中部署产品失败,因此研究了一下 Kubernetes 提供的 pod 安全策略。
文中的演示和示例均在 v1.18.17 集群中通过验证。
Pod Security Policies
Pod Security Policies (下文简称 psp 或 pod 安全策略)是一种集群级别的全局资源,能够对 pod 的创建和更新进行细粒度的授权控制。具体来说,一个 psp 对象定义了一组安全性条件,一个 pod 的 spec 字段必须满足这些条件以及适用相关字段的默认值,其创建或更新请求才会被 apiserver 所接受。
具体的 pod 字段和安全条件可见文档 what-is-a-pod-security-policy 。
启用 Pod Security Policies
Kubernetes 默认不开启 pod 安全策略功能,在集群中启用 pod 安全策略的步骤大体上分为三步:
- 在集群中创建指定的安全策略资源。
- 通过 RBAC 机制授予创建 pod 的 user 或者被创建 pod 的 service account 使用安全策略资源的权限,通常会将使用权限授予一组 users 或 service accounts。
- 启用 apiserver 的 admission-controller 插件。
注意步骤 1、2 可以单独执行,因为它们不会对集群产生实际影响,但需要确保步骤 3 在前两步之后执行。
因为一旦启用 admission-controller 插件,apiserver 会对所有的 pod 创建/更新请求强制执行安全策略检查,如果集群中没有可用的 pod 安全策略资源或者未对安全策略资源预先授权,所有的 pod 创建/更新请求都会被拒绝。包括 kube-system 命名空间下的系统管理组件如 apiserver 本身(由于 apiserver 是受 kubelet 管理的静态 pod,实际上容器依然会运行)。
启用的整体流程如下示意图:
创建安全策略资源
-
在集群中创建一个宽松限制的 PodSecurityPolicy 资源,命名为
privileged
。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: privileged annotations: seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' 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'
-
在集群中创建一个严格限制的 PodSecurityPolicy 资源,命名为
restricted
。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: restricted annotations: seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default' apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' spec: privileged: false # Required to prevent escalations to root. allowPrivilegeEscalation: false requiredDropCapabilities: - ALL # Allow core volume types. volumes: - 'configMap' - 'emptyDir' - 'projected' - 'secret' - 'downwardAPI' # Assume that ephemeral CSI drivers & persistentVolumes set up by the cluster admin are safe to use. - 'csi' - 'persistentVolumeClaim' hostNetwork: false hostIPC: false hostPID: false runAsUser: # Require the container to run without root privileges. rule: 'MustRunAsNonRoot' seLinux: # This policy assumes the nodes are using AppArmor rather than 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
RBAC 身份认证
-
分别创建可访问两种安全策略资源的 ClusterRole:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: privileged-psp rules: - apiGroups: ['policy'] resources: ['podsecuritypolicies'] verbs: ['use'] resourceNames: - privileged --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: restricted-psp rules: - apiGroups: ['policy'] resources: ['podsecuritypolicies'] verbs: ['use'] resourceNames: - restricted
-
通过 ClusterRoleBinding (或者 RoleBinding)将创建的 ClusterRole 绑定到指定命名空间下的所有 service account(也可以授权给指定的 user)。
在 Kubernetes 中大多数 pod 并不是直接使用 user 创建的,而是通常作为 Deployment、ReplicaSet 或其他模板 controller 的子资源,由 controller 间接创建。授予 controller 用户对安全策略的使用权等同于为该 controller 创建的所有 pod 授予使用权,因此授权的推荐做法是授权给目标 pod 的 service account。
为了进行后续的测试,我们将
privileged-psp
授权给 kubelet 所使用的system:nodes
用户和privileged-ns
命名空间下的所有 service account,将restricted-psp
授权给restricted-ns
命名空间下的所有 service account:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: privileged-psp-bind roleRef: kind: ClusterRole name: privileged-psp apiGroup: rbac.authorization.k8s.io subjects: # 授权给指定命名空间下的所有 service account(推荐做法): - kind: Group apiGroup: rbac.authorization.k8s.io name: system:serviceaccounts:privileged-ns - kind: Group apiGroup: rbac.authorization.k8s.io name: system:nodes namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: restricted-psp-bind roleRef: kind: ClusterRole name: restricted-psp apiGroup: rbac.authorization.k8s.io subjects: - kind: Group apiGroup: rbac.authorization.k8s.io name: system:serviceaccounts:restricted-ns
在
subjects
字段下添加更多记录还可以授权给所有的 service account 或者所有已授权的 user:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
subjects: # 授权给指定的 service account 或者用户(不推荐): - kind: ServiceAccount name: <authorized service account name> namespace: <authorized pod namespace> - kind: User apiGroup: rbac.authorization.k8s.io name: <authorized user name> # 授权给所有的 service accounts: - kind: Group apiGroup: rbac.authorization.k8s.io name: system:serviceaccounts # 授权给所有已认证的用户: - kind: Group apiGroup: rbac.authorization.k8s.io name: system:authenticated
启用 admission controller 插件
在 apiserver 启用 admission controller 的 psp 插件有两种方式:
-
在已存在的集群中通过修改 apiserver 的静态 manifest 文件,为 apiserver 增加启动参数
enable-admission-plugins=PodSecurityPolicy
。kubelet 会自动检测到变更并重启 apiserver。下面的示例使用 sed 对原有参数进行了替换:1
sed -i 's/enable-admission-plugins=NodeRestriction/enable-admission-plugins=NodeRestriction,PodSecurityPolicy/' /etc/kubernetes/manifests/kube-apiserver.yaml
-
或者在初始化集群时,在 kubeadm 配置文件中添加额外参数(不推荐,默认会拒绝所有 pod 的创建)。
1 2 3 4 5
apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration apiServer: extraArgs: enable-admission-plugins: "PodSecurityPolicy"
验证 psp 的安全限制
接下分别在上文授权过的 privileged-ns
和 restricted-ns
命名空间进行测试,验证 psp 对 pod 请求的限制。
首先尝试在 restricted-ns
命名空间通过 deployment 创建一个需要使用 hostNetwork
的 pod:
|
|
创建并查看结果:
|
|
由于授权给该命名空间 service account 的安全策略资源禁止 pod 使用 hostNetwork,因此该 deployment 创建 pod 的请求被拒绝。
接着在 privileged-ns
命名空间执行相同的操作:
|
|
授权给该命名空间 service account 的安全策略资源允许 pod 使用 hostNetwork,因此 pod 成功被创建。我们可以通过 pod 的 metadata.annotations
字段检查其适用的安全策略资源。
Pod Security Admission
从 Kubernetes v1.21开始,Pod Security Policy 将被弃用,并将在 v1.25 中删除,Kubernetes 在 1.22 版本引入了 Pod Security Admission 作为其替代者。
为什么要替换 psp
KEP-2579 详细阐述了引入 Pod Security Admission 替代 Pod Security Policy 的三点主要理由:
- 将策略与用户或 service account 绑定的模型削弱了安全性。
- 功能无法流畅切换,在没有安全策略资源的情况下无法关闭检查。
- API 不一致且缺乏灵活性。
新的 Pod Security Admission 机制在易用性和灵活性上都有了很大提升,从使用角度有以下四点显著不同:
- 可以在集群中默认开启,只要不设置约束条件就不会触发对 pod 的校验。
- 只在命名空间级别生效,可以为不同命名空间通过添加标签的方式设置不同的安全限制。
- 可以为特定的用户、命名空间或者运行时设置豁免规则。
- 根据实践预设了三种安全等级,不需要由用户单独去设置每一项安全条件。
工作方式
Pod Security Admission 将原来 Pod Security Policy 的安全条件划分成三种预设的安全等级:
privileged
: 不受限,向 pod 提供所有可用的权限。baseline
:最低限度的限制策略,防止已知的特权升级。restricted
:严格限制策略,遵循当前 Pod 加固的最佳实践。
三种等级从宽松到严格递增,各自包含了不同限度的安全条件,适用于不同的 pod 工作场景。此外还可以将安全等级设置为固定的 Kubernetes 版本,这样即使集群升级到了新的版本且新版本的安全等级定义发生变化,依然可以按旧版本的安全条件对 pod 进行检验。
当 pod 与安全等级冲突时,我们可通过三种模式来选择不同的处理方式:
enforce
:只允许符合安全等级要求的 pod,拒绝与安全等级冲突的 pod。audit
:只将安全等级冲突记录在集群 event 中,不会拒绝 pod。warn
:与安全等级冲突时会向用户返回一个警告信息,但不会拒绝 pod。
audit 和 warn 模式是独立的,如果同时需要两者的功能必须分别设置两种模式。
应用安全策略不再需要创建单独的集群资源,只需在启用 Pod Security Admission 后为命名空间设置如下控制标签:
|
|
在旧版本集群中启用 psa
虽然 Pod Security Admission 是一个在 Kubernetes v1.22 引入的功能,但旧版本可以通过安装 PodSecurity admission webhook 来启用该功能,具体步骤如下:
|
|
以上来自官方文档的步骤在 v1.18.17 集群中执行时会有两个兼容性问题,具体问题和解决方案如下:
-
kubectl 内置的 kustomize 版本不支持 "replacements" 字段:
$ kubectl apply -k . error: json: unknown field "replacements"
解决方案:安装最新版本的 kusomize 然后在同一目录执行
1
$ kustomize build . | kubectl apply -f -
-
manifest/50-deployment.yaml
文件中定义的Deployment.spec.template.spec.containers[0].securityContext
字段在 v1.19 版本才开始引入,因此 v1.18 需要将该字段修改为对应的 annotation 版本,详见 Seccomp:error: error validating "STDIN": error validating data: ValidationError(Deployment.spec.template.spec.containers[0].securityContext): unknown field "seccompProfile" in io.k8s.api.core.v1.SecurityContext; if you choose to ignore these errors, turn validation off with --validate=false
验证 psa 的安全限制
首先创建一个新的命名空间 psa-test
用于测试,并将其定义强制应用 baseline
安全等级,并对 restricted
等级进行警告和审计:
|
|
接着在该命名空间中创建上文示例中用过的 deployment:
|
|
与 psp 的示例相比,psa 实现了基本一致的安全检查结果,但易用程度有了很大提升。