使用 seccomp 限制容器的系統呼叫

功能狀態: Kubernetes v1.19 [穩定]

Seccomp 代表安全運算模式,自 Linux 核心版本 2.6.12 以來一直是其功能。它可以用來沙盒化程序的權限,限制其從使用者空間到核心的呼叫。Kubernetes 可讓您自動將載入到節點上的 seccomp 設定檔套用到您的 Pod 和容器。

識別您的工作負載所需的權限可能很困難。在本教學中,您將逐步了解如何將 seccomp 設定檔載入到本機 Kubernetes 叢集中、如何將它們套用到 Pod,以及如何開始製作僅為您的容器程序提供必要權限的設定檔。

目標

  • 學習如何在節點上載入 seccomp 設定檔
  • 學習如何將 seccomp 設定檔套用到容器
  • 觀察容器程序所發出的系統呼叫稽核
  • 觀察指定遺失設定檔時的行為
  • 觀察違反 seccomp 設定檔的行為
  • 學習如何建立細緻的 seccomp 設定檔
  • 學習如何套用容器執行階段預設 seccomp 設定檔

開始之前

為了完成本教學中的所有步驟,您必須安裝 kindkubectl

本教學中使用的命令假設您使用 Docker 作為您的容器執行階段。(kind 建立的叢集可能在內部使用不同的容器執行階段)。您也可以使用 Podman,但在這種情況下,您必須遵循特定的指示才能成功完成任務。

本教學展示了一些仍然是 beta (自 v1.25 以來) 的範例,以及其他僅使用一般可用 seccomp 功能的範例。您應該確保您的叢集已針對您使用的版本正確配置

本教學也使用 curl 工具將範例下載到您的電腦。如果您願意,您可以調整步驟以使用不同的工具。

下載範例 seccomp 設定檔

稍後將探討這些設定檔的內容,但現在請繼續將它們下載到名為 profiles/ 的目錄中,以便將它們載入到叢集中。

{
    "defaultAction": "SCMP_ACT_LOG"
}

{
    "defaultAction": "SCMP_ACT_ERRNO"
}

