添加 JSON Web Tokens 作为非持久性 Token 提供程序¶
JSON Web Token 是一种非持久性 bearer token,类似于我们今天使用的 fernet token。JWT 是一种 开放标准,拥有 积极维护的库。
问题描述¶
我们目前支持一种名为 fernet 的 token 格式。fernet token 格式是一种基于 Heroku 规范的非持久性格式,并被设为 keystone 的默认 token 格式。
然而,fernet 格式本身存在一些问题,使其不够理想。 fernet 规范 基本上已被废弃,这使得很难 将其纳入更改,从而影响到 cryptography 的实现。 此外,fernet 规范未被任何标准机构认可,因此不如 IETF 标准那样经过严格审核,更容易受到零日漏洞的影响。解决这些漏洞的责任完全在于 OpenStack,特别是 keystone 社区。
提供一种由广泛使用的标准支持的新型 token 会很好。 这也增加了 OpenStack 生态系统与其他支持 JWT 的社区之间的互操作性机会。
用例¶
作为操作员,我希望使用一种非持久性 token 提供程序,该提供程序不与对称加密或签名实现绑定。 基于非对称签名或加密的实现允许我将公钥从一个节点分发到另一个节点,而不是同步对称密钥库。 这使得部署仅用于 token 验证的只读 keystone 节点更加容易。 此特定用例允许我在保持区域内 token 验证的同时,部署只读区域,而 token 则由位于单独区域的中央身份管理系统颁发。
作为操作员,我希望在 fernet 规范 或 keystone 使用的 cryptography 实现中存在安全漏洞时,能够回退到 token 提供程序。
作为用户,我希望能够为可以使用其他软件(在 OpenStack 生态系统之外)证明我的身份的 token 进行身份验证。
提议的变更¶
创建一个基于 JSON Web Token 标准 的新的非持久性 keystone token 后端。 它们的行为将与我们当前的 fernet token 类似。
该 token 将是一个签名的 JWT (JWS),其中包含身份验证负载。 请注意,签名的 token 是 web 安全的并且经过完整性验证,但 token 负载对其持有者不透明。 可以使用 JWS token 解码 token 并检查负载。 使用嵌套的 JWE token 是 JSON Web Token 等效于 Fernet token,它们经过加密和签名。
此实现保留随时出于任何原因更改、修改或删除负载中项目的权利。 解码和依赖负载中的属性不受支持的 API,也不应将其视为支持的 API。 用户应使用正式的 API 从 keystone 请求信息。 开发团队不会阻止或延迟对 token 负载的更改,这些更改是 token 提供程序的内部实现细节,因为用户以不当方式依赖这些属性。 同样,将 token 负载中的信息视为敏感信息的部署应依赖 Fernet 以防止将该信息暴露给用户。
与 Fernet 类似,JWT 需要设置一个密钥库来用于签名 token。 将添加一个新的 keystone-manage 命令来处理密钥生成和轮换,该命令可能会重用 fernet_setup 和 fernet_rotate 命令中的许多实用程序。 建议使用的算法是 ES256。 Keystone 不应向最终用户公开请求特定 JWS 算法的能力。 支持应仅限于受支持或受信任的算法,最终用户无法指定。 JWS token 将使用私钥进行完整性验证,并使用相应的公钥进行验证。 由于 ES256 实现仅使用签名(而不是签名加密负载),因此它比 fernet 采用略好的安全实践,因为私钥不必在 keystone API 节点之间同步。 只需要将公钥传输到其他 keystone API 服务器即可在集群中验证 token。
JWS 的负载将使用以下 注册声明
sub 声明将是一个 必需的 字符串,其中包含为 token 身份验证的用户 ID
exp 声明将是一个 必需的 数字值,用于 token 过期时间
iat 声明将是一个 必需的 数字值,用于 token 签发时间
将使用以下私有声明来传递其他必需的信息,并将以 openstack 为前缀,以避免与未来的上游声明冲突
openstack_methods 将是一个 必需的 声明,表示用于获取 token 的身份验证方法
openstack_audit_ids 将是一个 必需的 声明,其中包含与 token 关联的审计信息
openstack_system 将是一个 可选的 声明,仅存在于系统范围的 token 中
openstack_domain_id 将是一个 可选的 声明,仅存在于域范围的 token 中
openstack_project_id 将是一个 可选的 声明,仅存在于项目范围的 token 中
openstack_trust_id 将是一个 可选的 声明,仅存在于信任范围的 token 中
openstack_app_cred_id 将是一个 可选的 声明,仅存在于应用程序凭证 token 中
openstack_group_ids 将是一个 可选的 声明,仅存在于联合 token 中,用于携带临时用户的组分配
openstack_idp_id 将是一个 可选的 声明,仅存在于联合 token 中,用于携带用户的身份提供商 ID
openstack_protocol_id 将是一个 可选的 声明,仅存在于联合 token 中,用于表示联合用户用于身份验证的协议
openstack_access_token 将是一个 可选的 声明,仅存在于 OAuth token 中
PyJWT 库 已经存在于需求存储库中,将是此实现的一个便捷选择。 PyJWT 库 和 JWCrypto 库 都实现了对 JWS 的支持。 由于本规范中详细的实现仅限于 ES256,因此不需要支持 JWE 的库。 如果将来支持另一种加密 token 类型(例如 fernet)是必需的,那么找到或为消耗库贡献 JWE 支持将是必要的。
用户将以与他们当前使用 Fernet token 相同的方式请求和呈现 token。 无需添加或更改任何 API。
密钥设置与轮换¶
与 Fernet 实现类似,JWT 提供程序将需要设置密钥轮换策略。 由于 ES256 依赖于非对称签名,因此建议的轮换策略将与 Fernet 已知的策略略有不同。
Fernet 实现需要使用分阶段密钥(即具有特殊名称的密钥),以确保在轮换过程中可以验证 token。 这对于 JWT 来说是不需要的,以下步骤足以执行密钥轮换而不会因缺少签名密钥而导致 token 无效。 假设以下步骤正在三个不同的 API 服务器上执行,分别命名为 A、B 和 C,这些服务器需要相互验证 token。
为每个 API 服务器创建一个密钥对。 A.priv、A.pub 用于 A,B.priv、B.pub 用于 B,以及 C.priv、C.pub 用于 C。
将每个公钥的副本传输到每个 API 服务器。 A、B 和 C 都拥有 A.pub、B.pub 和 C.pub 的副本。
此时,从任何 API 服务器发出的 token 可以在任何地方进行验证。 如果单个 API 服务器需要轮换密钥对
为 A 创建一个新的密钥对,称为 A’.priv 和 A’.pub。 在所有其他集群节点拥有 A’.pub 的副本之前,A 不会配置为开始使用 A’.priv 签名 token。
A’.pub 复制到每个 API 服务器的公钥存储库。 只要 B 和 C 拥有 A’.pub,它们就可以准备好使用新的私钥 A’.priv 签名的 token。
在 B 和 C 使用 A’.pub 的副本更新后,可以更新服务器 A 以开始使用新的密钥 A’.priv 签名 token。 一旦所有使用 A.priv 签名的 token 都已过期,就可以从所有服务器中删除 A.priv 和 A.pub。 在配置 A 使用 A’.priv 和删除 A.priv 之间允许一定的宽限期非常重要,以防止过早使未过期的 token 失效。
请注意,在步骤 #1 中,轮换过程可以稍微简化。 JWS 规范详细介绍了序列化以及在单个 token 中支持多个签名。 有关更多信息,请参阅 JWS 第 7 节。 PyJWT 库 不支持单个 token 上的多个签名。 如果它支持,那么在步骤 #1 中配置服务器 A 使用多个私钥进行签名是可能的。 服务器 B 和 C 仍然能够验证来自 A 的 token,因为它们拥有用于创建 token 的一个签名的公钥 (A.pub) 的副本。 轮换过程的其余部分与传播 A’.pub 到其他服务器以及最终配置 A 仅使用 A’.priv 签名 token 相同。 如果或当 PyJWT 支持此功能时,可以重新考虑 JWS 提供程序中对多个签名的支持。
可以使用撤销列表撤销传统的非对称密钥。 目前,我们不会为 JWT 密钥对支持撤销列表实现。 操作员有能力相应地同步公钥,并在密钥进出时轮换密钥。 Keystone 将仅使用磁盘上的公钥来验证 token。 这可能会在将来发生变化,但现在它使 keystone 中的密钥轮换和密钥实用程序更简单。
Crypto-Agility 与未来工作¶
本规范针对单个算法进行初始 JWT 实现。 如果 keystone 决定扩展实现以包含其他算法,我们应该允许配置算法之间的灵活性,这将使操作员更容易在需要时从一种算法切换到另一种算法。
例如,使用 JWT token 提供程序的验证过程可能支持验证多个受信任的算法,从而允许使用不同算法签名的多个 token 在无需进行配置更改(在签名节点上)的情况下进行验证。
目前,如果部署正在使用 JWT 并需要行使 crypto-agility,建议他们转换为 Fernet token。
备选方案¶
最近,已经开展了各种努力来帮助解决经过身份验证的加密。 其中一项努力是由对 JWT 的担忧引发的,即 ,即 JOSE 标头。 报告中详细说明的问题是用户能够指定算法并利用各种 JWT 库中的验证漏洞。 所有 python 库都已修补,但 keystone 应特别依赖于验证算法使用情况,并且绝不假定算法是由最终用户提供的。 请参阅完整的 报告,了解有关漏洞的详细信息以及为什么我们将严格验证此信息。
有一个名为 Platform Agnostic Security Tokens 或 PASETO 的概念验证实现,它对算法验证和 token 的预期受众采取更严格的立场。 版本协议 的严格立场以及 PASETO 具有一定的优势,但该实现和想法仍处于萌芽阶段。 值得注意的是,我们应该密切关注此发展,并在其获得更多采用时或何时重新评估它。
现在,如果 keystone 为 JWT 实现提供严格的算法验证,我们应该能够提供一个与 fernet 相当的备份选项。
安全影响¶
由于 JWT 是一种广泛使用的 Web 标准,这将对安全性产生积极影响。该实现将使用非对称签名,降低了在多个主机之间复制或传输私钥的风险。由于令牌载荷已签名,因此拥有令牌的任何人都可以读取令牌内的信息。只有使用用于最初签名令牌的私钥的相应公钥才能验证令牌。这些仍然是 Bearer 令牌,因此仍必须防止拦截。
已知漏洞¶
有记录在案的 漏洞 影响了几个 JWT 库,包括一个用 Python 编写的库。
在大多数情况下,JSON Web Tokens 将具有头部、载荷和签名,每个部分都由一个句点 (.) 分隔。头部包含重要信息,即如何保护令牌的完整性。这存储为头部的 alg 属性。验证令牌的库使用头部中指定的算法执行完整性检查,并将结果与令牌的签名部分进行比较。
已经记录并提出了安全问题,描述了允许客户端指定用于令牌验证的算法的问题。这对于支持非对称和对称签名的应用程序来说是一个特别关注点。攻击者可以使用已发布的或已知的公钥生成带有对称签名算法的 JWT,从而有效地绕过令牌的验证检查。
如果 keystone 支持签名令牌和加密令牌,并且使用相同的令牌提供程序实现,则适用。该漏洞在发现后已在各种库中得到解决,但 keystone 应该意识到导致它的总体技术。我们可以通过以下方式缓解 keystone 中的此类漏洞:
确保 keystone 不会盲目允许最终用户指定用于验证令牌完整性的算法(例如,仅实现对
ES256的支持)确保令牌头部的
alg供应品仅由 keystone 填充确保 keystone 只颁发单一加密或签名策略的令牌(例如,不允许用户从同一服务器获取签名令牌和加密令牌,从而在运行时混合使用非对称和对称密钥)
有关 漏洞 的具体信息可以在报告中找到。
通知影响¶
JWT 的通知行为将与 fernet 令牌的行为相同,包括撤销事件。
其他最终用户影响¶
这不会对最终用户产生任何影响。他们将以完全相同的方式请求和使用 JWT,就像他们当前使用 fernet 令牌一样。
性能影响¶
值得研究使用非对称签名(JWT)和对称加密(fernet)的令牌提供程序之间的性能差异。如果这些差异很大,则应在文档中发布,因为这对于运营商在选择令牌提供程序时可能很有用。
其他部署者影响¶
这是一个可选的、选择加入的功能,默认情况下不会启用,因此部署者除非选择使用 JWT,否则不会受到影响。在这种情况下,部署者需要在开始使用 JWT 之前设置密钥存储库。密钥存储库将包含非对称密钥对,而不是仅仅是密钥。部署者需要像处理 fernet 令牌一样注意同步和轮换密钥。
开发人员影响¶
新的令牌类型将重用已经为 fernet 令牌完成的大量工作,并将遵循相似的代码路径,因此维护起来相对容易。
实现¶
负责人¶
- 主要负责人
Gage Hugo (gagehugo) Lance Bragstad (lbragstad) XiYuan Wang (wxy)
工作项¶
重构 fernet 实用程序模块,使其足够通用,可以与 JWT 或可继承的模块一起使用
添加一个
keystone-manage命令来设置和轮换 JWT 签名密钥将
TokenFormatter类泛化为支持 JWT重构 fernet 令牌提供程序模块,使其可继承或通用
添加一个 keystone doctor 命令来验证设置,就像验证 fernet 一样
依赖项¶
有三个不同的库我们可以用来实现此功能。
-
这个库仅支持令牌签名,或 JWS。它尚未支持 JWE,或经过身份验证的加密。需要最低版本 1.0.1,但此库已经包含在 OpenStack 全局需求存储库中。
-
这个库仅支持令牌签名,或 JWS。它尚未支持 JWE,或经过身份验证的加密。此库未包含在 OpenStack 全局需求中。
-
这个库支持 JWS 和 JWE,但未包含在 OpenStack 全局需求中。
-
这个库支持 JWS 和 JWE,但其许可与 OpenStack 不兼容,因为它使用 AGPL。
鉴于 JWT 的初始实现将不依赖于嵌套 JWT 令牌或加密载荷,可以安全地假设签名支持就足够了。PyJWT 库已经包含在全局需求中,我们没有理由不使用该库,它与 OpenStack 许可兼容。
文档影响¶
新的 [token]/provider 配置选项需要记录,以及新的 keystone-manage 命令。