本文已超过一年。旧文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。

在边缘环境中尽享 seccomp 配置文件带来的乐趣

Security Profiles Operator (SPO) 是一个功能丰富的 Kubernetes Operator,它使得管理 seccomp、SELinux 和 AppArmor 配置文件比以往任何时候都更加容易。从头开始记录这些配置文件是该 Operator 的关键特性之一,这通常涉及集成到大型 CI/CD 系统中。SPO 最近的开发工作之一是在边缘案例中测试 Operator 的记录能力,这使得使用 seccomp 配置文件进行试验变得异常简单。

使用 spoc record 记录 seccomp 配置文件

Security Profiles Operator 的 v0.8.0 版本发布了一个名为 spoc 的新命令行界面,它是一个用于记录和重放 seccomp 配置文件的小辅助工具,同时还包含许多其他不在此博客文章讨论范围内的功能。

记录 seccomp 配置文件需要执行一个二进制文件,它可以是一个简单的 golang 应用程序,它只调用 uname(2)

package main

import (
	"syscall"
)

func main() {
	utsname := syscall.Utsname{}
	if err := syscall.Uname(&utsname); err != nil {
		panic(err)
	}
}

可以通过以下方式从该代码构建二进制文件

> go build -o main main.go
> ldd ./main
        not a dynamic executable

现在可以从 GitHub 下载最新版 spoc 二进制文件,并在 Linux 上用它来运行应用程序

> sudo ./spoc record ./main
10:08:25.591945 Loading bpf module
10:08:25.591958 Using system btf file
libbpf: loading object 'recorder.bpf.o' from buffer
libbpf: prog 'sys_enter': relo #3: patched insn #22 (ALU/ALU64) imm 16 -> 16
10:08:25.610767 Getting bpf program sys_enter
10:08:25.610778 Attaching bpf tracepoint
10:08:25.611574 Getting syscalls map
10:08:25.611582 Getting pid_mntns map
10:08:25.613097 Module successfully loaded
10:08:25.613311 Processing events
10:08:25.613693 Running command with PID: 336007
10:08:25.613835 Received event: pid: 336007, mntns: 4026531841
10:08:25.613951 No container ID found for PID (pid=336007, mntns=4026531841, err=unable to find container ID in cgroup path)
10:08:25.614856 Processing recorded data
10:08:25.614975 Found process mntns 4026531841 in bpf map
10:08:25.615110 Got syscalls: read, close, mmap, rt_sigaction, rt_sigprocmask, madvise, nanosleep, clone, uname, sigaltstack, arch_prctl, gettid, futex, sched_getaffinity, exit_group, openat
10:08:25.615195 Adding base syscalls: access, brk, capget, capset, chdir, chmod, chown, close_range, dup2, dup3, epoll_create1, epoll_ctl, epoll_pwait, execve, faccessat2, fchdir, fchmodat, fchown, fchownat, fcntl, fstat, fstatfs, getdents64, getegid, geteuid, getgid, getpid, getppid, getuid, ioctl, keyctl, lseek, mkdirat, mknodat, mount, mprotect, munmap, newfstatat, openat2, pipe2, pivot_root, prctl, pread64, pselect6, readlink, readlinkat, rt_sigreturn, sched_yield, seccomp, set_robust_list, set_tid_address, setgid, setgroups, sethostname, setns, setresgid, setresuid, setsid, setuid, statfs, statx, symlinkat, tgkill, umask, umount2, unlinkat, unshare, write
10:08:25.616293 Wrote seccomp profile to: /tmp/profile.yaml
10:08:25.616298 Unloading bpf module

我必须以 root 身份执行 spoc,因为它将内部运行一个 ebpf 程序,重用 Security Profiles Operator 本身的代码部分。我可以看到 bpf 模块已成功加载,并且 spoc 已附加所需的跟踪点。然后它将通过使用其 mount namespace 跟踪主应用程序,并处理记录的系统调用数据。ebpf 程序的特性在于它们可以看到内核的整个上下文,这意味着 spoc 会跟踪系统的所有系统调用,但不会干扰它们的执行。

日志显示 spoc 找到了 readclosemmap 等系统调用,包括 uname。除了 uname 之外的所有其他系统调用都来自 golang 运行时及其垃圾回收,这已经给像我们 demo 中这样的基本应用程序增加了开销。我还可以从日志行 Adding base syscalls: … 中看到 spoc 向生成的配置文件添加了一堆基础系统调用。这些系统调用被 OCI 运行时(如 runccrun)使用以便能够运行容器。这意味着 spoc 可以用来记录 seccomp 配置文件,然后可以直接将其容器化。这种行为可以通过使用 --no-base-syscalls/-n 标志在 spoc 中禁用,或者通过 --base-syscalls/-b 命令行标志进行自定义。当使用不同于 crun 和 runc 的 OCI 运行时,或者我只想记录应用程序的 seccomp 配置文件并将其与另一个基础配置文件堆叠使用时,这会很有帮助。

生成的配置文件现已在 /tmp/profile.yaml 中可用,但默认位置可以使用 --output-file value/-o 标志更改

> cat /tmp/profile.yaml
apiVersion: security-profiles-operator.x-k8s.io/v1beta1
kind: SeccompProfile
metadata:
  creationTimestamp: null
  name: main
