1 - 云原生安全概述

在云原生安全的背景下思考 Kubernetes 安全模型。

本概述定义了一个模型,用于在 Cloud Native 安全性上下文中考虑 Kubernetes 安全性。

云原生安全的 4 个 C

你可以分层去考虑安全性,云原生安全的 4 个 C 分别是云(Cloud)、集群(Cluster)、容器(Container)和代码(Code)。

云原生安全的 4C

云原生安全模型的每一层都是基于下一个最外层,代码层受益于强大的基础安全层(云、集群、容器)。 你无法通过在代码层解决安全问题来为基础层中糟糕的安全标准提供保护。

在许多方面,云(或者位于同一位置的服务器,或者是公司数据中心)是 Kubernetes 集群中的 可信计算基。 如果云层容易受到攻击(或者被配置成了易受攻击的方式),就不能保证在此基础之上构建的组件是安全的。 每个云提供商都会提出安全建议,以在其环境中安全地运行工作负载。

云提供商安全性

如果你是在你自己的硬件或者其他不同的云提供商上运行 Kubernetes 集群, 请查阅相关文档来获取最好的安全实践。

下面是一些比较流行的云提供商的安全性文档链接:

云提供商安全
IaaS 提供商 链接
Alibaba Cloud https://www.alibabacloud.com/trust-center
Amazon Web Services https://aws.amazon.com/security/
Google Cloud Platform https://cloud.google.com/security/
IBM Cloud https://www.ibm.com/cloud/security
Microsoft Azure https://docs.microsoft.com/en-us/azure/security/azure-security
Oracle Cloud Infrastructure https://www.oracle.com/security/
VMWare VSphere https://www.vmware.com/security/hardening-guides.html

基础设施安全

关于在 Kubernetes 集群中保护你的基础设施的建议:

基础设施安全
Kubernetes 基础架构关注领域 建议
通过网络访问 API 服务(控制平面) 所有对 Kubernetes 控制平面的访问不允许在 Internet 上公开,同时应由网络访问控制列表控制,该列表包含管理集群所需的 IP 地址集。
通过网络访问 Node(节点) 节点应配置为 仅能 从控制平面上通过指定端口来接受(通过网络访问控制列表)连接,以及接受 NodePort 和 LoadBalancer 类型的 Kubernetes 服务连接。如果可能的话,这些节点不应完全暴露在公共互联网上。
Kubernetes 访问云提供商的 API 每个云提供商都需要向 Kubernetes 控制平面和节点授予不同的权限集。为集群提供云提供商访问权限时,最好遵循对需要管理的资源的最小特权原则Kops 文档提供有关 IAM 策略和角色的信息。
访问 etcd 对 etcd(Kubernetes 的数据存储)的访问应仅限于控制平面。根据配置情况,你应该尝试通过 TLS 来使用 etcd。更多信息可以在 etcd 文档中找到。
etcd 加密 在所有可能的情况下,最好对所有存储进行静态数据加密,并且由于 etcd 拥有整个集群的状态(包括机密信息),因此其磁盘更应该进行静态数据加密。

集群

保护 Kubernetes 有两个方面需要注意:

  • 保护可配置的集群组件
  • 保护在集群中运行的应用程序

集群组件

如果想要保护集群免受意外或恶意的访问,采取良好的信息管理实践,请阅读并遵循有关保护集群的建议。

集群中的组件(你的应用)

根据你的应用程序的受攻击面,你可能需要关注安全性的特定面,比如: 如果你正在运行中的一个服务(A 服务)在其他资源链中很重要,并且所运行的另一工作负载(服务 B) 容易受到资源枯竭的攻击,则如果你不限制服务 B 的资源的话,损害服务 A 的风险就会很高。 下表列出了安全性关注的领域和建议,用以保护 Kubernetes 中运行的工作负载:

工作负载安全性关注领域 建议
RBAC 授权(访问 Kubernetes API) https://kubernetes.io/zh/docs/reference/access-authn-authz/rbac/
认证方式 https://kubernetes.io/zh/docs/concepts/security/controlling-access/
应用程序 Secret 管理 (并在 etcd 中对其进行静态数据加密) https://kubernetes.io/zh/docs/concepts/configuration/secret/
https://kubernetes.io/zh/docs/tasks/administer-cluster/encrypt-data/
确保 Pod 符合定义的 Pod 安全标准 https://kubernetes.io/zh/docs/concepts/security/pod-security-standards/#policy-instantiation
服务质量(和集群资源管理) https://kubernetes.io/zh/docs/tasks/configure-pod-container/quality-service-pod/
网络策略 https://kubernetes.io/zh/docs/concepts/services-networking/network-policies/
Kubernetes Ingress 的 TLS 支持 https://kubernetes.io/zh/docs/concepts/services-networking/ingress/#tls

容器

容器安全性不在本指南的探讨范围内。下面是一些探索此主题的建议和连接:

容器关注领域 建议
容器漏洞扫描和操作系统依赖安全性 作为镜像构建的一部分,你应该扫描你的容器里的已知漏洞。
镜像签名和执行 对容器镜像进行签名,以维护对容器内容的信任。
禁止特权用户 构建容器时,请查阅文档以了解如何在具有最低操作系统特权级别的容器内部创建用户,以实现容器的目标。
使用带有较强隔离能力的容器运行时 选择提供较强隔离能力的容器运行时类

代码

应用程序代码是你最能够控制的主要攻击面之一,虽然保护应用程序代码不在 Kubernetes 安全主题范围内,但以下是保护应用程序代码的建议:

代码安全性

代码安全
代码关注领域 建议
仅通过 TLS 访问 如果你的代码需要通过 TCP 通信,请提前与客户端执行 TLS 握手。除少数情况外,请加密传输中的所有内容。更进一步,加密服务之间的网络流量是一个好主意。这可以通过被称为双向 TLS 或 mTLS 的过程来完成,该过程对两个证书持有服务之间的通信执行双向验证。
限制通信端口范围 此建议可能有点不言自明,但是在任何可能的情况下,你都只应公开服务上对于通信或度量收集绝对必要的端口。
第三方依赖性安全 最好定期扫描应用程序的第三方库以了解已知的安全漏洞。每种编程语言都有一个自动执行此检查的工具。
静态代码分析 大多数语言都提供给了一种方法,来分析代码段中是否存在潜在的不安全的编码实践。只要有可能,你都应该使用自动工具执行检查,该工具可以扫描代码库以查找常见的安全错误,一些工具可以在以下连接中找到: https://owasp.org/www-community/Source_Code_Analysis_Tools
动态探测攻击 你可以对服务运行一些自动化工具,来尝试一些众所周知的服务攻击。这些攻击包括 SQL 注入、CSRF 和 XSS。OWASP Zed Attack 代理工具是最受欢迎的动态分析工具之一。

接下来

学习了解相关的 Kubernetes 安全主题:

2 - Pod 安全性标准

详细了解 Pod 安全性标准(Pod Security Standards)中所定义的不同策略级别。

Pod 安全性标准定义了三种不同的 策略(Policy),以广泛覆盖安全应用场景。 这些策略是 渐进式的(Cumulative),安全级别从高度宽松至高度受限。 本指南概述了每个策略的要求。

