二十六、K8s系统强化2-seccomp与sysdig

一、实验环境

二十六、K8s系统强化2-seccomp与sysdig_第1张图片
底层系统为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。

二、Seccomp

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所包含的动作:

  1. SCMP_ACT_KILL:当进程进行对应的系统调用时,内核发出SIGSYS信号终止该进程,该进程不会接受到这个信号
  2. SCMP_ACT_TRAP:当进程进行对应的系统调用时,该进程会接收到SIGSYS信号,并改变自身行为
  3. SCMP_ACT_ERRNO:当进程进行对应的系统调用时,系统调用失败,进程会接收到errno返回值
  4. SCMP_ACT_TRACE:当进程进行对应的系统调用时,进程会被跟踪
  5. SCMP_ACT_ALLOW:允许进程进行对应的系统调用行为
  6. SCMP_ACT_LOG:记录所有信息

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地址)。
二十六、K8s系统强化2-seccomp与sysdig_第2张图片
最后会看到 syslog 中没有任何输出,因为这个配置文件允许了所有需要的系统调用, 并指定如果有发生列表之外的系统调用将发生错误。从安全角度来看,这是理想的情况, 但是在分析程序时需要多付出一些努力。
在这里插入图片描述

三、Sysdig

如果我们想要在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

常见的输出值如下:

  1. evt.num: 递增的事件号;
  2. evt.time: 事件发生的时间;
  3. evt.cpu: 事件被捕获时所在的 CPU,也就是系统调用是在哪个 CPU 执行的。比较上面的例子中,值 0 代表机器的第一个 CPU;
  4. proc.name: 生成事件的进程名字,也就是哪个进程在运行;
  5. thread.tid: 线程的 id,如果是单线程的程序,这也是进程的 pid;
  6. evt.dir: 事件的方向(direction),> 代表进入事件,< 代表退出事件;
  7. evt.type: 事件的名称,比如 open、stat等,一般是系统调用;
  8. evt.args: 事件的参数。如果是系统调用,这些对应着系统调用的参数。

实时监控如下:
二十六、K8s系统强化2-seccomp与sysdig_第3张图片
当我们重新访问容器的主页时,可以看到更多的系统调用。然后我们再根据其他设计到的系统调用,编写seccomp规则。

整理资料来源:
《老段CKS课程》
docker seccomp:https://docs.docker.com/engine/security/seccomp/
K8s seccomp:https://kubernetes.io/zh/docs/tutorials/clusters/seccomp/

你可能感兴趣的:(云计算,linux,运维,服务器,K8s)