使用 Seccomp 限制容器的系统调用

FEATURE STATE: Kubernetes v1.19 [stable]

Seccomp 代表安全计算模式,自 2.6.12 版本以来一直是 Linux 内核的功能。 它可以用来对进程的特权进行沙盒处理,从而限制了它可以从用户空间向内核进行的调用。 Kubernetes 允许你将加载到节点上的 seccomp 配置文件自动应用于 Pod 和容器。

确定工作负载所需的特权可能很困难。在本教程中,你将了解如何将 seccomp 配置文件 加载到本地 Kubernetes 集群中,如何将它们应用到 Pod,以及如何开始制作仅向容器 进程提供必要特权的配置文件。

教程目标

  • 了解如何在节点上加载 seccomp 配置文件
  • 了解如何将 seccomp 配置文件应用于容器
  • 观察由容器进程进行的系统调用的审核
  • 观察当指定了一个不存在的配置文件时的行为
  • 观察违反 seccomp 配置的情况
  • 了解如何创建精确的 seccomp 配置文件
  • 了解如何应用容器运行时默认 seccomp 配置文件

准备开始

您的 Kubernetes 服务器版本必须不低于版本 v1.22. 要获知版本信息,请输入 kubectl version.

为了完成本教程中的所有步骤,你必须安装 kindkubectl。本教程将显示同时具有 alpha(v1.22 新版本) 和通常可用的 seccomp 功能的示例。 你应该确保为所使用的版本正确配置了集群。

启用 RuntimeDefault 作为所有工作负载的默认 seccomp 配置文件

FEATURE STATE: Kubernetes v1.22 [alpha]

SeccompDefault 是一个可选的 kubelet 特性门控, 相应地,--seccomp-default 是此特性门控的 命令行标志。 必须同时启用两者才能使用该功能。

如果启用,kubelet 将默认使用 RuntimeDefault seccomp 配置, 而不是使用 Unconfined(禁用 seccomp)模式,该配置由容器运行时定义。 默认配置旨在提供一组强大的安全默认值设置,同时避免影响工作负载的功能。 不同的容器运行时之间及其不同的发布版本之间的默认配置可能不同, 例如在比较 CRI-O 和 containerd 的配置文件时(就会发现这点)。

某些工作负载可能相比其他工作负载需要更少的系统调用限制。 这意味着即使使用 RuntimeDefault 配置文件,它们也可能在运行时失败。 要处理此类失效,你可以:

  • 将工作负载显式运行为 Unconfined
  • 禁用节点的 SeccompDefault 功能。 还要确保工作负载被安排在禁用该功能的节点上。
  • 为工作负载创建自定义 seccomp 配置文件。

如果你将此功能引入到类似生产的集群中, Kubernetes 项目建议你在节点的子集上启用此特性门控, 然后在集群范围内推出更改之前测试工作负载的执行情况。

有关可能的升级和降级策略的更多详细信息, 请参见相关 Kubernetes 增强提案 (KEP)

由于该功能处于 alpha 状态,因此默认情况下是被禁用的。要启用它, 请将标志 --feature-gates=SeccompDefault=true --seccomp-default 传递给 kubelet CLI 或通过 kubelet 配置文件启用它。 要在 kind 中启用特性门控, 请确保 kind 提供所需的最低 Kubernetes 版本并 在 kind 配置中 启用 SeccompDefault 功能:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  SeccompDefault: true

创建 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"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

使用 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.yaml。然后使用这个配置创建集群。

kind create cluster --config=kind.yaml

一旦这个集群已经就绪,找到作为单节点集群运行的容器:

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 中运行命令:

docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles
audit.json  fine-grained.json  violation.json

使用 seccomp 配置文件创建 Pod 以进行系统调用审核

首先,将 audit.json 配置文件应用到新的 Pod 中,该配置文件将记录该进程的所有系统调用。

为你的 Kubernetes 版本下载正确的清单:

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:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

apiVersion: v1
kind: Pod
metadata:
  name: audit-pod
  labels:
    app: audit-pod
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: localhost/profiles/audit.json
spec:
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

在集群中创建 Pod:

kubectl apply -f audit-pod.yaml

这个配置文件并不限制任何系统调用,所以这个 Pod 应该会成功启动。

