使用 AppArmor 限制容器對資源的訪問
Kubernetes v1.31 [stable]
(預設啟用: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 的 Pod 之前,會驗證主機上是否已啟用 AppArmor。
容器執行時支援 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 attr 來檢查容器的根程序是否正在使用正確的配置檔案執行:
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 已成功建立,但進一步檢查將顯示它卡在 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.34 不提供任何內建機制來將 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 配置檔案。有效選項有:
Localhost
- 節點上預載入的配置檔案(由
localhostProfile
指定)。 RuntimeDefault
- 容器執行時的預設配置檔案。
不受限制(Unconfined)
- 不進行 AppArmor 強制執行。
localhostProfile
- 節點上載入的應使用的配置檔案的名稱。配置檔案必須預先配置在節點上才能工作。僅當 type
為 Localhost
時才必須提供此選項。
下一步
其他資源