使用 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,但在这种情况下,您必须遵循特定的 说明 才能成功完成任务。
本教程展示了一些仍然处于测试阶段(自 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.31。
作为测试版功能,您可以将 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 或容器的 securityContext
中的 seccomp 类型设置为 RuntimeDefault
来采用这些默认值。
注意
如果您启用了seccompDefault
配置,那么当未指定其他 seccomp 配置文件时,Pod 将使用 RuntimeDefault
seccomp 配置文件。否则,默认值为 Unconfined
。以下是请求其所有容器使用 RuntimeDefault
seccomp 配置文件的 Pod 的清单
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
注意
较旧版本的 Kubernetes 允许您使用 注释 配置 seccomp 行为。Kubernetes 1.31 仅支持使用.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。
此演示的清单是
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
配置文件,您会注意到在第一个示例(配置文件设置"defaultAction": "SCMP_ACT_LOG"
)的 syslog 中看到的一些系统调用。现在,配置文件正在设置"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 配置文件默认值,您必须在要使用它的每个节点上使用--seccomp-default
命令行标志 运行 kubelet。
如果启用,kubelet 将默认使用RuntimeDefault
seccomp 配置文件(由容器运行时定义),而不是使用Unconfined
(seccomp 禁用)模式。默认配置文件旨在提供一组强大的安全默认值,同时保留工作负载的功能。默认配置文件可能在容器运行时及其发布版本之间有所不同,例如比较 CRI-O 和 containerd 的配置文件。
注意
启用此功能不会更改 Kubernetes 的securityContext.seccompProfile
API 字段,也不会添加工作负载的已弃用注释。这为用户提供了在不实际更改工作负载配置的情况下随时回滚的可能性。诸如 crictl inspect
之类的工具可用于验证容器正在使用哪个 seccomp 配置文件。某些工作负载可能需要比其他工作负载更少的系统调用限制。这意味着它们即使在使用RuntimeDefault
配置文件的情况下也可能在运行时失败。为了减轻这种失败,您可以
- 以
Unconfined
方式显式运行工作负载。 - 禁用节点的
SeccompDefault
功能。还要确保工作负载被调度到禁用此功能的节点上。 - 为工作负载创建自定义 seccomp 配置文件。
如果您将此功能引入类似生产的集群,Kubernetes 项目建议您在部分节点上启用此功能门,然后测试工作负载执行,然后再将更改推广到整个集群。
您可以在相关的 Kubernetes 增强提案 (KEP) 中找到有关可能的升级和降级策略的更详细的信息:默认启用 seccomp。
Kubernetes 1.31 允许您配置当 Pod 的规范没有定义特定的 seccomp 配置文件时应用的 seccomp 配置文件。但是,您仍然需要为要使用它的每个节点启用此默认值。
如果您运行的是 Kubernetes 1.31 集群并希望启用此功能,请使用--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 的更多信息