Kubernetes 使用seccomp限制容器的系統(tǒng)調(diào)用

2022-06-25 08:59 更新

使用 seccomp 限制容器的系統(tǒng)調(diào)用

特性狀態(tài): Kubernetes v1.19 [stable]

Seccomp 代表安全計算(Secure Computing)模式,自 2.6.12 版本以來,一直是 Linux 內(nèi)核的一個特性。 它可以用來沙箱化進程的權(quán)限,限制進程從用戶態(tài)到內(nèi)核態(tài)的調(diào)用。 Kubernetes 能使你自動將加載到 節(jié)點上的 seccomp 配置文件應(yīng)用到你的 Pod 和容器。

識別你的工作負(fù)載所需要的權(quán)限是很困難的。在本篇教程中, 你將了解如何將 seccomp 配置文件加載到本地的 Kubernetes 集群中, 如何將它們應(yīng)用到 Pod,以及如何開始制作只為容器進程提供必要的權(quán)限的配置文件。

教程目標(biāo)

  • 了解如何在節(jié)點上加載 seccomp 配置文件
  • 了解如何將 seccomp 配置文件應(yīng)用到容器上
  • 觀察容器進程對系統(tǒng)調(diào)用的審計
  • 觀察指定的配置文件缺失時的行為
  • 觀察違反 seccomp 配置文件的行為
  • 了解如何創(chuàng)建細(xì)粒度的 seccomp 配置文件
  • 了解如何應(yīng)用容器運行時所默認(rèn)的 seccomp 配置文件

在開始之前

為了完成本篇教程中的所有步驟,你必須安裝 ?kind ?和 ?kubectl?。

本篇教程演示的某些示例仍然是 alpha 狀態(tài)(自 v1.22 起),另一些示例則僅使用 seccomp 正式發(fā)布的功能。 你應(yīng)該確保,針對你使用的版本, 正確配置了集群。

本篇教程也使用了 ?curl ?工具來下載示例到你的計算機上。 你可以使用其他自己偏好的工具來自適應(yīng)這些步驟。

說明:
無法將 seccomp 配置文件應(yīng)用于在容器的 ?securityContext ?中設(shè)置了 ?privileged: true? 的容器。 特權(quán)容器始終以 ?Unconfined ?的方式運行。

下載示例 seccomp 配置文件

這些配置文件的內(nèi)容將在稍后進行分析, 現(xiàn)在先將它們下載到名為 ?profiles/? 的目錄中,以便將它們加載到集群中。

  • audit.json
  • {
        "defaultAction": "SCMP_ACT_LOG"
    }
  • violation.json
  • {
        "defaultAction": "SCMP_ACT_ERRNO"
    }
  • fine-grained.json
  • {
        "defaultAction": "SCMP_ACT_ERRNO",
        "architectures": [
            "SCMP_ARCH_X86_64",
            "SCMP_ARCH_X86",
            "SCMP_ARCH_X32"
        ],
        "syscalls": [
            {
                "names": [
                    "accept4",
                    "epoll_wait",
                    "pselect6",
                    "futex",
                    "madvise",
                    "epoll_ctl",
                    "getsockname",
                    "setsockopt",
                    "vfork",
                    "mmap",
                    "read",
                    "write",
                    "close",
                    "arch_prctl",
                    "sched_getaffinity",
                    "munmap",
                    "brk",
                    "rt_sigaction",
                    "rt_sigprocmask",
                    "sigaltstack",
                    "gettid",
                    "clone",
                    "bind",
                    "socket",
                    "openat",
                    "readlinkat",
                    "exit_group",
                    "epoll_create1",
                    "listen",
                    "rt_sigreturn",
                    "sched_yield",
                    "clock_gettime",
                    "connect",
                    "dup2",
                    "epoll_pwait",
                    "execve",
                    "exit",
                    "fcntl",
                    "getpid",
                    "getuid",
                    "ioctl",
                    "mprotect",
                    "nanosleep",
                    "open",
                    "poll",
                    "recvfrom",
                    "sendto",
                    "set_tid_address",
                    "setitimer",
                    "writev"
                ],
                "action": "SCMP_ACT_ALLOW"
            }
        ]
    }

執(zhí)行這些命令:

mkdir ./profiles
curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json
curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json
curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json
ls profiles

你應(yīng)該看到在最后一步的末尾列出有三個配置文件:

audit.json  fine-grained.json  violation.json

使用 kind 創(chuàng)建本地 Kubernetes 集群 

