Kubernetes 使用AppArmor限制容器對資源的訪問

2022-06-20 11:23 更新

使用 AppArmor 限制容器對資源的訪問

特性狀態(tài): Kubernetes v1.4 [beta]

AppArmor 是一個 Linux 內(nèi)核安全模塊, 它補充了基于標準 Linux 用戶和組的權(quán)限,將程序限制在一組有限的資源中。 AppArmor 可以配置為任何應用程序減少潛在的攻擊面,并且提供更加深入的防御。 它通過調(diào)整配置文件進行配置,以允許特定程序或容器所需的訪問, 如 Linux 權(quán)能字、網(wǎng)絡訪問、文件權(quán)限等。 每個配置文件都可以在 強制(enforcing) 模式(阻止訪問不允許的資源)或 投訴(complain) 模式(僅報告沖突)下運行。

AppArmor 可以通過限制允許容器執(zhí)行的操作, 和/或通過系統(tǒng)日志提供更好的審計來幫助你運行更安全的部署。 但是,重要的是要記住 AppArmor 不是靈丹妙藥, 只能做部分事情來防止應用程序代碼中的漏洞。 提供良好的限制性配置文件,并從其他角度強化你的應用程序和集群非常重要。

教程目標

  • 查看如何在節(jié)點上加載配置文件示例
  • 了解如何在 Pod 上強制執(zhí)行配置文件
  • 了解如何檢查配置文件是否已加載
  • 查看違反配置文件時會發(fā)生什么
  • 查看無法加載配置文件時會發(fā)生什么

在開始之前

確保:

  1. Kubernetes 版本至少是 v1.4 —— AppArmor 在 Kubernetes v1.4 版本中才添加了對 AppArmor 的支持。 早于 v1.4 版本的 Kubernetes 組件不知道新的 AppArmor 注解 并且將會 默認忽略 提供的任何 AppArmor 設置。 為了確保你的 Pod 能夠得到預期的保護,必須驗證節(jié)點的 Kubelet 版本:
  2. kubectl get nodes -o=jsonpath={range .items[*]}{@.metadata.name}: {@.status.nodeInfo.kubeletVersion}\n{end}' 
    gke-test-default-pool-239f5d02-gyn2: v1.4.0
    gke-test-default-pool-239f5d02-x1kf: v1.4.0
    gke-test-default-pool-239f5d02-xwux: v1.4.0
  3. AppArmor 內(nèi)核模塊已啟用 —— 要使 Linux 內(nèi)核強制執(zhí)行 AppArmor 配置文件, 必須安裝并且啟動 AppArmor 內(nèi)核模塊。默認情況下,有幾個發(fā)行版支持該模塊, 如 Ubuntu 和 SUSE,還有許多發(fā)行版提供可選支持。要檢查模塊是否已啟用,請檢查 ?/sys/module/apparmor/parameters/enabled? 文件:
  4. cat /sys/module/apparmor/parameters/enabled
    Y

    如果 Kubelet 包含 AppArmor 支持(>= v1.4), 但是內(nèi)核模塊未啟用,它將拒絕運行帶有 AppArmor 選項的 Pod。

    說明: Ubuntu 攜帶了許多沒有合并到上游 Linux 內(nèi)核中的 AppArmor 補丁, 包括添加附加鉤子和特性的補丁。Kubernetes 只在上游版本中測試過,不承諾支持其他特性。

  5. 容器運行時支持 AppArmor —— 目前所有常見的 Kubernetes 支持的容器運行時都應該支持 AppArmor, 像 Docker,CRI-O 或 containerd。 請參考相應的運行時文檔并驗證集群是否滿足使用 AppArmor 的要求。
  6. 配置文件已加載 —— 通過指定每個容器都應使用的 AppArmor 配置文件, AppArmor 會被應用到 Pod 上。如果指定的任何配置文件尚未加載到內(nèi)核, Kubelet(>= v1.4) 將拒絕 Pod。 通過檢查 ?/sys/kernel/security/apparmor/profiles? 文件, 可以查看節(jié)點加載了哪些配置文件。例如:
  7. 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)

只要 Kubelet 版本包含 AppArmor 支持(>=v1.4), 如果不滿足這些先決條件,Kubelet 將拒絕帶有 AppArmor 選項的 Pod。 你還可以通過檢查節(jié)點就緒狀況消息來驗證節(jié)點上的 AppArmor 支持(盡管這可能會在以后的版本中刪除):

kubectl get nodes -o=jsonpath={range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}\n{end}' 
gke-test-default-pool-239f5d02-gyn2: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-x1kf: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-xwux: kubelet is posting ready status. AppArmor enabled

保護 Pod

