使用 seccomp 限制容器的系统调用
Kubernetes v1.19 [稳定]
Seccomp 代表安全计算模式,并且自 2.6.12 版本以来一直是 Linux 内核的一项特性。它可以用来沙箱化进程的特权,限制其从用户空间到内核的调用。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 的清单,该清单请求为其所有容器使用 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.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 配置文件的基础。
在继续下一节之前,请删除 Service 和 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 Service 公开 Pod。
kubectl expose pod fine-pod --type NodePort --port 5678
检查 Service 在节点上分配了哪个端口。
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
中看不到任何输出。这是因为配置文件允许了所有必要的系统调用,并指定如果调用列表中以外的系统调用,则会发生错误。从安全角度来看,这是一种理想的情况,但需要在分析程序上付出一些努力。如果有一种简单的方法可以在不需要付出太多努力的情况下更接近这种安全性,那就太好了。
在继续下一节之前,请删除 Service 和 Pod。
kubectl delete service fine-pod --wait
kubectl delete pod fine-pod --wait --now
启用将 RuntimeDefault
用作所有工作负载的默认 seccomp 配置文件的功能。
Kubernetes v1.27 [stable]
要使用 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 的规范未定义特定 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 工作节点上为容器运行 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 的更多信息