Kubernetes 中的 Seccomp:从一开始就需要了解的 7 件事

笔记。 翻译。:我们向您展示英国公司 ASOS.com 的高级应用程序安全工程师翻译的一篇文章。 以此为基础,他开始发表一系列出版物,致力于通过使用 seccomp 来提高 Kubernetes 的安全性。 如果读者喜欢这个介绍,我们将关注作者并继续他未来关于该主题的材料。

Kubernetes 中的 Seccomp:从一开始就需要了解的 7 件事

本文是关于如何本着 SecDevOps 精神创建 seccomp 配置文件而不诉诸魔法和巫术的系列文章中的第一篇。 在第 XNUMX 部分中,我将介绍在 Kubernetes 中实现 seccomp 的基础知识和内部细节。

Kubernetes 生态系统提供了多种保护和隔离容器的方法。 这篇文章是关于安全计算模式,也称为 赛康。 其本质是过滤容器可供执行的系统调用。

它为什么如此重要? 容器只是在特定机器上运行的进程。 它像其他应用程序一样使用内核。 如果容器可以执行任何系统调用,恶意软件很快就会利用这一点绕过容器隔离并影响其他应用程序:拦截信息、更改系统设置等。

seccomp 配置文件定义应允许或禁用哪些系统调用。 容器运行时在启动时激活它们,以便内核可以监视它们的执行。 如果容器内的任何程序(即您的依赖项或其依赖项)开始执行不允许执行的操作,使用此类配置文件可以限制攻击向量并减少损坏。

了解基础知识

基本的 seccomp 配置文件包括三个元素: defaultAction, architecturesarchMap)和 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"
        }
    ]
}

(中等-基本-seccomp.json)

defaultAction 确定本节中未指定的任何系统调用的默认命运 syscalls。 为了让事情变得更简单,让我们重点关注将使用的两个主要值:

  • SCMP_ACT_ERRNO — 阻止系统调用的执行,
  • SCMP_ACT_ALLOW - 允许。

在第 architectures 列出了目标架构。 这很重要,因为在内核级别应用的过滤器本身取决于系统调用标识符,而不是配置文件中指定的名称。 容器运行时会将它们与使用前的标识符进行匹配。 这个想法是,根据系统架构,系统调用可以具有完全不同的 ID。 例如系统调用 recvfrom (用于从套接字接收信息)在 x64 系统上 ID = 64,在 x517 上 ID = 86。 这是 您可以找到 x86-x64 架构的所有系统调用的列表。

在该部分 syscalls 列出所有系统调用并指定如何处理它们。 例如,您可以通过设置创建白名单 defaultActionSCMP_ACT_ERRNO,并在该部分中调用 syscalls 分配 SCMP_ACT_ALLOW。 因此,您只允许在该部分中指定的呼叫 syscalls,并禁止所有其他。 对于黑名单,您应该更改值 defaultAction 和相反的行动。

现在我们应该谈谈不太明显的细微差别。 请注意,下面的建议假设您正在 Kubernetes 上部署一系列业务应用程序,并且希望它们以尽可能少的权限运行。

1.AllowPrivilegeEscalation=false

В securityContext 容器有一个参数 AllowPrivilegeEscalation。 如果它安装在 false,容器将以 (on) 少量 no_new_priv。 这个参数的含义从名字上就很明显了:它阻止容器启动比它本身拥有更多权限的新进程。

该选项设置的副作用是 true (默认)是容器运行时在启动过程的一开始就应用 seccomp 配置文件。 因此,运行内部运行时进程所需的所有系统调用(例如设置用户/组ID、删除某些功能)必须在配置文件中启用。

到一个做琐碎事情的容器 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 配置文件可以在 pod 级别设置:

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段所述的问题,并且这是无法改变的。

通过在容器级别使用 seccomp 配置文件,您可以避免此陷阱,并且可以创建针对特定容器定制的配置文件。 这必须在开发人员修复错误并且新版本(可能是 1.18?)可供所有人使用之前完成。

提示 #2:在容器级别设置 seccomp 配置文件。

从实际意义上讲,这条规则通常可以作为以下问题的通用答案:“为什么我的 seccomp 配置文件可以与 docker run但部署到 Kubernetes 集群后不起作用?

3.仅将运行时/默认作为最后的手段

Kubernetes 有两个内置配置文件选项: runtime/default и docker/default。 两者都是由容器运行时实现的,而不是 Kubernetes。 因此,它们可能会有所不同,具体取决于所使用的运行时环境及其版本。

换句话说,由于运行时的更改,容器可能有权访问一组不同的系统调用,它可能会使用也可能不会使用这些系统调用。 大多数运行时使用 Docker 实现。 如果您想使用此配置文件,请确保它适合您。

轮廓 docker/default 自 Kubernetes 1.11 起已被弃用,因此请避免使用它。

在我看来,个人资料 runtime/default 完全适合其创建目的:保护用户免受与执行命令相关的风险 docker run 在他们的车上。 然而,当涉及到在 Kubernetes 集群上运行的业务应用程序时,我敢说这样的配置文件过于开放,开发人员应该专注于为他们的应用程序(或应用程序类型)创建配置文件。

提示 #3:为特定应用程序创建 seccomp 配置文件。 如果不可能,请为应用程序类型创建配置文件,例如,创建一个包含 Golang 应用程序的所有 Web API 的高级配置文件。 仅使用运行时/默认作为最后的手段。

在以后的文章中,我将介绍如何创建受 SecDevOps 启发的 seccomp 配置文件、如何自动化它们以及如何在管道中测试它们。 换句话说,您没有理由不升级到特定于应用程序的配置文件。

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 不会影响进行系统调用的线程,但会记录有关系统调用的信息。

以下是使用此功能的典型策略:

  1. 允许需要的系统调用。
  2. 阻止来自您知道没有用的系统的调用。
  3. 在日志中记录有关所有其他调用的信息。

一个简化的示例如下所示:

{
    "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"
        }
    ]
}

(中混合 seccomp.json)

但请记住,您需要阻止所有您知道不会使用且可能会损害集群的调用。 编制清单的良好基础是官方 Docker 文档。 它详细解释了默认配置文件中阻止哪些系统调用以及原因。

然而,有一个问题。 虽然 SCMT_ACT_LOG 它自 2017 年底以来就受到 Linux 内核的支持,直到最近才进入 Kubernetes 生态系统。 因此,要使用此方法,您需要 Linux 内核 4.14 和 runC 版本不低于 v1.0.0-rc9.

提示 #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 把他挂起来o 无限期。 因此,您将在集群中获得较高的 CPU 使用率:

Kubernetes 中的 Seccomp:从一开始就需要了解的 7 件事

在这种情况下,公用事业公司可以提供救援 strace - 它将显示问题可能是什么:

Kubernetes 中的 Seccomp:从一开始就需要了解的 7 件事
sudo strace -c -p 9331

确保配置文件包含应用程序在运行时所需的所有系统调用。

提示 #7:注意细节并确保所有必要的系统调用都列入白名单。

关于本着 SecDevOps 的精神在 Kubernetes 中使用 seccomp 的系列文章的第一部分到此结束。 在以下部分中,我们将讨论为什么这很重要以及如何自动化该过程。

译者PS

另请阅读我们的博客:

来源: habr.com

添加评论