自定义容器安全:在Kubernete中使用seccomp notify
本文主要介绍如何在Kubernetes中通过seccomp notify机制来自定义容器的安全机制。
seccomp是什么
seccomp
是Linux内核中用于限制应用使用 系统调用(System Calls) 的机制,类比于iptables
是网络的防火墙,seccomp
是系统调用的防火墙。seccomp
包括STRICT模式、BPF模式(cBPF,非eBFP)。
STRICT
:严格限制系统调用,只允许使用read
,wirte
,_exit
,sigreturn
四个系统调用,对于其他的系统调用,会发送SIGKILL
型号,使用场景有限。BPF
:当系统调用发生时,seccomp-bpf
调用指定的bpf过滤程序,bpf程序通过返回SCMP_ACT_ALLOW
,SCMP_ACT_KILL
,SCMP_ACT_ERRNO
等值,来决定系统调用如何处理。一般是允许或拒绝,也可以向调用者假装返回调用成功的code,但实际上未进行系统调用。seccomp-bpf
需要静态加载一个bpf程序,一般不需要自己重头编写,而是使用libseccomp
进行编写。通过libseccomp
,我们只需要编写如下所示的json文件,就能实现seccomp-bpf
对系统调用的过滤。{ "defaultAction": "SCMP_ACT_ERRNO", // 默认拒绝,白名单形式 "syscalls": [ { "names": [ // 允许的系统调用 "accept", "chown", "kill", "mmap", ... ], "action": "SCMP_ACT_ALLOW", "args": [], "comment": "", "includes": {}, "excludes": {} } ] }
seccomp用于容器
通过seccomp可以限制容器内进程的系统调用,防止恶意攻击。
docker可以通过--security-opt seccomp
对seccomp进行配置,不配置的情况下使用docker的默认的seccomp配置。
- 不对容器开启seccomp
$ docker run --security-opt seccomp=unconfined xxxx
- 指定自定义的seccomp策略在只使用containerd的环境中,其配置方式与docker是一致的,containerd的
$ docker run --security-opt seccomp=/path/to/seccomp/config.json
default seccomp
也基本是与docker是一致的。
kubernetes中使用seccomp
在1.19之前的K8S版本中,可以通过添加seccomp.security.alpha.kubernetes.io/pod
的Annotation来指定容器的seccomp配置:
apiVersion: v1
kind: Pod
metadata:
name: some-pod
labels:
app: some-pod
annotations:
seccomp.security.alpha.kubernetes.io/pod: localhost/profiles/some-profile.json
spec:
在1.19以后通过pod.spec.securityContext.seccompProfile
来指定:
apiVersion: v1
kind: Pod
metadata:
name: some-pod
labels:
app: some-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/some-profile.json
containers:
其中type
可以设置为:
Localhost
:使用指定的seccomp的配置文件。RuntimeDefault
:使用runtime默认的seccomp配置,例如上面的docker、containerd。Unconfined
:关闭seccomp。
另外,可以通过开启kubelet的SeccompDefault 特性,来对容器默认配置runtime default seccomp策略。
seccomp notify
虽然有了seccomp-bpf,但仍有诸多限制。seccomp-bpf整体上仍然是静态加载规则,bpf程序不能解引用指针(dereference pointer),去获取指针指向的内容。比如open("/tmp/file.log",O_WRONLY)
的系统调用,在seccomp_data
的args
中可能是[0x55fffff,0x0001]
,即open(0x55fffff,0x0001)
,而0x55fffff
指向的/tmp/file.log
并不能获得到。
// seccomp_data是bpf程序的输入
struct seccomp_data {
int nr; // system call number
__u32 arch; // AUDIT_ARCH_* in <linux/audit.h>
__u64 instruction_pointer; // CPU instruction pointer
__u64 args[6]; // system call arguments
};
同时,cBPF也无法像eBPF那样通过BPF maps改变程序的行为。因此,无法实现例如:根据不同的pod、namespace执行不同的seccomp策略;或是阻塞容器的系统调用,直至Kubernetes裁决系统调用可以执行等一些场景。
seccomp notify可以解决这些问题。在seccomp notify机制下,seccomp会将系统调用的决定权转交给另一个用户态的进程,这个过程是通过seccomp返回对应的seccomp-bpf程序的文件描述符fd来实现的。以系统调用mount为例(kinvolk上的一个例子),具体的流程如下:
- 程序调用
mount()
系统调用 seccomp
执行bpf程序,收到返回值SCMP_ACT_MOTIFY
seccomp agent
通过使用SECCOMP_IOCTL_NOTIF_RECV
调用ioctl
,获取seccomp_notify
(结构如图所示)seccomp agent
从程序对应的/proc/pid/mem
中读取mount()
的参数,并执行mount操作(如果程序运行在容器里,需要进入对应的ns。例子中是执行mount操作,除此外可以根据需要进行审计记录等其他操作)seccomp agent
使用SECCOMP_IOCTL_NOTIF_SEND
调用ioctl
,返回seccomp_notify_resp
(结构如图所示),告诉seccomp
返回success。seccomp
返回0给程序
使用seccomp notify需要:
- Runc >= 1.1.0
- Libseccomp >= 2.5.0 (>= 2.5.2 recommended)
- Linux kernel >= 5.9
kinvolk seccomp agent
随着runc实现对seccomp notify的支持,在K8S中使用seccomp notify也成为可能。简单来说,首先为runc运行的container配置bpf程序,用于返回notify;在收到seccomp的notify后,runc主进程调用seccomp agent(提前配置了socket地址),来进行加下来的处理逻辑。seccomp agent可以通过查询kube-apiserver来进一步实现复杂的处理逻辑。在runc中有个测试用的seccompagent ,实现了简单的对"chmod", "fchmod", "fchmodat", "mkdir"
四个系统调用的,如果自己实现,则可以通过kinvolk/seccompagent 实现。
引用kinvolk社区的一张图,说明K8S+seccomp notify的整个流程。
1)配置pod的seccompProfile
,指定配置文件为foo.json
。文件中通过SCMP_ACT_NOTIFY
的action设置了哪些系统调用需要使用seccomp notify机制,同时通过listenerPath
配置了seccomp agent的调用地址。除此外,还可以通过listenerMetadata
指定传递给seccomp agent的参数。
2)pod调度到节点,containerd根据设置的foo.json
生成配置调用runc启动容器。
3)在pod调用mount后,runc收到seccomp返回的fd,将container process state与fd发送到seccomp agent中,由seccomp agent完成处理逻辑。
kinvolk/seccompagent代码库的结构:
pkg/agent
:agent的实现,启动socket监听,接受OCI hook传递过来的fd。pkg/handlers
:一些基本的系统调用实现,比如mkdir
、mount
等pkg/kuberesolver
:用于实现基于Kubernetes Pod的自定义handlerpkg/nsenter
:用于操作、进入其他namespacepkg/readarg
:用于获取系统调用的参数pkg/registry
:用于注册一组系统调用的handler
Demo
在kinvolk/seccompagent中有个Demo,下面以mkdir
的调用演示功能。
为了简化demo,对官方的Demo部分代码做了精简:
$ cat demo.go
package main
import (
"fmt"
"github.com/kinvolk/seccompagent/pkg/agent"
"github.com/kinvolk/seccompagent/pkg/handlers"
"github.com/kinvolk/seccompagent/pkg/kuberesolver"
"github.com/kinvolk/seccompagent/pkg/nsenter"
"github.com/kinvolk/seccompagent/pkg/registry"
)
func main() {
nsenter.Init()
kubeResolverFunc := func(pod *kuberesolver.PodContext, metadata map[string]string) *registry.Registry {
r := registry.New()
r.SyscallHandler["mkdir"] = handlers.MkdirWithSuffix(fmt.Sprintf("-%s-%s", pod.Pod, pod.Namespace)) // 注册mkdir的系统调用handler
return r
}
resolver, err := kuberesolver.KubeResolver(kubeResolverFunc)
if err != nil {
panic(err)
}
err = agent.StartAgent("/run/seccomp-agent.socket", resolver)
}
$ cat notify.json
{
"architectures" : [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"defaultAction" : "SCMP_ACT_ALLOW",
"listenerPath": "/run/seccomp-agent.socket",
"syscalls" : [
{
"action" : "SCMP_ACT_NOTIFY",
"names" : [
"mkdir"
]
}
]
}
用Dockerfile创建镜像,部署seccomp agent 与测试用的busybox,将notify.json
拷贝到 /var/lib/kubelet/seccomp/notify.json
:
$ cat busybox.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-deploy
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: busybox-deploy
template:
metadata:
labels:
app: busybox-deploy
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: notify.json #相对于/var/lib/kubelet/seccomp的文件路径
tolerations:
- key: "node-role.kubernetes.io/master"
operator: Exists
containers:
- image: busybox
command: ["sleep","3600"]
imagePullPolicy: IfNotPresent
name: busybox
$ ls /var/lib/kubelet/seccomp/notify.json
/var/lib/kubelet/seccomp/notify.json
$ kubectl get po -n seccomp-agent -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
seccomp-agent-7z2bl 1/1 Running 0 2m31s 10.222.0.20 maao-dev <none> <none>
$ kubectl get po -n test -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-deploy-697468b5dd-dxr7q 1/1 Running 0 2m 10.222.0.21 maao-dev <none> <none>
在busybox里调用mkdir
,会由seccomp agent创建带有相应后缀的文件夹:
$ kubectl exec -it busybox-deploy-697468b5dd-dxr7q -n test sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # ls
bin dev etc home proc root sys tmp usr var
/ # mkdir a
/ # ls
a-busybox-deploy-697468b5dd-dxr7q-test etc root usr
bin home sys var
dev proc tmp
参考
Bringing Seccomp Notify to Runc and Kubernetes(主要介绍Seccomp Notify在Kubernetes里的实践,里面有链接很多相关的博文)
Seccomp Notify – New Frontiers in Unprivileged Container Development(很全面的一篇文章,从seccomp介绍到seccomp notify)
Hardening Docker and Kubernetes with seccomp
(主要介绍Kubernetes中原生的seccomp使用)
Seccomp Notify on Kubernetes FOSDEM 2021的一个分享
转载请注明出处