Profile 描述
Privileged 不受限制的策略,提供最大可能范围的权限许可。此策略允许已知的特权提升。
Baseline 限制性最弱的策略,禁止已知的策略提升。允许使用默认的(规定最少)Pod 配置。
Restricted 限制性非常强的策略,遵循当前的保护 Pod 的最佳实践。

Profile 细节

Privileged

Privileged 策略是有目的地开放且完全无限制的策略。 此类策略通常针对由特权较高、受信任的用户所管理的系统级或基础设施级负载。

Privileged 策略定义中限制较少。默认允许的(Allow-by-default)实施机制(例如 gatekeeper) 可以缺省设置为 Privileged。 与此不同,对于默认拒绝(Deny-by-default)的实施机制(如 Pod 安全策略)而言, Privileged 策略应该禁止所有限制。

Baseline

Baseline 策略的目标是便于常见的容器化应用采用,同时禁止已知的特权提升。 此策略针对的是应用运维人员和非关键性应用的开发人员。 下面列举的控制应该被实施(禁止):

Baseline 策略规范
控制(Control) 策略(Policy)
HostProcess

Windows Pod 提供了运行 HostProcess 容器 的能力, 这使得对 Windows 节点的特权访问成为可能。 基线策略中对宿主的特权访问是被禁止的。 HostProcess Pod 是 Kubernetes v1.22 版本的 alpha 特性。

限制的字段

  • spec.securityContext.windowsOptions.hostProcess
  • spec.containers[*].securityContext.windowsOptions.hostProcess
  • spec.initContainers[*].securityContext.windowsOptions.hostProcess
  • spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess

允许的值

  • 未定义/nil
  • false
宿主名字空间

必须禁止共享宿主名字空间。

限制的字段

  • spec.hostNetwork
  • spec.hostPID
  • spec.hostIPC

允许的值

  • 未定义/nil
  • false
特权容器

特权 Pod 关闭了大多数安全性机制,必须被禁止。

限制的字段

  • spec.containers[*].securityContext.privileged
  • spec.initContainers[*].securityContext.privileged
  • spec.ephemeralContainers[*].securityContext.privileged

允许的值

  • 未定义/nil
  • false
权能

必须禁止添加除下列字段之外的权能。

限制的字段

  • spec.containers[*].securityContext.capabilities.add
  • spec.initContainers[*].securityContext.capabilities.add
  • spec.ephemeralContainers[*].securityContext.capabilities.add

允许的值

  • Undefined/nil
  • AUDIT_WRITE
  • CHOWN
  • DAC_OVERRIDE
  • FOWNER
  • FSETID
  • KILL
  • MKNOD
  • NET_BIND_SERVICE
  • SETFCAP
  • SETGID
  • SETPCAP
  • SETUID
  • SYS_CHROOT
HostPath 卷

必须禁止 HostPath 卷。

限制的字段

  • spec.volumes[*].hostPath

允许的值

  • 未定义/nil
宿主端口

应禁止使用宿主端口,或者至少限定为已知列表。

限制的字段

  • spec.containers[*].ports[*].hostPort
  • spec.initContainers[*].ports[*].hostPort
  • spec.ephemeralContainers[*].ports[*].hostPort

允许的值

  • 未定义/nil
  • 已知列表
  • 0
AppArmor

在受支持的主机上,默认使用 runtime/default AppArmor Profile。 基线策略应避免覆盖或者禁用默认策略,以及限制覆盖一些 Profile 集合的权限。

限制的字段

  • metadata.annotations["container.apparmor.security.beta.kubernetes.io/*"]

