使用 AppArmor 限制容器对资源的访问

特性状态: Kubernetes v1.31 [stable]

本页面向你展示如何在节点上加载 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
    Y
    

    kubelet 会先验证主机上是否已启用 AppArmor,然后再接纳显式配置了 AppArmor 的 Pod。

  1. 容器运行时支持 AppArmor —— 所有常见的 Kubernetes 支持的容器运行时都应该支持 AppArmor, 包括 CRI-Ocontainerd。 请参考相应的运行时文档并验证集群是否满足使用 AppArmor 的要求。
  1. 配置文件已加载 —— 通过指定每个容器应使用的 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 配置文件可以在 Pod 级别或容器级别指定。容器 AppArmor 配置文件优先于 Pod 配置文件。

securityContext:
  appArmorProfile:
    type: <profile_type>

其中 <profile_type> 是以下之一:

  • RuntimeDefault 使用运行时的默认配置文件
  • Localhost 使用主机上加载的配置文件(见下文)
  • Unconfined 无需 AppArmor 即可运行

有关 AppArmor 配置文件 API 的完整详细信息,请参阅指定 AppArmor 限制

要验证是否应用了配置文件, 你可以通过检查容器根进程的进程属性来检查该进程是否正在使用正确的配置文件运行:

kubectl exec <pod_name> -- cat /proc/1/attr/current

输出应如下所示:

cri-containerd.apparmor.d (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:

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 创建成功,但进一步检查会发现它陷入 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 1.31 目前不提供任何本地机制来将 AppArmor 配置文件加载到节点上。 可以通过自定义基础设施或工具(例如 Kubernetes Security Profiles Operator) 加载配置文件。

调度程序不知道哪些配置文件加载到哪个节点上,因此必须将全套配置文件加载到每个节点上。 另一种方法是为节点上的每个配置文件(或配置文件类)添加节点标签, 并使用节点选择器确保 Pod 在具有所需配置文件的节点上运行。

编写配置文件

获得正确指定的 AppArmor 配置文件可能是一件棘手的事情。幸运的是,有一些工具可以帮助你做到这一点:

  • aa-genprofaa-logprof 通过监视应用程序的活动和日志并准许它所执行的操作来生成配置文件规则。 AppArmor 文档提供了进一步的指导。
  • bane 是一个用于 Docker的 AppArmor 配置文件生成器,它使用一种简化的画像语言(profile language)。

想要调试 AppArmor 的问题,你可以检查系统日志,查看具体拒绝了什么。 AppArmor 将详细消息记录到 dmesg, 错误通常可以在系统日志中或通过 journalctl 找到。 更多详细信息参见 AppArmor 失败

指定 AppArmor 限制

安全上下文中的 AppArmor 配置文件

你可以在容器的 securityContext 或 Pod 的 securityContext 中设置 appArmorProfile。 如果在 Pod 级别设置配置文件,该配置将被用作 Pod 中所有容器(包括 Init、Sidecar 和临时容器)的默认配置文件。 如果同时设置了 Pod 和容器 AppArmor 配置文件,则将使用容器的配置文件。

AppArmor 配置文件有 2 个字段:

type (必需) - 指示将应用哪种 AppArmor 配置文件。有效选项是:

Localhost
节点上预加载的配置文件(由 localhostProfile 指定)。
RuntimeDefault
容器运行时的默认配置文件。
Unconfined
不强制执行 AppArmor。

localhostProfile - 在节点上加载的、应被使用的配置文件的名称。 该配置文件必须在节点上预先配置才能工作。 当且仅当 typeLocalhost 时,必须提供此选项。

接下来

其他资源:

最后修改 October 15, 2024 at 3:18 AM PST: Merge pull request #48346 from windsonsea/metricy (50a9341)