本文介绍如何保护容器对 Azure Kubernetes 服务 (AKS) 工作负载资源的访问。
概述
与应该向用户或组授予所需最少权限的方式一样,也应将容器限制为只能访问它们所需的操作和进程。 为了尽量减少攻击风险,避免配置需要提升的权限或 root 访问权限的应用程序和容器。
可以使用内置的 Kubernetes Pod 安全上下文来定义更多权限,例如要作为哪个用户或组运行、要公开的 Linux 功能,或在 Pod 清单中设置 。allowPrivilegeEscalation: false
有关更多最佳做法,请参阅保护 Pod 对资源的访问。
若要改进主机隔离并减少 Linux 上的横向移动,可以使用 用户命名空间。
若要更精确地控制容器操作,可以使用内置 Linux 安全功能,例如 AppArmor 和 seccomp。
- 在节点级别定义 Linux 安全功能。
- 通过 pod 清单实现功能。
内置的 Linux 安全功能仅在 Linux 节点和 Pod 上提供。
注意
目前,Kubernetes 环境并不完全安全,因为可能存在恶意的多租户使用情况。 其他安全功能,例如 Microsoft Defender for Containers、 AppArmor、 seccomp、 user-namespaces、 Pod Security Admission 或 Kubernetes RBAC for nodes,有效阻止攻击。
若要在运行恶意多租户工作负荷时获得真正的安全性,应只信任虚拟机监控程序。 Kubernetes 的安全域成为整个群集,而不是单个节点。
对于这些类型的恶意多租户工作负荷,应使用物理隔离的群集。
用户命名空间
默认情况下,Linux Pod 使用多个命名空间运行:用于隔离网络标识的网络命名空间和用于隔离进程的 PID 命名空间。 用户命名空间将容器中的用户与主机上的用户隔离开来。 它还会限制功能的范围和 Pod 与系统的其余部分的交互。
容器内的 UID 和 GID 映射到主机上的未特权用户,因此与主机其余部分的所有交互都发生在那些不受特权的 UID 和 GID 中。 例如,容器(UID 0)内的根可以映射到主机上的用户 65536。 Kubernetes 会创建映射,以确保它不会与系统上使用用户命名空间(user namespaces)的其他 Pod 发生重叠。
Kubernetes 实现具有一些关键优势:
增加主机隔离:如果容器突破 Pod 边界,即使在容器中以 root 身份运行,它在主机上也不具有特权。 原因是容器的 UID 和 GID 映射到主机上的未特权用户。 如果发生容器逃逸,用户命名空间能够极大地限制容器对主机上的文件的读取/写入权限,并限制对哪些进程可以发送信号。 授予的功能仅在用户命名空间内有效,而不适用于主机上。
防止横向移动:由于不同容器的 UID 和 GID 映射到主机上的不同、不重叠的 UID 和 GID,因此容器很难相互攻击。 例如,假设容器 A 在主机上运行的 UID 和 GID 与容器 B 不同。在发生容器逃逸的情况下,它可以在容器 B 的文件和进程上执行的操作受到限制:只能读取/写入文件中允许他人访问的部分。 这最终还是不可能的,因为在 Pod 根卷的父目录上有额外的防护措施,以确保只有 Pod GID 可以访问它。
遵循最低特权原则:由于 UID 和 GID 映射到主机上的未特权用户,因此只有需要主机特权的用户(并禁用用户命名空间)才能获取它。 如果没有用户命名空间,容器的用户和主机的用户之间没有分隔。 当它们需要容器内的特权时,我们无法避免向不需要它的进程授予权限。
启用新的用例:用户命名空间允许容器在其自己的用户命名空间内获取某些功能,而不会影响主机。 将能力限制为 Pod 可以解锁新的可能性,例如运行需要特权操作的应用程序,而无需授予主机上的完整根访问权限。 可以安全实现的常见新用例包括:运行嵌套容器和未特权容器生成。
非特权容器设置:大多数容器创建和设置不会在主机上作为根运行,这大大限制了许多 CVE 的影响。
未使用用户命名空间时,这些内容都不属实。 如果容器作为根运行,则不使用用户命名空间时,进程在主机上作为根运行,则功能在主机上有效,并且容器设置作为主机上的根执行。
在您开始之前
在开始之前,请确保具有以下各项:
- 现有的 AKS 群集。 如果没有群集,请使用 Azure CLI、 Azure PowerShell 或 Azure 门户创建群集。
- 控制平面和工作节点最低需要 Kubernetes 版本 1.33。 如果不使用 kubernetes 版本 1.33 或更高版本,则需要 升级 kubernetes 版本。
- 运行 Azure Linux 3.0 或 Ubuntu 24.04 的工作器节点。 如果不使用这些 OS 版本,则不会有启用用户命名空间的最低 堆栈要求 。 需要 升级 OS 版本。
限制
- 用户命名空间是 Linux 内核功能,不支持 Windows 节点池。
- 请毫不犹豫地检查 用户命名空间的 Kubernetes 文档,特别是限制部分。
启用用户命名空间
无需配置即可使用此功能。 如果使用所需的 AKS 版本,那么一切都会立即有效运行。
创建名为
mypod.yaml
的文件,并将以下清单复制到其中:若要使用用户命名空间,yaml 需要具有字段
hostUsers: false
。apiVersion: v1 kind: Pod metadata: name: userns spec: hostUsers: false containers: - name: shell command: ["sleep", "infinity"] image: debian
使用
kubectl apply
命令部署应用程序,并指定 YAML 清单的名称。kubectl apply -f mypod.yaml
使用
kubectl get pods
命令查看已部署的 Pod 的状态。kubectl get pods
使用
/proc/self/uid_map
命令进入 Pod 以检查kubectl exec
:kubectl exec -ti userns -- bash # Now inside the pod run cat /proc/self/uid_map
输出结果的最后一列应为 65536。 例如:
0 833617920 65536
CVE 已缓解
下面是使用用户命名空间完全/部分缓解的一些 CVE。
请记住,该列表并不详尽,它只是对若干已被缓解的高分 CVE 的选择:
- CVE-2019-5736 - 分数 8.6 (高)
- CVE 2024-21262:得分 8.6 (高)
- CVE 2022-0492:得分 7.8 (高)
- CVE-2021-25741:分数:8.1(高) / 8.8 (高)
- CVE-2017-1002101:分数:9.6(关键) / 8.8(高)
若要了解详细信息,请阅读此 博客文章 ,其中包含有关用户命名空间的其他信息。
应用程序防护
若要限制容器操作,可以使用 AppArmor Linux 内核安全模块。 AppArmor 作为基础 AKS 节点 OS 的一部分提供,默认情况下处于启用状态。 可以创建 AppArmor 配置文件来限制读取、写入或执行等操作或者装载文件系统等系统功能。 默认 AppArmor 配置文件限制对各种 /proc
和 /sys
位置的访问,并提供一种在逻辑上将容器与基础节点隔离的方法。 AppArmor 适用于 Linux 上运行的任何应用程序,而不仅仅是 Kubernetes Pod。
为了通过实际操作了解 AppArmor,以下示例将创建一个阻止写入文件的配置文件。
通过 SSH 连接到 AKS 节点。
创建一个名为 deny-write.profile 的文件。
复制并粘贴以下内容:
#include <tunables/global> profile k8s-apparmor-example-deny-write flags=(attach_disconnected) { #include <abstractions/base> file, # Deny all file writes. deny /** w, }
使用 apparmor_parser
命令添加 AppArmor 配置文件。
将配置文件添加到 AppArmor。
指定在上一步中创建的配置文件的名称:
sudo apparmor_parser deny-write.profile
如果已正确分析配置文件并将其应用于 AppArmor,则不会显示任何输出,并且你将回到命令提示符。
在本地计算机上,创建一个名为 aks-apparmor.yaml 的 Pod 清单。 此清单:
- 为
container.apparmor.security.beta.kubernetes
定义一个注释。 - 引用在前面的步骤中创建的 deny-write 配置文件。
apiVersion: v1 kind: Pod metadata: name: hello-apparmor annotations: container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write spec: containers: - name: hello image: mcr.azk8s.cn/dotnet/runtime-deps:6.0 command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
- 为
部署 Pod 后,运行以下命令并验证 hello-apparmor Pod 是否显示“正在运行”状态:
kubectl get pods NAME READY STATUS RESTARTS AGE aks-ssh 1/1 Running 0 4m2s hello-apparmor 0/1 Running 0 50s
有关 AppArmor 的详细信息,请参阅 Kubernetes 中的 AppArmor 配置文件。
安全计算 (seccomp)
AppArmor 适用于任何 Linux 应用程序,seccomp(安全计算)则在进程级别运行。 Seccomp 也是一个 Linux 内核安全模块,并由 AKS 节点所用的 containerd
运行时提供本机支持。 借助 seccomp,你可以限制容器的系统调用。 Seccomp 建立了一个额外的保护层来防范恶意参与者利用的常见系统调用漏洞,并使你能够为节点中的所有工作负载指定一个默认的配置文件。
配置默认 seccomp 配置文件(预览)
创建新的 Linux 节点池时,可以使用自定义节点配置来应用默认的 seccomp 配置文件。 AKS 支持两个值:RuntimeDefault
和 Unconfined
。 某些工作负载需要的 syscall 限制可能比其他工作负载要少。 这意味着它们在使用“RuntimeDefault”配置文件的运行时期间可能会失败。 若要缓解此类失败,可以指定 Unconfined
配置文件。 如果工作负载需要自定义配置文件,请参阅配置自定义 seccomp 配置文件。
限制
- SeccompDefault 不是 Windows 节点池支持的参数。
- SeccompDefault 从 2024-09-02-preview API 开始可用。
重要
AKS 预览功能是可选择启用的自助功能。 预览功能是“按现状”和“按可用”提供的,不包括在服务级别协议和有限保证中。 AKS 预览功能是由客户支持尽最大努力部分覆盖。 因此,这些功能并不适合用于生产。 有关详细信息,请参阅以下支持文章:
注册 KubeletDefaultSeccompProfilePreview
功能标志
使用
KubeletDefaultSeccompProfilePreview
命令注册az feature register
功能标志。az feature register --namespace "Microsoft.ContainerService" --name "KubeletDefaultSeccompProfilePreview"
状态显示为“已注册”需要几分钟时间。
使用
az feature show
命令验证注册状态。az feature show --namespace "Microsoft.ContainerService" --name "KubeletDefaultSeccompProfilePreview"
当状态反映为已注册时,使用 命令刷新
az provider register
资源提供程序的注册。az provider register --namespace Microsoft.ContainerService
使用 seccomp 限制容器的系统调用
1.按照步骤,通过指定 "seccompDefault": "RuntimeDefault"
。
RuntimeDefault
使用容器的默认 seccomp 配置文件,限制某些系统调用来提高安全性。 受限制的 syscall 将失败。 有关详细信息,请参阅 containerD 默认 seccomp 配置文件。
2.检查是否已应用配置。
可以通过连接到主机并验证是否已在文件系统上进行了配置更改,确认设置已应用到节点。
3.排查工作负载失败情况。
启用 SeccompDefault 后,默认情况下,容器运行时默认 seccomp 配置文件用于节点上计划的所有工作负载。 这可能会导致工作负载由于 syscall 被阻止而失败。 如果工作负载失败,可能会看到如下错误:
- 启用该功能后,工作负载意外存在,出现“权限被拒绝”错误。
- 通过在默认配置文件中将 SCMP_ACT_ERRNO 替换为 SCMP_ACT_LOG,也可以在 auditd 或 syslog 中看到 Seccomp 错误消息。
如果遇到上述错误,建议将 seccomp 配置文件更改为 Unconfined
。
Unconfined
对 syscall 没有限制,允许所有系统调用,这降低了安全性。
配置自定义 seccomp 配置文件
使用自定义 seccomp 配置文件,可以更精细地控制受限制的 syscall。 通过以下方式,遵循授予容器运行所需要的最低权限的最佳做法:
- 定义时采用筛选器,以筛选允许或拒绝的操作。
- 在 pod YAML 清单内批注以与 seccomp 筛选器相关联。
为了通过实际操作了解 seccomp,请创建一个筛选器来阻止更改文件权限。
通过 SSH 连接到 AKS 节点。
创建名为 /var/lib/kubelet/seccomp/prevent-chmod 的 seccomp 筛选器。
复制并粘贴以下内容:
{ "defaultAction": "SCMP_ACT_ALLOW", "syscalls": [ { "name": "chmod", "action": "SCMP_ACT_ERRNO" }, { "name": "fchmodat", "action": "SCMP_ACT_ERRNO" }, { "name": "chmodat", "action": "SCMP_ACT_ERRNO" } ] }
在版本 1.19 及更高版本中,需要配置以下各项:
{ "defaultAction": "SCMP_ACT_ALLOW", "syscalls": [ { "names": ["chmod","fchmodat","chmodat"], "action": "SCMP_ACT_ERRNO" } ] }
在本地计算机上,创建一个名为 aks-seccomp.yaml 的 Pod 清单并粘贴以下内容。 此清单:
- 为
seccomp.security.alpha.kubernetes.io
定义一个注释。 - 引用在上一步中创建的 prevent-chmod 筛选器。
apiVersion: v1 kind: Pod metadata: name: chmod-prevented annotations: seccomp.security.alpha.kubernetes.io/pod: localhost/prevent-chmod spec: containers: - name: chmod image: mcr.azk8s.cn/dotnet/runtime-deps:6.0 command: - "chmod" args: - "777" - /etc/hostname restartPolicy: Never
在版本 1.19 及更高版本中,需要配置以下各项:
apiVersion: v1 kind: Pod metadata: name: chmod-prevented spec: securityContext: seccompProfile: type: Localhost localhostProfile: prevent-chmod containers: - name: chmod image: mcr.azk8s.cn/dotnet/runtime-deps:6.0 command: - "chmod" args: - "777" - /etc/hostname restartPolicy: Never
- 为
使用 kubectl apply 命令部署示例 Pod:
kubectl apply -f ./aks-seccomp.yaml
使用 kubectl get pods 命令查看 Pod 状态。
- 该 Pod 报告一个错误。
- seccomp 筛选器阻止
chmod
命令运行,如示例输出所示:
kubectl get pods NAME READY STATUS RESTARTS AGE chmod-prevented 0/1 Error 0 7s
seccomp 安全配置文件选项
seccomp 安全配置文件是一组定义的被允许或受到限制的 syscall。 大多数容器运行时都有一个默认的 seccomp 配置文件,该文件如果与 Docker 使用的不相同,则与之相似。 有关可用配置文件的详细信息,请参阅 Docker 或 containerD 默认 seccomp 配置文件。
使用自定义节点配置配置 seccomp 时,AKS 使用 RuntimeDefault 的 containerD 默认 seccomp 配置文件。
默认配置文件阻止的重要 syscall
Docker 和 containerD 都维护有安全 syscall 的允许列表。 此表列出了因不在允许列表上而被有效阻止的重要(但并非全部)syscall。 如果工作负载需要任何被阻止的 syscall,请不要使用 RuntimeDefault
seccomp 配置文件。
对 Docker 和 containerD 进行更改时,AKS 会更新其默认配置来进行匹配。 更新此列表可能会导致工作负载失败。 有关发布更新,请参阅 AKS 发行说明。
被阻止的 syscall | 说明 |
---|---|
acct |
可让容器禁用自己的资源限制或进程计帐的计帐 syscall。 也受到 CAP_SYS_PACCT 限制。 |
add_key |
阻止容器使用未加入命名空间的内核 keyring。 |
bpf |
拒绝将潜在的持久性 bpf 程序加载到内核中,已受到 CAP_SYS_ADMIN 限制。 |
clock_adjtime |
时间/日期未加入命名空间。 也受到 CAP_SYS_TIME 限制。 |
clock_settime |
时间/日期未加入命名空间。 也受到 CAP_SYS_TIME 限制。 |
clone |
拒绝克隆新的命名空间。 此外受到 CAP_SYS_ADMIN for CLONE_* 标志限制(CLONE_NEWUSER 除外)。 |
create_module |
拒绝内核模块上的操作和函数。 已过时。 也受到 CAP_SYS_MODULE 限制。 |
delete_module |
拒绝内核模块上的操作和函数。 也受到 CAP_SYS_MODULE 限制。 |
finit_module |
拒绝内核模块上的操作和函数。 也受到 CAP_SYS_MODULE 限制。 |
get_kernel_syms |
拒绝检索导出的内核和模块符号。 已过时。 |
get_mempolicy |
修改内核内存和 NUMA 设置的 syscall。 已受到 CAP_SYS_NICE 限制。 |
init_module |
拒绝内核模块上的操作和函数。 也受到 CAP_SYS_MODULE 限制。 |
ioperm |
防止容器修改内核 I/O 特权级别。 已受到 CAP_SYS_RAWIO 限制。 |
iopl |
防止容器修改内核 I/O 特权级别。 已受到 CAP_SYS_RAWIO 限制。 |
kcmp |
限制进程检查功能,已通过删除 CAP_SYS_PTRACE 来阻止。 |
kexec_file_load |
kexec_load 的同类型 syscall,执行相同的操作,但参数略有不同。 也受到 CAP_SYS_BOOT 限制。 |
kexec_load |
拒绝加载新内核来供稍后执行。 也受到 CAP_SYS_BOOT 限制。 |
keyctl |
阻止容器使用未加入命名空间的内核 keyring。 |
lookup_dcookie |
跟踪/分析 syscall,这可能会泄露主机上的信息。 也受到 CAP_SYS_ADMIN 限制。 |
mbind |
修改内核内存和 NUMA 设置的 syscall。 已受到 CAP_SYS_NICE 限制。 |
mount |
拒绝装载,已受到 CAP_SYS_ADMIN 限制。 |
move_pages |
修改内核内存和 NUMA 设置的 syscall。 |
nfsservctl |
拒绝与内核 nfs 守护程序交互。 自 Linux 3.1 起已过时。 |
open_by_handle_at |
旧容器中断服务的原因。 也受到 CAP_DAC_READ_SEARCH 限制。 |
perf_event_open |
跟踪/分析 syscall,这可能会泄露主机上的信息。 |
personality |
阻止容器启用 BSD 仿真。 本质上并非是危险的,但测试效果不佳,可能存在内核漏洞。 |
pivot_root |
拒绝 pivot_root,应是特权操作。 |
process_vm_readv |
限制进程检查功能,已通过删除 CAP_SYS_PTRACE 来阻止。 |
process_vm_writev |
限制进程检查功能,已通过删除 CAP_SYS_PTRACE 来阻止。 |
ptrace |
跟踪/分析 syscall。 在低于 4.8 的 Linux 内核版本中遭到阻止,以避免绕过 seccomp。 已通过删除 CAP_SYS_PTRACE 来阻止跟踪/分析任意进程,因为它可能会泄露主机上的信息。 |
query_module |
拒绝内核模块上的操作和函数。 已过时。 |
quotactl |
可让容器禁用自己的资源限制或进程计帐的配额 syscall。 也受到 CAP_SYS_ADMIN 限制。 |
reboot |
不让容器重新启动主机。 也受到 CAP_SYS_BOOT 限制。 |
request_key |
阻止容器使用未加入命名空间的内核 keyring。 |
set_mempolicy |
修改内核内存和 NUMA 设置的 syscall。 已受到 CAP_SYS_NICE 限制。 |
setns |
拒绝将线程与命名空间关联。 也受到 CAP_SYS_ADMIN 限制。 |
settimeofday |
时间/日期未加入命名空间。 也受到 CAP_SYS_TIME 限制。 |
stime |
时间/日期未加入命名空间。 也受到 CAP_SYS_TIME 限制。 |
swapon |
拒绝开始/停止交换到文件/设备。 也受到 CAP_SYS_ADMIN 限制。 |
swapoff |
拒绝开始/停止交换到文件/设备。 也受到 CAP_SYS_ADMIN 限制。 |
sysfs |
已过时的 syscall。 |
_sysctl |
已过时,被 /proc/sys 替换。 |
umount |
应为特权操作。 也受到 CAP_SYS_ADMIN 限制。 |
umount2 |
应为特权操作。 也受到 CAP_SYS_ADMIN 限制。 |
unshare |
拒绝为进程克隆新的命名空间。 此外,受到 CAP_SYS_ADMIN 限制,但 unshare --user 除外。 |
uselib |
与共享库相关的较旧的 syscall,长时间未使用。 |
userfaultfd |
用户空间页面错误处理,这在很大程度上是进程迁移所必需的。 |
ustat |
已过时的 syscall。 |
vm86 |
在内核 x86 实模式虚拟机中。 也受到 CAP_SYS_ADMIN 限制。 |
vm86old |
在内核 x86 实模式虚拟机中。 也受到 CAP_SYS_ADMIN 限制。 |
后续步骤
如需相关的最佳做法,请参阅 AKS 中群集安全性和升级的最佳做法和 AKS 中的 Pod 安全的最佳做法。