{
    "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",
                "fstatfs",
                "getdents64",
                "pipe2",
                "getrlimit"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

執行這些命令

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

您應該在最後一個步驟的結尾看到列出的三個設定檔

audit.json  fine-grained.json  violation.json

使用 kind 建立本機 Kubernetes 叢集

為了簡化,可以使用 kind 建立載入 seccomp 設定檔的單一節點叢集。Kind 在 Docker 中執行 Kubernetes,因此叢集的每個節點都是一個容器。這允許將檔案掛載在每個容器的檔案系統中,類似於將檔案載入到節點上。

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

您可以透過設定節點的容器映像檔來設定特定的 Kubernetes 版本。請參閱 kind 文件中關於配置的 節點 以取得更多詳細資訊。本教學假設您使用 Kubernetes v1.32。

作為 Beta 功能,您可以將 Kubernetes 配置為使用容器執行階段預設偏好的設定檔,而不是退回到 Unconfined。如果您想嘗試,請在繼續之前參閱啟用使用 RuntimeDefault 作為所有工作負載的預設 seccomp 設定檔

一旦您有了 kind 配置,請使用該配置建立 kind 叢集

kind create cluster --config=kind.yaml

在新的 Kubernetes 叢集準備就緒後,識別作為單一節點叢集執行的 Docker 容器

docker ps

您應該看到輸出指示容器正在執行,名稱為 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

如果觀察該容器的檔案系統,您應該看到 profiles/ 目錄已成功載入到 kubelet 的預設 seccomp 路徑中。使用 docker exec 在 Pod 中執行命令

# Change 6a96207fed4b to the container ID you saw from "docker ps"
docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles
audit.json  fine-grained.json  violation.json

您已驗證這些 seccomp 設定檔可用於在 kind 中執行的 kubelet。

建立使用容器執行階段預設 seccomp 設定檔的 Pod

大多數容器執行期會提供一組合理的預設系統呼叫許可清單或封鎖清單。您可以透過將 Pod 或容器安全情境中的 seccomp 類型設定為 RuntimeDefault,為您的工作負載採用這些預設值。

以下是一個 Pod 的 Manifest 範例,它為其所有容器請求 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:1.0
    args:
    - "-text=just made some more syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

建立該 Pod

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

Pod 應顯示為已成功啟動

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

在移至下一節之前,請刪除 Pod

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

建立一個具有 seccomp 設定檔以進行系統呼叫稽核的 Pod

首先,將 audit.json 設定檔套用至新的 Pod,這將記錄程序的所有系統呼叫。

以下是該 Pod 的 Manifest 範例

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:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

在叢集中建立 Pod

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

此設定檔不會限制任何系統呼叫,因此 Pod 應成功啟動。

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

為了能夠與此容器公開的端點互動,請建立一個 NodePort 服務,允許從 kind 控制平面容器內部存取該端點。

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

檢查節點上已為服務分配的埠號。

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

現在您可以使用 curl 從 kind 控制平面容器內部存取該端點,埠號是此服務公開的埠號。使用 docker exec 在屬於該控制平面容器的容器內執行 curl 命令

# Change 6a96207fed4b to the control plane container ID and 32373 to the port number you saw from "docker ps"
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

您可以看到程序正在執行,但它實際上發出了哪些系統呼叫?由於此 Pod 正在本機叢集中執行,您應該能夠在本機系統上的 /var/log/syslog 中看到這些呼叫。開啟一個新的終端機視窗,並 tail 輸出中來自 http-echo 的呼叫

# The log path on your computer might be different from "/var/log/syslog"
tail -f /var/log/syslog | grep 'http-echo'

您應該已經看到一些 http-echo 發出的系統呼叫記錄,如果您再次在控制平面容器內執行 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 程序所需的系統呼叫。雖然這些不太可能包含它使用的所有系統呼叫,但它可以作為此容器 seccomp 設定檔的基礎。

在移至下一節之前,請刪除服務和 Pod

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

建立一個具有 seccomp 設定檔的 Pod,該設定檔會導致違規

為了示範,將一個不允許任何系統呼叫的設定檔套用至 Pod。

此示範的 Manifest 範例為

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:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

嘗試在叢集中建立 Pod

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

Pod 已建立,但出現問題。如果您檢查 Pod 的狀態,您應該會看到它啟動失敗。

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

如先前的範例所示,http-echo 程序需要相當多的系統呼叫。在這裡,seccomp 已被指示在任何系統呼叫上產生錯誤,方法是將 "defaultAction": "SCMP_ACT_ERRNO" 設定為錯誤。這非常安全,但移除了執行任何有意義操作的能力。您真正想要的是僅授予工作負載它們需要的權限。

在移至下一節之前,請刪除 Pod

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

建立一個具有 seccomp 設定檔的 Pod,該設定檔僅允許必要的系統呼叫

如果您查看 fine-grained.json 設定檔,您會注意到第一個範例中 syslog 中看到的一些系統呼叫,其中設定檔設定了 "defaultAction": "SCMP_ACT_LOG"。現在設定檔設定為 "defaultAction": "SCMP_ACT_ERRNO",但明確允許 "action": "SCMP_ACT_ALLOW" 區塊中的一組系統呼叫。理想情況下,容器將成功執行,並且您將看不到任何訊息傳送到 syslog

此範例的 Manifest 範例為

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:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

在您的叢集中建立 Pod

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

Pod 應顯示為已成功啟動

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

開啟一個新的終端機視窗,並使用 tail 監控日誌條目中提及來自 http-echo 的呼叫

# The log path on your computer might be different from "/var/log/syslog"
tail -f /var/log/syslog | grep 'http-echo'

接下來,使用 NodePort 服務公開 Pod

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

檢查節點上已為服務分配的埠號

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 控制平面容器內部存取該端點

# Change 6a96207fed4b to the control plane container ID and 32373 to the port number you saw from "docker ps"
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

您應該在 syslog 中看不到任何輸出。這是因為設定檔允許所有必要的系統呼叫,並指定如果調用清單外的系統呼叫,則應發生錯誤。從安全角度來看,這是理想的情況,但需要一些努力來分析程式。如果有一種簡單的方法可以在不需要太多努力的情況下更接近這種安全性,那就太好了。

在移至下一節之前,請刪除服務和 Pod

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

啟用 RuntimeDefault 作為所有工作負載的預設 seccomp 設定檔

功能狀態: Kubernetes v1.27 [穩定]

若要使用 seccomp 設定檔預設值,您必須為每個要使用它的節點啟用 --seccomp-default 命令列旗標 來執行 kubelet。

如果啟用,kubelet 將預設使用容器執行期定義的 RuntimeDefault seccomp 設定檔,而不是使用 Unconfined(停用 seccomp)模式。預設設定檔旨在提供一組強大的安全性預設值,同時保留工作負載的功能。預設設定檔在不同容器執行期及其發行版本之間可能有所不同,例如比較 CRI-O 和 containerd 的設定檔時。

某些工作負載可能需要比其他工作負載更低的系統呼叫限制。這表示即使使用 RuntimeDefault 設定檔,它們也可能在執行期間失敗。為了減輕這種失敗,您可以

  • 將工作負載明確以 Unconfined 執行。
  • 針對節點停用 SeccompDefault 功能。同時確保工作負載排程在停用此功能的節點上。
  • 為工作負載建立自訂 seccomp 設定檔。

如果您要將此功能引入類似生產環境的叢集,Kubernetes 專案建議您在節點子集中啟用此功能閘道,然後在叢集範圍內推出變更之前測試工作負載執行。

您可以在相關的 Kubernetes 增強提案 (KEP) 中找到有關可能升級和降級策略的更詳細資訊:預設啟用 seccomp

Kubernetes 1.32 允許您設定在 Pod 的 spec 未定義特定 seccomp 設定檔時套用的 seccomp 設定檔。但是,您仍然需要為每個要使用它的節點啟用此預設值。

如果您正在執行 Kubernetes 1.32 叢集並想要啟用此功能,請使用 --seccomp-default 命令列旗標執行 kubelet,或透過 kubelet 組態檔 啟用它。若要在 kind 中啟用此功能閘道,請確保 kind 提供最低要求的 Kubernetes 版本,並在 kind 組態中啟用 SeccompDefault 功能

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
    kubeadmConfigPatches:
      - |
        kind: JoinConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            seccomp-default: "true"        
  - role: worker
    image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
    kubeadmConfigPatches:
      - |
        kind: JoinConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            seccomp-default: "true"        

如果叢集已準備就緒,則執行 Pod

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

現在應該已附加預設 seccomp 設定檔。這可以使用 docker exec 在 kind worker 上執行 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": ["..."]
    }
  ]
}

下一步

您可以進一步了解 Linux seccomp