使用 AppArmor 限制容器对资源的访问
特性状态:Kubernetes v1.31 [稳定](默认启用:是)
本页面展示如何在节点上加载 AppArmor 配置文件,并在 Pod 中强制实施这些配置文件。要了解更多关于 Kubernetes 如何使用 AppArmor 限制 Pod 的信息,请参阅《Pod 和容器的 Linux 内核安全约束》。
目标
- 了解在节点上加载配置文件的示例。
- 学习如何在 Pod 上强制实施配置文件。
- 学习如何检查配置文件是否已加载。
- 了解违反配置文件时会发生什么。
- 了解配置文件无法加载时会发生什么。
开始之前
AppArmor 是一个可选的内核模块和 Kubernetes 特性,因此在继续操作之前,请先验证你的节点是否支持它:
1.AppArmor 内核模块已启用:为了让 Linux 内核强制实施 AppArmor 配置文件,必须安装并启用 AppArmor 内核模块。一些发行版默认启用该模块,例如 Ubuntu 和 SUSE,许多其他发行版也提供可选支持。要检查该模块是否已启用,请查看 /sys/module/apparmor/parameters/enabled 文件:
cat /sys/module/apparmor/parameters/enabled
Ykubelet 在接纳显式配置了 AppArmor 的 Pod 之前,会先验证主机上的 AppArmor 是否已启用。 2. 容器运行时支持 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 是通过注解(annotations)来指定的。请使用文档版本选择器查看使用此已弃用 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)Example
This example assumes you have already set up a cluster with AppArmor support.
First, load the profile you want to use onto your Nodes. This profile blocks all file write operations:
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# Deny all file writes.
deny /** w,
}The profile needs to be loaded onto all nodes, since you don't know where the pod will be scheduled. For this example you can use SSH to install the profiles, but other approaches are discussed in Setting up nodes with profiles.
# 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'
doneNext, run a simple "Hello AppArmor" Pod with the deny-write profile:
# pods/security/hello-apparmor.yaml
# Copy pods/security/hello-apparmor.yaml to clipboard
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.yamlYou can verify that the container is actually running with that profile by checking /proc/1/attr/current:
kubectl exec hello-apparmor -- cat /proc/1/attr/currentThe output should be:
k8s-apparmor-example-deny-write (enforce)Finally, you can see what happens if you violate the profile by writing to a file:
kubectl exec hello-apparmor -- touch /tmp/testtouch: /tmp/test: Permission denied
error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1To wrap up, see what happens if you try to specify a profile that hasn't been loaded:
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" ]
EOFpod/hello-apparmor-2 createdAlthough the Pod was created successfully, further examination will show that it is stuck in pending:
kubectl describe pod hello-apparmor-2Name: 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)An Event provides the error message with the reason, the specific wording is runtime-dependent:
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 中所有容器(包括初始化容器、边车容器和临时容器)的默认配置文件。如果同时设置了 Pod 和容器的 AppArmor 配置文件,则将使用容器的配置文件。
一个 AppArmor 配置文件有两个字段:
- type(必填):表示将应用哪种类型的 AppArmor 配置文件。有效的选项如下:
* Localhost:使用节点上预加载的配置文件(由 localhostProfile 指定)。
* RuntimeDefault:使用容器运行时的默认配置文件。
* Unconfined:不实施 AppArmor 限制。- localhostProfile:要使用的已加载到节点上的配置文件的名称。该配置文件必须在节点上预先配置才能生效。只有当 type 为 Localhost 时,才必须提供此选项。