使用 AppArmor 限制容器對資源的存取

功能狀態: Kubernetes v1.31 [穩定] (預設啟用:true)

本頁說明如何在節點上載入 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。

  2. 容器執行期支援 AppArmor -- 所有常見的 Kubernetes 支援容器執行期都應支援 AppArmor,包括 containerdCRI-O。請參閱對應的執行期文件,並驗證叢集是否符合使用 AppArmor 的需求。

  3. 描述檔已載入 -- 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 限制

若要驗證是否已套用描述檔,您可以檢查容器的根程序是否正在使用正確的描述檔執行,方法是檢查其 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-genprofaa-logprof 透過監控應用程式的活動和日誌,並允許其執行的動作,來產生配置規則。更多指示請參考 AppArmor 文件
  • bane 是一個用於 Docker 的 AppArmor 配置產生器,它使用簡化的配置語言。

若要偵錯 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 時,才必須提供此選項。

接下來

額外資源

最後修改時間為 2024 年 12 月 04 日 11:29 AM PST:更新 apparmor.md (633e85b9ca)