用户和组实体的跨后端 ID¶
启用 Keystone 生成和维护用户和组实体的唯一 ID,这些 ID 在 Keystone 安装范围内是唯一的,无论实体是 Keystone 本地、LDAP 还是联合身份验证的。
问题描述¶
最初,Keystone 负责创建所有用户和组实体,并为它们分配唯一的 ID。随着 LDAP 和联合身份验证的出现,情况不再如此。但是,Keystone API 仍然假定可以仅从提供的 ID 唯一标识用户和组。此外,对于支持多租户的 Openstack 安装,其中每个租户都表示为一个拥有自己 LDAP 的域,仅仅在所有后端中搜索实体是不切实际的。
需要的是 Keystone 能够继续通过其 API 为其遇到的所有实体(无论如何创建)提供 ID,并在收到使用调用者提供的 ID 来操作该实体的 API 调用时能够有效地找到该实体。Keystone 的客户端不应意识到 API 下方的任何此类更改。
一个关键要求是,任何此类支持的引入不应使现有安装中 Keystone 已经发出的任何 ID 失效或更改,因为这些 ID 可能会被 Keystone 的客户端持久存储(例如 Swift)。
提议的变更¶
该提议是 Keystone 将构建公共 ID 到底层后端标识符的身份映射,以便 Keystone 可以将 API 调用路由到正确的驱动程序。Keystone 的客户端只会看到公共 ID,并且公共 ID 的生成将在 Keystone 内部动态进行。此更改最初是为 IceHouse 版本提出的,并接近集成,但经双方同意,为了在 Juno 会议上讨论此主题而推迟,该会议认可了本提案概述的一般方向。
此映射仅与 Keystone 的身份组件(即用户和组)相关,并将作为可插拔的映射后端实现,最初提供的唯一存储后端是 SQL。
如摘要中所述,此要求的的主要驱动因素之一是支持 Openstack 安装的多域特定后端(例如 LDAP),这些安装支持多个租户(甚至企业中的多个部门)。该框架已添加到 Keystone 代码库的 Havana 版本中的 identity/core.py 模块中。但是,此支持被标记为实验性的,因为它不支持 ID 映射的概念 - 而是试图从调用的上下文中推断 API 的域范围(例如令牌的范围)。虽然这在许多情况下有效,但它是一个不完整的解决方案 - 无法推断正确域的示例包括
通过用户 ID 进行身份验证
跨域角色分配
因此,此更改基于当前代码库中的多域框架,但删除了“域推断”代码,而赞成 ID 映射。
在实施此更改时,需要考虑许多设计事项
确保与现有实体 ID 兼容。当今 Keystone 安装中存在两种用户和组实体 ID
SQL 后端身份的 UUID ID,或在 RW LDAP 后端中由 Keystone 创建的那些实体(uuid 存储在 LDAP 属性中)
由操作员选择的各种 LDAP 属性(例如电子邮件地址),用于在 Keystone 外部创建的 LDAP 后端身份
由于(忽略当前对域特定后端的支持),所有 ID 都只能与单个选择的 SQL 或 LDAP 身份驱动程序相关联,因此升级到 Juno 并使用多后端 UUID 不应影响这些已经公开的 ID。该提议是默认情况下不会创建任何身份映射条目,因为只有一个标准的身份驱动程序。只有当云提供商希望确保正在用作实体 ID 的任何 LDAP 属性未作为公共 ID 公开时,标准身份驱动程序的实体 ID 才会映射。这通过将
backward_compatible_ids配置选项设置为False来实现。公共 ID 生成器算法。虽然显而易见的选择是另一个常规 uuid4 十六进制字符串,但建议使用哈希算法,以便可以从底层本地标识符组件重新生成公共 ID。这具有许多优点
丢失整个映射表。如果映射表丢失,那么一种额外的恢复机制(而不是还原备份)将是简单地在 Keystone 遇到用户和组实体时动态地重新生成表。但是,应该注意的是,如果分配表也丢失,那么这种机制只能作为恢复的一部分。
轻松清除映射表。在身份在 Keystone 外部管理的情况下(例如 LDAP 或联合身份验证),表示已从底层身份存储中删除的用户和组的陈旧条目可能会保留在映射表中。虽然无害,但显然需要某种定期的清理。如果表可以重新生成,那么只需使用 keystone-manage 中的选项清除整个表(或给定域的所有条目)就是一个简单的解决方案(尽管每次遇到给定的实体时都会产生轻微的性能下降)。此外,将提供一个选项,以基于本地实体信息删除单个条目。
确保在使用多个 Keystone 时生成一致的公共 ID。对于某些大型安装,可能会运行多个 Keystone,并由复制的数据库提供支持。在这种情况下,两个不同的 Keystone 可能会第一次遇到一个实体,并都为其创建映射(由于数据库复制延迟)。在可重新生成的情况下,它们将创建相同的公共 ID 并避免表行冲突。应该注意的是,此问题已经存在于常规 RW SQL 后端中(例如,可以通过两个不同的 Keystone 使用不同的 UUID 创建两个具有相同名称的用户),但当 RO LDAP 后端通过映射提供身份存储时,更有可能产生冲突(因为只需要两个请求才能在多个 Keystone 运行的同时大致同时读取相同的实体)。
选择要使用的哈希算法是在生成的密钥大小(以及任何需要存储生成的 ID 的客户端的列宽)、碰撞概率和编码数据的安全性之间取得平衡。应该注意的是,对于大多数安装,此处进行哈希的数据(本地 ID 和域 ID)可能不会被认为是机密的 - 因此哈希的实际安全性不太重要。但是,为了提供跨所有用例的灵活性,将实现一个可插拔的生成器后端,并提供 sha256 生成器作为默认值。生成器生成的密钥将限制为最大字符串大小 64 字节(并且任何生成器生成超过该大小的密钥将导致抛出异常)。
创建身份映射层也会产生一些复杂性
在今天的代码库中,控制器为他们正在创建的实体生成 UUID ID,并将此传递给管理器/驱动程序层。我们对管理器/驱动程序层的所有单元测试也假定调用者指定了 ID。现在,管理器层正在动态构建公共 ID 映射表,因此控制器层认为它控制着 ID 生成似乎是不合适的。因此,该提议是删除用户和组控制器中的 ID 生成,并让身份管理器承担此角色。请注意,这仅影响控制器-管理器接口,而不影响驱动程序接口本身。此更改虽然只影响几行生产代码,但将导致大量的机械更改到我们的单元测试。
如果底层驱动程序支持 UUID(例如当前的 SQL 后端),那么对 UUID 进行哈希似乎没有太多优势。因此,该提议是使用本地 UUID 作为此类驱动程序的公共 ID,并在映射表中加载一个空映射。
备选方案¶
在 IceHouse 开发和 Juno 会议上讨论了一种替代方法,即在 ID 本身内创建映射 - 即在 ID 字符串中编码查找后端中实体所需的所有详细信息。一种提议是
<local ID>@@<domain-name>
此提议的问题在于,就目前而言,域名称和任何本地 ID 都可以长达 64 字节 - 而 Keystone 需要返回的实体 ID 也只是 64 字节。开发列表上的讨论探讨了增加 Keystone 返回的实体 ID 的大小的选项,这导致其他项目(这些项目是这些实体 ID 的消费者)强烈反对。讨论线程可以在这里找到
https://www.mail-archive.com/openstack-dev@lists.openstack.org/msg17506.html
上述解决方案实际上在 IceHouse 开发期间作为此更改的开发的一部分进行了原型设计,可以在这里找到
https://review.openstack.org/#/c/74214/14
对这种替代想法的进一步完善也讨论了压缩域信息所需的字节数,同时限制分配给本地 ID 的字节数,以便将整个标识符适应当前 64 字节的规范。虽然这在许多实际情况下都可能有效,但普遍共识是这会过度限制我们在其中存储以唯一标识相关本地后端中的实体所需的信息。一个简单的例子是,某些后端存储可能会在不同的命名空间中存储用户和组 ID,因此映射也应存储这是哪种类型的实体。此外,对于联合身份验证,我们可能希望存储其他信息。
如果将来我们想实施某种类似的方法,那么当前提出的此更改的架构将允许实现一个映射后端,该后端只需提供公共 ID 的编码和解码,而不是实际将映射属性存储在表中。
此提议的早期版本建议提供公共 ID 生成算法的选项,例如在常规 UUID 和哈希之间进行选择。由于使用常规 UUID 没有太多优势,因此已放弃这一点。
数据模型影响¶
数据模型更改涉及创建提供映射的新表
class IDMapping(sql.ModelBase, sql.ModelDictMixin):
__tablename__ = 'id_mapping'
public_id = sql.Column(sql.String(64), primary_key=True)
domain_id = sql.Column(sql.String(64), nullable=False)
local_id = sql.Column(sql.String(64), nullable=False)
type = sql.Column(
sql.Enum(map.EntityType.USER, map.EntityType.GROUP,
name='type'),
nullable=False)
__table_args__ = (sql.UniqueConstraint('domain_id', 'local_id', 'type'),
{})
定义唯一约束以确保不能将两个映射存储到表中相同的公共 ID。
不建议添加进一步的索引,因为(除了 keystone-manage 之外),只有两种实际访问模式:通过公共 ID(这是主键)和通过指定所有本地标识符(由于唯一约束,应该已经索引)。鉴于 keystone-manage 不太可能是时间关键的,因此进一步索引的权衡可能不值得。
REST API 影响¶
此提议没有新的 API 调用。一些现有的 API 调用施加了额外的限制。
“列出用户”和“列出组” 在配置了域特定后端(通过配置文件)的 Keystone 的情况下,这些 API 调用需要指定域范围。可以通过使用已经支持的 domain_id 过滤器或通过使用域范围的令牌来完成。如果未提供这两者,则调用将返回 401(未授权)错误代码。
“将用户添加到组” 由于组 membership 被认为是身份和底层驱动程序后端的函数,因此跨不同域特定后端不支持 membership,并将返回 403(禁止)错误代码。这不会影响跨域和后端的角色分配,后者不受限制。
此外,如果在 Keystone 配置文件中指定了不受支持的身份映射生成器算法,那么任何身份 API 可能会生成 500(内部服务器错误)返回代码。
安全影响¶
此提议中描述的身份映射功能不存储用户数据 - 它只是将公共 ID 映射到本地标识符。虽然通常本地 ID(即使对于 LDAP)也不被认为是敏感数据,但此提议的一个好处是 LDAP 本地 ID 不会离开 Keystone(因为仅公开公共 ID)。这与当前的单域 LDAP 实现相反,该实现将 LDAP 驱动程序定义的本地 ID 作为公开可见的实体 ID 公开。
通知影响¶
现有的身份通知将继续起作用,尽管它引用的是公共 ID 而不是任何本地标识符信息。
其他最终用户影响¶
无
性能影响¶
引入映射层显然会产生一些影响。但是,由于映射层默认情况下仅在域特定后端情况下使用,而这些情况在生产环境中今天不受支持,因此对默认或现有安装不会产生任何影响。在使用时,将进行以下额外的数据库调用
对“列出”用户和组调用返回的每个实体进行非 PK 表查找(以便映射到公共 ID)
创建映射表条目是第一次遇到用户或组项目时创建的(以创建映射)
用于操作实体的每个用户和组 API 的 PK 查找(以查找映射)
只有第一个才有可能对性能产生任何明显的负面影响。如果事实证明是这种情况,那么上述在 ID 生成中使用哈希的部分中列出的优化可以在后续补丁中实现。
其他部署者影响¶
对部署者的两个主要影响是
公共 ID 生成器算法的选择 此前在本文档中讨论过。
定期清除映射表中的陈旧条目 如前所述,对于在 Keystone 外部管理的后端实体存储,通常没有可靠的通知机制可供 Keystone 用于自动清除映射表中的陈旧条目。为了启用手动清除,keystone-manage 将支持一个新的选项
mapping_purge,该选项将允许操作员指定以下选项keystone-manage mapping_purge --all– 这将清除所有映射keystone-manage mapping_purge --domain-name <name>– 这将清除指定域的所有映射keystone-manage mapping_purge --domain-name <name> --local-id <ID> --type <user|group>– 这将清除指定本地标识符的映射keystone-manage mapping_purge --public-id <ID>– 这将清除指定公共 ID 的映射
开发人员影响¶
此提议没有更改身份驱动程序接口,但控制器-管理器接口有两个更改
将删除(现在未使用的)可选参数
domain_scope(这是早期 Havana 实现中的“域推断”)。在为这些实体创建调用的过程中,移除显式的
user_id和group_id参数,因为管理器层现在将生成 ID。管理器会将此 ID 传递给驱动层。
实现¶
负责人¶
- 主要负责人
henry-nash
工作项¶
所需项目集如下:
移除身份控制器-管理器接口中的“域扣除”参数。
确保在控制器中,对于列出用户和组实体,域是显式或隐式定义的。
移除身份管理器为这些实体创建调用的
user_id和group_id参数。实现后端映射层。
通过配置选项,由控制器提供两个 ID 生成器,UUID 和哈希。
修改身份管理器层,调用身份映射层,以确保只有公共 ID 暴露给控制器。
修改
keystone-manage,提供清除映射的选项。修改现有的 LDAP 后端单元测试,以覆盖向后兼容和不向后兼容 ID 的情况,并为多后端场景提供更好的覆盖。
为身份映射层提供特定的单元测试。
修改现有的调用创建用户和组 API 的单元测试,以支持管理器生成 ID。
一个与上述规范匹配的完整实现已经可用在
依赖项¶
无
测试¶
由于现有测试足以捕获公共 ID 中的潜在异常,因此不建议进行额外的 tempest 测试。
文档影响¶
由于 API 没有更改,因此唯一需要的文档更改是针对配置指南。