使用 NGINX 单元进行应用程序隔离

NGINX Unit 功能集的最新发展之一是对应用程序隔离的支持,该支持在版本 1.11.0 中引入并通过 Linux 命名空间实现。该功能于几周前宣布,这是有原因的:该功能背后的开发者 Tiago de Bem Natel de Moura 于今年夏天才加入 NGINX Unit 团队。

让我们首先简单回顾一下 Linux 命名空间:本质上,它们是一种内核机制,使一组进程能够共享多种类型的系统资源,与其他进程组共享的资源分开。内核确保命名空间中的进程仅访问分配给该命名空间的资源。尽管两个不同命名空间中的进程可以共享一些资源,但其他资源对于另一个命名空间中的进程是“不可见的”。可以在命名空间中隔离的资源类型因操作系统而异,但包括进程和用户 ID、interpro进程通信实体、文件系统中的挂载点、网络对象等等。

听起来有点平淡?也许吧,特别是如果您不熟悉操作系统技术细节的话。然而,命名空间是容器化革命背后的关键因素之一——在单个操作系统实例中分离和隔离应用程序进程可以实现在容器中运行应用程序所需的关键安全和扩展机制。

想法

好的,现在我们已经确定命名空间可能是一件好事,但 NGINX Unit 对此事有何看法?在进一步讨论之前,让我们先概述一下背景,听听蒂亚戈本人的说法:

我正在研究用于监视和拦截应用程序流量的更好选择。在业余时间,我正在研究 NGINX Unit 的内部结构,并认为进程隔离可能是一个不错的选择。但是,我不确定最好的方法是t。早些时候,我考虑过 eBPF 并研究了它如何在内核级别重定向数据包,但后来我有了不同的想法。由于 NGINX Unit 以类似于容器运行时的方式运行和管理应用程序,因此如果我们为 NGINX Unit 添加[应用程序]隔离支持并使用它代替运行时会怎么样?一次偶然的机会,这恰好是 NGINX Unit 团队对未来的设想之一。

在集群中,容器运行时启动和停止应用程序,因此我们了解集群中运行的所有内容。同时,NGINX Unit 的架构也是如此,但也默认实现流量监控和拦截:到达应用程序的唯一途径是 NGINX Unit 的共享内存模型。有趣的是,我们甚至可以隔离网络,类似于跳过容器内的接口设置,但应用程序仍然可以通过与 NGINX Unit 共享内存来[与外界]通信,而无需任何费用ive 网络黑客。

配置

从配置的角度来看,一切都归结为新的隔离对象,该对象在应用程序对象中定义与命名空间相关的设置。

隔离对象中的命名空间选项取决于系统,因为可以隔离到命名空间的资源类型因操作系统而异。下面是一个为应用程序创建单独的用户 ID 和挂载点命名空间的基本示例:

目前,NGINX Unit 支持 Linux 内核支持的七种命名空间隔离类型中的六种配置。对应的配置选项有cgroup、credential、pid、mount、network、uname。最后一种类型,ipc,被保留。

默认情况下,所有隔离类型均处于禁用状态(选项设置为 false),这意味着应用驻留在 NGINX Unit 的命名空间中。当您通过将应用程序的选项设置为 true 来启用某种隔离类型时,NGINX Unit 为应用创建该类型的单独命名空间。因此,例如,应用程序可以驻留在与 NGINX 单元相同的命名空间中,除了其自身具有单独的安装或凭据命名空间之外。

有关隔离对象中的选项的更多详细信息,请参阅 NGINX 单元文档。

注意:在撰写本文时,所有应用程序都需要使用与 NGINX Unit 相同的 ipc 命名空间;这是共享内存机制所必需的。您可以在配置中包含 ipc 选项,但其设置无效。这种情况可能会在未来的版本中发生变化。

用户和组 ID 映射

NGINX 单元中的应用程序隔离包括对 UID 和 GID 映射的支持,如果启用了凭据隔离(这意味着您的应用程序在单独的凭据命名空间中运行),则可以配置这些映射。您可以将应用程序命名空间(我们称之为容器命名空间)中的一系列 ID 映射到凭证中相同长度的 ID 范围应用程序父进程的命名空间(我们称之为主机命名空间)。

例如,假设您有一个使用非特权用户凭据运行的应用程序,然后启用凭据隔离,为该应用程序创建一个容器命名空间。 NGINX Unit 允许您将主机命名空间中非特权用户的 UID 映射到容器命名空间内的 UID 0(root)。根据设计,任何命名空间中的 UID 0 都拥有该命名空间中的完全权限,而主机命名空间中其映射对应项的权限仍然受到限制。因此,该应用程序看似拥有 root 功能,但仅限于其命名空间内的资源。同样的注意事项也适用于 GID 映射。

在这里,我们将主机命名空间中从 UID 500 开始的 10 项 UID 范围映射到容器命名空间中从 UID 0 开始的 UID 范围(主机:500–509,容器:0–9)。同样,我们映射了 20 项 GID 范围,从g 从主机命名空间中的 GID 1000 到容器命名空间中从 GID 0 开始的范围(主机:1000–1019,容器:0–19):

如果您不创建显式 UID 和 GID 映射,则默认情况下,主机命名空间中非特权 NGINX 单元进程的当前有效 UID (EUID) 将映射到容器命名空间中的根 UID。另请注意,仅当主机操作系统支持用户命名空间时,UID/GID 映射才可用。说了这么多,让我们继续探索应用程序隔离对 NGINX Unit 中运行的应用程序的影响。

入门:基本应用程序隔离

让我们从基础知识开始,看看该功能在运行时的行为方式。为此,我们使用官方存储库中的一个 Go 应用程序(我们在新版本测试期间运行):

此代码使用 JSON 格式的应用程序清单来响应请求”s 进程和命名空间 ID,枚举 /proc/self/ns/ 目录的内容。让我们在 NGINX Unit 中配置应用程序,暂时忽略隔离对象:

来自正在运行的应用实例的 HTTP 响应:

$curl -X GET http://localhost:8080

{
“PID”:5778,
“UID”:65534,
“GID”:65534,
“NS”:{
“用户”:4026531837,
“PID”:4026531836,
“IPC”:4026531839,
“集团”:4026531835,
“UTS”:4026531838,
“MNT”:4026531840,
“网”:4026531992
}
}

现在我们添加隔离对象以启用应用程序隔离。应用程序需要重新启动才能使隔离机制生效。方便的是,NGINX Unit 在幕后处理此问题,因此从最终用户的角度来看,更新是相当透明的。

请注意,用户选项设置为 root。这是启用到容器命名空间中 UID/GID 0 的映射所必需的。

我们再次发出命令:

$ 卷曲 -X GEThttp://本地主机:8080

{
“PID”:1,
“UID”:0,
“GID”:0,
“NS”:{
“用户”:4026532180,
“PID”:4026532184,
“IPC”:4026531839,
“集团”:4026532185,
“UTS”:4026532183,
“MNT”:4026532181,
“网”:4026532187
}
}

现在我们已经启用了应用程序隔离,命名空间 ID 已更改 – 它们现在是容器命名空间中的 ID,而不是主机命名空间中的 ID。由于上述原因,唯一保持不变的是 IPC。

更进一步:网络应用程序隔离

为了更深入地研究,让我们探讨应用程序隔离对网络的实际影响,这对于网络应用程序来说非常重要。为此,我们选择的工具是 nsenter,它可用于 NGINX Unit 支持的许多操作系统发行版。该实用程序允许我们在进程命名空间内运行任意命令,我们将使用它来具体化由不同设置引起的更改在我们上面配置的同一个 Go 应用程序的隔离对象中。首先,我们找出主机PID:

# ps 辅助| grep go 应用程序
1000 5795 0.0 0.3 424040 7380?当天 14:51 0:00 /tmp/go-app

知道了 PID,我们就可以进入容器命名空间并探索其内部结构:

# nsenter –all -t 5795 /bin/sh
# ip 一个
1: lo: mtu 65536 qdisc noop 状态 DOWN 组默认 qlen 1000
链接/环回 00:00:00:00:00:00 brd 00:00:00:00:00:00
# ID
uid=0(root) gid=0(root) groups=0(root)

注意,只有loopback接口可用;但是,该应用程序完全能够通过 NGINX Unit 处理外部 HTTP 请求。接下来,我们从配置中的命名空间列表中删除网络选项,以查看禁用网络隔离后应用程序的网络接口配置结果:

然后我们重复上面相同的步骤:

# ps 辅助| grep go 应用程序
没有人 7615 0.0 0.4 403552 8356 ?当天 15:12 0:00 /tmp/go-app
# nsenter –all -t 7615 /bi不存在/不存在
# ip 一个
1:lo:mtu 65536 qdisc noqueue状态未知组默认qlen 1000
链接/环回 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 范围主机 lo
永远有效_lft 永远首选_lft
inet6 ::1/128 范围主机
永远有效_lft 永远首选_lft
2: eth0: mtu 1500 qdisc pfifo_fast 状态 UP 组默认 qlen 1000
链接/以太 52:34:01:6d:37:22 brd ff:ff:ff:ff:ff:ff
inet 192.168.128.41/21 brd 192.168.134.255 范围全局动态 eth0
valid_lft 600225 秒 Preferred_lft 600225 秒
inet6 fe80::5054:ff:fe6e:3621/64 范围链接
永远有效_lft 永远首选_lft

现在还有应用程序进程在启动时从 NGINX Unit 继承的接口 (eth0)。瞧!

下一步是什么

我们已经开始期待我们的用户提出质疑,所以你们中的许多人可能会想:这就是全部吗?当然不是!在实施的早期阶段,应用程序隔离水平相当低,因此 NGINX 在我们的用户能够充分受益之前,该装置还需要其他功能。例如,避免在应用程序配置中维护单独的容器相关选项将简化设置并使其不易出错。

目前,我们正在努力将 rootfs 功能添加到应用程序隔离实现中,以将应用程序安全地限制在文件系统目录中。从应用程序的角度来看,该目录成为文件系统根目录,出于所有实际目的,您可以将应用程序制作成易于配置的容器。恩,那就对了;我们正在快速实施应用程序容器化——NGINX Unit 方式。与往常一样,请继续关注我们,并花时间体验我们的新功能!请随时在我们的 GitHub 存储库或通过我们的邮件列表分享您的第一印象、疑虑和改进想法。


评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注