使用 seccomp 限制容器的系统调用
Kubernetes v1.19 [stable]
Seccomp 表示安全计算模式(secure computing mode),自 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.33。
作为 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.33 只支持使用.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 Service,允许从 kind 控制平面容器内部访问该端点。
kubectl expose pod audit-pod --type NodePort --port 5678
检查 Service 在节点上分配了哪个端口。
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 控制平面容器内部访问该端点,端口由该 Service 暴露。使用 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
进程需要相当多的系统调用。这里通过设置 "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 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 配置文件默认设置,你必须为你希望使用它的每个节点启用 kubelet 的 --seccomp-default
命令行参数。
如果启用,kubelet 将默认使用 RuntimeDefault
seccomp 配置文件,该配置文件由容器运行时定义,而不是使用 Unconfined
(seccomp 已禁用)模式。默认配置文件旨在提供一组强大的安全默认设置,同时保留工作负载的功能。容器运行时及其发布版本之间的默认配置文件可能不同,例如比较 CRI-O 和 containerd 的配置文件时。
注意
启用此特性不会改变 KubernetessecurityContext.seccompProfile
API 字段,也不会添加工作负载的已弃用注解。这让用户可以在不实际改变工作负载配置的情况下随时回滚。可以使用 crictl inspect
等工具来验证容器正在使用哪个 seccomp 配置文件。有些工作负载可能需要比其他工作负载更少的系统调用限制。这意味着即使使用 RuntimeDefault
配置文件,它们也可能在运行时失败。为了缓解此类失败,你可以
- 显式地以
Unconfined
运行工作负载。 - 对节点禁用
SeccompDefault
特性。同时确保工作负载调度到该特性被禁用的节点上。 - 为工作负载创建自定义 seccomp 配置文件。
如果你正在将此特性引入生产环境集群,Kubernetes 项目建议你在部分节点上启用此特性门控,然后测试工作负载执行情况,最后再在整个集群中推广此更改。
你可以在相关的 Kubernetes 增强提案 (KEP) 中找到有关可能的升级和降级策略的更详细信息:Enable seccomp by default。
Kubernetes 1.33 允许你配置当 Pod 规约未定义特定的 seccomp 配置文件时应用的 seccomp 配置文件。但是,你仍然需要在你希望使用此默认设置的每个节点上启用它。
如果你正在运行 Kubernetes 1.33 集群并想启用此特性,可以通过 kubelet 的 --seccomp-default
命令行参数或通过 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 的信息