spec:
  architectures:
    - SCMP_ARCH_X86_64
  defaultAction: SCMP_ACT_ERRNO
  syscalls:
    - action: SCMP_ACT_ALLOW
      names:
        - access
        - arch_prctl
        - brk
        - …
        - uname
        - …
status: {}

seccomp 配置文件自定义资源定义 (CRD) 可以直接与 Security Profiles Operator 一起使用,以便在 Kubernetes 中对其进行管理。spoc 也能够生成原始 seccomp 配置文件 (JSON 格式),通过使用 --type/-t raw-seccomp 标志

> sudo ./spoc record --type raw-seccomp ./main
52.628827 Wrote seccomp profile to: /tmp/profile.json
> jq . /tmp/profile.json
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": ["access", "…", "write"],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

spoc record 工具允许我们在任何能够在内核中运行 ebpf 代码的 Linux 系统上直接从二进制调用中记录复杂的 seccomp 配置文件。但它能做的更多:如何修改 seccomp 配置文件然后使用 spoc run 进行测试呢?

使用 spoc run 运行 seccomp 配置文件

spoc 也能够使用已应用的 seccomp 配置文件运行二进制文件,从而轻松测试对其进行的任何修改。为此,只需运行

> sudo ./spoc run ./main
10:29:58.153263 Reading file /tmp/profile.yaml
10:29:58.153311 Assuming YAML profile
10:29:58.154138 Setting up seccomp
10:29:58.154178 Load seccomp profile
10:29:58.154189 Starting audit log enricher
10:29:58.154224 Enricher reading from file /var/log/audit/audit.log
10:29:58.155356 Running command with PID: 437880
>

看起来应用程序已成功退出,这是预期的,因为我还没有修改之前记录的配置文件。我也可以使用 --profile/-p 标志指定配置文件的自定义位置,但这并不是必需的,因为我没有修改记录时的默认输出位置。spoc 将自动判断它是基于原始 (JSON) 还是基于 CRD (YAML) 的 seccomp 配置文件,然后将其应用于进程。

Security Profiles Operator 支持日志增强器功能,它通过解析审计日志提供额外的 seccomp 相关信息。spoc run 以相同的方式使用增强器,以便在调试 seccomp 配置文件时向最终用户提供更多数据。

现在我必须修改配置文件才能在输出中看到任何有价值的信息。例如,我可以删除允许的 uname 系统调用

> jq 'del(.syscalls[0].names[] | select(. == "uname"))' /tmp/profile.json > /tmp/no-uname-profile.json

然后尝试再次使用新的配置文件 /tmp/no-uname-profile.json 运行它

> sudo ./spoc run -p /tmp/no-uname-profile.json ./main
10:39:12.707798 Reading file /tmp/no-uname-profile.json
10:39:12.707892 Setting up seccomp
10:39:12.707920 Load seccomp profile
10:39:12.707982 Starting audit log enricher
10:39:12.707998 Enricher reading from file /var/log/audit/audit.log
10:39:12.709164 Running command with PID: 480512
panic: operation not permitted

goroutine 1 [running]:
main.main()
        /path/to/main.go:10 +0x85
10:39:12.713035 Unable to run: launch runner: wait for command: exit status 2

好的,这是意料之中的!应用的 seccomp 配置文件阻止了 uname 系统调用,导致“操作不被允许”错误。这个错误非常通用,没有提供任何关于 seccomp 阻止了什么操作的提示。一般来说,预测当 seccomp 禁止单个系统调用时应用程序会如何行为是极其困难的。应用程序可能会像我们的简单 demo 中那样终止,但也可能导致奇怪的错误行为并且应用程序根本不停下来。

如果我现在将配置文件的默认 seccomp 动作从 SCMP_ACT_ERRNO 更改为 SCMP_ACT_LOG,如下所示

> jq '.defaultAction = "SCMP_ACT_LOG"' /tmp/no-uname-profile.json > /tmp/no-uname-profile-log.json

那么使用 spoc run 时,日志增强器将提示我们 uname 系统调用被阻止了

> sudo ./spoc run -p /tmp/no-uname-profile-log.json ./main
10:48:07.470126 Reading file /tmp/no-uname-profile-log.json
10:48:07.470234 Setting up seccomp
10:48:07.470245 Load seccomp profile
10:48:07.470302 Starting audit log enricher
10:48:07.470339 Enricher reading from file /var/log/audit/audit.log
10:48:07.470889 Running command with PID: 522268
10:48:07.472007 Seccomp: uname (63)

应用程序将不再终止,但 seccomp 会将行为记录到 /var/log/audit/audit.log,而 spoc 将解析数据并将其直接关联到我们的程序。向审计子系统生成日志消息会带来巨大的性能开销,在生产系统中应谨慎处理。在生产环境中以审计模式运行不受信任的应用程序时,也存在安全风险。

这个 demo 应该让你了解如何使用我们的全新辅助工具(由 Security Profiles Operator 的功能支持)来调试应用程序的 seccomp 配置文件问题。spoc 是一个灵活且便携的二进制文件,适用于资源有限甚至 Kubernetes 本身可能无法充分发挥其能力的边缘情况。

感谢阅读这篇博客文章!如果你对此更感兴趣,提供反馈或寻求帮助,请随时通过 Slack (#security-profiles-operator)邮件列表直接与我们联系。