為簡單起見,kind 可用來創(chuàng)建加載了 seccomp 配置文件的單節(jié)點集群。 Kind 在 Docker 中運行 Kubernetes,因此集群的每個節(jié)點都是一個容器。 這允許將文件掛載到每個容器的文件系統(tǒng)中,類似于將文件加載到節(jié)點上。

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
  extraMounts:
  - hostPath: "./profiles"
    containerPath: "/var/lib/kubelet/seccomp/profiles"

下載該示例 kind 配置,并將其保存到名為 ?kind.yaml? 的文件中:

curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml

你可以通過設(shè)置節(jié)點的容器鏡像來設(shè)置特定的 Kubernetes 版本。 有關(guān)此類配置的更多信息, 參閱 kind 文檔中節(jié)點小節(jié)。 本篇教程假定你正在使用 Kubernetes v1.24。

作為 alpha 特性,你可以將 Kubernetes 配置為使用 容器運行時 默認(rèn)首選的配置文件,而不是回退到 ?Unconfined?。 

有了 kind 配置后,使用該配置創(chuàng)建 kind 集群:

kind create cluster --config=kind.yaml

新的 Kubernetes 集群準(zhǔn)備就緒后,找出作為單節(jié)點集群運行的 Docker 容器:

docker ps

你應(yīng)該看到輸出中名為 ?kind-control-plane? 的容器正在運行。 輸出類似于:

CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                       NAMES
6a96207fed4b        kindest/node:v1.18.2   "/usr/local/bin/entr…"   27 seconds ago      Up 24 seconds       127.0.0.1:42223->6443/tcp   kind-control-plane

如果觀察該容器的文件系統(tǒng), 你應(yīng)該會看到 ?profiles/? 目錄已成功加載到 kubelet 的默認(rèn) seccomp 路徑中。 使用 ?docker exec? 在 Pod 中運行命令:

# 將 6a96207fed4b 更改為你從 “docker ps” 看到的容器 ID
docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles
audit.json  fine-grained.json  violation.json

你已驗證這些 seccomp 配置文件可用于在 kind 中運行的 kubelet。

啟用使用 RuntimeDefault 作為所有工作負(fù)載的默認(rèn) seccomp 配置文件

特性狀態(tài): Kubernetes v1.22 [alpha]

?SeccompDefault ?是一個可選的 kubelet 特性門控 以及相應(yīng)的 ?--seccomp-default? 命令行標(biāo)志。 兩者必須同時啟用才能使用該功能。

如果啟用,kubelet 將會默認(rèn)使用 ?RuntimeDefault ?seccomp 配置文件, (這一配置文明是由容器運行時定義的),而不是使用 ?Unconfined?(禁用 seccomp)模式。 默認(rèn)的配置文件旨在提供一組限制性較強且能保留工作負(fù)載功能的安全默認(rèn)值。 不同容器運行時及其不同發(fā)布版本之間的默認(rèn)配置文件可能有所不同, 例如在比較來自 CRI-O 和 containerd 的配置文件時。

說明:
啟用該功能既不會更改 Kubernetes ?securityContext.seccompProfile? API 字段, 也不會添加已棄用的工作負(fù)載注解。 這為用戶提供了隨時回滾的可能性,而且無需實際更改工作負(fù)載配置。 ?crictl inspect? 之類的工具可用于驗證容器正在使用哪個 seccomp 配置文件。

與其他工作負(fù)載相比,某些工作負(fù)載可能需要更少的系統(tǒng)調(diào)用限制。 這意味著即使使用 ?RuntimeDefault ?配置文件,它們也可能在運行時失敗。 要應(yīng)對此類故障,你可以:

  • 將工作負(fù)載顯式運行為 ?Unconfined?。
  • 禁用節(jié)點的 ?SeccompDefault ?功能。還要確保工作負(fù)載被調(diào)度到禁用該功能的節(jié)點上。
  • 為工作負(fù)載創(chuàng)建自定義 seccomp 配置文件。

如果你將此功能引入到類似生產(chǎn)的集群中, Kubernetes 項目建議你在部分節(jié)點上啟用此特性門控, 然后在整個集群范圍內(nèi)推出更改之前,測試工作負(fù)載執(zhí)行情況。

有關(guān)可能的升級和降級策略的更多詳細(xì)信息, 請參閱相關(guān)的 Kubernetes 增強提案 (KEP)。

由于此特性處于 alpha 階段,默認(rèn)是被禁用的。 要啟用它,傳遞標(biāo)志 ?--feature-gates=SeccompDefault=true --seccomp-default? 到 kubelet CLI 或者通過 kubelet 配置文件啟用。 要在 kind 啟用特性門控, 請確保 ?kind ?提供所需的最低 Kubernetes 版本, 并在 kind 配置 中啟用了 ?SeccompDefault ?特性:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  SeccompDefault: true
nodes:
  - role: control-plane
    image: kindest/node:v1.23.0@sha256:49824ab1727c04e56a21a5d8372a402fcd32ea51ac96a2706a12af38934f81ac
    kubeadmConfigPatches:
      - |
        kind: JoinConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            seccomp-default: "true"        
  - role: worker
    image: kindest/node:v1.23.0@sha256:49824ab1727c04e56a21a5d8372a402fcd32ea51ac96a2706a12af38934f81ac
    kubeadmConfigPatches:
      - |
        kind: JoinConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            feature-gates: SeccompDefault=true
            seccomp-default: "true"

如果集群已就緒,則運行一個 Pod:

kubectl run --rm -it --restart=Never --image=alpine alpine -- sh

現(xiàn)在應(yīng)該附加了默認(rèn)的 seccomp 配置文件。 這可以通過使用 ?docker exec? 為 kind 上的容器運行 ?crictl inspect? 來驗證:

docker exec -it kind-worker bash -c \
    'crictl inspect $(crictl ps --name=alpine -q) | jq .info.runtimeSpec.linux.seccomp'
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
  "syscalls": [
    {
      "names": ["..."]
    }
  ]
}

使用 seccomp 配置文件創(chuàng)建 Pod 以進行系統(tǒng)調(diào)用審計

首先,將 ?audit.json? 配置文件應(yīng)用到新的 Pod 上,該配置文件將記錄進程的所有系統(tǒng)調(diào)用。

這是該 Pod 的清單:

apiVersion: v1
kind: Pod
metadata:
  name: audit-pod
  labels:
    app: audit-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/audit.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false
說明:
已棄用的 seccomp 注解 ?seccomp.security.alpha.kubernetes.io/pod?(針對整個 Pod)和 ?container.seccomp.security.alpha.kubernetes.io/[name]?(針對單個容器) 將隨著 Kubernetes v1.25 的發(fā)布而被刪除。 請在可能的情況下使用原生 API 字段而不是注解。

在集群中創(chuàng)建 Pod:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml

此配置文件不限制任何系統(tǒng)調(diào)用,因此 Pod 應(yīng)該成功啟動。

kubectl get pod/audit-pod
NAME        READY   STATUS    RESTARTS   AGE
audit-pod   1/1     Running   0          30s

為了能夠與容器暴露的端點交互, 創(chuàng)建一個 NodePort 類型的 Service, 允許從 kind 控制平面容器內(nèi)部訪問端點。

kubectl expose pod audit-pod --type NodePort --port 5678

檢查 Service 在節(jié)點上分配的端口。

kubectl get service audit-pod

輸出類似于:

NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
audit-pod   NodePort   10.111.36.142   <none>        5678:32373/TCP   72s

現(xiàn)在,你可以使用 ?curl ?從 kind 控制平面容器內(nèi)部訪問該端點,位于該服務(wù)所公開的端口上。 使用 ?docker exec? 在屬于該控制平面容器的容器中運行 ?curl ?命令:

# 將 6a96207fed4b 更改為你從 “docker ps” 看到的控制平面容器 ID
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

你可以看到該進程正在運行,但它實際上進行了哪些系統(tǒng)調(diào)用? 因為這個 Pod 在本地集群中運行,你應(yīng)該能夠在 ?/var/log/syslog? 中看到它們。 打開一個新的終端窗口并 ?tail ?來自 ?http-echo? 的調(diào)用的輸出:

tail -f /var/log/syslog | grep 'http-echo'

你應(yīng)該已經(jīng)看到了一些由 ?http-echo? 進行的系統(tǒng)調(diào)用的日志, 如果你在控制平面容器中 ?curl ?端點,你會看到更多的寫入。

例如:

Jul  6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul  6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul  6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000

通過查看每一行的 ?syscall=? 條目,你可以開始了解 ?http-echo? 進程所需的系統(tǒng)調(diào)用。 雖然這些不太可能包含它使用的所有系統(tǒng)調(diào)用,但它可以作為此容器的 seccomp 配置文件的基礎(chǔ)。

在轉(zhuǎn)到下一部分之前清理該 Pod 和 Service:

kubectl delete service audit-pod --wait
kubectl delete pod audit-pod --wait --now

使用導(dǎo)致違規(guī)的 seccomp 配置文件創(chuàng)建 Pod 

出于演示目的,將配置文件應(yīng)用于不允許任何系統(tǒng)調(diào)用的 Pod 上。

此演示的清單是:

apiVersion: v1
kind: Pod
metadata:
  name: violation-pod
  labels:
    app: violation-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/violation.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

嘗試在集群中創(chuàng)建 Pod:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml

Pod 創(chuàng)建,但存在問題。 如果你檢查 Pod 狀態(tài),你應(yīng)該看到它沒有啟動。

kubectl get pod/violation-pod
NAME            READY   STATUS             RESTARTS   AGE
violation-pod   0/1     CrashLoopBackOff   1          6s

如上例所示,?http-echo? 進程需要相當(dāng)多的系統(tǒng)調(diào)用。 這里 seccomp 已通過設(shè)置 ?"defaultAction": "SCMP_ACT_ERRNO"? 被指示為在發(fā)生任何系統(tǒng)調(diào)用時報錯。 這是非常安全的,但消除了做任何有意義的事情的能力。 你真正想要的是只給工作負(fù)載它們所需要的權(quán)限。

在轉(zhuǎn)到下一部分之前清理該 Pod:

kubectl delete pod violation-pod --wait --now

使用只允許必要的系統(tǒng)調(diào)用的 seccomp 配置文件創(chuàng)建 Pod

如果你看一看 ?fine-grained.json? 配置文件, 你會注意到第一個示例的 syslog 中看到的一些系統(tǒng)調(diào)用, 其中配置文件設(shè)置為 ?"defaultAction": "SCMP_ACT_LOG"?。 現(xiàn)在的配置文件設(shè)置 ?"defaultAction": "SCMP_ACT_ERRNO"?, 但在 ?"action": "SCMP_ACT_ALLOW"? 塊中明確允許一組系統(tǒng)調(diào)用。 理想情況下,容器將成功運行,并且你看到?jīng)]有消息發(fā)送到 ?syslog?。

此示例的清單是:

apiVersion: v1
kind: Pod
metadata:
  name: fine-pod
  labels:
    app: fine-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/fine-grained.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

在你的集群中創(chuàng)建 Pod:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml
kubectl get pod fine-pod

此 Pod 應(yīng)該顯示為已成功啟動:

NAME        READY   STATUS    RESTARTS   AGE
fine-pod   1/1     Running   0          30s

打開一個新的終端窗口并使用 ?tail ?來監(jiān)視提到來自 ?http-echo? 的調(diào)用的日志條目:

# 你計算機上的日志路徑可能與 “/var/log/syslog” 不同
tail -f /var/log/syslog | grep 'http-echo'

接著,使用 NodePort Service 公開 Pod:

kubectl expose pod fine-pod --type NodePort --port 5678

檢查節(jié)點上的 Service 分配了什么端口:

kubectl get service fine-pod

輸出類似于:

NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
fine-pod    NodePort   10.111.36.142   <none>        5678:32373/TCP   72s

使用 ?curl ?從 kind 控制平面容器內(nèi)部訪問端點:

# 將 6a96207fed4b 更改為你從 “docker ps” 看到的控制平面容器 ID
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

你應(yīng)該在 ?syslog ?中看不到任何輸出。 這是因為配置文件允許所有必要的系統(tǒng)調(diào)用,并指定如果調(diào)用列表之外的系統(tǒng)調(diào)用應(yīng)發(fā)生錯誤。 從安全角度來看,這是一種理想的情況,但需要在分析程序時付出一些努力。 如果有一種簡單的方法可以在不需要太多努力的情況下更接近這種安全性,那就太好了。

在轉(zhuǎn)到下一部分之前清理該 Pod 和服務(wù):

kubectl delete service fine-pod --wait
kubectl delete pod fine-pod --wait --now

創(chuàng)建使用容器運行時默認(rèn) seccomp 配置文件的 Pod

大多數(shù)容器運行時都提供了一組合理的默認(rèn)系統(tǒng)調(diào)用,以及是否允許執(zhí)行這些系統(tǒng)調(diào)用。 你可以通過將 Pod 或容器的安全上下文中的 seccomp 類型設(shè)置為 ?RuntimeDefault ?來為你的工作負(fù)載采用這些默認(rèn)值。

說明:
如果你已經(jīng)啟用了 ?SeccompDefault ?特性門控, 只要沒有指定其他 seccomp 配置文件,那么 Pod 就會使用 ?SeccompDefault ?的 seccomp 配置文件。 否則,默認(rèn)值為 ?Unconfined?。

這是一個 Pod 的清單,它要求其所有容器使用 ?RuntimeDefault ?seccomp 配置文件:

apiVersion: v1
kind: Pod
metadata:
  name: default-pod
  labels:
    app: default-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some more syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

創(chuàng)建此 Pod:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml
kubectl get pod default-pod

此 Pod 應(yīng)該顯示為成功啟動:

NAME        READY   STATUS    RESTARTS   AGE
default-pod 1/1     Running   0          20s

最后,你看到一切正常之后,請清理:

kubectl delete pod default-pod --wait --now


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號