Kubernetes Pod安全策略
Pod安全策略
FEATURE STATE: Kubernetes v1.21 [deprecated]
Caution: PodSecurityPolicy 自 Kubernetes v1.21 起已弃用,并将在 v1.25 中删除。 我们建议迁移到 Pod 安全准入或 3rd 方准入插件。
Pod 安全策略启用对 pod 创建和更新的细粒度授权。
什么是 Pod 安全策略?
Pod 安全策略是控制 pod 规范的安全敏感方面的集群级资源。 PodSecurityPolicy 对象定义了一组条件,Pod 必须满足这些条件才能被系统接受,以及相关字段的默认值。 它们允许管理员控制以下内容:
控制方面 | 字段名称 |
---|---|
运行特权容器 | privileged |
主机命名空间的使用 | hostPID, hostIPC |
主机网络和端口的使用 | hostNetwork, hostPorts |
卷类型的使用 | volumes |
主机文件系统的使用 | allowedHostPaths |
允许特定的 FlexVolume 驱动程序 | allowedFlexVolumes |
分配拥有 pod 卷的 FSGroup | fsGroup |
要求使用只读根文件系统 | readOnlyRootFilesystem |
容器的用户和组 ID | runAsUser, runAsGroup, supplementalGroups |
限制升级到 root 权限 | allowPrivilegeEscalation, defaultAllowPrivilegeEscalation |
Linux 功能 | defaultAddCapabilities, requiredDropCapabilities, allowedCapabilities |
容器的 SELinux 上下文 | seLinux |
容器的 Allowed Proc Mount 类型 | allowedProcMountTypes |
容器使用的 AppArmor 配置文件 | annotations |
容器使用的 seccomp 配置文件 | annotations |
容器使用的 sysctl 配置文件 | forbiddenSysctls,allowedUnsafeSysctls |
启用 Pod 安全策略
Pod 安全策略控制被实现为可选的准入控制器。 PodSecurityPolicies 是通过启用准入控制器来实施的,但是在没有授权任何策略的情况下这样做会阻止在集群中创建任何 Pod。
由于 pod 安全策略 API (policy/v1beta1/podsecuritypolicy
) 独立于准入控制器启用,因此对于现有集群,建议在启用准入控制器之前添加和授权策略。
授权政策
创建 PodSecurityPolicy 资源时,它什么也不做。 为了使用它,请求用户或目标 pod 的服务帐户必须被授权使用该策略,方法是允许策略上的 use
verb。
大多数 Kubernetes pod 不是由用户直接创建的。 相反,它们通常作为 Deployment、ReplicaSet 或其他模板化控制器的一部分通过控制器管理器间接创建。 授予控制器对策略的访问权限将授予该控制器创建的所有 Pod 的访问权限,因此授权策略的首选方法是授予对 Pod 的服务帐户的访问权限
通过 RBAC
RBAC 是一种标准的 Kubernetes 授权模式,可以很容易地用于授权策略的使用。
首先,Role
或 ClusterRole
需要授予使用所需策略的访问权限。 授予访问权限的规则如下所示:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: <role name>
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames:
- <list of policies to authorize>
然后 (Cluster)Role
绑定到授权用户:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: <binding name>
roleRef:
kind: ClusterRole
name: <role name>
apiGroup: rbac.authorization.k8s.io
subjects:
# Authorize all service accounts in a namespace (recommended):
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:serviceaccounts:<authorized namespace>
# Authorize specific service accounts (not recommended):
- kind: ServiceAccount
name: <authorized service account name>
namespace: <authorized pod namespace>
# Authorize specific users (not recommended):
- kind: User
apiGroup: rbac.authorization.k8s.io
name: <authorized user name>
如果使用 RoleBinding
(不是 ClusterRoleBinding
),它只会授予与绑定在同一命名空间中运行的 Pod 的使用权。 这可以与系统组配对以授予对命名空间中运行的所有 pod 的访问权限:
# Authorize all service accounts in a namespace:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:serviceaccounts
# Or equivalently, all authenticated users in a namespace:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:authenticated
推荐做法
PodSecurityPolicy 正在被一个新的、简化的 PodSecurity
准入控制器所取代。请遵循以下指南来简化从 PodSecurityPolicy 到新准入控制器的迁移:
- 将您的 PodSecurityPolicies 限制为 Pod 安全标准定义的策略:
- 仅使用
system:serviceaccounts:<namespace>
组(其中 <namespace>
是目标命名空间)将 PSP 绑定到整个命名空间。 例如:
apiVersion: rbac.authorization.k8s.io/v1
# This cluster role binding allows all pods in the "development" namespace to use the baseline PSP.
kind: ClusterRoleBinding
metadata:
name: psp-baseline-namespaces
roleRef:
kind: ClusterRole
name: psp-baseline
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: Group
name: system:serviceaccounts:development
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: system:serviceaccounts:canary
apiGroup: rbac.authorization.k8s.io
故障排除
控制器管理器必须针对安全的 API 端口运行,并且不得具有超级用户权限。
如果控制器管理器通过受信任的 API 端口(也称为 localhost
侦听器)连接,请求将绕过身份验证和授权模块; 所有 PodSecurityPolicy 对象都将被允许,并且用户将能够创建授予自己创建特权容器的能力。
Policy Order
除了限制 pod 创建和更新之外,pod 安全策略还可用于为其控制的许多字段提供默认值。 当有多个策略可用时,Pod 安全策略控制器根据以下标准选择策略:
- PodSecurityPolicies 允许 pod 保持原样,而不更改默认值或改变 pod,是首选。 这些非变异 PodSecurityPolicies 的顺序无关紧要。
- 如果 pod 必须是默认的或变异的,则选择第一个允许该 pod 的 PodSecurityPolicy(按名称排序)。
Note: 在更新操作期间(在此期间不允许对 pod 规范进行更改),仅使用非变异 PodSecurityPolicies 来验证 pod。
例子
此示例假设您有一个正在运行的集群并启用了 PodSecurityPolicy 准入控制器,并且您拥有集群管理员权限。
设置
设置命名空间和服务帐户以作为本示例的行为。 我们将使用此服务帐户来模拟非管理员用户。
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
为了明确我们所扮演的用户并保存一些输入,创建 2 个别名:
alias kubectl-admin='kubectl -n psp-example'
alias kubectl-user='kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example'
创建策略和 pod
在文件中定义示例 PodSecurityPolicy 对象。 这是一项阻止创建特权 pod 的策略。 PodSecurityPolicy 对象的名称必须是有效的 DNS 子域名。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: example
spec:
privileged: false # Don't allow privileged pods!
# The rest fills in some required fields.
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
runAsUser:
rule: RunAsAny
fsGroup:
rule: RunAsAny
volumes:
- '*'
并使用 kubectl 创建它:
kubectl-admin create -f example-psp.yaml
现在,作为非特权用户,尝试创建一个简单的 pod:
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: pause
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
EOF
输出类似于:
Error from server (Forbidden): error when creating "STDIN": pods "pause" is forbidden: unable to validate against any pod security policy: []
尽管创建了 PodSecurityPolicy,但 pod 的服务帐户和 fake-user
都无权使用新策略:
kubectl-user auth can-i use podsecuritypolicy/example
no
创建角色绑定以授予 fake-user
示例策略上的 use
verb:
Note: 这不是推荐的方式!
kubectl-admin create role psp:unprivileged \
--verb=use \
--resource=podsecuritypolicy \
--resource-name=example
role "psp:unprivileged" created
kubectl-admin create rolebinding fake-user:psp:unprivileged \
--role=psp:unprivileged \
--serviceaccount=psp-example:fake-user
rolebinding "fake-user:psp:unprivileged" created
kubectl-user auth can-i use podsecuritypolicy/example
yes
现在重试创建 pod:
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: pause
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
EOF
输出类似于此
pod "pause" created
它按预期工作! 但仍应拒绝任何创建特权 pod 的尝试:
kubectl-user 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:
kubectl-user delete pod pause
运行另一个 pod
让我们再试一次,稍有不同:
kubectl-user create deployment pause --image=k8s.gcr.io/pause
deployment "pause" created
kubectl-user get pods
No resources found.
kubectl-user get events | head -n 2
LASTSEEN FIRSTSEEN COUNT NAME KIND SUBOBJECT TYPE REASON SOURCE MESSAGE
1m 2m 15 pause-7774d79b5 ReplicaSet Warning FailedCreate replicaset-controller Error creating: pods "pause-7774d79b5-" is forbidden: no providers available to validate pod request
我们已经为我们的 fake-user
绑定了 psp:unprivileged
角色,为什么我们会收到错误 Error created: pods "pause-7774d79b5-" is disabled: no providers available to validate pod request
? 答案在于源——replicaset-controller
。 Fake-user 成功创建了部署(成功创建了一个副本集),但是当副本集去创建 pod 时,它没有被授权使用示例 podsecuritypolicy。
为了解决这个问题,请将 psp:unprivileged
角色绑定到 pod 的服务帐户。 在这种情况下(因为我们没有指定它)服务帐户是默认的:
kubectl-admin create rolebinding default:psp:unprivileged \
--role=psp:unprivileged \
--serviceaccount=psp-example:default
rolebinding "default:psp:unprivileged" created
现在,如果你给它一分钟重试,replicas-controller 最终应该会成功创建 pod:
kubectl-user get pods --watch
NAME READY STATUS RESTARTS AGE
pause-7774d79b5-qrgcb 0/1 Pending 0 1s
pause-7774d79b5-qrgcb 0/1 Pending 0 1s
pause-7774d79b5-qrgcb 0/1 ContainerCreating 0 1s
pause-7774d79b5-qrgcb 1/1 Running 0 2s
清理
删除命名空间以清理大部分示例资源:
kubectl-admin delete ns psp-example
namespace "psp-example" deleted
请注意,PodSecurityPolicy
资源没有命名空间,必须单独清理:
kubectl-admin delete psp example
podsecuritypolicy "example" deleted
示例策略
这是您可以创建的限制最少的策略,相当于不使用 pod 安全策略准入控制器:
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'
这是一个限制性策略的示例,它要求用户以非特权用户身份运行,阻止可能升级到 root,并需要使用多种安全机制。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
annotations:
# docker/default identifies a profile for seccomp, but it is not particularly tied to the Docker runtime
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'
- 'ephemeral'
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
策略参考
特权
Privileged - 确定 pod 中的任何容器是否可以启用特权模式。 默认情况下,不允许容器访问主机上的任何设备,但“特权”容器可以访问主机上的所有设备。 这允许容器几乎与主机上运行的进程具有相同的访问权限。 这对于想要使用 linux 功能(例如操作网络堆栈和访问设备)的容器很有用。
主机命名空间
-
HostPID
- 控制 pod 容器是否可以共享主机进程 ID 命名空间。 请注意,当与 ptrace 配对时,这可用于提升容器外部的权限(默认情况下禁止使用 ptrace)。 -
HostIPC
- 控制 pod 容器是否可以共享主机 IPC 命名空间。 -
HostNetwork
- 控制 pod 是否可以使用节点网络命名空间。 这样做可以让 pod 访问环回设备、侦听 localhost 的服务,并可用于窥探同一节点上其他 pod 的网络活动。 -
HostPorts
- 提供主机网络命名空间中允许的端口范围列表。 定义为 HostPortRange
的列表,具有 min
(inclusive) 和 max
(inclusive)。 默认为不允许的主机端口。
卷和文件系统
卷 - 提供允许的卷类型列表。 允许的值对应于创建卷时定义的卷源。 此外,*
可用于允许所有卷类型。
新 PSPs 的建议最小允许卷集是:
-
configMap
-
downwardAPI
-
emptyDir
-
persistentVolumeClaim
-
secret
-
projected
Warning: PodSecurityPolicy 不限制
PersistentVolumeClaim
可以引用的 PersistentVolume
对象的类型,hostPath 类型的 PersistentVolume
不支持只读访问模式。 只有受信任的用户才应被授予创建 PersistentVolume
对象的权限。
FSGroup - 控制应用于某些卷的补充组。
-
MustRunAs
- 至少需要指定一个范围。 使用第一个范围的最小值作为默认值。 针对所有范围进行验证。 -
MayRunAs
- 至少需要指定一个范围。 允许不设置 FSGroups
而不提供默认值。 如果设置了 FSGroups
,则针对所有范围进行验证。 -
RunAsAny
- 未提供默认值。 允许指定任何 fsGroup
ID。
AllowedHostPaths - 这指定允许由 hostPath 卷使用的主机路径列表。 空列表意味着对使用的主机路径没有限制。 这被定义为具有单个 pathPrefix
字段的对象列表,它允许 hostPath 卷安装以允许的前缀开头的路径,并且 readOnly
字段指示它必须以只读方式安装。 例如:
allowedHostPaths:
# This allows "/foo", "/foo/", "/foo/bar" etc., but
# disallows "/fool", "/etc/foo" etc.
# "/foo/../" is never valid.
- pathPrefix: "/foo"
readOnly: true # only allow read-only mounts
Warning:
对主机文件系统具有无限制访问权限的容器可以通过多种方式提升权限,包括从其他容器读取数据,以及滥用系统服务(如 Kubelet)的凭据。
可写的 hostPath 目录卷允许容器以允许它们在 pathPrefix
之外遍历主机文件系统的方式写入文件系统。 readOnly: true
,在 Kubernetes 1.11+ 中可用,必须在所有 allowedHostPaths
上使用,以有效限制对指定 pathPrefix
的访问。
ReadOnlyRootFilesystem - 要求容器必须使用只读根文件系统(即无可写层)运行。
弹性卷驱动程序
这指定了允许 flexvolume 使用的 FlexVolume 驱动程序列表。 空列表或 nil 表示对驱动程序没有限制。 请确保 volumes
字段包含 flexVolume
卷类型; 否则不允许使用任何 FlexVolume 驱动程序。
例如:
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: allow-flex-volumes
spec:
# ... other spec fields
volumes:
- flexVolume
allowedFlexVolumes:
- driver: example/lvm
- driver: example/cifs
用户和组
RunAsUser - 控制容器运行时使用的用户 ID。
-
MustRunAs
- 至少需要指定一个范围。 使用第一个范围的最小值作为默认值。 针对所有范围进行验证。 -
MustRunAsNonRoot
- 要求使用非零 runAsUser
提交 pod 或在映像中定义 USER 指令(使用数字 UID)。 既没有指定 runAsNonRoot
也没有指定 runAsUser
设置的 Pod 将被更改为设置 runAsNonRoot=true
,因此需要在容器中定义一个非零数字 USER 指令。 没有提供默认值。 强烈建议使用此策略设置 allowPrivilegeEscalation=false
。 -
RunAsAny
- 未提供默认值。 允许指定任何 runAsUser
。
RunAsGroup - 控制容器运行的主要组 ID。
-
MustRunAs
- 至少需要指定一个范围。 使用第一个范围的最小值作为默认值。 针对所有范围进行验证。 -
MayRunAs
- 不需要指定 RunAsGroup。 但是,当指定 RunAsGroup 时,它们必须落在定义的范围内。 -
RunAsAny
- 未提供默认值。 允许指定任何 runAsGroup
SupplementalGroups - 控制容器添加的组 ID。
-
MustRunAs
- 至少需要指定一个范围。 使用第一个范围的最小值作为默认值。 针对所有范围进行验证。 -
MayRunAs
- 至少需要指定一个范围。 允许在不提供默认值的情况下不设置supplementalGroups
。 如果设置了补充组,则验证所有范围。 -
RunAsAny
- 未提供默认值。 允许指定任何补充组。
权限提升
这些选项控制 allowPrivilegeEscalation
容器选项。 此布尔值直接控制是否在容器进程上设置 no_new_privs
标志。 此标志将阻止 setuid
二进制文件更改有效用户 ID,并阻止文件启用额外功能(例如,它将阻止使用 ping
工具)。 此行为是有效实施 MustRunAsNonRoot
所必需的。
AllowPrivilegeEscalation - 决定是否允许用户将容器的安全上下文设置为 allowPrivilegeEscalation=true
。 这默认为允许,以免破坏 setuid 二进制文件。 将其设置为 false
可确保容器的任何子进程都无法获得比其父进程更多的权限。
DefaultAllowPrivilegeEscalation - 设置 allowPrivilegeEscalation
选项的默认值。 没有这个的默认行为是允许权限升级,以免破坏 setuid 二进制文件。 如果不需要该行为,则可以使用此字段默认为禁止,同时仍允许 pod 显式请求 allowPrivilegeEscalation
。
能力
Linux 功能提供了对传统上与超级用户相关的权限的更细粒度的细分。 其中一些功能可用于提升权限或用于容器突破,并且可能受 PodSecurityPolicy 限制。
以下字段采用功能列表,指定为 ALL_CAPS 中不带 CAP_
前缀的功能名称。
-
AllowedCapabilities
- 提供允许添加到容器的功能列表。 默认功能集是隐式允许的。 空集意味着除了默认集之外不能添加其他功能。 *
可用于允许所有功能。 -
RequiredDropCapabilities
- 必须从容器中删除的功能。 这些功能已从默认设置中删除,不得添加。 RequiredDropCapabilities
中列出的功能不得包含在 AllowedCapabilities
或 DefaultAddCapabilities
中。 -
DefaultAddCapabilities
- 除了运行时默认值之外,默认情况下添加到容器的功能。
SELinux
-
MustRunAs
- 需要配置 seLinuxOptions
。 使用 seLinuxOptions
作为默认值。 针对 seLinuxOptions
进行验证。 -
RunAsAny
- 未提供默认值。 允许指定任何 seLinuxOptions
。
AllowedProcMountTypes
allowedProcMountTypes
是允许的 ProcMountTypes 的列表。 Empty 或 nil 表示只能使用 DefaultProcMountType
。
DefaultProcMount
对 /proc 的只读路径和掩码路径使用容器运行时默认值。 大多数容器运行时会屏蔽 /proc 中的某些路径,以避免特殊设备或信息的意外安全暴露。 这表示为字符串 Default
。
唯一的其他 ProcMountType 是 UnmaskedProcMount
,它绕过了容器运行时的默认屏蔽行为,并确保新创建的 /proc 容器保持不变而无需修改。 这表示为字符串 Unmasked
。
AppArmor
通过 PodSecurityPolicy 上的注释进行控制。
Seccomp
从 Kubernetes v1.19 开始,您可以使用 Pod 或容器的 securityContext
中的 seccompProfile
字段来控制 seccomp 配置文件的使用。 在之前的版本中,seccomp 是通过向 Pod 添加注释来控制的。 相同的 PodSecurityPolicies 可以与任一版本一起使用,以强制应用这些字段或注释。
seccomp.security.alpha.kubernetes.io/defaultProfileName - 指定要应用于容器的默认 seccomp 配置文件的注释。 可能的值为:
-
unconfined
- 如果没有提供替代方案,则不将 Seccomp 应用于容器进程(这是 Kubernetes 中的默认设置)。 -
runtime/default
- 使用默认容器运行时配置文件。 -
docker/default
- 使用 Docker 默认的 seccomp 配置文件。 自 Kubernetes 1.11 起已弃用。 请改用runtime/default。 -
localhost/<path>
- 将配置文件指定为位于 <seccomp_root>/<path>
的节点上的文件,其中 <seccomp_root>
是通过 Kubelet 上的 --seccomp-profile-root
标志定义的。 如果未定义 --seccomp-profile-root
标志,将使用默认路径,即 <root-dir>/seccomp
其中 <root-dir>
由 --root-dir
标志指定。
Note:
--seccomp-profile-root
标志自 Kubernetes v1.19 起已弃用。 鼓励用户使用默认路径。
seccomp.security.alpha.kubernetes.io/allowedProfileNames
- 指定 pod seccomp 注释允许哪些值的注释。 指定为允许值的逗号分隔列表。 可能的值是上面列出的值,加上 *
以允许所有配置文件。 缺少此注释意味着无法更改默认值。
Sysctl
默认情况下,允许所有安全的 sysctl。
-
forbiddenSysctls
- 不包括特定的 sysctl。 您可以在列表中禁止安全和不安全 sysctl 的组合。 要禁止设置任何 sysctls,请单独使用 *
。 -
allowedUnsafeSysctls
- 允许被默认列表禁止的特定 sysctl,只要这些未在forbiddenSysctls
中列出。