使用 AppArmor 限制容器对资源的访问
Kubernetes v1.31 [稳定]
(默认启用:true)本页介绍如何在节点上加载 AppArmor 配置文件并在 Pod 中强制执行这些配置文件。要了解有关 Kubernetes 如何使用 AppArmor 限制 Pod 的更多信息,请参阅 Pod 和容器的 Linux 内核安全约束。
目标
- 查看如何在节点上加载配置文件的示例
- 学习如何在 Pod 上强制执行配置文件
- 学习如何检查配置文件是否已加载
- 查看违反配置文件时会发生什么情况
- 查看无法加载配置文件时会发生什么情况
开始之前
AppArmor 是一个可选的内核模块和 Kubernetes 功能,因此在继续操作之前,请验证您的节点是否支持它
AppArmor 内核模块已启用 -- 要使 Linux 内核强制执行 AppArmor 配置文件,必须安装并启用 AppArmor 内核模块。一些发行版默认启用该模块,例如 Ubuntu 和 SUSE,并且许多其他发行版提供可选支持。要检查模块是否已启用,请检查
/sys/module/apparmor/parameters/enabled
文件cat /sys/module/apparmor/parameters/enabled Y
kubelet 会验证主机上是否启用了 AppArmor,然后再允许显式配置了 AppArmor 的 Pod。
容器运行时支持 AppArmor -- 所有常见的 Kubernetes 支持的容器运行时都应该支持 AppArmor,包括 containerd 和 CRI-O。请参考相应的运行时文档并验证集群是否满足使用 AppArmor 的要求。
配置文件已加载 -- AppArmor 通过指定每个容器应运行的 AppArmor 配置文件来应用于 Pod。如果任何指定的配置文件未加载到内核中,kubelet 将拒绝该 Pod。您可以通过检查
/sys/kernel/security/apparmor/profiles
文件来查看节点上加载了哪些配置文件。例如ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort"
apparmor-test-deny-write (enforce) apparmor-test-audit-write (enforce) docker-default (enforce) k8s-nginx (enforce)
有关在节点上加载配置文件的更多详细信息,请参阅使用配置文件设置节点。
保护 Pod
注意
在 Kubernetes v1.30 之前,AppArmor 是通过注解指定的。使用文档版本选择器查看使用此已弃用 API 的文档。AppArmor 配置文件可以在 Pod 级别或容器级别指定。容器 AppArmor 配置文件优先于 Pod 配置文件。
securityContext:
appArmorProfile:
type: <profile_type>
其中 <profile_type>
是以下之一
RuntimeDefault
使用运行时的默认配置文件Localhost
使用加载在主机上的配置文件(见下文)Unconfined
在没有 AppArmor 的情况下运行
有关 AppArmor 配置文件 API 的完整详细信息,请参阅指定 AppArmor 限制。
要验证配置文件是否已应用,您可以通过检查容器的根进程是否以正确的配置文件运行来检查其 proc 属性
kubectl exec <pod_name> -- cat /proc/1/attr/current
输出应如下所示
cri-containerd.apparmor.d (enforce)
示例
此示例假设您已设置了一个支持 AppArmor 的集群。
首先,将要使用的配置文件加载到您的节点上。此配置文件阻止所有文件写入操作
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# Deny all file writes.
deny /** w,
}
该配置文件需要加载到所有节点上,因为您不知道 pod 将被调度到哪里。对于此示例,您可以使用 SSH 来安装配置文件,但 使用配置文件设置节点中讨论了其他方法。
# This example assumes that node names match host names, and are reachable via SSH.
NODES=($( kubectl get node -o jsonpath='{.items[*].status.addresses[?(.type == "Hostname")].address}' ))
for NODE in ${NODES[*]}; do ssh $NODE 'sudo apparmor_parser -q <<EOF
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# Deny all file writes.
deny /** w,
}
EOF'
done
接下来,运行一个简单的“Hello AppArmor” Pod,使用 deny-write 配置文件
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor
spec:
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: k8s-apparmor-example-deny-write
containers:
- name: hello
image: busybox:1.28
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
kubectl create -f hello-apparmor.yaml
您可以通过检查 /proc/1/attr/current
来验证容器是否实际以该配置文件运行
kubectl exec hello-apparmor -- cat /proc/1/attr/current
输出应为
k8s-apparmor-example-deny-write (enforce)
最后,您可以查看如果您通过写入文件违反配置文件会发生什么情况
kubectl exec hello-apparmor -- touch /tmp/test
touch: /tmp/test: Permission denied
error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1
总结一下,看看如果您尝试指定一个尚未加载的配置文件会发生什么情况
kubectl create -f /dev/stdin <<EOF
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor-2
spec:
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: k8s-apparmor-example-allow-write
containers:
- name: hello
image: busybox:1.28
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created
尽管 Pod 已成功创建,但进一步检查会发现它处于挂起状态
kubectl describe pod hello-apparmor-2
Name: hello-apparmor-2
Namespace: default
Node: gke-test-default-pool-239f5d02-x1kf/10.128.0.27
Start Time: Tue, 30 Aug 2016 17:58:56 -0700
Labels: <none>
Annotations: container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write
Status: Pending
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 10s default-scheduler Successfully assigned default/hello-apparmor to gke-test-default-pool-239f5d02-x1kf
Normal Pulled 8s kubelet Successfully pulled image "busybox:1.28" in 370.157088ms (370.172701ms including waiting)
Normal Pulling 7s (x2 over 9s) kubelet Pulling image "busybox:1.28"
Warning Failed 7s (x2 over 8s) kubelet Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found k8s-apparmor-example-allow-write
Normal Pulled 7s kubelet Successfully pulled image "busybox:1.28" in 90.980331ms (91.005869ms including waiting)
一个事件提供了错误消息以及原因,具体措辞取决于运行时
Warning Failed 7s (x2 over 8s) kubelet Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found
管理
使用配置文件设置节点
Kubernetes 1.32 不提供任何内置机制来将 AppArmor 配置文件加载到节点上。可以通过自定义基础设施或工具(如Kubernetes 安全配置文件操作器)加载配置文件。
调度器不知道哪些配置文件加载到哪个节点上,因此必须将全套配置文件加载到每个节点上。另一种方法是为节点上的每个配置文件(或配置文件类)添加一个节点标签,并使用节点选择器来确保 Pod 在具有所需配置文件的节点上运行。
编写配置文件
正确指定 AppArmor 配置文件可能是一件棘手的事情。幸运的是,有一些工具可以提供帮助
aa-genprof
和aa-logprof
通过监视应用程序的活动和日志并允许其执行的操作来生成配置文件规则。 AppArmor 文档提供了进一步的说明。- bane 是一个用于 Docker 的 AppArmor 配置文件生成器,它使用简化的配置文件语言。
要调试 AppArmor 的问题,您可以检查系统日志以查看具体拒绝了什么。AppArmor 将详细消息记录到 dmesg
,并且错误通常可以在系统日志中或通过 journalctl
找到。 AppArmor 故障中提供了更多信息。
指定 AppArmor 限制
注意
在 Kubernetes v1.30 之前,AppArmor 是通过注解指定的。使用文档版本选择器查看使用此已弃用 API 的文档。安全上下文中的 AppArmor 配置文件
您可以在容器的 securityContext
或 Pod 的 securityContext
上指定 appArmorProfile
。如果在 pod 级别设置了配置文件,它将用作 pod 中所有容器(包括 init、sidecar 和临时容器)的默认配置文件。如果同时设置了 pod 和容器 AppArmor 配置文件,则将使用容器的配置文件。
AppArmor 配置文件有 2 个字段
type
(必需) - 指示将应用哪种 AppArmor 配置文件。有效选项为
本地主机
- 预加载在节点上的配置文件(由
localhostProfile
指定)。 RuntimeDefault
- 容器运行时的默认配置文件。
Unconfined
- 没有 AppArmor 强制执行。
localhostProfile
- 应使用的加载在节点上的配置文件的名称。配置文件必须在节点上预先配置才能工作。当且仅当 type
为 Localhost
时,才必须提供此选项。
下一步
其他资源