說明:
AppArmor 目前處于 Beta 階段,因此選項以注解形式設定。 一旦 AppArmor 支持進入正式發(fā)布階段,注解將被替換為一階的資源字段。

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 ?表示不加載配置文件

Kubernetes AppArmor 強制執(zhí)行機制首先檢查所有先決條件都已滿足, 然后將所選的配置文件轉(zhuǎn)發(fā)到容器運行時進行強制執(zhí)行。 如果未滿足先決條件,Pod 將被拒絕,并且不會運行。

要驗證是否應用了配置文件,可以在容器創(chuàng)建事件中查找所列出的 AppArmor 安全選項:

kubectl get events | grep Created
22s        22s         1         hello-apparmor     Pod       spec.containers{hello}   Normal    Created     {kubelet e2e-test-stclair-node-pool-31nt}   Created container with docker id 269a53b202d3; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]

你還可以通過檢查容器的 proc attr,直接驗證容器的根進程是否以正確的配置文件運行:

kubectl exec <pod_name> cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)

舉例

本例假設你已經(jīng)設置了一個集群使用 AppArmor 支持。

首先,我們需要將要使用的配置文件加載到節(jié)點上。配置文件拒絕所有文件寫入:

#include <tunables/global>

profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>

  file,

  # Deny all file writes.
  deny /** w,
}

由于我們不知道 Pod 將被調(diào)度到哪里,我們需要在所有節(jié)點上加載配置文件。在本例中,我們將使用 SSH 來安裝概要文件。

NODES=(
    # The SSH-accessible domain names of your nodes
    gke-test-default-pool-239f5d02-gyn2.us-central1-a.my-k8s
    gke-test-default-pool-239f5d02-x1kf.us-central1-a.my-k8s
    gke-test-default-pool-239f5d02-xwux.us-central1-a.my-k8s)
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
  annotations:
    # Tell Kubernetes to apply the AppArmor profile "k8s-apparmor-example-deny-write".
    # Note that this is ignored if the Kubernetes node is not running version 1.4 or greater.
    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

如果我們查看 Pod 事件,我們可以看到 Pod 容器是用 AppArmor 配置文件 “k8s-apparmor-example-deny-write” 所創(chuàng)建的:

kubectl get events | grep hello-apparmor
14s        14s         1         hello-apparmor   Pod                                Normal    Scheduled   {default-scheduler }                           Successfully assigned hello-apparmor to gke-test-default-pool-239f5d02-gyn2
14s        14s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Pulling     {kubelet gke-test-default-pool-239f5d02-gyn2}   pulling image "busybox"
13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Pulled      {kubelet gke-test-default-pool-239f5d02-gyn2}   Successfully pulled image "busybox"
13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Created     {kubelet gke-test-default-pool-239f5d02-gyn2}   Created container with docker id 06b6cd1c0989; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]
13s        13s         1         hello-apparmor   Pod       spec.containers{hello}   Normal    Started     {kubelet gke-test-default-pool-239f5d02-gyn2}   Started container with docker id 06b6cd1c0989

我們可以通過檢查該配置文件的 proc attr 來驗證容器是否實際使用該配置文件運行:

kubectl exec hello-apparmor -- cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)

最后,我們可以看到,如果我們嘗試通過寫入文件來違反配置文件會發(fā)生什么:

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

最后,讓我們看看如果我們試圖指定一個尚未加載的配置文件會發(fā)生什么:

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
kubectl describe pod hello-apparmor-2
Name:          hello-apparmor-2
Namespace:     default
Node:          gke-test-default-pool-239f5d02-x1kf/
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
Reason:        AppArmor
Message:       Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
IP:
Controllers:   <none>
Containers:
  hello:
    Container ID:
    Image:     busybox
    Image ID:
    Port:
    Command:
      sh
      -c
      echo 'Hello AppArmor!' && sleep 1h
    State:              Waiting
      Reason:           Blocked
    Ready:              False
    Restart Count:      0
    Environment:        <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-dnz7v (ro)
Conditions:
  Type          Status
  Initialized   True
  Ready         False
  PodScheduled  True
Volumes:
  default-token-dnz7v:
    Type:    Secret (a volume populated by a Secret)
    SecretName:    default-token-dnz7v
    Optional:   false
QoS Class:      BestEffort
Node-Selectors: <none>
Tolerations:    <none>
Events:
  FirstSeen    LastSeen    Count    From                        SubobjectPath    Type        Reason        Message
  ---------    --------    -----    ----                        -------------    --------    ------        -------
  23s          23s         1        {default-scheduler }                         Normal      Scheduled     Successfully assigned hello-apparmor-2 to e2e-test-stclair-node-pool-t1f5
  23s          23s         1        {kubelet e2e-test-stclair-node-pool-t1f5}             Warning        AppArmor    Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded

注意 Pod 呈現(xiàn) Pending 狀態(tài),并且顯示一條有用的錯誤信息: ?Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded?。 還用相同的消息記錄了一個事件。

管理

使用配置文件設置節(jié)點

Kubernetes 目前不提供任何本地機制來將 AppArmor 配置文件加載到節(jié)點上。 有很多方法可以設置配置文件,例如:

  • 通過在每個節(jié)點上運行 Pod 的 DaemonSet來確保加載了正確的配置文件。 可以在這里找到實現(xiàn)示例。
  • 在節(jié)點初始化時,使用節(jié)點初始化腳本(例如 Salt、Ansible 等)或鏡像。
  • 通過將配置文件復制到每個節(jié)點并通過 SSH 加載它們。

調(diào)度程序不知道哪些配置文件加載到哪個節(jié)點上,因此必須將全套配置文件加載到每個節(jié)點上。 另一種方法是為節(jié)點上的每個配置文件(或配置文件類)添加節(jié)點標簽, 并使用節(jié)點選擇器確保 Pod 在具有所需配置文件的節(jié)點上運行。

使用 PodSecurityPolicy 限制配置文件

說明:
PodSecurityPolicy 在 Kubernetes v1.21 版本中已被廢棄,將在 v1.25 版本移除。

如果啟用了 PodSecurityPolicy 擴展,則可以應用集群范圍的 AppArmor 限制。 要啟用 PodSecurityPolicy,必須在 ?apiserver ?上設置以下標志:

--enable-admission-plugins=PodSecurityPolicy[,others...]

AppArmor 選項可以指定為 PodSecurityPolicy 上的注解:

apparmor.security.beta.kubernetes.io/defaultProfileName: <profile_ref>
apparmor.security.beta.kubernetes.io/allowedProfileNames: <profile_ref>[,others...]

默認配置文件名選項指定默認情況下在未指定任何配置文件時應用于容器的配置文件。 所允許的配置文件名稱選項指定允許 Pod 容器運行期間所對應的配置文件列表。 如果同時提供了這兩個選項,則必須允許默認值。 配置文件的指定格式與容器上的相同。

禁用 AppArmor 

如果你不希望 AppArmor 在集群上可用,可以通過命令行標志禁用它:

--feature-gates=AppArmor=false

禁用時,任何包含 AppArmor 配置文件的 Pod 都將導致驗證失敗,且返回 “Forbidden” 錯誤。

說明:
即使此 Kubernetes 特性被禁用,運行時仍可能強制執(zhí)行默認配置文件。 當 AppArmor 升級為正式版 (GA) 時,禁用 AppArmor 功能的選項將被刪除。

編寫配置文件

獲得正確指定的 AppArmor 配置文件可能是一件棘手的事情。幸運的是,有一些工具可以幫助你做到這一點:

  • ?aa-genprof? 和 ?aa-logprof? 通過監(jiān)視應用程序的活動和日志并準許它所執(zhí)行的操作來生成配置文件規(guī)則。 AppArmor 文檔提供了進一步的指導。
  • bane 是一個用于 Docker的 AppArmor 配置文件生成器,它使用一種簡化的畫像語言(profile language)

想要調(diào)試 AppArmor 的問題,你可以檢查系統(tǒng)日志,查看具體拒絕了什么。 AppArmor 將詳細消息記錄到 ?dmesg?, 錯誤通常可以在系統(tǒng)日志中或通過 ?journalctl ?找到。 更多詳細信息見 AppArmor 失敗

API 參考

Pod 注解

指定容器將使用的配置文件:

  • 鍵名: ?container.apparmor.security.beta.kubernetes.io/<container_name>? ,其中 ?<container_name>? 與 Pod 中某容器的名稱匹配。 可以為 Pod 中的每個容器指定單獨的配置文件。
  • 鍵值: 對配置文件的引用,如下所述

配置文件引用

  • ?runtime/default?: 指默認運行時配置文件。
  • ?localhost/<profile_name>?: 按名稱引用加載到節(jié)點(localhost)上的配置文件。
  • ?unconfined?: 這相當于為容器禁用 AppArmor。

任何其他配置文件引用格式無效。

PodSecurityPolicy 注解

指定在未提供容器時應用于容器的默認配置文件:

  • 鍵名: ?apparmor.security.beta.kubernetes.io/defaultProfileName ?
  • 鍵值: 如上述文件參考所述

上面描述的指定配置文件,Pod 容器列表的配置文件引用允許指定:

  • 鍵名: ?apparmor.security.beta.kubernetes.io/allowedProfileNames ?
  • 鍵值: 配置文件引用的逗號分隔列表(如上所述)

盡管轉(zhuǎn)義逗號是配置文件名中的合法字符,但此處不能顯式允許。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號