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

特性狀態: Kubernetes v1.19 [stable]

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.34。

作為一項 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 的清單,它請求為其所有容器使用 `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 的清單

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。

此演示的清單是

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` 程序需要相當多的系統呼叫。在這裡,透過設定 `"defaultAction": "SCMP_ACT_ERRNO"`,seccomp 被指示在任何系統呼叫上出錯。這非常安全,但消除了執行任何有意義操作的能力。你真正想要的是隻賦予工作負載所需的特權。

在進入下一節之前刪除該 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`。

此示例的清單是

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 配置檔案預設值,你必須為要使用它的每個節點啟用 kubelet 的 `--seccomp-default` 命令列標誌

如果啟用,kubelet 將預設使用 `RuntimeDefault` seccomp 配置檔案(由容器執行時定義),而不是使用 `Unconfined`(seccomp 已停用)模式。預設配置檔案旨在提供一組強大的安全預設值,同時保留工作負載的功能。預設配置檔案可能因容器執行時及其釋出版本而異,例如比較 CRI-O 和 containerd 的配置檔案。

有些工作負載可能需要比其他工作負載更少的系統呼叫限制。這意味著即使使用 `RuntimeDefault` 配置檔案,它們也可能在執行時失敗。為了減輕此類失敗,你可以

  • 明確將工作負載作為 `Unconfined` 執行。
  • 為節點停用 `SeccompDefault` 功能。還要確保工作負載排程到停用了該功能的節點上。
  • 為工作負載建立自定義 seccomp 配置檔案。

如果你要在生產類叢集中引入此功能,Kubernetes 專案建議你在部分節點上啟用此功能門,然後在叢集範圍推廣此更改之前測試工作負載執行。

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

Kubernetes 1.34 允許你配置當 Pod 規範未定義特定 seccomp 配置檔案時應用的 seccomp 配置檔案。但是,你仍然需要為要使用它的每個節點啟用此預設設定。

如果你正在執行 Kubernetes 1.34 叢集並希望啟用此功能,請使用 `--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 的資訊

上次修改時間:2023 年 10 月 31 日太平洋時間上午 9:48:重構(content.en.docs.tutorials.security.SecComp):為 kindes/node 映象新增 sha (1d7e34fcf2)