1、背景介绍
Seccomp(全称 “Secure computing”),早在 2.6.12 版本(2005年3月8日)就引入到内核中,是通过只允许内核支持部分 syscall(系统调用),或者拒绝内核认为可能有危险的 syscall 集合的方式,来限制一个进程所支持的 syscall调用。最初,Seccomp 只允许使用read、 write、 _exit、sigreturn 4个系统调用,一旦调用其他系统调用时,内核就会发送 SIGKILL 信号终止进程。因此也被称为 Seccomp-strict 模式。但由于其限制过于严格,导致实际上,并没有多少应用能够用的上这一安全特性。
直到 3.5 版本(2012年7月12日)的内核中引入 Seccomp的第二种匹配模式。在这种模式下,可以通过所谓的 filter自定义被允许使用的 syscall,而自定义过滤规则是借由BPF 语言来实现,故可以事先在用户态下定义好需要匹配的系统调用,并且加载到内核中,这种模式也被称为Seccomp-BPF。
相比于 Seccomp-strict 模式,Seccomp-BPF 极大的增添了 syscall 的可扩展性,并且使用 BPF 编写也大大的降低了开发者的编写难度。现如今,关键的程序限制只允许调用那些它绝对需要的成功运行的系统调用,这种思想被越来越多的产品采纳。基于浏览器的沙箱和在云原生上大火的容器就是就是两个很好的示例,甚至于 systemd 服务也能够通过 Seccomp 来限制。
一个 seccomp profile 简单示例如下:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"madvise",
"epoll_ctl",
"getsockname",
"setsockopt",
"vfork",
"mmap",
"read",
],
"action": "SCMP_ACT_ALLOW",
}
]
}
如何理解这个 Seccomp profile 呢?
SCMP_ACT_ALLOW 表示 syscall 允许通过,将defaultAction 设置为 SCMP_ACT_ERRNO 则不在 syscall白名单范围内的 syscall 都会被拒绝,还可以将defaultAction 设置为 SCMP_ACT_LOG 则不在 syscall 白名单范围内的 syscall 会产生日志。
通过 Seccomp Profile 用于可以根据服务所需的 syscall 自行定义,通过 SCMP_ACT_ERRNO 与SCMP_ACT_ALLOW 配合实现对允许的 syscall 放行,对不在 syscall 列表中的 syscall 进行拦截。从而实现 syscall维度服务的安全防护。
SCMP_ACT_LOG与SCMP_ACT_ERRNO 可以在不同场景使用,进而实现不同程度的安全防护即告警和拦截。
Linux 内核主要支持的 Seccomp Filter 动作如下:
2、Seccomp in Kubernetes
从安全的角度来看,Kubernetes 中包含如下所示的潜在攻击面:
为了保证集群以及容器应用的安全,Kubernetes 提供了多种安全机制,限制容器的行为,减少容器和集群的攻击面,保证整个系统的安全性。本文主要介绍 Kubernetes 中的 Seccomp 功能。
Seccomp (Secure computing mode缩写)代表安全计算模式,自 2.6.12 版本以来一直是 Linux 内核的一个特性。它可以用来设置沙箱化进程的权限,限制它从用户空间到内核的系统调用。Kubernetes 可以自动将 Seccomp profile 加载到pod 和容器所在的节点。Kubernetes 提供两种方式用于 pod 绑定 seccomp profile。
通过 annotation 标签绑定 seccomp profile
用户可以通过 pod 中的 annotation 标签向 pod 中添加seccomp 安全配置,并且选择对 pod 还是其中某个容器添加 seccomp 策略。
pod 层面:
annotations:
seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"
container 层面:
annotations:
container.security.alpha.kubernetes.io/: "localhost/profile.json"
这里容器运行时会默认从节点上配置的 seccomp 策略文件目录(k8s 集群上默认为/var/lib/kubelet/seccomp)中加载名称为 profile.json 的配置文件,这里的配置 value 支持以下三种策略:
- runtime/default - 使用运行时默认的 seccomp 配置,比如docker 默认的 profile、containerd 的默认profile;
- unconfined - 不使用 seccomp 过滤;
- localhost/ - 使用节点本地 seccomp profile root 路径下自定义的配置文件;
注意:从 Kubernetes v1.25 开始,kubelet 不再支持这些注解, 也不再支持在静态 Pod 中使用注解,并且当创建带有 seccomp 字段的 Pod 时不再自动填充 seccomp 注解。后续统一使用下面介绍的 securityContext 配置。
通过 security-context 配置 seccomp profile
社区从 1.19 版本 seccomp 特性 GA 开始在securityContext 配置中增加了 seccompProfile 的字段,用户可以直接在此配置 Pod 和 Container 维度的 seccomp策略,由于 annotations 配置的方式已经是废弃状态,为了今后不必要的兼容性调整,推荐使用该方式配置:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/audit.json
有三种 seccomp 配置类型:
- Unconfined: 不启用 seccomp;
- RuntimeDefault: kubelet 会启用默认的 seccomp 配置(默认 seccomp 配置文件是有容器运行时定义的);
- Localhost: 节点本地 seccomp 配置根目录(一般默认为/var/lib/kubelet/seccomp)下配置文件的相对路径;
所以基于 k8s 提供的 seccomp 配置方式,我们可以实现对 pod 进行 syscall 级别的安全防护。
Seccomp 在云原生场景下的应用方式
云原生场景下,业务一般是通过微服务的方式对外提供服务,一般每个微服务的行为相比与主机业务行为会更为单一。因此当通过一系列的测试手段激发出服务所需的全部syscall 从而生成 syscall 白名单,通过 syscall 白名单限制服务系统调用,可以在 syscall 维度保证服务安全。当有攻击通过服务漏洞进入到服务对应的 Pod 中,进行一些操作时,由于这些操作对应的 syscall 不在白名单内,此时攻击失败。
我们将 syscall 白名单的生成与使用分为三个阶段:学习阶段,监视阶段,保护阶段,整个过程如图所示:
学习阶段:
seccomp controller 组件为 crd 资源,它可以实现对 k8s 集群内指定 pod 进行 syscall 采集并且将采集得到的 syscall 列表落盘到每个工作节点。seccomp controller 的 syscall 学习功能是通过 ebpf 实现的,通过 ebpf 可以获取工作节点每个进程产生的系统调用,进而获取到指定 pod 对应的系统调用。通过 ebpf 的方式,将集群中每个容器产生的 syscall 调用进行收集,生成 syscall 白名单。
监视阶段:
使用 SCMP_ACT_LOG 过滤方式,可以将不在白名单的系统调用,以日志的形式打印出来,此时测试人员和安全运维人员可以对系统调用进行判断,确定业务是否需要该 syscall,如果需要则加入到 syscall 白名单中。在监视阶段可以完成 syscall 白名单的收敛,进而得到与业务完全匹配的 syscall 白名单。
监视模式创建的 seccomp 配置文件如下所示:(syscall列表只是举例,实际 Pod 所需 syscall 列表会更多)
{
"defaultAction": "SCMP_ACT_LOG",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"madvise",
"epoll_ctl",
"getsockname",
"setsockopt",
"vfork",
"mmap",
"read",
"write",
"close",
"arch_prctl"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
保护阶段:
使用 SCMP_ACT_ERRNO 过滤方式,对每个业务容器进行 syscall 级别的保护。一旦业务容器中存在异常系统调用,对其进行拦截。SCMP_ACT_ERRNO 对 syscall 拦截发生在内核态,所以不用担心 seccomp profile 安全防护的效率问题。
保护模式创建的 seccomp 配置文件如下所示:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"madvise",
"epoll_ctl",
"getsockname",
"setsockopt",
"vfork",
"mmap",
"read",
"write",
"close",
"arch_prctl"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
3、总结
本文从 Seccomp 机制出发,在 linux 内核层面介绍了 Seccomp 可以实现的安全能力即对进程的系统调用限制能力。Kubernetes 为我们提供了可以对容器进行系统调用层面保护的接口即 Seccomp 能力。在云原生场景,通过使用 ebpf 的手段获取业务系统调用白名单,通过linux 内核支持的 Seccomp 机制对业务容器进行系统调用维度的防护,进而保证业务容器 syscall 层面的安全。
一旦有攻击者希望通过不在白名单内的 syscall 实现攻击行为,通过 Seccomp 机制我们可以对攻击进行告警和拦截,最终保证业务容器的安全。Seccomp 机制为云原生场景提供了 Syscall 维度的安全保证,后续将对此持续探索。
4、参考链接
https://man7.org/linux/man-pages/man3/seccomp_rule_add.3.html
https://github.com/feiskyer/kubernetes-handbook/blob/master/practice/security.md
https://kubernetes.io/docs/tutorials/security/seccomp/
http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/04/seccomp
https://developer.aliyun.com/article/891215