使用 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.30。
作为一项测试版功能,您可以将 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
配置,则 Pod 在未指定其他 seccomp 配置文件时使用 RuntimeDefault
seccomp 配置文件。否则,默认值为 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: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.30 仅支持使用.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
配置文件,您会注意到在第一个示例的 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 配置文件默认值,您必须使用 --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.30 允许您配置在 Pod 的规范未定义特定 seccomp 配置文件时应用的 seccomp 配置文件。但是,您仍然需要为要使用它的每个节点启用此默认值。
如果您正在运行 Kubernetes 1.30 集群并希望启用该功能,请使用 --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 的更多信息