底层系统为ubuntu18.04,然后在每个node上安装k8s,并构建集群。Master node的IP地址为192.168.26.71/24,两个Worker node的IP地址为192.168.26.72/24、192.168.26.73/24。
1.Seccomp概念
seccomp(全称secure computing mode 安全计算模式)是linux kernel从2.6.23版本开始所支持的一种安全机制。在Linux系统里,大量的系统调用(systemcall)直接暴露给用户态程序。
例如,我们再linux设备上输入一条任意一条命令,其实背后会调用大量的syscall,如下所示,可以使用strace -fqc命令来查看:
[root@localhost ~]# strace -fqc cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.26.72 www.ck8s.top www
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
36.58 0.000698 22 31 13 openat
21.54 0.000411 21 19 fstat
18.03 0.000344 17 20 close
17.92 0.000342 18 19 mmap
2.10 0.000040 40 1 write
1.78 0.000034 5 6 read
1.52 0.000029 29 1 fadvise64
0.52 0.000010 5 2 munmap
0.00 0.000000 0 1 lseek
0.00 0.000000 0 4 mprotect
0.00 0.000000 0 4 brk
0.00 0.000000 0 1 1 access
0.00 0.000000 0 1 execve
0.00 0.000000 0 2 1 arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00 0.001908 112 15 total
可以看到,一个简单的命令,背后存在112个系统调用。其他很多系统调用是必要的,比如read。但是,很多时候,并不是所有的系统调用都被需要,而且不安全的代码滥用系统调用会对系统造成安全威胁。通过seccomp,我们限制程序使用某些系统调用,这样可以减少系统的暴露面,同时是程序进入一种“安全”的状态。
如下是seccomp所包含的动作:
2.Docker中的seccomp
默认我们的宿主机没有限制哪些系统调用。而容器又和宿主机共享内核,那么如果不对容器做任何限制,容器也能够调用所有宿主机上的系统调用。但其实,Docker容器存在一个默认的seccomp规则,限制了某些系统调用。
默认seccomp配置文件为使用 seccomp 运行容器提供了一个合理的默认值,并禁用了 300 多个系统调用中的大约 44 个。它具有适度的保护作用,同时提供广泛的应用兼容性。可以在https://github.com/moby/moby/blob/master/profiles/seccomp/default.json找到默认的 Docker 配置文件 。
实际上,配置文件是一个allowlist,默认情况下拒绝访问系统调用,然后允许列出特定的系统调用。该配置文件通过定义SCMP_ACT_ERRNO的defaultAction并仅针对特定的系统调用覆盖该操作来工作。SCMP_ACT_ERRNO的作用当进程进行对应的系统调用时,系统调用失败,进程会接收到errno返回值。接下来, 配置文件定义了一个完全允许的系统调用的特定列表,因为它们的操作被覆盖为SCMP_ACT_ALLOW。最后,一些特定的规则用于单个系统调用,如personality,以及其他一些规则,以允许这些系统调用的变体具有特定的参数。
seccomp是运行Docker容器的工具。不建议修改默认的seccomp配置文件。运行容器时,它使用默认配置文件,除非使用——security-opt选项覆盖它。例如,下面显式指定了一个策略:
docker run --rm -it --security-opt seccomp=/path/to/seccomp/profile.json hello-world
如下分别是允许所有和拒绝所有的seccomp:
cat aa1.json
{
"defaultAction": "SCMP_ACT_ALLOW"
}
cat bb.json
{
"defaultAction": "SCMP_ACT_ERRNO"
}
注意,这两个seccomp规则不要使用,允许所有的规则安全漏洞太多;而拒绝所有的seccomp规则一旦运行,由于容器守护进程无法运行(不允许任何系统调用),容器无法被正常创建。
另外,如果将seccomp=Unconfined,表示的是禁用seccomp,其效果和允许所有的seccomp规则相同。
3.K8s集群中的seccomp
K8s集群中创建工作负载时,可以加载所设置的seccomp规则文件,达到对容器的系统调用的控制。
如果我们想要在K8s集群中启用 RuntimeDefault 作为所有工作负载的默认seccomp配置文件(在本环境中,也就是docker的默认seccomp),可以在工作负载的yaml文件的spec下,添加如下的内容:
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
我们当然也可以自己编写对应的seccomp的Json文件,以官网给出的为例子:
{
"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"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
规则文件默认禁用了所有的系统调用,但随后允许了[ ]内的系统调用。另外,需要注意的是,我们需要将自己编写的seccomp规则放在集群中所有Worker Node的/var/lib/kubelet/seccomp/(需要自己创建)目录下,或其子目录下。这里我们将其保持为此目录下的fine-grained.json文件。接着使用pod进行测试,yaml文件如下:
apiVersion: v1
kind: Pod
metadata:
name: fine-pod
labels:
app: fine-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: fine-grained.json
containers:
- name: test-container
image: hashicorp/http-echo:0.2.3
imagePullPolicy: IfNotPresent
args:
- "-text=just made some syscalls!"
securityContext:
allowPrivilegeEscalation: false
apiVersion: v1
其中,如下部分设置了调用的seccomp规则:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: fine-grained.json
Localhost关键字表示pod运行的node设备,localhostProfile则表示调用的pod所运行在的node上的/var/lib/kubelet/seccomp目录中的fine-grained.json规则文件。如果fine-grained.json文件在aa目录中,则需要以aa/fine-grained.json表示规则文件所在的子目录。
使用该yaml文件创建好pod后,可以做相应的测试,检测其seccomp文件是否生效。在我们使用的fine-grained.json文件中,可以注意到在第一个示例中配置文件设置为 “defaultAction”: “SCMP_ACT_LOG” 的一些系统调用。 现在,配置文件设置为 “defaultAction”: “SCMP_ACT_ERRNO”,但是在 “action”: “SCMP_ACT_ALLOW” 块中明确允许一组系统调用。 理想情况下,容器将成功运行,并且我们将不会看到任何发送到 syslog 的消息。
测试开始,我们首先需要打开一个新的终端窗口,使用 tail 命令查看来自 http-echo 的调用的输出:
tail -f /var/log/syslog | grep 'http-echo'
接着使用 NodePort 服务为该 Pod 开一个端口:
kubectl expose pod/fine-pod --type NodePort --port 5678
检查服务在该节点被分配了什么端口,为32028。
root@vms71:/var/lib/kubelet/seccomp# kubectl get svc/fine-pod
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
fine-pod NodePort 10.105.125.123 <none> 5678:32028/TCP 16m
我们可以使用游览器访问刚刚部署的容器,可以看到,是能够正常的访问的(192.168.26.72为此容器运行的worker1地址)。
最后会看到 syslog 中没有任何输出,因为这个配置文件允许了所有需要的系统调用, 并指定如果有发生列表之外的系统调用将发生错误。从安全角度来看,这是理想的情况, 但是在分析程序时需要多付出一些努力。
如果我们想要在K8s环境中自己设置seccomp文件,无疑是麻烦的,因为我们需要明确整个容器到底需要允许或者禁止哪些系统调用。而Sysdig是一个现成的工具,可以帮助我们监测容器中到底使用了哪些系统调用。
首先我们在K8s集群的Node上以容器的方式安装sysdig,命令如下:
docker run -i -dt --name sysdig --restart=always --privileged -v /var/run/docker.sock:/host/var/run/docker.sock -v /dev:/host/dev -v /proc:/host/proc:ro -v /boot:/host/boot:ro -v /lib/modules:/host/lib/modules:ro -v /usr:/host/usr:ro sysdig/sysdig
此命令的可以帮助我们创建一个sysdig的容器,并且一旦我们在此容器中使用sysdig相关的命令,如docker exec -it sysdig sysdig时,实际上就是获取了宿主机的信息,因为在创建容器时已经做了卷挂载。
接下来我们可以使用如下命令在Node上设置执行sysdig命令映射到docker exec -it sysdig sysdig这个命令,方便我们直接再sysdig容器外调用sysdig。
alias sysdig='docker exec -it sysdig sysdig '
然后,我们可以使用sysdig查看上一章节中部署fine-pod这个pod所执行的系统调用。首先在运行此pod的worker1 node上找到此pod的containerid值:
root@vms72:/var/lib/kubelet/seccomp# docker ps | grep k8s_test-container
f15f04664e8a a6838e9a6ff6 "/http-echo '-text=j…" 28 minutes ago Up 28 minutes k8s_test-container_fine-pod_default_3df3bcde-3141-47d6-b8f9-cd0aba50df2e_0
可以看到,ID值为f15f04664e8。然后继续在worker1上使用如下命令格式化查看此容器的系统调用:
sysdig -p "*%evt.time,%proc.name,%evt.type" container.id=f15f04664e8a
常见的输出值如下:
实时监控如下:
当我们重新访问容器的主页时,可以看到更多的系统调用。然后我们再根据其他设计到的系统调用,编写seccomp规则。
整理资料来源:
《老段CKS课程》
docker seccomp:https://docs.docker.com/engine/security/seccomp/
K8s seccomp:https://kubernetes.io/zh/docs/tutorials/clusters/seccomp/