使用 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 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
接下來,使用 deny-write 描述檔執行簡單的「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 已成功建立,但進一步檢查會顯示它卡在擱置中
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 安全描述檔 Operator。
排程器不知道哪些描述檔已載入到哪個節點上,因此必須將完整的描述檔集載入到每個節點上。另一種方法是為節點上的每個描述檔 (或描述檔類別) 新增節點標籤,並使用節點選取器來確保 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
時,才必須提供此選項。
接下來
額外資源