1 保护 oslo.messaging.rpc 消息¶
目录
Trove 利用 oslo_messaging.rpc 执行 RPC 调用,而这背后的传输机制是 oslo_messaging。目前,在 oslo.messaging 上发送的消息被视为真实有效的。增加一层验证,以确保 RPC 调用确实是真实有效的,这将是有益的。我们建议使用唯一密钥对 RPC 调用进行加密。
Launchpad 蓝图:https://blueprints.launchpad.net/trove/+spec/secure-oslo-messaging-messages
1.1 问题描述¶
目前,接收方将 oslo.messaging 上发送的消息视为真实有效的。鉴于所使用的主题名称是可预测的,如果有人具备足够多的 Trove 知识,例如,破坏客户实例或以其他方式获取连接到 RabbitMQ(或 oslo-messaging 的底层传输)的凭据,然后生成消息到,例如,任务管理器,通过冒充 API 服务,这是有可能的。虽然已经存在一些保障措施来控制此范围,例如要求消息包含具有适当访问权限的有效 keystone 令牌,但这仍然是一个漏洞点。
当前,当客户端希望发出异步 RPC (cast()) 时,方法名称和参数将被编组并发送到 oslo_messaging.rpc。oslo_messaging.rpc 的责任是将信息传输到远程端,然后找到并调用指定的方法。在客户端调用 cast() 之后,oslo_messaging.rpc 的消费者看到的下一件事是在服务器端调用所需的方法。
对于同步 RPC (call()) 也是如此,只是客户端会阻塞,服务器完成操作并将响应发送到客户端,然后客户端接收该响应并取消阻塞。
1.2 提议的变更¶
在试验了几种其他替代方法之后,我们建议实现自定义序列化器(和反序列化器),这些序列化器可以提供给 oslo_messaging.rpc。
所有通过 RPC call() 发送的消息和返回值都将通过这些自定义方法进行序列化,从而加密内容。由于一个错误 Failure to use serializer in exception,如果 RPC 函数抛出异常,则该异常不会被加密。
1.2.1 TroveRPCDispatcher 如何验证消息的合法性¶
所提出的实现依赖于密码学以及控制平面和客户的唯一密钥。我们建议使用对称密钥进行加密。
Trove 有以下实体参与 RPC 调用
Trove API 服务(客户端)
Trove 任务管理器服务(客户端和服务器)
Trove Conductor 服务(服务器)
Trove 客户代理(客户端和服务器)
当发出 RPC call() 或 cast() 时,客户端调用序列化器,该序列化器将加密所有参数。当在服务器端接收到时,oslo_messaging.rpc 将调用反序列化器,该反序列化器将解密参数。
假设控制平面是安全的,并且控制平面对称密钥是安全的。如果它被破坏,那么一切都将失效。
在与客户代理通信时,每个客户都有一个由控制平面生成的唯一对称密钥,并在启动时传递给客户。
1.2.2 保护响应¶
如前所述,call() 方法的响应将以与请求相同的方式进行保护。如前所述,由于一个错误,RPC 函数抛出的异常当前未被序列化,因此将以未加密的形式返回。当(如果)oslo_messaging.rpc 中修复了该错误,则可以最大限度地减少这种暴露。
1.2.5 为什么这是安全的?¶
我们上面做出了两个假设;这些是
控制平面是安全的,控制平面密钥未被破坏,并且
将客户密钥传输到客户是安全的,并且未被破坏。
鉴于 OpenStack 系统的架构,这些是有意义且合理的假设。
如果客户被破坏,恶意行为者可以连接到底层传输(例如 Rabbit),但他们只能看到他们无法解密的加密消息。
1.2.6 配置¶
控制平面密钥以安全的方式存储在控制平面上,并且有配置选项告诉每个服务在哪里找到它。
每个客户实例都将有一个密钥,该密钥将安全地存储在实例上,并且配置设置将告诉客户代理在哪里找到它。
cfg.StrOpt('tm_rpc_encr_key',
default='bzH6y0SGmjuoY0FNSTptrhgieGXNDX6PIhvz',
help='OpenSSL aes_cbc key for taskmanager RPC encryption.'),
cfg.StrOpt('inst_rpc_key_encr_key',
default='emYjgHFqfXNB1NGehAFIUeoyw4V4XwWHEaKP',
help='OpenSSL aes_cbc key to encrypt instance keys in DB.'),
cfg.StrOpt('instance_rpc_encr_key',
help='OpenSSL aes_cbc key for instance RPC encryption.'),
1.2.7 数据库¶
每个客户实例的客户密钥将存储在数据库中。建议为此创建一个表 instance_keys。
字段 |
类型 |
是否为空 |
键 |
默认值 |
额外信息 |
id instance_id encrypted_key created updated deleted deleted_at |
varchar(64) varchar(64) varchar(255) datetime datetime tinyint(1) datetime |
NO NO NO NO NO NO YES |
PRI UNI |
NULL NULL NULL NULL NULL NULL NULL |
加密的客户实例密钥存储在 encrypted_key 列中。外键约束将 instance_id 与 instances.id 关联。此表上放置 instance_id 上的唯一约束。
1.2.8 公共 API¶
公共 API 没有更改。
1.2.9 公共 API 安全性¶
没有更改。
1.2.10 Python API¶
没有更改。
1.2.12 内部 API¶
从开发人员的角度来看,以及调用方面,内部 API 将不受此更改的影响,因为该实现旨在在 Trove 代码完全下方工作。因此,内部 API 将大不相同,并且必须有代码来确保加密和非加密客户端和服务器知道如何互操作。
1.2.13 客户代理¶
客户代理将在 configdrive/boot 过程中接收其密钥,并可以使用它来解密所有消息。
1.2.14 替代方案¶
考虑、原型设计并放弃了几种替代方案。下面提供了每个方案的简短摘要。
我们建议 oslo_messaging.rpc 团队在他们的代码中实现轻量级的消息签名和加密机制,通过提供允许消费者(trove)执行签名和加密的回调机制。oslo_messaging 团队不希望这样做,因为他们认为消息包含其他私有数据结构,我们(消费者)可能会修改这些数据结构并导致意外行为。
我们建议 oslo_messaging.rpc 允许消费者为接收器上的消息提供自定义调度器。使用此实现,可以在客户端对签名或消息加密执行操作,并在服务器端拦截和反转,从而使我们能够在服务器端进行最小的更改。同样,oslo_messaging.rpc 团队认为调度器是一个私有数据结构,他们不认为我们应该封装它。
我们原型设计并试验了一种更改,其中每个 RPC 端点都将被装饰,并且装饰器将提供一种构造适当参数和调用 RPC 方法的机制。客户端的更改与 (b) 相同,但服务器端的更改涉及对每个 RPC 方法添加装饰器。此外,在这种方法中,调用上下文未加密,因此被放弃了。
我们被告知不应该以我们正在使用的方式使用 oslo_messaging.rpc,因为它仅用于控制平面。相反,我们应该使客户成为 RPC 服务器。不幸的是,这不是我们需要的?在 Trove 中,客户代理是控制平面的扩展,不适合基于 REST 的通信策略。我们需要的是一种 RPC 机制,很遗憾 oslo_messaging.rpc 似乎无法提供安全的机制。
1.4 实现¶
1.4.1 分配人(s)¶
- 主要负责人
amrith
- Dashboard 指定人
无
1.4.3 工作项目¶
在控制平面和客户上实现代码
实现 devstack 插件的更改以创建控制平面密钥
实现单元测试
实现升级处理
Update documentation
1.5 升级影响¶
预计升级影响最小,建议的代码处理此转换。
控制平面密钥将在所有控制平面节点上生成并持久化。
当客户升级时,密钥将作为 nova 迁移过程的一部分发送给他们。
API 将修订一个主要版本以考虑这一点。
1.9 参考文献¶
Failure to use serializer in exception:https://bugs.launchpad.net/oslo.messaging/+bug/1648254