Files
website/content/zh/docs/concepts/policy/pod-security-policy.md
T
2020-04-26 15:40:42 +08:00

46 KiB
Raw Blame History

approvers, title, content_template, weight
approvers title content_template weight
pweil-
Pod 安全策略 templates/concept 20

{{% capture overview %}}

{{< feature-state state="beta" >}}

Pod 安全策略支持针对 pod 创建和更新进行精细的权限控制。

{{% /capture %}}

{{% capture body %}}

什么是 Pod 安全策略?

Pod 安全策略 是集群级别的资源,它能够控制 Pod 规范中对安全敏感的方面。 [PodSecurityPolicy](/docs/reference/generated/kubernetes-api/{{< param "version" >}}/#podsecuritypolicy-v1beta1-policy) 对象定义了一组 pod 必须在其上运行才能被系统接受的条件,以及相关字段的默认设置。 它们允许管理员控制如下方面:

所控制的方面 字段名称
运行特权容器 privileged
使用主机 PID 的命名空间 hostPID, hostIPC
主机网络的使用 hostNetwork,hostPorts
控制卷类型的使用 volumes
主机路径的使用 allowedHostPaths
FlexVolume 卷驱动的白名单 allowedFlexVolumes
分配拥有 Pod 数据卷的 FSGroup fsGroup
必须使用一个只读的 root 文件系统 [readOnlyRootFilesystem(#volumes-and-file-systems)
容器的用户和组的 ID runAsUser, runAsGroup, supplementalGroups
限制提升为 root 特权 allowPrivilegeEscalation, defaultAllowPrivilegeEscalation
为容器添加默认的一组能力 defaultAddCapabilities, requiredDropCapabilities, allowedCapabilities
容器的 SELinux 上下文 seLinux
容器允许的 Proc 挂载类型 allowedProcMountTypes
容器使用的 AppArmor 配置 annotations
容器使用的 seccomp 配置 annotations
容器使用的 sysctl 配置 forbiddenSysctls,allowedUnsafeSysctls

启用 Pod 安全策略

Pod 安全策略控制是 admission 控制器 的一个可选实现。PodSecurityPolicy通过 启用 admission 控制器 被强制启用, 但是如果集群内没有授权任何策略时这样做 将导致任何 pod 都无法被创建

由于 Pod 安全策略 API (policy/v1beta1/podsecuritypolicy) 时独立于 admission 控制器启用的, 所以对于现有集群推荐在启用 admission 控制器之前就添加并授权安全策略。

授权策略

当一个 PodSecurityPolicy 资源被创建后,不会执行任何动作。想要应用这个策略,请求用户或者目标 pod 的 服务账户 必须在策略中使用 use 动词,从而被授权使用这个策略。

多数 Kubernetes pod 并非由用户直接创建。相反,通常他们作为 DeploymentReplicaSet 或者其他的模板控制器的组成部分由控制器管理器被间接地创建。 授权策略给控制器即授权给 所有 由该控制器创建的 pod,因此授权策略的授权方式应该时授权给 pod 的 service account (参见 示例)。

使用 RBAC

RBAC 是一个标准的 Kubernetes 授权模式,同时也能够方便地用于授权策 略的使用。

首先,需要有一个 Role 或者 ClusterRole 被授予访问权限来 use 预期策略。授予访问权限的策略如下所示:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: <角色名称>
rules:
- apiGroups: ['policy']
  resources: ['podsecuritypolicies']
  verbs:     ['use']
  resourceNames:
  - <需要授权的策略列表>

随后该 (Cluster)Role 即绑定给了授权的用户:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: <绑定名称>
roleRef:
  kind: ClusterRole
  name: <角色名称>
  apiGroup: rbac.authorization.k8s.io
subjects:
# 授权给特定的服务账户:
- kind: ServiceAccount
  name: <授权的服务账户名称>
  namespace: <授权的 pod 命名空间>
# 授权给指定用户 (不推荐):
- kind: User
  apiGroup: rbac.authorization.k8s.io
  name: <授权的用户名>

如果一个 RoleBinding (非 ClusterRoleBinding) 被使用,它只会授权给那些运行在与绑定相同命名空间下的 pod。 这可以与系统组一起授权给所有运行在该命名空间下的所有 pod。

# 授权命名空间下的所有 service accounts:
- kind: Group
  apiGroup: rbac.authorization.k8s.io
  name: system:serviceaccounts
# 或者同样的,授权给命名空间下的所有已验证的用户:
- kind: Group
  apiGroup: rbac.authorization.k8s.io
  name: system:authenticated

更多关于 RBAC 绑定的示例,查看 角色绑定示例。 关于授权 PodSecurityPolicy 的完整示例,查看 下面

故障诊断

  • 控制器管理器 必须运行于 安全的 API 端口, 并且必须不能具备超级用户权限。否则请求将可绕过身份验证和授权模块,所有的 PodSecurityPolicy 对象将被允许,并且所有的用户将能够创建特权容器。 关于配置控制器管理器授权的更多细节,查看 控制器角色

策略顺序

除了限制 pod 的创建和更新之外,pod 安全策略也可以用于为由其控制的许多字段提供默认值。当同时多个策略可用时,pod 安全策略控制器依据以下标准选择策略:

  1. 允许 pod 维持原状,不改变默认设置或者变更 pod 的 PodSecurityPolicy 是首选。这些非变更的 PodSecurityPolicy 的顺序无关紧要。
  2. 如果 pod 必须默认或者变更,则使用第一个 PodSecurityPolicy(按名称排序)以允许该 pod。

{{< note >}}

在更新操作期间(在 pod 规范不允许变更的期间)只有非变更的 PodSecurityPolicy 会用于校验 pod。 {{< /note >}}

示例

这个示例假定你有一个运行中且已启用 PodSecurityPolicy admission 控制器的集群,并且你拥有集群管理员权限。

设置

以此为例设置一个命名空间和 service account. 我们将使用这个 service account 来模拟一个非管理员用户.

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

为了清晰展示我们所演示的具体用户以及省去一些键入操作,首先创建两个别名:

alias kubectl-admin='kubectl -n psp-example'
alias kubectl-user='kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example'

创建一个策略和一个 pod

在一个文件中定义 PodSecurityPolicy 对象实例。这里的策略只是用来禁止创建有特权 要求的 Pods。 PodSecurityPolicy 对象的命名必须是合法的 DNS 子域名.

{{< codenew file="policy/example-psp.yaml" >}}

然后通过 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 的 service account 和 fake-user 都没有权限使用这个策略:

kubectl-user auth can-i use podsecuritypolicy/example
no

创建 rolebinding 来为示例策略中的 fake-useruse 动词授权:

{{< note >}}

不推荐这种方式! 更推荐的方式请查看 下一部分. {{< /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 run 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

发生了什么? 我们已经将 psp:unprivileged 角色绑定给了 fake-user, 为何仍然报错 Error creating: pods "pause-7774d79b5-" is forbidden: no providers available to validate pod request? 答案在 replicaset-controller 源码中. Fake-user 成功创建了 deployment (并且该 deployment 成功创建了一个 replicaset), 然而当该 replicaset 想要创建 pod 时却没有权限使用我们的示例 pod 安全策略.

为了修复这个问题,将 psp:unprivileged 角色绑定给 pod 的 service account. 在这种情况下(由于我们没有显式指定) 该 service account 为 default:

kubectl-admin create rolebinding default:psp:unprivileged \
    --role=psp:unprivileged \
    --serviceaccount=psp-example:default
rolebinding "default:psp:unprivileged" created

现在如果你给它一分钟让它重试, replicaset-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 安全策略准入控制器:

{{< codenew file="policy/privileged-psp.yaml" >}}

这是一个限制性策略示例,要求用户以非特权用户运行, 屏蔽了权限提升至 root 的可能性, 同时需要使用几个安全机制.

{{< codenew file="policy/restricted-psp.yaml" >}}

策略参考

特权

特权 - 决定 pod 中的任意容器能否启用特权模式。默认情况下一个容器不允许访问主机上的任意设备, 但是一个 "特权" 容器是允许访问主机上的所有设备的。 这样容器具备的权限几乎等同于主机上运行的进程。 这对于像比如操作网络堆栈和访问设备等需要使用 linux capabilities 的容器来说非常有用。

主机命名空间

HostPID - 控制 pod 容器能否共享主机进程 ID 命名空间。 注意当它与 ptrace 搭配使用时可被利用于在容器外部提升权限(ptrace 默认被禁用)。

HostIPC - 控制 pod 容器能够共享主机 IPC

HostNetwork - 控制 pod 能否使用节点网络命名空间。 这样做可允许 pod 访问回环设备、监听 localhost 的服务以及能够用于探测同意节点上其他 pod 的网络活动。

HostPorts - 提供主机网络名称空间中允许端口范围的白名单。定义为 HostPortRange 列表,其中包含 min(包含)max(包含)。默认不允许任何主机端口。

存储卷和文件系统

Volumes - 提供允许的存储卷类型的白名单。允许值对应于在创建卷时定义的源卷。完整的卷类型列表,请查阅 卷类型。 此外 * 可用于允许所有卷类型。

对于新 PSP 允许的卷推荐的最小化组合 是:

  • configMap
  • downwardAPI
  • emptyDir
  • persistentVolumeClaim
  • secret
  • projected

{{< warning >}}

PodSecurityPolicy 并不会对可能被 PersistentVolumeClaim 引用的 PersistentVolume 资源对象的类型做限制, 并且 hostPath 类型的 PersistentVolumes 并不支持只读访问模式。只有可信用户才应当给予创建 PersistentVolume 资源对象的权限。 {{< /warning >}}

FSGroup - 控制应用于某些卷的补充组。

  • MustRunAs - 要求至少指定一个 range。使用第一个范围的最小值作为默认值。针对所有范围进行验证。
  • MayRunAs - 要求至少指定一个 range。允许 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 的访问。 {{< /warning >}}

ReadOnlyRootFilesystem - 要求容器必须运行于一个只读的根文件系统(即不可写的层)。

FlexVolume 驱动

这指定了 FlexVolume 驱动程序的白名单,允许 FlexVolume 使用这些驱动程序。空列表或 nil 表示对驱动程序没有限制。 请确保 volumes 字段包含 flexVolume 卷类型;否则任何 FlexVolume 驱动都不允许。

例如:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: allow-flex-volumes
spec:
  # ... 其他特性字段
  volumes:
    - flexVolume
  allowedFlexVolumes:
    - driver: example/lvm
    - driver: example/cifs

用户和组

RunAsUser - 控制容器以哪个用户 ID 运行。

  • MustRunAs - 要求至少指定一个 range。使用第一个范围的最小值作为默认值。针对所有范围进行验证。
  • MustRunAsNonRoot - 要求 pod 使用非零的 runAsUser 提交,或者在镜像中定义 USER 命令 (使用数字 UID)。 没有指定 runAsNonRootrunAsUser 设置的 pod 将被修改为设置 runAsNonRoot=true, 因此需要在容器中定义一个非零的数字 USER 命令。不提供默认值。这个策略强烈建议设置 allowPrivilegeEscalation=false
  • RunAsAny - 不提供默认值。允许指定任意 runAsUser

RunAsGroup - 控制容器以哪个首要组 ID 运行。

  • MustRunAs - 要求至少指定一个 range。使用第一个范围的最小值作为默认值。针对所有范围进行验证。
  • MayRunAs - 不要求指定 RunAsGroup。但是,当指定 RunAsGroup 时,它们必须落在定义的范围内。
  • RunAsAny - 不提供默认值。允许指定任意 runAsGroup

SupplementalGroups - 控制容器添加那些组 ID。

  • MustRunAs - 要求至少指定一个 range。使用第一个范围的最小值作为默认值。针对所有范围进行验证。
  • MayRunAs - 要求至少指定一个 range。允许在不提供默认值的情况下不设置 supplementalGroups。 如果设置了 supplementalGroups,则对所有范围进行验证。
  • RunAsAny - 不提供默认值。允许指定任意 supplementalGroups

特权提升

这个选项控制容器的 allowPrivilegeEscalation 选项。这个布尔值直接控制 no_new_privs 标志是否设置到容器进程上。这个标志将防止 setuid 二进制文件改变有效的用户 ID,并防止文件启用额外的功能 (例如,它将防止使用 ping 工具)。 这种行为前提是需要有效地强制指定 MustRunAsNonRoot

AllowPrivilegeEscalation - 是否允许用户将容器的安全上下文设置为 allowPrivilegeEscalation=true。 这是默认设置,以便不破坏 setuid 二进制文件。将其设置为 false 可以确保容器的子进程不能获得比其父进程更多的特权。

DefaultAllowPrivilegeEscalation - 设置 allowPrivilegeEscalation 选项的默认值。没有这个设置的默认行为是允许 特权升级,以避免破坏 setuid 二进制文件。如果这种行为并非有意,可以使此字段默认为不允许,但仍然允许 显式请求 allowPrivilegeEscalation 的 pods。

能力

Linux 能力提供了对传统上与超级用户相关的特权的细粒度分解。其中一些能力可用于特权提升或容器断接,并且可能受到 PodSecurityPolicy 的限制。 有关Linux功能的更多信息,查看 capabilities(7)

以下字段列出了能力列表,在 ALL_CAPS 中指定为不带 CAP_ 前缀的能力名称。

AllowedCapabilities - 提供可添加到容器中的能力白名单。默认的能力集是隐式允许的。 空集意味着在默认集之外不能添加任何附加能力。* 可用于允许所有能力。

RequiredDropCapabilities - 必须从容器中删除的能力。这些能力将从默认设置中删除,并且不能被添加。 在 requireddropcapability 中列出的能力不能包含在 allowedcapabilitydefaultaddcapability 中。

DefaultAddCapabilities - 除了运行时默认设置外,默认添加到容器中的能力。查看 Docker 文档 中关于使用 Docker 运行时时默认的能力列表。

SELinux

  • MustRunAs - 要求设置 seLinuxOptions。使用 seLinuxOptions 作为默认值。针对所有范围进行验证。
  • RunAsAny - 不提供默认值。允许指定任意 seLinuxOptions

AllowedProcMountTypes

allowedProcMountTypes 是允许 ProcMountTypes 的白名单。空列表或 nil 表示只允许 DefaultProcMountType 指定的默认值。

DefaultProcMount 对只读路径使用容器运行时默认值,对/proc 使用掩码路径。大多数容器运行时屏蔽 /proc 中的某些路径, 以避免特殊设备或信息的意外安全性暴露。使用字符串 Default 表示。

另一个 ProcMountType 是 UnmaskedProcMount,它绕过了容器运行时的默认屏蔽行为,并确保新创建的 /proc 容器保持不变。 使用字符串 unmask 表示。

AppArmor

通过 PodSecurityPolicy 上面的注解进行控制。参考 AppArmor 文档

Seccomp

可以通过 PodSecurityPolicy 上的注解来控制 pod 中 seccomp 配置文件的使用。 Seccomp 是 Kubernetes 的一个 alpha 特性。

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> 目录下的文件, 其中通过 Kubelet 上的 --seccomp-profile-root 标志来定义 <seccomp_root>

seccomp.security.alpha.kubernetes.io/allowedProfileNames - 用于指定 pod seccomp 注解允许哪些取值的注解。 指定为允许值的逗号分隔列表。可选值是上面列出的值,加上 * 以允许所有配置文件。缺少此注解意味着无法更改默认值。

Sysctl

默认情况下允许所有安全的 sysctl。

  • forbiddenSysctls - 排除特定的 sysctl。您可以在列表中禁止安全的和不安全的 sysctl 的组合。要禁止设置任意 sysctl,请单独使用 *
  • allowedUnsafeSysctls - 允许在默认列表中被禁用的特定 sysctl,只要这些 sysctl 没有在 forbiddenSysctls 中列出。

参考 Sysctl 文档。 {{% /capture %}}

{{% capture whatsnext %}}

关于 api 细节参考 [Pod 安全策略指南](/docs/reference/generated/kubernetes-api/{{< param "version" >}}/#podsecuritypolicy-v1beta1-policy)。

{{% /capture %}}