kubectl get pod/audit-pod
NAME        READY   STATUS    RESTARTS   AGE
audit-pod   1/1     Running   0          30s

为了能够与该容器公开的端点进行交互,请创建一个 NodePort 服务, 该服务允许从 kind 控制平面容器内部访问该端点。

kubectl expose pod/audit-pod --type NodePort --port 5678

检查这个服务在这个节点上被分配了什么端口。

kubectl get svc/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 控制平面容器内部通过该服务暴露出来的端口来访问这个端点。

docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

你可以看到该进程正在运行,但是实际上执行了哪些系统调用?因为该 Pod 是在本地集群中运行的, 你应该可以在 /var/log/syslog 日志中看到这些。打开一个新的终端窗口,使用 tail 命令来 查看来自 http-echo 的调用输出:

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 配置文件的基础。

开始下一节之前,请清理该 Pod 和 Service:

kubectl delete pod/audit-pod
kubectl delete svc/audit-pod

使用导致违规的 seccomp 配置文件创建 Pod

为了进行演示,请将不允许任何系统调用的配置文件应用于 Pod。

为你的 Kubernetes 版本下载正确的清单:

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:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

apiVersion: v1
kind: Pod
metadata:
  name: violation-pod
  labels:
    app: violation-pod
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: localhost/profiles/violation.json
spec:
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

在集群中创建 Pod:

kubectl apply -f 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 和 Service:

kubectl delete pod/violation-pod
kubectl delete svc/violation-pod

使用设置仅允许需要的系统调用的 seccomp 配置文件来创建 Pod

如果你看一下 fine-pod.json 文件,你会注意到在第一个示例中配置文件设置为 "defaultAction": "SCMP_ACT_LOG" 的一些系统调用。 现在,配置文件设置为 "defaultAction": "SCMP_ACT_ERRNO",但是在 "action": "SCMP_ACT_ALLOW" 块中明确允许一组系统调用。 理想情况下,容器将成功运行,并且你将不会看到任何发送到 syslog 的消息。

为你的 Kubernetes 版本下载正确的清单:

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:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

apiVersion: v1
kind: Pod
metadata:
  name: fine-pod
  labels:
    app: fine-pod
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: localhost/profiles/fine-grained.json
spec:
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

在你的集群上创建Pod:

kubectl apply -f fine-pod.yaml

Pod 应该被成功启动。

kubectl get pod/fine-pod
NAME        READY   STATUS    RESTARTS   AGE
fine-pod   1/1     Running   0          30s

打开一个新的终端窗口,使用 tail 命令查看来自 http-echo 的调用的输出:

tail -f /var/log/syslog | grep 'http-echo'

使用 NodePort 服务为该 Pod 开一个端口:

kubectl expose pod/fine-pod --type NodePort --port 5678

检查服务在该节点被分配了什么端口:

kubectl get svc/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 控制面板容器内部请求这个端点:

docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

你会看到 syslog 中没有任何输出,因为这个配置文件允许了所有需要的系统调用, 并指定如果有发生列表之外的系统调用将发生错误。从安全角度来看,这是理想的情况, 但是在分析程序时需要多付出一些努力。如果有一种简单的方法无需花费太多精力就能更接近此安全性,那就太好了。

开始下一节之前,请清理该 Pod 和 Service:

kubectl delete pod/fine-pod
kubectl delete svc/fine-pod

使用容器运行时默认的 seccomp 配置文件创建 Pod

大多数容器运行时都提供一组允许或不允许的默认系统调用。通过使用 runtime/default 注释 或将 Pod 或容器的安全上下文中的 seccomp 类型设置为 RuntimeDefault,可以轻松地在 Kubernetes 中应用默认值。

为你的 Kubernetes 版本下载正确的清单:

apiVersion: v1
kind: Pod
metadata:
  name: audit-pod
  labels:
    app: audit-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

apiVersion: v1
kind: Pod
metadata:
  name: default-pod
  labels:
    app: default-pod
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: runtime/default
spec:
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

默认的 seccomp 配置文件应该为大多数工作负载提供足够的权限。

接下来

额外的资源:

最后修改 August 16, 2021 at 10:49 AM PST : [zh]sync tutorials files for 1.22 (9075aa237)