使用 AppArmor 限制容器对资源的访问
特性状态: Kubernetes v1.4 [beta]
AppArmor 是一个 Linux 内核安全模块, 它补充了基于标准 Linux 用户和组的权限,将程序限制在一组有限的资源中。 AppArmor 可以配置为任何应用程序减少潜在的攻击面,并且提供更加深入的防御。 它通过调整配置文件进行配置,以允许特定程序或容器所需的访问, 如 Linux 权能字、网络访问、文件权限等。 每个配置文件都可以在 强制(enforcing) 模式(阻止访问不允许的资源)或 投诉(complain) 模式(仅报告冲突)下运行。
在 Kubernetes 中,AppArmor 可以通过限制允许容器执行的操作, 和/或通过系统日志提供更好的审计来帮助你运行更安全的部署。 但是,重要的是要记住 AppArmor 不是灵丹妙药, 只能做部分事情来防止应用程序代码中的漏洞。 提供良好的限制性配置文件,并从其他角度强化你的应用程序和集群非常重要。
教程目标
- 查看如何在节点上加载配置文件示例
- 了解如何在 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, 包括 CRI-O 和 containerd。 请参考相应的运行时文档并验证集群是否满足使用 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
说明:
AppArmor 目前处于 Beta 阶段,因此选项以注解形式设定。 一旦 AppArmor 支持进入正式发布阶段,注解将被替换为一阶的资源字段。
AppArmor 配置文件是按逐个容器的形式来设置的。 要指定用来运行 Pod 容器的 AppArmor 配置文件,请向 Pod 的 metadata 添加注解:
container.apparmor.security.beta.kubernetes.io/<container_name>: <profile_ref>
<container_name>
的名称是配置文件所针对的容器的名称,<profile_def>
则设置要应用的配置文件。 <profile_ref>
可以是以下取值之一:
runtime/default
应用运行时的默认配置localhost/<profile_name>
应用在主机上加载的名为<profile_name>
的配置文件unconfined
表示不加载配置文件
有关注解和配置文件名称格式的详细信息,请参阅 API 参考。
要验证是否应用了配置文件, 你可以通过检查容器根进程的进程属性来检查该进程是否正在使用正确的配置文件运行:
kubectl exec <pod_name> -- cat /proc/1/attr/current
输出应如下所示:
k8s-apparmor-example-deny-write (enforce)
你还可以通过检查容器的 proc attr,直接验证容器的根进程是否以正确的配置文件运行:
kubectl exec <pod_name> -- cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)
举例
本例假设你已经设置了一个集群使用 AppArmor 支持。
首先,将要使用的配置文件加载到节点上,此配置文件拒绝所有文件写入:
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# 拒绝所有文件写入
deny /** w,
}
由于不知道 Pod 将被调度到哪里,该配置文件需要加载到所有节点上。 在本例中,我们将使用 SSH 来安装概要文件, 但是在使用配置文件设置节点中讨论了其他方法。
# 此示例假设节点名称与主机名称匹配,并且可通过 SSH 访问。
NODES=($(kubectl get nodes -o name))
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:
pods/security/hello-apparmor.yaml
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor
annotations:
# 告知 Kubernetes 去应用 AppArmor 配置 "k8s-apparmor-example-deny-write"。
# 请注意,如果节点上运行的 Kubernetes 不是 1.4 或更高版本,此注解将被忽略。
container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write
spec:
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
annotations:
container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-allow-write
spec:
containers:
- name: hello
image: busybox:1.28
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created
虽然 Pod 创建成功,但进一步检查会发现它陷入 pending 状态:
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 目前不提供任何本地机制来将 AppArmor 配置文件加载到节点上。 可以通过自定义基础设施或工具(例如 Kubernetes Security Profiles Operator) 加载配置文件。
调度程序不知道哪些配置文件加载到哪个节点上,因此必须将全套配置文件加载到每个节点上。 另一种方法是为节点上的每个配置文件(或配置文件类)添加节点标签, 并使用节点选择器确保 Pod 在具有所需配置文件的节点上运行。
编写配置文件
获得正确指定的 AppArmor 配置文件可能是一件棘手的事情。幸运的是,有一些工具可以帮助你做到这一点:
aa-genprof
和aa-logprof
通过监视应用程序的活动和日志并准许它所执行的操作来生成配置文件规则。 AppArmor 文档提供了进一步的指导。- bane 是一个用于 Docker的 AppArmor 配置文件生成器,它使用一种简化的画像语言(profile language)。
想要调试 AppArmor 的问题,你可以检查系统日志,查看具体拒绝了什么。 AppArmor 将详细消息记录到 dmesg
, 错误通常可以在系统日志中或通过 journalctl
找到。 更多详细信息参见 AppArmor 失败。
API 参考
Pod 注解
指定容器将使用的配置文件:
- 键名:
container.apparmor.security.beta.kubernetes.io/<container_name>
, 其中<container_name>
与 Pod 中某容器的名称匹配。 可以为 Pod 中的每个容器指定单独的配置文件。 - 键值:对配置文件的引用,如下所述
配置文件引用
runtime/default
:指默认运行时配置文件。- 等同于不指定配置文件,只是它仍然需要启用 AppArmor。
- 实际上,许多容器运行时使用相同的 OCI 默认配置文件,在此处定义: https://github.com/containers/common/blob/main/pkg/apparmor/apparmor_linux_template.go
localhost/<profile_name>
:按名称引用加载到节点(localhost)上的配置文件。- 可能的配置文件名在核心策略参考。
unconfined
:这相当于为容器禁用 AppArmor。
任何其他配置文件引用格式无效。
接下来
其他资源: