Rootwrap 守护进程模式¶
https://blueprints.launchpad.net/oslo.rootwrap/+spec/rootwrap-daemon-mode
正如多次在邮件列表中指出的那样 [1] [2] 不同的服务(最显著的是 Neutron 和 Nova)因需要为每个需要 root 权限的调用运行新的 rootwrap 可执行文件而遭受性能损失。[2] 基本上以“谁来完成这项任务?”的问题告终。
问题描述¶
这种开销的结构已经在 [1] 中进行了分析。 显而易见,这里的主要问题是 rootwrap 启动时间,包括 Python 解释器启动和 rootwrap 配置文件解析。
提议的变更¶
我建议为 rootwrap 创建一种新的操作模式 - 守护进程模式。 在这种模式下,rootwrap 将启动,读取配置文件,并等待需要 root 权限的命令执行。 每个服务的进程都将拥有自己的 rootwrap 守护进程。
守护进程启动¶
守护进程将使用一个单独的二进制文件启动(例如,Neutron 的 neutron-rootwrap-daemon),指向 oslo.rootwrap.cmd:daemon 端点(而不是 :main)。 该二进制文件将接收相同的选项(配置文件),就像正常的 rootwrap 一样,除了要以特权模式运行的命令。 例如
rootwrap-daemon /etc/myservice/rootwrap.conf
启动过程与正常模式相同,直到要运行命令的那一点。 在守护进程模式下,将调用一个单独的方法,该方法启动 RPC [3] 服务器并进入无限循环以处理请求。
守护进程 API¶
启动后,守护进程将推送到其 stdout
UNIX 域套接字路径(以 UTF-8 编码);
换行符
\n;32 字节的身份验证密钥。
可以使用这些凭据连接基于 multiprocessing.BaseManager 的客户端。 由于 pickle 和 xmlrpclib 不安全,它使用自己的 JSON 序列化(参见 幕后原理 部分)。 唯一公开的对象是 rootwrap,它有一个方法:run_one_command(userargs, env=None, stdin=None)。 参数是
userargs- 用于运行命令的命令行参数列表;env- 要为其设置的环境变量字典(默认情况下,它是一个空字典,因此会清除所有环境变量);stdin- 要传递给子进程标准输入的字符串。
该方法返回一个包含以下内容的 3 元组
子进程的返回码;
从其 stdout 流捕获的所有内容字符串;
从其 stderr 流捕获的所有内容字符串。
以下是 rootwrap 守护进程的基本用法示例
>>> from subprocess import *
>>> from multiprocessing.managers import BaseManager
>>> process = Popen(["rootwrap-daemon", "rootwrap.conf"], stdout=PIPE)
>>> address = process.stdout.readline()[:-1].decode('utf-8')
>>> authkey = process.stdout.read(32)
>>> class MyManager(BaseManager): pass
...
>>> MyManager.register("rootwrap")
>>> from oslo.rootwrap import client # to set up 'jsonrpc' serializer only
>>> manager = MyManager(address, authkey, serializer='jsonrpc')
>>> manager.connect()
>>> proxy = manager.rootwrap()
>>> proxy.run_one_command(["cat"], stdin="Hello, world!")
[0, u'Hello, world!', u'']
>>> process.kill()
请注意,这需要 rootwrap-daemon 指向 oslo.rootwrap.cmd:daemon 位于 PATH 中。
由于 JSON 序列化,run_one_command 调用返回一个列表,但这不会改变 Python API 的用法。
客户端 API¶
为了简化守护进程的使用,提供了一个 oslo.rootwrap.client 模块,其中包含一个类 Client,它封装了与 rootwrap 守护进程交互所需的所有步骤。
它的构造函数期望一个参数 - 可以传递给 Popen 以创建 rootwrap 守护进程进程的列表。 例如,对于 Neutron,它将是 ["sudo", "neutron-rootwrap-daemon", "/etc/neutron/rootwrap.conf"]。
该类提供了一个方法 execute,其配置文件与上面显示的 run_one_command 方法相同。
该类延迟创建守护进程的实例,连接到它并传递参数。 请注意,将存在一些重新连接和重新生成机制,以便如果守护进程进程死亡或挂起,Client 将在下一次调用时检测到这一点并简单地重新启动它。
这种延迟将允许用户杀死所有 rootwrap 守护进程以重新加载配置文件,例如。
幕后原理¶
此提案中最大的预期安全风险是客户端与守护进程通信的方式,因此我将详细讨论底层协议。
凭据传递
连接到守护进程所需的凭据传递到 stdout 流,预计该流将通过管道直接绑定到调用进程。 它们仅暴露给内核和调用进程。
身份验证
multiprocessing涉及对每次与服务器建立的新连接进行摘要身份验证。 密钥从不通过套接字传递,因此我们甚至可以使用 TCP 套接字(不行)。 密钥使用os.urandom(32)调用生成。连接(非)池
管理器使用 threadlocal 连接,因此无需创建连接池。 尽管为每个线程创建新连接似乎很浪费,但与生成新进程的时间相比,通过 UNIX 套接字创建连接几乎是无关紧要的。
线路协议
默认情况下,
multiprocessing使用pickle来序列化 RPC [3] 请求和响应,但它非常不安全,因为它允许在接收端调用任何方法(参见 [4] 中的警告)。 另一种选择是使用xmlrpclib作为序列化器,但它不安全,因为它容易受到资源耗尽攻击 [9]。 这就是为什么实现了 JSON 序列化的原因。 它非常简单(约 50 SLOC)并且安全,因为 JSON 序列化被广泛认为是安全的。使用
multiprocessing.managers模块的未记录功能插入了此序列化。listener_client- 可用序列化选项的字典,键是字符串,值是(侦听器、客户端)对;serializer-BaseManager.__init__构造函数的参数,包含listener_client字典中的键。
multiprocessing模块的作者保证这种机制不会很快消失 [5]。注意
虽然依赖于未记录的功能有风险,但 stdlib 模块作者的保证减轻了这种风险。
备选方案¶
有许多替代方法可以优化 rootwrap 调用的数量以减轻开销(参见 [6])。 在邮件列表的原始线程中 [1] 和相应的 etherpad [6] 中有许多建议。
废弃 rootwrap,切换到 sudo。
我们将失去当前对可以以 root 身份运行的内容的细粒度控制。
使用其他解释器来运行 rootwrap。
这无法解决解释器启动成本。
用其他语言重写 rootwrap。
这包括完全或部分用 C 或可以转换为 C 的 Python 方言重写的建议。
由于 OpenStack 社区专注于 Python 开发,将其他语言引入该领域需要更多熟悉该语言的开发人员。
在调用进程侧过滤命令并使用 sudo。
这将提供与第一个选项相同的安全性。
将使用 rootwrap 的调用合并到脚本中
例如,我们可以创建脚本,这些脚本只需要一个 rootwrap 调用即可为单个请求完成所有必要的工作。 但这些脚本要么会变得非常复杂(例如,在 shell 中重写 Neutron 代理的部分),要么会太多。 无论哪种方式,都违背了 sudo 和 rootwrap 的目的 - 最小化以 root 权限运行的代码量和复杂性。
每个主机的守护进程
这将需要一些 D-Bus 或 MQ 设置和保护。 设置这样的安全守护进程看起来不可行。 由于每个项目都使用自己的 rootwrap 配置,并且这些配置可能依赖于主机,因此在整个项目中支持它们似乎更不可行。
Impact on Existing APIs¶
独立模式下的操作不受任何影响,因此所有现有用法将像以前一样工作。
安全影响¶
此更改需要向 sudoers 添加另一个二进制文件,以用于将使用守护进程模式的项目。
守护进程本身将侦听 UNIX 域套接字,但每个连接都通过摘要身份验证传递。
JSON 用作传输以避免其他类型的序列化机制中已知的漏洞。
性能影响¶
在 [7] 的提交消息中提供了基准测试结果。 它们表明,虽然初始守护进程启动时间略大于使用常规 rootwrap 调用,但平均而言,守护进程显示出 10 倍以上的性能提升。
Configuration Impact¶
oslo.rootwrap 本身没有。 使用它的项目可能需要为新行为提供一个单独的选项。
开发人员影响¶
无
实现¶
功能实现正在进行中,地址为 [7]。 Neutron 的示例用法在 [8]。
负责人¶
- 主要负责人
yorik-sar (Yuriy Taraday, YorikSar @ freenode)
里程碑¶
- 完成目标里程碑
Juno-2
工作项¶
此蓝图建议对 rootwrap 进行相对较小的添加。
应单独涵盖进一步集成到不同项目中的内容。
文档影响¶
守护进程的工作及其 API 中涉及的两种机制应在文档中涵盖。
依赖项¶
无
参考资料¶
注意
本作品采用知识共享署名 3.0 非移植许可协议授权。 http://creativecommons.org/licenses/by/3.0/legalcode