注意事项 佩雷夫 :由英国公司ASOS.com提供高级应用程序安全工程师的文章翻译。 与她一起,他开始通过使用seccomp来提高Kubernetes的安全性的一系列出版物。 如果读者喜欢该介绍,我们将关注作者并继续他将来关于该主题的材料。
本文是有关如何在不诉诸魔术和巫术的情况下以SecDevOps精神创建seccomp配置文件的系列出版物中的第一篇。 在第一部分中,我将讨论在Kubernetes中实现seccomp的基础知识和内部细节。
Kubernetes生态系统提供了多种方式来确保容器安全和隔离。 本文介绍了安全计算模式,也称为
seccomp 。 其实质在于过滤可用于容器执行的系统调用。
为什么这很重要? 容器只是在特定计算机上运行的进程。 而且它使用的内核与其他应用程序相当。 如果容器可以进行任何系统调用,恶意软件很快就会利用它绕过容器的隔离并影响其他应用程序:拦截信息,更改系统设置等。
seccomp配置文件确定应允许或拒绝哪些系统调用。 容器运行时会在启动过程中激活它们,以便内核可以控制它们的执行。 如果容器中的任何程序(即您的依存关系或其依存关系)开始执行不允许执行的操作,则使用此类配置文件可以限制攻击向量并减少破坏。
了解基础
seccomp基本配置文件包括三个元素:
defaultAction
,
architectures
(或
archMap
)和
syscalls
:
{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": [ "SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32" ], "syscalls": [ { "names": [ "arch_prctl", "sched_yield", "futex", "write", "mmap", "exit_group", "madvise", "rt_sigprocmask", "getpid", "gettid", "tgkill", "rt_sigaction", "read", "getpgrp" ], "action": "SCMP_ACT_ALLOW" } ] }
( medium-basic-seccomp.json )defaultAction
确定
syscalls
部分中未指定的任何系统调用的默认命运。 为了简化任务,我们关注将要使用的两个主要值:
SCMP_ACT_ERRNO
阻止系统调用的执行,SCMP_ACT_ALLOW
允许。
architectures
部分列出了目标体系结构。 这很重要,因为应用在内核级别的过滤器本身取决于系统调用的标识符,而不取决于配置文件中注册的名称。 在使用之前,容器运行时会将它们映射到标识符。 关键是,根据系统体系结构,系统调用可以具有完全不同的ID。 例如,
recvfrom
系统调用(用于从套接字获取信息)在x64系统上的ID = 64,在x86系统上的ID = 517。
在这里,您可以找到x86-x64体系结构的所有系统调用的列表。
syscalls
部分列出了所有系统调用并指示如何处理它们。 例如,您可以通过将
defaultAction
设置为
SCMP_ACT_ERRNO
来创建白名单,并将对
syscalls
部分的
syscalls
分配给
SCMP_ACT_ALLOW
。 因此,您只允许在
syscalls
部分中注册的
syscalls
,禁止所有其他
syscalls
。 对于黑名单,应将
defaultAction
值和操作更改为相反的值。
现在,对于不那么明显的细微差别应该说几句话。 请注意,以下建议来自以下事实:您正在Kubernetes中部署一系列业务应用程序,并且对他们来说,使用最少的特权对您很重要。
1. AllowPrivilegeEscalation = false
容器的
securityContext
有一个
AllowPrivilegeEscalation
参数。 如果将其设置为
false
,则容器将从设置为(
on
)的
no_new_priv
位开始。 从名称中可以明显看出该参数的含义:它不允许容器以大于其所拥有特权的权限启动新进程。
将此参数设置为
true
(默认值)的副作用是容器运行时在启动过程的最开始就应用seccomp配置文件。 因此,必须在概要文件中允许启动运行时内部过程所需的所有系统调用(例如,设置用户/组标识符,删除某些功能)。
执行banal
echo hi
的容器将需要以下权限:
{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": [ "SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32" ], "syscalls": [ { "names": [ "arch_prctl", "brk", "capget", "capset", "chdir", "close", "execve", "exit_group", "fstat", "fstatfs", "futex", "getdents64", "getppid", "lstat", "mprotect", "nanosleep", "newfstatat", "openat", "prctl", "read", "rt_sigaction", "statfs", "setgid", "setgroups", "setuid", "stat", "uname", "write" ], "action": "SCMP_ACT_ALLOW" } ] }
( hi-pod-seccomp.json )...而不是这些:
{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": [ "SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32" ], "syscalls": [ { "names": [ "arch_prctl", "brk", "close", "execve", "exit_group", "futex", "mprotect", "nanosleep", "stat", "write" ], "action": "SCMP_ACT_ALLOW" } ] }
( hi-container-seccomp.json )但是再说一次,为什么这是一个问题? 就个人而言,我避免将以下系统调用列入白名单(如果确实不需要):
capset
,
set_tid_address
,
setgid
,
setgroups
和
setuid
。 但是,真正的困难在于,通过允许您完全无法控制的进程将概要文件绑定到容器运行时的实现。 换句话说,有一天您可能会遇到这样一个事实,即在更新容器的运行时环境(由您,或更可能是由云服务提供商)之后,容器将突然停止启动。
提示1 :使用
AllowPrivilegeEscaltion=false
运行容器。 这将减少seccomp配置文件的大小,并使它们对容器运行时的更改不太敏感。
2.设置容器级别的seccomp配置文件
seccomp配置文件可以在窗格级别设置:
annotations: seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"
...或在容器级别:
annotations: container.security.alpha.kubernetes.io/<container-name>: "localhost/profile.json"
请注意,当Kubernetes seccomp 成为GA时,以上语法将发生变化(此事件在下一Kubernetes版本-1.18-大约翻译中将会发生)。很少有人知道Kubernetes总是有一个
错误 ,该
错误会导致seccomp配置文件应用于
暂停容器 。 运行时部分弥补了这一缺点,但是此容器不会从pod中消失,因为它用于配置其基础结构。
问题在于此容器始终以
AllowPrivilegeEscalation=true
开头,从而导致第1段中
AllowPrivilegeEscalation=true
的问题,并且无法更改。
在容器级别应用seccomp配置文件,可以避免此陷阱,并可以创建将针对特定容器“锐化”的配置文件。 必须这样做,直到开发人员修复该错误并且新版本(也许是1.18?)对所有人可用为止。
提示2 :在容器级别设置seccomp配置文件。
从实际意义上讲,此规则通常可以回答以下问题:“为什么我的seccomp配置文件为什么与docker
docker run
,但是在Kubernetes集群中部署后却不起作用?”
3.使用运行时/默认作为最后的手段
Kubernetes有两个用于内置配置文件的选项:
runtime/default
和
docker/default
。 两者都是由容器运行时而不是Kubernetes实现的。 因此,它们可能会有所不同,具体取决于所使用的运行时及其版本。
换句话说,由于更改了运行时,容器可以访问它可以使用或不使用的另一组系统调用。 大多数运行时使用
Docker实现 。 如果要使用此配置文件,请确保它适合您。
从Kubernetes 1.11开始不推荐使用docker
docker/default
配置文件,因此请避免使用它。
我认为,
runtime/default
配置文件非常适合创建该配置文件:保护用户免受在其计算机上运行
docker run
带来的风险。 但是,如果我们谈论在Kubernetes集群中运行的业务应用程序,我敢于宣称这样的配置文件过于开放,开发人员应该专注于为其应用程序(或应用程序类型)创建配置文件。
提示3 :为特定应用程序创建seccomp配置文件。 如果不可能,请处理应用程序类型的配置文件,例如,创建一个包含所有Golang Web应用程序API的高级配置文件。 仅在不得已时才使用运行时/默认值。
在以后的出版物中,我将告诉您如何本着SecDevOps的精神创建secccomp配置文件,并在管道中对其进行自动化和测试。 换句话说,您将没有任何理由不切换到特定应用程序的配置文件。
4.无限制的不是一种选择
从第
一次Kubernetes安全审核起,事实证明默认情况下
seccomp被禁用 。 这意味着,如果您未指定将在集群中启用它的
PodSecurityPolicy
,则所有未定义seccomp配置文件的Pod都将在
seccomp=unconfined
。
在这种模式下工作意味着将失去整个隔离层,从而提供了群集保护。 安全专业人员不建议使用此方法。
提示4 :群集中的任何容器都不能以
seccomp=unconfined
,尤其是在生产环境中。
5.“审核模式”
这一点并不是Kubernetes独有的,但仍属于“开始之前应了解的知识”类别。
碰巧的是,创建seccomp配置文件始终是一件棘手的事情,并且很大程度上基于反复试验。 事实是,用户根本没有机会在生产环境中对其进行测试,而不会冒着“丢弃”应用程序的风险。
Linux 4.14内核问世之后,便可以在审核模式下运行配置文件的某些部分,在syslog中记录有关所有系统调用的信息,但不会阻止它们。 您可以使用
SCMT_ACT_LOG
参数激活此模式:
SCMP_ACT_LOG :如果seccomp不在过滤器的任何规则之内,则seccomp不会影响进行系统调用的线程的操作,但是将记录有关系统调用的信息。这是使用此功能的示例策略:
- 允许所需的系统调用。
- 已知没有用的块调用系统。
- 在日志中记录有关所有其他呼叫的信息。
简化示例如下:
{ "defaultAction": "SCMP_ACT_LOG", "architectures": [ "SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32" ], "syscalls": [ { "names": [ "arch_prctl", "sched_yield", "futex", "write", "mmap", "exit_group", "madvise", "rt_sigprocmask", "getpid", "gettid", "tgkill", "rt_sigaction", "read", "getpgrp" ], "action": "SCMP_ACT_ALLOW" }, { "names": [ "add_key", "keyctl", "ptrace" ], "action": "SCMP_ACT_ERRNO" } ] }
( medium-mixed-seccomp.json )但是请记住,您需要阻止所有已知未使用的呼叫,这些呼叫可能会损害群集。 正式列出的一个很好的基础是
Docker文档 。 它详细说明了默认配置文件中阻止了哪些系统调用以及原因。
但是,有一个陷阱。 尽管
SCMT_ACT_LOG
自2017年底以来
SCMT_ACT_LOG
受到Linux内核的支持,但它直到最近才进入Kubernetes生态系统。 因此,要使用此方法,您将需要Linux 4.14内核和不低于
v1.0.0-rc9的 runC版本。
提示5 :您可以通过组合黑名单和白名单来创建审核模式配置文件以进行生产中的测试,并记录所有异常。
6.使用白名单
创建白名单需要付出额外的努力,因为您必须识别应用程序可能需要的每个呼叫,但是这种方法可以显着提高安全性:
强烈建议您使用白名单方法,因为它更简单,更可靠。 每当添加潜在危险的系统调用(或危险标志/选项,如果它们在黑名单中)时,都需要更新黑名单。 另外,您通常可以在不改变参数本质的情况下更改其表示形式,从而规避黑名单的限制。
对于Go应用程序,我开发了一个专用工具,该工具随附于该应用程序并收集在运行时进行的所有调用。 例如,对于以下应用程序:
package main import "fmt" func main() { fmt.Println("test") }
...像这样运行
gosystract
:
go install https://github.com/pjbgf/gosystract gosystract --template='{{- range . }}{{printf "\"%s\",\n" .Name}}{{- end}}' application-path
...并获得以下结果:
"sched_yield", "futex", "write", "mmap", "exit_group", "madvise", "rt_sigprocmask", "getpid", "gettid", "tgkill", "rt_sigaction", "read", "getpgrp", "arch_prctl",
到目前为止,这只是一个示例-有关工具的详细信息将进一步介绍。
提示6 :仅允许您真正需要的呼叫并阻止其他所有人。
7.奠定基础(或为意外行为做好准备)
无论您在配置文件中注册了什么,内核都会监视其对配置文件的遵从性。 即使这不是我想要的。 例如,如果您阻止对诸如
exit
或
exit_group
类的调用的访问,则容器将无法正确完成作业,即使是诸如
echo hi
类的简单命令
echo hi
无限期地
echo hi
暂停 。 结果,您将在群集中获得较高的CPU利用率:

在这种情况下,
strace
实用程序可能会
strace
-它会显示问题所在:

sudo strace -c -p 9331
确保配置文件包含应用程序运行时需要的所有系统调用。
提示#7 :请注意一些小事情,并确保所有必需的系统调用都包括在白名单中。
至此,有关本着SecDevOps精神在Kubernetes中使用seccomp的系列文章的第一部分结束了。 在以下各部分中,我们将讨论为什么这很重要以及如何使过程自动化。
译者的PS
另请参阅我们的博客: