使用 seccomp 限制容器的系統呼叫
Kubernetes v1.19 [穩定]
Seccomp 代表安全運算模式,自 Linux 核心版本 2.6.12 以來一直是其功能。它可以用來沙盒化程序的權限,限制其從使用者空間到核心的呼叫。Kubernetes 可讓您自動將載入到節點上的 seccomp 設定檔套用到您的 Pod 和容器。
識別您的工作負載所需的權限可能很困難。在本教學中,您將逐步了解如何將 seccomp 設定檔載入到本機 Kubernetes 叢集中、如何將它們套用到 Pod,以及如何開始製作僅為您的容器程序提供必要權限的設定檔。
目標
- 學習如何在節點上載入 seccomp 設定檔
- 學習如何將 seccomp 設定檔套用到容器
- 觀察容器程序所發出的系統呼叫稽核
- 觀察指定遺失設定檔時的行為
- 觀察違反 seccomp 設定檔的行為
- 學習如何建立細緻的 seccomp 設定檔
- 學習如何套用容器執行階段預設 seccomp 設定檔
開始之前
為了完成本教學中的所有步驟,您必須安裝 kind 和 kubectl。
本教學中使用的命令假設您使用 Docker 作為您的容器執行階段。(kind
建立的叢集可能在內部使用不同的容器執行階段)。您也可以使用 Podman,但在這種情況下,您必須遵循特定的指示才能成功完成任務。
本教學展示了一些仍然是 beta (自 v1.25 以來) 的範例,以及其他僅使用一般可用 seccomp 功能的範例。您應該確保您的叢集已針對您使用的版本正確配置。
本教學也使用 curl
工具將範例下載到您的電腦。如果您願意,您可以調整步驟以使用不同的工具。
注意
無法將 seccomp 設定檔套用到在容器的securityContext
中設定為 privileged: true
執行的容器。特權容器始終以 Unconfined
執行。下載範例 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
,為您的工作負載採用這些預設值。
注意
如果您已啟用seccompDefault
組態設定,則每當未指定其他 seccomp 設定檔時,Pod 都會使用 RuntimeDefault
seccomp 設定檔。否則,預設值為 Unconfined
。以下是一個 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
注意
舊版本的 Kubernetes 允許您使用註解來設定 seccomp 行為。 Kubernetes 1.32 僅支援使用.spec.securityContext
中的欄位來設定 seccomp,本教學課程將說明這種方法。在叢集中建立 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 的設定檔時。
注意
啟用此功能既不會變更 KubernetessecurityContext.seccompProfile
API 欄位,也不會新增工作負載的已棄用註解。這為使用者提供了隨時回滾的可能性,而無需實際變更工作負載組態。諸如 crictl inspect
之類的工具可用於驗證容器正在使用的 seccomp 設定檔。某些工作負載可能需要比其他工作負載更低的系統呼叫限制。這表示即使使用 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