允许的值

  • 未定义/nil
  • runtime/default
  • localhost/*
SELinux

设置 SELinux 类型的操作是被限制的,设置自定义的 SELinux 用户或角色选项是被禁止的。

限制的字段

  • spec.securityContext.seLinuxOptions.type
  • spec.containers[*].securityContext.seLinuxOptions.type
  • spec.initContainers[*].securityContext.seLinuxOptions.type
  • spec.ephemeralContainers[*].securityContext.seLinuxOptions.type

允许的值

  • 未定义/""
  • container_t
  • container_init_t
  • container_kvm_t

限制的字段

  • spec.securityContext.seLinuxOptions.user
  • spec.containers[*].securityContext.seLinuxOptions.user
  • spec.initContainers[*].securityContext.seLinuxOptions.user
  • spec.ephemeralContainers[*].securityContext.seLinuxOptions.user
  • spec.securityContext.seLinuxOptions.role
  • spec.containers[*].securityContext.seLinuxOptions.role
  • spec.initContainers[*].securityContext.seLinuxOptions.role
  • spec.ephemeralContainers[*].securityContext.seLinuxOptions.role

允许的值

  • 未定义/""
/proc 挂载类型

要求使用默认的 /proc 掩码以减小攻击面。

限制的字段

  • spec.containers[*].securityContext.procMount
  • spec.initContainers[*].securityContext.procMount
  • spec.ephemeralContainers[*].securityContext.procMount

允许的值

  • 未定义/nil
  • Default
Seccomp

Seccomp Profile 禁止被显式设置为 Unconfined

限制的字段

  • spec.securityContext.seccompProfile.type
  • spec.containers[*].securityContext.seccompProfile.type
  • spec.initContainers[*].securityContext.seccompProfile.type
  • spec.ephemeralContainers[*].securityContext.seccompProfile.type

允许的值

  • 未定义/nil
  • RuntimeDefault
  • Localhost
Sysctls

Sysctls 可以禁用安全机制或影响宿主上所有容器,因此除了若干“安全”的子集之外,应该被禁止。 如果某 sysctl 是受容器或 Pod 的名字空间限制,且与节点上其他 Pod 或进程相隔离,可认为是安全的。

限制的字段

  • spec.securityContext.sysctls[*].name

允许的值

  • 未定义/nil
  • kernel.shm_rmid_forced
  • net.ipv4.ip_local_port_range
  • net.ipv4.ip_unprivileged_port_start
  • net.ipv4.tcp_syncookies
  • net.ipv4.ping_group_range

Restricted

Restricted 策略旨在实施当前保护 Pod 的最佳实践,尽管这样作可能会牺牲一些兼容性。 该类策略主要针对运维人员和安全性很重要的应用的开发人员,以及不太被信任的用户。 下面列举的控制需要被实施(禁止):

Restricted 策略规范
控制(Control) 策略(Policy)
基线策略的所有要求。
卷类型

除了限制 HostPath 卷之外,此类策略还限制可以通过 PersistentVolumes 定义的非核心卷类型。

限制的字段

  • spec.volumes[*]

允许的值

spec.volumes[*] 列表中的每个条目必须将下面字段之一设置为非空值:
  • spec.volumes[*].configMap
  • spec.volumes[*].csi
  • spec.volumes[*].downwardAPI
  • spec.volumes[*].emptyDir
  • spec.volumes[*].ephemeral
  • spec.volumes[*].persistentVolumeClaim
  • spec.volumes[*].projected
  • spec.volumes[*].secret
特权提升(v1.8+)

禁止(通过 SetUID 或 SetGID 文件模式)获得特权提升。


限制的字段

  • spec.containers[*].securityContext.allowPrivilegeEscalation
  • spec.initContainers[*].securityContext.allowPrivilegeEscalation
  • spec.ephemeralContainers[*].securityContext.allowPrivilegeEscalation

允许的值

  • false
以非 root 账号运行

必须要求容器以非 root 用户运行。

限制的字段

  • spec.securityContext.runAsNonRoot
  • spec.containers[*].securityContext.runAsNonRoot
  • spec.initContainers[*].securityContext.runAsNonRoot
  • spec.ephemeralContainers[*].securityContext.runAsNonRoot

允许的值

  • true
如果 Pod 级别 spec.securityContext.runAsNonRoot 设置为 true,则允许容器组的安全上下文字段设置为 未定义/nil
非 root 用户(v1.23+)

Containers 不可以将 runAsUser 设置为 0

限制的字段

  • spec.securityContext.runAsUser
  • spec.containers[*].securityContext.runAsUser
  • spec.initContainers[*].securityContext.runAsUser
  • spec.ephemeralContainers[*].securityContext.runAsUser

允许的字段

  • any non-zero value
  • 未定义/空值
Seccomp (v1.19+)

Seccomp Profile 必须被显式设置成一个允许的值。禁止使用 Unconfined Profile 或者指定 不存在的 Profile。

限制的字段

  • spec.securityContext.seccompProfile.type
  • spec.containers[*].securityContext.seccompProfile.type
  • spec.initContainers[*].securityContext.seccompProfile.type
  • spec.ephemeralContainers[*].securityContext.seccompProfile.type

允许的值

  • RuntimeDefault
  • Localhost
如果 Pod 级别的 spec.securityContext.seccompProfile.type 已设置得当,容器级别的安全上下文字段可以为 未定义/nil。 反过来说,如果 _所有的_ 容器级别的安全上下文字段已设置,则 Pod 级别的字段可为 未定义/nil
权能(v1.22+)

容器组必须弃用 ALL 权能,并且只允许添加 NET_BIND_SERVICE 权能。

限制的字段

  • spec.containers[*].securityContext.capabilities.drop
  • spec.initContainers[*].securityContext.capabilities.drop
  • spec.ephemeralContainers[*].securityContext.capabilities.drop

允许的值

  • 包含 ALL 的任何一种权能列表。

限制的字段

  • spec.containers[*].securityContext.capabilities.add
  • spec.initContainers[*].securityContext.capabilities.add
  • spec.ephemeralContainers[*].securityContext.capabilities.add

允许的值

  • 未定义/nil
  • NET_BIND_SERVICE

策略实例化

将策略定义从策略实例中解耦出来有助于形成跨集群的策略理解和语言陈述, 以免绑定到特定的下层实施机制。

随着相关机制的成熟,这些机制会按策略分别定义在下面。特定策略的实施方法不在这里定义。

Pod 安全性准入控制器

PodSecurityPolicy (已弃用)

替代方案

在 Kubernetes 生态系统中还在开发一些其他的替代方案,例如:

常见问题

为什么不存在介于 Privileged 和 Baseline 之间的策略类型

这里定义的三种策略框架有一个明晰的线性递进关系,从最安全(Restricted)到最不安全, 并且覆盖了很大范围的工作负载。特权要求超出 Baseline 策略者通常是特定于应用的需求, 所以我们没有在这个范围内提供标准框架。 这并不意味着在这样的情形下仍然只能使用 Privileged 框架, 只是说处于这个范围的策略需要因地制宜地定义。

SIG Auth 可能会在将来考虑这个范围的框架,前提是有对其他框架的需求。

安全策略与安全上下文的区别是什么?

安全上下文在运行时配置 Pod 和容器。安全上下文是在 Pod 清单中作为 Pod 和容器规约的一部分来定义的, 所代表的是传递给容器运行时的参数。

安全策略则是控制面用来对安全上下文以及安全性上下文之外的参数实施某种设置的机制。 在 2020 年 7 月, Pod 安全性策略已被废弃, 取而代之的是内置的 Pod 安全性准入控制器

我应该为我的 Windows Pod 实施哪种框架?

Kubernetes 中的 Windows 负载与标准的基于 Linux 的负载相比有一些局限性和区别。 尤其是 Pod SecurityContext 字段对 Windows 不起作用。 因此,目前没有对应的标准 Pod 安全性框架。

如果你为一个 Windows Pod 应用了 Restricted 策略,可能会 对该 Pod 的运行时产生影响。 Restricted 策略需要强制执行 Linux 特有的限制(如 seccomp Profile,并且禁止特权提升)。 如果 kubelet 和/或其容器运行时忽略了 Linux 特有的值,那么应该不影响 Windows Pod 正常工作。 然而,对于使用 Windows 容器的 Pod 来说,缺乏强制执行意味着相比于 Restricted 策略,没有任何额外的限制。

你应该只在 Privileged 策略下使用 HostProcess 标志来创建 HostProcess Pod。 在 Baseline 和 Restricted 策略下,创建 Windows HostProcess Pod 是被禁止的, 因此任何 HostProcess Pod 都应该被认为是有特权的。

沙箱(Sandboxed) Pod 怎么处理?

现在还没有 API 标准来控制 Pod 是否被视作沙箱化 Pod。 沙箱 Pod 可以通过其是否使用沙箱化运行时(如 gVisor 或 Kata Container)来辨别, 不过目前还没有关于什么是沙箱化运行时的标准定义。

沙箱化负载所需要的保护可能彼此各不相同。例如,当负载与下层内核直接隔离开来时, 限制特权化操作的许可就不那么重要。这使得那些需要更多许可权限的负载仍能被有效隔离。

此外,沙箱化负载的保护高度依赖于沙箱化的实现方法。 因此,现在还没有针对所有沙箱化负载的建议策略。

3 - Pod 安全性准入

对 Pod 安全性准入控制器的概述,Pod 安全性准入控制器可以实施 Pod 安全性标准。
特性状态: Kubernetes v1.23 [beta]

Kubernetes Pod 安全性标准(Security Standards) 为 Pod 定义不同的隔离级别。这些标准能够让你以一种清晰、一致的方式定义如何限制 Pod 行为。

作为一项 Beta 功能特性,Kubernetes 提供一种内置的 Pod 安全性 准入控制器, 作为 PodSecurityPolicies 特性的后继演化版本。Pod 安全性限制是在 Pod 被创建时在 名字空间层面实施的。

准备开始

要使用此机制,你的集群必须强制执行 Pod 安全准入。

内置 Pod 安全准入强制执行

在 Kubernetes v1.24 中,PodSecurity 特性门控是一项 Beta 特性, 默认被启用。你必须启用此功能门控。如果你运行的是不同版本的 Kubernetes,请查阅该版本的文档。

替代方案:安装 PodSecurity 准入 Webhook

PodSecurity 准入逻辑也可用作验证性准入 Webhook。 该实现也是 Beta 版本。 对于无法启用内置 PodSecurity 准入插件的环境,你可以改为通过验证准入 Webhook 启用该逻辑。

https://git.k8s.io/pod-security-admission/webhook 上可以找到一个预先构建的容器镜像、证书生成脚本以及一些示例性质的清单。

git clone git@github.com:kubernetes/pod-security-admission.git
cd pod-security-admission/webhook
make certs
kubectl apply -k .

Pod 安全性级别

Pod 安全性准入插件对 Pod 的安全性上下文 有一定的要求,并且依据 Pod 安全性标准 所定义的三个级别(privilegedbaselinerestricted)对其他字段也有要求。 关于这些需求的更进一步讨论,请参阅 Pod 安全性标准页面。

为名字空间设置 Pod 安全性准入控制标签

一旦特性被启用或者安装了 Webhook,你可以配置名字空间以定义每个名字空间中 Pod 安全性准入控制模式。 Kubernetes 定义了一组标签, 你可以设置这些标签来定义某个名字空间上要使用的预定义的 Pod 安全性标准级别。 你所选择的标签定义了检测到潜在违例时,控制面 要采取什么样的动作。

Pod 安全准入模式
模式 描述
enforce 策略违例会导致 Pod 被拒绝
audit 策略违例会触发审计日志中记录新事件时添加审计注解;但是 Pod 仍是被接受的。
warn 策略违例会触发用户可见的警告信息,但是 Pod 仍是被接受的。

名字空间可以配置任何一种或者所有模式,或者甚至为不同的模式设置不同的级别。

对于每种模式,决定所使用策略的标签有两个:

# 针对模式的级别标签用来标示针对该模式所应用的策略级别
#
# MODE 必须是 `enforce`、`audit` 或 `warn` 之一
# LEVEL 必须是 `privileged`、baseline` 或 `restricted` 之一
pod-security.kubernetes.io/<MODE>: <LEVEL>

# 可选:针对每个模式版本的版本标签可以将策略锁定到
# 给定 Kubernetes 小版本号所附带的版本(例如 v1.24)
#
# MODE 必须是 `enforce`、`audit` 或 `warn` 之一
# VERSION 必须是一个合法的 Kubernetes 小版本号或者 `latest`
pod-security.kubernetes.io/<MODE>-version: <VERSION>

关于用法示例,可参阅 使用名字空间标签来强制实施 Pod 安全标准

负载资源和 Pod 模板

Pod 通常是通过创建 DeploymentJob 这类工作负载对象 来间接创建的。工作负载对象为工作负载资源定义一个 Pod 模板 和一个对应的 负责基于该模板来创建 Pod 的控制器。 为了尽早地捕获违例状况,auditwarn 模式都应用到负载资源。 不过,enforce 模式并 应用到工作负载资源,仅应用到所生成的 Pod 对象上。

豁免

你可以为 Pod 安全性的实施设置 豁免(Exemptions) 规则, 从而允许创建一些本来会被与给定名字空间相关的策略所禁止的 Pod。 豁免规则可以在准入控制器配置 中静态配置。

豁免规则可以显式枚举。满足豁免标准的请求会被准入控制器 忽略 (所有 enforceauditwarn 行为都会被略过)。 豁免的维度包括:

  • Username: 来自用户名已被豁免的、已认证的(或伪装的)的用户的请求会被忽略。
  • RuntimeClassName: 指定了已豁免的运行时类名称的 Pod 和负载资源会被忽略。
  • Namespace: 位于被豁免的名字空间中的 Pod 和负载资源 会被忽略。

策略检查时会对以下 Pod 字段的更新操作予以豁免,这意味着如果 Pod 更新请求仅改变这些字段时,即使 Pod 违反了当前的策略级别,请求也不会被拒绝。

  • 除了对 seccomp 或 AppArmor 注解之外的所有 meatadata 更新操作:
    • seccomp.security.alpha.kubernetes.io/pod (已弃用)
    • container.seccomp.security.alpha.kubernetes.io/* (已弃用)
    • container.apparmor.security.beta.kubernetes.io/*
  • .spec.activeDeadlineSeconds 的合法更新
  • .spec.tolerations 的合法更新

接下来

4 - Pod 安全策略

特性状态: Kubernetes v1.21 [deprecated]

Pod 安全策略使得对 Pod 创建和更新进行细粒度的权限控制成为可能。

什么是 Pod 安全策略?

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

控制的角度 字段名称
运行特权容器 privileged
使用宿主名字空间 hostPIDhostIPC
使用宿主的网络和端口 hostNetwork, hostPorts
控制卷类型的使用 volumes
使用宿主文件系统 allowedHostPaths
允许使用特定的 FlexVolume 驱动 allowedFlexVolumes
分配拥有 Pod 卷的 FSGroup 账号 fsGroup
以只读方式访问根文件系统 readOnlyRootFilesystem
设置容器的用户和组 ID runAsUser, runAsGroup, supplementalGroups
限制 root 账号特权级提升 allowPrivilegeEscalation, defaultAllowPrivilegeEscalation
Linux 权能字(Capabilities) defaultAddCapabilities, requiredDropCapabilities, allowedCapabilities
设置容器的 SELinux 上下文 seLinux
指定容器可以挂载的 proc 类型 allowedProcMountTypes
指定容器使用的 AppArmor 模版 annotations
指定容器使用的 seccomp 模版 annotations
指定容器使用的 sysctl 模版 forbiddenSysctls,allowedUnsafeSysctls

启用 Pod 安全策略

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

由于 Pod 安全策略 API(policy/v1beta1/podsecuritypolicy)是独立于准入控制器 来启用的,对于现有集群而言,建议在启用准入控制器之前先添加策略并对其授权。

授权策略

PodSecurityPolicy 资源被创建时,并不执行任何操作。为了使用该资源, 需要对发出请求的用户或者目标 Pod 的服务账号授权, 通过允许其对策略执行 use 动词允许其使用该策略。

大多数 Kubernetes Pod 不是由用户直接创建的。相反,这些 Pod 是由 DeploymentReplicaSet 或者经由控制器管理器模版化的控制器创建。 赋予控制器访问策略的权限意味着对应控制器所创建的 所有 Pod 都可访问策略。 因此,对策略进行授权的优先方案是为 Pod 的服务账号授予访问权限 (参见示例)。

通过 RBAC 授权

RBAC 是一种标准的 Kubernetes 鉴权模式,可以很容易地用来授权策略访问。

首先,某 RoleClusterRole 需要获得使用 use 访问目标策略的权限。 访问授权的规则看起来像这样:

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

接下来将该 Role(或 ClusterRole)绑定到授权的用户:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: <绑定名称>
roleRef:
  kind: ClusterRole
  name: <角色名称>
  apiGroup: rbac.authorization.k8s.io
subjects:
  # 授权命名空间下的所有服务账号(推荐):
  - kind: Group
    apiGroup: rbac.authorization.k8s.io
    name: system:serviceaccounts:<authorized namespace>
  # 授权特定的服务账号(不建议这样操作):
  - kind: ServiceAccount
    name: <被授权的服务账号名称>
    namespace: <被授权的 Pod 名字空间>
  # 授权特定的用户(不建议这样操作):
  - kind: User
    apiGroup: rbac.authorization.k8s.io
    name: <被授权的用户名>

如果使用的是 RoleBinding(而不是 ClusterRoleBinding),授权仅限于与该 RoleBinding 处于同一名字空间中的 Pod。 可以考虑将这种授权模式和系统组结合,对名字空间中的所有 Pod 授予访问权限。

# 授权某名字空间中所有服务账号
- kind: Group
  apiGroup: rbac.authorization.k8s.io
  name: system:serviceaccounts
# 或者与此等价,授权给某名字空间中所有被认证过的用户
- kind: Group
  apiGroup: rbac.authorization.k8s.io
  name: system:authenticated

参阅角色绑定示例查看 RBAC 绑定的更多实例。 参阅下文,查看对 PodSecurityPolicy 进行授权的完整示例。

PodSecurityPolicy 正在被一个新的、简化的 PodSecurity 准入控制器替代。 有关此变更的更多详细信息,请参阅 PodSecurityPolicy Deprecation: Past, Present, and Future。 参照下述指导,简化从 PodSecurityPolicy 迁移到新的准入控制器步骤:

  1. 将 PodSecurityPolicies 限制为 Pod 安全性标准所定义的策略:

  1. 通过配置 system:serviceaccounts:<namespace> 组(<namespace> 是目标名字空间), 仅将 PSP 绑定到整个命名空间。示例:

    apiVersion: rbac.authorization.k8s.io/v1
    # 此集群角色绑定允许 "development" 名字空间中的所有 Pod 使用 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 端口之上,并且不能拥有超级用户的访问权限。 参阅控制 Kubernetes API 的访问以了解 API 服务器的访问控制。

    如果控制器管理器通过可信的 API 端口连接(也称作 localhost 监听组件), 其请求会绕过身份认证和鉴权模块控制,从而导致所有 PodSecurityPolicy 对象都被允许, 用户亦能授予自身创建特权容器的特权。

    关于配置控制器管理器鉴权的进一步细节, 请参阅控制器角色

策略顺序

除了限制 Pod 创建与更新,Pod 安全策略也可用来为其所控制的很多字段设置默认值。 当存在多个策略对象时,Pod 安全策略控制器依据以下条件选择策略:

  1. 优先考虑允许 Pod 保持原样,不会更改 Pod 字段默认值或其他配置的 PodSecurityPolicy。 这类非更改性质的 PodSecurityPolicy 对象之间的顺序无关紧要。
  2. 如果必须要为 Pod 设置默认值或者其他配置,(按名称顺序)选择第一个允许 Pod 操作的 PodSecurityPolicy 对象。

示例

本示例假定你已经有一个启动了 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

创建两个别名,以更清晰地展示我们所使用的用户账号,同时减少一些键盘输入:

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 子域名

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(使用)示例策略的权限:

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 creating: pods "pause-7774d79b5-" is forbidden: no providers available to validate pod request (创建错误:pods "pause-7774d79b5" 被禁止:没有可用来验证 pod 请求的驱动)? 答案在于源文件 - replicaset-controllerfake-user 用户成功地创建了 Deployment,而后者也成功地创建了 ReplicaSet, 不过当 ReplicaSet 创建 Pod 时,发现未被授权使用示例 PodSecurityPolicy 资源。

为了修复这一问题,将 psp:unprivileged 角色绑定到 Pod 的服务账号。 在这里,因为我们没有给出服务账号名称,默认的服务账号是 default

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

输出为:

rolebinding "default:psp:unprivileged" created

现在如果你给 ReplicaSet 控制器一分钟的时间来重试,该控制器最终将能够 成功地创建 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 标识 seccomp 的配置文件,但它与 Docker 运行时没有特别关联
    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
  # 防止权限升级到 root
  allowPrivilegeEscalation: false
  requiredDropCapabilities:
    - ALL
  # 允许的核心卷类型.
  volumes:
    - 'configMap'
    - 'emptyDir'
    - 'projected'
    - 'secret'
    - 'downwardAPI'
    # 假设集群管理员设置的临时 CSI 驱动程序和持久卷可以安全使用
    - 'csi'
    - 'persistentVolumeClaim'
    - 'ephemeral'
  hostNetwork: false
  hostIPC: false
  hostPID: false
  runAsUser:
    # 要求容器在没有 root 权限的情况下运行
    rule: 'MustRunAsNonRoot'
  seLinux:
    # 此策略假定节点使用 AppArmor 而不是 SELinux
    rule: 'RunAsAny'
  supplementalGroups:
    rule: 'MustRunAs'
    ranges:
      # 禁止添加 root 组
      - min: 1
        max: 65535
  fsGroup:
    rule: 'MustRunAs'
    ranges:
      # 禁止添加 root 组
      - min: 1
        max: 65535
  readOnlyRootFilesystem: false

更多的示例可参考 Pod 安全标准

策略参考

Privileged

Privileged - 决定是否 Pod 中的某容器可以启用特权模式。 默认情况下,容器是不可以访问宿主上的任何设备的,不过一个“privileged(特权的)” 容器则被授权访问宿主上所有设备。 这种容器几乎享有宿主上运行的进程的所有访问权限。 对于需要使用 Linux 权能字(如操控网络堆栈和访问设备)的容器而言是有用的。

宿主名字空间

HostPID - 控制 Pod 中容器是否可以共享宿主上的进程 ID 空间。 注意,如果与 ptrace 相结合,这种授权可能被利用,导致向容器外的特权逃逸 (默认情况下 ptrace 是被禁止的)。

HostIPC - 控制 Pod 容器是否可共享宿主上的 IPC 名字空间。

HostNetwork - 控制是否 Pod 可以使用节点的网络名字空间。 此类授权将允许 Pod 访问本地回路(loopback)设备、在本地主机(localhost) 上监听的服务、还可能用来监听同一节点上其他 Pod 的网络活动。

HostPorts -提供可以在宿主网络名字空间中可使用的端口范围列表。 该属性定义为一组 HostPortRange 对象的列表,每个对象中包含 min(含)与 max(含)值的设置。 默认不允许访问宿主端口。

卷和文件系统

Volumes - 提供一组被允许的卷类型列表。可被允许的值对应于创建卷时可以设置的卷来源。 卷类型的完整列表可参见卷类型。 此外,* 可以用来允许所有卷类型。

对于新的 Pod 安全策略设置而言,建议设置的卷类型的最小列表包含:

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

FSGroup - 控制应用到某些卷上的附加用户组。

  • MustRunAs - 要求至少指定一个 range。 使用范围中的最小值作为默认值。所有 range 值都会被用来执行验证。
  • MayRunAs - 要求至少指定一个 range。 允许不设置 FSGroups,且无默认值。 如果 FSGroup 被设置,则所有 range 值都会被用来执行验证检查。
  • RunAsAny - 不提供默认值。允许设置任意 fsGroup ID 值。

AllowedHostPaths - 设置一组宿主文件目录,这些目录项可以在 hostPath 卷中使用。 列表为空意味着对所使用的宿主目录没有限制。 此选项定义包含一个对象列表,表中对象包含 pathPrefix 字段,用来表示允许 hostPath 卷挂载以所指定前缀开头的路径。 对象中还包含一个 readOnly 字段,用来表示对应的卷必须以只读方式挂载。 例如:

allowedHostPaths:
  # 下面的设置允许 "/foo"、"/foo/"、"/foo/bar" 等路径,但禁止
  # "/fool"、"/etc/foo" 这些路径。
  # "/foo/../" 总会被当作非法路径。
  - pathPrefix: "/foo"
    readOnly: true # 仅允许只读模式挂载

ReadOnlyRootFilesystem - 要求容器必须以只读方式挂载根文件系统来运行 (即不允许存在可写入层)。

FlexVolume 驱动

此配置指定一个可以被 FlexVolume 卷使用的驱动程序的列表。 空的列表或者 nil 值意味着对驱动没有任何限制。 请确保volumes 字段包含了 flexVolume 卷类型, 否则所有 FlexVolume 驱动都被禁止。

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

用户和组

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

  • MustRunAs - 必须至少设置一个 range。使用该范围内的第一个值作为默认值。 所有范围值都会被验证检查。

  • MustRunAsNonRoot - 要求提交的 Pod 具有非零 runAsUser 值,或在镜像中 (使用 UID 数值)定义了 USER 环境变量。 如果 Pod 既没有设置 runAsNonRoot,也没有设置 runAsUser,则该 Pod 会被修改以设置 runAsNonRoot=true,从而要求容器通过 USER 指令给出非零的数值形式的用户 ID。 此配置没有默认值。采用此配置时,强烈建议设置 allowPrivilegeEscalation=false

  • RunAsAny - 没有提供默认值。允许指定任何 runAsUser 配置。

RunAsGroup - 控制运行容器时使用的主用户组 ID。

  • MustRunAs - 要求至少指定一个 range 值。第一个范围中的最小值作为默认值。 所有范围值都被用来执行验证检查。
  • MayRunAs - 不要求设置 RunAsGroup。 不过,如果指定了 RunAsGroup 被设置,所设置值必须处于所定义的范围内。
  • RunAsAny - 未指定默认值。允许 runAsGroup 设置任何值。

SupplementalGroups - 控制容器可以添加的组 ID。

  • MustRunAs - 要求至少指定一个 range 值。第一个范围中的最小值用作默认值。 所有范围值都被用来执行验证检查。
  • MayRunAs - 要求至少指定一个 range 值。 允许不指定 supplementalGroups 且不设置默认值。 如果 supplementalGroups 被设置,则所有 range 值都被用来执行验证检查。
  • RunAsAny - 未指定默认值。允许为 supplementalGroups 设置任何值。

特权提升

这一组选项控制容器的allowPrivilegeEscalation 属性。该属性直接决定是否为容器进程设置 no_new_privs 参数。此参数会禁止 setuid 属性的可执行文件更改有效用户 ID(EUID), 并且禁止启用额外权能的文件。例如,no_new_privs 会禁止使用 ping 工具。 如果想有效地实施 MustRunAsNonRoot 控制,需要配置这一选项。

AllowPrivilegeEscalation - 决定是否用户可以将容器的安全上下文设置为 allowPrivilegeEscalation=true。默认设置下,这样做是允许的, 目的是避免造成现有的 setuid 应用无法运行。将此选项设置为 false 可以确保容器的所有子进程都无法获得比父进程更多的特权。

DefaultAllowPrivilegeEscalation - 为 allowPrivilegeEscalation 选项设置默认值。 不设置此选项时的默认行为是允许特权提升,以便运行 setuid 程序。 如果不希望运行 setuid 程序,可以使用此字段将选项的默认值设置为禁止, 同时仍然允许 Pod 显式地请求 allowPrivilegeEscalation

权能字

Linux 权能字(Capabilities)将传统上与超级用户相关联的特权作了细粒度的分解。 其中某些权能字可以用来提升特权,打破容器边界,可以通过 PodSecurityPolicy 来限制。 关于 Linux 权能字的更多细节,可参阅 capabilities(7)

下列字段都可以配置为权能字的列表。表中的每一项都是 ALL_CAPS 中的一个权能字名称, 只是需要去掉 CAP_ 前缀。

AllowedCapabilities - 给出可以被添加到容器的权能字列表。 默认的权能字集合是被隐式允许的那些。空集合意味着只能使用默认权能字集合, 不允许添加额外的权能字。* 可以用来设置允许所有权能字。

RequiredDropCapabilities - 必须从容器中去除的权能字。 所给的权能字会从默认权能字集合中去除,并且一定不可以添加。 RequiredDropCapabilities 中列举的权能字不能出现在 AllowedCapabilitiesDefaultAddCapabilities 所给的列表中。

DefaultAddCapabilities - 默认添加到容器的权能字集合。 这一集合是作为容器运行时所设值的补充。 关于使用 Docker 容器运行引擎时默认的权能字列表, 可参阅你的容器运行时的文档来了解使用 Linux 权能字的信息。

SELinux

  • MustRunAs - 要求必须配置 seLinuxOptions。默认使用 seLinuxOptions。 针对 seLinuxOptions 所给值执行验证检查。
  • RunAsAny - 没有提供默认值。允许任意指定的 seLinuxOptions 选项。

AllowedProcMountTypes

allowedProcMountTypes 是一组可以允许的 proc 挂载类型列表。 空表或者 nil 值表示只能使用 DefaultProcMountType

DefaultProcMount 使用容器运行时的默认值设置来决定 /proc 的只读挂载模式和路径屏蔽。 大多数容器运行时都会屏蔽 /proc 下面的某些路径以避免特殊设备或信息被不小心暴露给容器。 这一配置使所有 Default 字符串值来表示。

此外唯一的ProcMountType 是 UnmaskedProcMount,意味着即将绕过容器运行时的路径屏蔽行为, 确保新创建的 /proc 不会被容器修改。此配置用字符串 Unmasked 来表示。

AppArmor

通过 PodSecurityPolicy 上的注解来控制。 详情请参阅 AppArmor 文档

Seccomp

从 Kubernetes v1.19 开始,你可以使用 Pod 或容器的 securityContext 中的 seccompProfile 字段来控制 seccomp 配置的使用。 在更早的版本中,seccomp 是通过为 Pod 添加注解来控制的。 相同的 PodSecurityPolicy 可以用于不同版本,进而控制如何应用对应的字段或注解。

seccomp.security.alpha.kubernetes.io/defaultProfileName - 注解用来指定为容器配置默认的 seccomp 模版。可选值为:

  • unconfined - 如果没有指定其他替代方案,Seccomp 不会被应用到容器进程上 (Kubernets 中的默认设置)。

  • runtime/default - 使用默认的容器运行时模版。

  • docker/default - 使用 Docker 的默认 seccomp 模版。自 1.11 版本废弃。 应改为使用 runtime/default

  • localhost/<路径名> - 指定节点上路径 <seccomp_root>/<路径名> 下的一个文件作为其模版。 其中 <seccomp_root> 是通过 kubelet 的标志 --seccomp-profile-root 来指定的。 如果未定义 --seccomp-profile-root 标志,则使用默认的路径 <root-dir>/seccomp, 其中 <root-dir> 是通过 --root-dir 标志来设置的。

seccomp.security.alpha.kubernetes.io/allowedProfileNames - 指定可以为 Pod seccomp 注解配置的值的注解。取值为一个可用值的列表。 表中每项可以是上述各值之一,还可以是 *,用来表示允许所有的模版。 如果没有设置此注解,意味着默认的 seccomp 模版是不可更改的。

Sysctl

默认情况下,所有的安全的 sysctl 都是被允许的。

  • forbiddenSysctls - 用来排除某些特定的 sysctl。 你可以在此列表中禁止一些安全的或者不安全的 sysctl。 此选项设置为 * 意味着禁止设置所有 sysctl。
  • allowedUnsafeSysctls - 用来启用那些被默认列表所禁用的 sysctl, 前提是所启用的 sysctl 没有被列在 forbiddenSysctls 中。

参阅 Sysctl 文档

接下来

5 - Windows 节点的安全性

本篇介绍特定于 Windows 操作系统的安全注意事项和最佳实践。

保护节点上的 Secret 数据

在 Windows 上,来自 Secret 的数据以明文形式写入节点的本地存储 (与在 Linux 上使用 tmpfs / 内存中文件系统不同)。 作为集群操作员,你应该采取以下两项额外措施:

  1. 使用文件 ACL 来保护 Secret 的文件位置。
  2. 使用 BitLocker 进行卷级加密。

容器用户

可以为 Windows Pod 或容器指定 RunAsUsername 以作为特定用户执行容器进程。这大致相当于 RunAsUser

Windows 容器提供两个默认用户帐户,ContainerUser 和 ContainerAdministrator。 在微软的 Windows 容器安全文档 何时使用 ContainerAdmin 和 ContainerUser 用户帐户 中介绍了这两个用户帐户之间的区别。

在容器构建过程中,可以将本地用户添加到容器镜像中。

Windows 容器还可以通过使用组管理的服务账号作为 Active Directory 身份运行。

Pod 级安全隔离

Windows 节点不支持特定于 Linux 的 Pod 安全上下文机制(例如 SELinux、AppArmor、Seccomp 或自定义 POSIX 权能字)。

Windows 上不支持特权容器。 然而,可以在 Windows 上使用 HostProcess 容器来执行 Linux 上特权容器执行的许多任务。

6 - Kubernetes API 访问控制

本页面概述了对 Kubernetes API 的访问控制。

用户使用 kubectl、客户端库或构造 REST 请求来访问 Kubernetes API。 人类用户和 Kubernetes 服务账户都可以被鉴权访问 API。 当请求到达 API 时,它会经历多个阶段,如下图所示:

Kubernetes API 请求处理步骤示意图

传输安全

在典型的 Kubernetes 集群中,API 服务器在 443 端口上提供服务,受 TLS 保护。 API 服务器出示证书。 该证书可以使用私有证书颁发机构(CA)签名,也可以基于链接到公认的 CA 的公钥基础架构签名。

如果你的集群使用私有证书颁发机构,你需要在客户端的 ~/.kube/config 文件中提供该 CA 证书的副本, 以便你可以信任该连接并确认该连接没有被拦截。

你的客户端可以在此阶段出示 TLS 客户端证书。

认证

如上图步骤 1 所示,建立 TLS 后, HTTP 请求将进入认证(Authentication)步骤。 集群创建脚本或者集群管理员配置 API 服务器,使之运行一个或多个身份认证组件。 身份认证组件在认证节中有更详细的描述。

认证步骤的输入整个 HTTP 请求;但是,通常组件只检查头部或/和客户端证书。

认证模块包含客户端证书、密码、普通令牌、引导令牌和 JSON Web 令牌(JWT,用于服务账户)。

可以指定多个认证模块,在这种情况下,服务器依次尝试每个验证模块,直到其中一个成功。

如果请求认证不通过,服务器将以 HTTP 状态码 401 拒绝该请求。 反之,该用户被认证为特定的 username,并且该用户名可用于后续步骤以在其决策中使用。 部分验证器还提供用户的组成员身份,其他则不提供。

鉴权

如上图的步骤 2 所示,将请求验证为来自特定的用户后,请求必须被鉴权。

请求必须包含请求者的用户名、请求的行为以及受该操作影响的对象。 如果现有策略声明用户有权完成请求的操作,那么该请求被鉴权通过。

例如,如果 Bob 有以下策略,那么他只能在 projectCaribou 名称空间中读取 Pod。

{
    "apiVersion": "abac.authorization.kubernetes.io/v1beta1",
    "kind": "Policy",
    "spec": {
        "user": "bob",
        "namespace": "projectCaribou",
        "resource": "pods",
        "readonly": true
    }
}

如果 Bob 执行以下请求,那么请求会被鉴权,因为允许他读取 projectCaribou 名称空间中的对象。

{
  "apiVersion": "authorization.k8s.io/v1beta1",
  "kind": "SubjectAccessReview",
  "spec": {
    "resourceAttributes": {
      "namespace": "projectCaribou",
      "verb": "get",
      "group": "unicorn.example.org",
      "resource": "pods"
    }
  }
}

如果 Bob 在 projectCaribou 名字空间中请求写(createupdate)对象,其鉴权请求将被拒绝。 如果 Bob 在诸如 projectFish 这类其它名字空间中请求读取(get)对象,其鉴权也会被拒绝。

Kubernetes 鉴权要求使用公共 REST 属性与现有的组织范围或云提供商范围的访问控制系统进行交互。 使用 REST 格式很重要,因为这些控制系统可能会与 Kubernetes API 之外的 API 交互。

Kubernetes 支持多种鉴权模块,例如 ABAC 模式、RBAC 模式和 Webhook 模式等。 管理员创建集群时,他们配置应在 API 服务器中使用的鉴权模块。 如果配置了多个鉴权模块,则 Kubernetes 会检查每个模块,任意一个模块鉴权该请求,请求即可继续; 如果所有模块拒绝了该请求,请求将会被拒绝(HTTP 状态码 403)。

要了解更多有关 Kubernetes 鉴权的更多信息,包括有关使用支持鉴权模块创建策略的详细信息, 请参阅鉴权

准入控制

准入控制模块是可以修改或拒绝请求的软件模块。 除鉴权模块可用的属性外,准入控制模块还可以访问正在创建或修改的对象的内容。

准入控制器对创建、修改、删除或(通过代理)连接对象的请求进行操作。 准入控制器不会对仅读取对象的请求起作用。 有多个准入控制器被配置时,服务器将依次调用它们。

这一操作如上图的步骤 3 所示。

与身份认证和鉴权模块不同,如果任何准入控制器模块拒绝某请求,则该请求将立即被拒绝。

除了拒绝对象之外,准入控制器还可以为字段设置复杂的默认值。

可用的准入控制模块在准入控制器中进行了描述。

请求通过所有准入控制器后,将使用检验例程检查对应的 API 对象,然后将其写入对象存储(如步骤 4 所示)。

审计

Kubernetes 审计提供了一套与安全相关的、按时间顺序排列的记录,其中记录了集群中的操作序列。 集群对用户、使用 Kubernetes API 的应用程序以及控制平面本身产生的活动进行审计。

更多信息请参考 审计.

API 服务器端口和 IP

前面的讨论适用于发送到 API 服务器的安全端口的请求(典型情况)。 API 服务器实际上可以在 2 个端口上提供服务:

默认情况下,Kubernetes API 服务器在 2 个端口上提供 HTTP 服务:

  1. localhost 端口:

    • 用于测试和引导,以及主控节点上的其他组件(调度器,控制器管理器)与 API 通信
    • 没有 TLS
    • 默认为端口 8080
    • 默认 IP 为 localhost,使用 --insecure-bind-address 进行更改
    • 请求 绕过 身份认证和鉴权模块
    • 由准入控制模块处理的请求
    • 受需要访问主机的保护
  2. “安全端口”:

    • 尽可能使用
    • 使用 TLS。 用 --tls-cert-file 设置证书,用 --tls-private-key-file 设置密钥
    • 默认端口 6443,使用 --secure-port 更改
    • 默认 IP 是第一个非本地网络接口,使用 --bind-address 更改
    • 请求须经身份认证和鉴权组件处理
    • 请求须经准入控制模块处理
    • 身份认证和鉴权模块运行

接下来

阅读更多有关身份认证、鉴权和 API 访问控制的文档:

你可以了解

  • Pod 如何使用 Secrets 获取 API 凭证.

7 - 基于角色的访问控制良好实践

为集群操作人员提供的良好的 RBAC 设计原则和实践。

Kubernetes RBAC 是一项重要的安全控制措施,用于保证集群用户和工作负载只能访问履行自身角色所需的资源。 在为集群用户设计权限时,请务必确保集群管理员知道可能发生特权提级的地方, 降低因过多权限而导致安全事件的风险。

此文档的良好实践应该与通用 RBAC 文档一起阅读。

通用的良好实践

最小特权

理想情况下,分配给用户和服务帐户的 RBAC 权限应该是最小的。 仅应使用操作明确需要的权限,虽然每个集群会有所不同,但可以应用的一些常规规则:

  • 尽可能在命名空间级别分配权限。授予用户在特定命名空间中的权限时使用 RoleBinding 而不是 ClusterRoleBinding。
  • 尽可能避免通过通配符设置权限,尤其是对所有资源的权限。 由于 Kubernetes 是一个可扩展的系统,因此通过通配符来授予访问权限不仅会授予集群中当前的所有对象类型, 还包含所有未来被创建的所有对象类型。
  • 管理员不应使用 cluster-admin 账号,除非特别需要。为低特权帐户提供 伪装权限 可以避免意外修改集群资源。
  • 避免将用户添加到 system:masters 组。任何属于此组成员的用户都会绕过所有 RBAC 权限检查, 始终具有不受限制的超级用户访问权限,并且不能通过删除 RoleBindingClusterRoleBinding 来取消其权限。顺便说一句,如果集群是使用 Webhook 鉴权,此组的成员身份也会绕过该 Webhook(来自属于该组成员的用户的请求永远不会发送到 Webhook)。

最大限度地减少特权令牌的分发

理想情况下,不应为 Pod 分配具有强大权限(例如,在特权提级的风险中列出的任一权限)的服务帐户。 如果工作负载需要比较大的权限,请考虑以下做法:

  • 限制运行此类 Pod 的节点数量。确保你运行的任何 DaemonSet 都是必需的, 并且以最小权限运行,以限制容器逃逸的影响范围。
  • 避免将此类 Pod 与不可信任或公开的 Pod 在一起运行。 考虑使用污点和容忍度节点亲和性Pod 反亲和性确保 Pod 不会与不可信或不太受信任的 Pod 一起运行。 特别注意可信度不高的 Pod 不符合 Restricted Pod 安全标准的情况。

加固

Kubernetes 默认提供访问权限并非是每个集群都需要的。 审查默认提供的 RBAC 权限为安全加固提供了机会。 一般来说,不应该更改 system: 帐户的某些权限,有一些方式来强化现有集群的权限:

  • 审查 system:unauthenticated 组的绑定,并在可能的情况下将其删除, 因为这会给所有能够访问 API 服务器的人以网络级别的权限。
  • 通过设置 automountServiceAccountToken: false 来避免服务账号令牌的默认自动挂载, 有关更多详细信息,请参阅使用默认服务账号令牌。 此参数可覆盖 Pod 服务账号设置,而需要服务账号令牌的工作负载仍可以挂载。

定期检查

定期检查 Kubernetes RBAC 设置是否有冗余条目和提权可能性是至关重要的。 如果攻击者能够创建与已删除用户同名的用户账号, 他们可以自动继承被删除用户的所有权限,尤其是分配给该用户的权限。

Kubernetes RBAC - 权限提权的风险

在 Kubernetes RBAC 中有许多特权,如果被授予, 用户或服务帐户可以提升其在集群中的权限并可能影响集群外的系统。

本节旨在提醒集群操作员需要注意的不同领域, 以确保他们不会无意中授予超出预期的集群访问权限。

列举 Secret

大家都很清楚,若允许对 Secrets 执行 get 访问,用户就获得了访问 Secret 内容的能力。 同样需要注意的是:listwatch 访问也会授权用户获取 Secret 的内容。 例如,当返回 List 响应时(例如,通过 kubectl get secrets -A -o yaml),响应包含所有 Secret 的内容。

工作负载的创建

能够创建工作负载的用户(Pod 或管理 Pod 的工作负载资源) 能够访问下层的节点,除非基于 Kubernetes 的 Pod 安全标准做限制。

可以运行特权 Pod 的用户可以利用该访问权限获得节点访问权限, 并可能进一步提升他们的特权。如果你不完全信任某用户或其他主体, 不相信他们能够创建比较安全且相互隔离的 Pod,你应该强制实施 BaselineRestricted Pod 安全标准。 你可以使用 Pod 安全性准入或其他(第三方)机制来强制实施这些限制。

你还可以使用已弃用的 PodSecurityPolicy 机制以限制用户创建特权 Pod 的能力 (特别注意:PodSecurityPolicy 已计划在版本 1.25 中删除)。

在命名空间中创建工作负载还会授予对该命名空间中 Secret 的间接访问权限。 在 kube-system 或类似特权的命名空间中创建 Pod 可以授予用户不需要通过 RBAC 即可获取 Secret 访问权限。

持久卷的创建

PodSecurityPolicy 文档中所述,创建 PersistentVolumes 的权限可以提权访问底层主机。 如果需要访问 PersistentVolume,受信任的管理员应该创建 PersistentVolume, 受约束的用户应该使用 PersistentVolumeClaim 访问该存储。

访问 Node 的 proxy 子资源

有权访问 Node 对象的 proxy 子资源的用户有权访问 Kubelet API, 这允许在他们有权访问的节点上的所有 Pod 上执行命令。 此访问绕过审计日志记录和准入控制,因此在授予对此资源的权限前应小心。

esclate 动词

通常,RBAC 系统会阻止用户创建比他所拥有的更多权限的 ClusterRole。 而 escalate 动词是个例外。如 RBAC 文档 中所述,拥有此权限的用户可以有效地提升他们的权限。

bind 动词

escalate 动作类似,授予此权限的用户可以绕过 Kubernetes 对权限提升的内置保护,用户可以创建并绑定尚不具有的权限的角色。

impersonate 动词

此动词允许用户伪装并获得集群中其他用户的权限。 授予它时应小心,以确保通过其中一个伪装账号不会获得过多的权限。

CSR 和证书颁发

CSR API 允许用户拥有 create CSR 的权限和 update certificatesigningrequests/approval 的权限, 其中签名者是 kubernetes.io/kube-apiserver-client, 通过此签名创建的客户端证书允许用户向集群进行身份验证。 这些客户端证书可以包含任意的名称,包括 Kubernetes 系统组件的副本。 这将有利于特权提级。

令牌请求

拥有 serviceaccounts/tokencreate 权限的用户可以创建 TokenRequest 来发布现有服务帐户的令牌。

控制准入 Webhook

可以控制 validatingwebhookconfigurationsmutatingwebhookconfigurations 的用户可以控制能读取任何允许进入集群的对象的 webhook, 并且在有变更 webhook 的情况下,还可以变更准入的对象。

Kubernetes RBAC - 拒绝服务攻击的风险

对象创建拒绝服务

有权在集群中创建对象的用户根据创建对象的大小和数量可能会创建足够大的对象, 产生拒绝服务状况,如 Kubernetes 使用的 etcd 容易受到 OOM 攻击中的讨论。 允许太不受信任或者不受信任的用户对系统进行有限的访问在多租户集群中是特别重要的。

缓解此问题的一种选择是使用资源配额以限制可以创建的对象数量。