支持 OAuth 2.0 Mutual-TLS¶
提供基于 RFC8705 OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens [1] (以下简称 OAuth2.0 mutual TLS) 的 OAuth2.0 访问令牌的持有证明选项。需要 Identity API 的 v3.0+ 版本,并假设 mutual TLS 使用 TLS 1.2。此方法将保护 Keystone Identity 和其他 OpenStack 服务免受伪造的 OAuth 客户端的影响。
问题描述¶
Keystone 中实现的 OAuth2.0 客户端凭据授权使用 id/密码来请求访问令牌。但是,密码可以通过各种技术(例如字典攻击)被破解。此外,授权服务器(即 Keystone)无法检测到伪造的密码,更重要的是,它无法检测到伪造的访问令牌。因此,提供 NFV 编排 API 的 OpenStack Tacker 需要 OAuth2.0 mutual TLS 支持,以满足一个众所周知的 NFV 标准,即 ETSI NFV` [2]。如该标准所述,当双方都必须使用 TLS 证书进行身份验证时,此类攻击更加困难,即 mutual TLS [3]。
提议的变更¶
拟议的更改是支持 OAuth2.0 mutual TLS。此方法可确保只有拥有与 X.509 TLS 客户端证书对应的私钥的方才能使用令牌访问受保护的资源。
为了实现这一点,必须实现以下项目。
将基于客户端证书的身份验证使用 mutual TLS 添加到 Keystone
添加创建与证书绑定的访问令牌的选项到 Keystone identity,并能够颁发与访问令牌绑定的证书
将客户端证书验证添加到 Keystone 中间件
将访问令牌的持有证明确认添加到 Keystone 中间件
将使用 mutual TLS 所需的配置参数(例如,客户端证书、CA 证书)添加到 keystoneauth
注意
为了使用 mutual TLS,客户端必须从其公钥/私钥对生成客户端证书。客户端证书必须是由一个私有/公共证书颁发机构 (CA) 签名的 X509 证书,其证书必须在客户端和 Keystone 上可用。这些文件应由外部生成。
注意
为了使用 OAuth2.0 mutual TLS,应在 Keystone 上配置 mutual TLS。虽然在其他服务端点上也启用 mutual TLS 可以提高系统的安全性,但此类配置的详细信息不在本文档的范围之内。
术语¶
客户端: 发出受保护资源请求的应用程序。客户端可以是由 Identity API 创建的用户,其属性必须与客户端证书的主题 DN 相同。
访问令牌: 客户端用于使用委托的角色发出受保护资源请求的令牌。
客户端证书: 客户端用于证明拥有访问令牌的证书
OAuth2.0 mutual TLS 流程¶
流程包括以下步骤,如上图所示
客户端向 Keystone 请求新的访问令牌
客户端使用其客户端证书作为凭据向 Keystone 身份验证并请求新的访问令牌。客户端必须预先注册为 Keystone 用户 [4],并且其属性(例如
username、project和domain)必须与客户端证书的主题相同。Keystone 的 Web 服务器验证客户端证书的有效性
在从请求中获取客户端证书后,Keystone 的 Web 服务器(即 Apache)验证证书的信任链以及对应私钥的持有情况,然后仅在证书有效时(例如,它是由已知的受信任的 CA 签名的,并且客户端提供了私钥的持有证明)才继续到下一步,否则拒绝请求。
Keystone 向客户端颁发与证书绑定的访问令牌
Keystone 创建一个访问令牌,确认客户端证书的主题 DN 是否与用户属性匹配。客户端证书的主题 DN 与用户属性之间的映射规则由 OS-FEDERATION API 描述 [5]。如果令牌创建成功,Keystone 将证书绑定到令牌,并将令牌作为 OAuth2.0 访问令牌颁发给客户端。
客户端通过 mutual TLS 使用颁发的访问令牌发出 API 请求
客户端通过 mutual TLS 发出带有已颁发访问令牌的 API 请求。mutual TLS 由服务器触发,因此客户端的要求只是能够将其证书设置为 API 请求。
OpenStack 服务的 Web 服务器验证客户端证书的有效性
在从请求中获取客户端证书后,OpenStack 服务的 Web 服务器验证证书的信任链以及对应私钥的持有情况,然后仅在证书有效时才继续到下一步,否则拒绝请求。
Keystone 中间件验证访问令牌的有效性
Keystone 通过检查元数据中的证书指纹与 mutual TLS 中的证书指纹匹配以及访问令牌是否未过期来验证请求中包含的访问令牌的有效性。证书指纹和令牌状态可以从 Keystone 身份验证和令牌管理 API 获取 [6]。
以下现有 API 应该被修改以处理使用 OAuth2.0 mutual TLS 的请求。
访问令牌 API
创建访问令牌
此外,为了将客户端证书指纹从 Keystone 提供给 Keystone 中间件,以下现有 API 的响应需要被修改。
Keystone 身份验证和令牌管理 API
验证并显示令牌信息
API 资源¶
访问令牌 API¶
访问令牌 API 已经实现。因此,本文档仅描述处理 OAuth2.0 mutual TLS 请求所需的更改。
创建访问令牌¶
POST /identity/v3/OS-OAUTH2/token
请求
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
client_id=jFtpUlndpRGaAHuh9TsP3wtj
现有补丁中的客户端身份验证使用 client_id/client_secret 作为凭据,而 OAuth2.0 mutual TLS 假定使用客户端证书作为凭据。此 API 将更改为使用客户端证书对客户端进行身份验证,以处理两种情况。当通过 mutual TLS 发送请求时,只有当客户端证书有效且证书的主题 DN 与用户属性匹配时,客户端才能成功进行身份验证。请注意,客户端证书的有效性可以通过 mutual TLS 的常规过程进行检查。还请注意,主题 DN 与用户属性之间的映射规则可以由 OS-FEDERATION API 中的映射进行配置。
以下是映射规则的示例。此示例包含两个 CA(即 root_a.openstack.host 和 root_b.openstack.host)的映射规则。当客户端证书的颁发者的 CN 名称为 root_a.openstack.host 时,客户端证书必须包含在 remote 块的规则中定义的五个字段(即 SSL_CLIENT_SUBJECT_DN_CN、SSL_CLIENT_SUBJECT_DN_UID、SSL_CLIENT_SUBJECT_DN_EMAILADDRESS、SSL_CLIENT_SUBJECT_DN_O 和 SSL_CLIENT_SUBJECT_DN_DC),并且这些字段必须与规则中 local 块中指定的现有 Keystone 用户的属性(即 name、id、email、domain name 和 domain id)匹配。如果 remote 字段与相应的 local 字段不匹配,Keystone 不会颁发令牌。同样,当 CN 名称为 root_b.openstack.host 时,Keystone 需要客户端证书,该证书在 remote 块中定义了两个字段,并且需要这些字段的值与 local 块中定义的现有 Keystone 用户属性的值匹配。
[
{
"local": [
{
"user": {
"name": "{0}",
"id": "{1}",
"email": "{2}",
"domain": {
"name": "{3}",
"id": "{4}"
}
}
}
],
"remote": [
{
"type": "SSL_CLIENT_SUBJECT_DN_CN"
},
{
"type": "SSL_CLIENT_SUBJECT_DN_UID"
},
{
"type": "SSL_CLIENT_SUBJECT_DN_EMAILADDRESS"
},
{
"type": "SSL_CLIENT_SUBJECT_DN_O"
},
{
"type": "SSL_CLIENT_SUBJECT_DN_DC"
},
{
"type": "SSL_CLIENT_ISSUER_DN_CN",
"any_one_of": [
"root_a.openstack.host"
]
}
]
},
{
"local": [
{
"user": {
"id": "{0}",
"domain": {
"id": "{1}"
}
}
}
],
"remote": [
{
"type": "SSL_CLIENT_SUBJECT_DN_UID"
},
{
"type": "SSL_CLIENT_SUBJECT_DN_DC"
},
{
"type": "SSL_CLIENT_ISSUER_DN_CN",
"any_one_of": [
"root_b.openstack.host"
]
}
]
}
]
如果身份验证成功,Keystone 将客户端证书绑定到访问令牌。假设使用 fernet 令牌作为访问令牌,可以通过将客户端证书的指纹添加到 fernet 令牌的有效负载来完成此操作。
Keystone 身份验证和令牌管理 API¶
Keystone 身份验证和令牌管理 API 已经实现。因此,本文档仅描述处理 OAuth2.0 mutual TLS 所需的更改。
验证并显示令牌信息¶
GET /v3/auth/tokens
响应
HTTP/1.1 200 OK
Content-Type: application/json
{
"token": {
"audit_ids": [
"mAjXQhiYRyKwkB4qygdLVg"
],
"catalog": [
{
"type": "identity",
"name": "keystone",
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://10.10.1.100/identity",
"publicURL": "http://10.10.1.100/identity"
}
]
}
],
"expires_at": "2015-11-07T02:58:43.578887Z",
"is_domain": false,
"issued_at": "2015-11-07T01:58:43.578929Z",
"methods": [
"password"
],
"project": {
"domain": { "id": "default", "name": "Default" },
"id": "f2796050af304441b5f1eabecb33e808",
"name": "service"
},
"roles": [
{
"description": null,
"domain_id": null,
"id": "d229bd3566fe4abe96a5d02c211e2f10",
"name": "admin",
"options": { "immutable": true }
},
{
"description": null,
"domain_id": null,
"id": "c9b1b27aeff440959db75bdc91dd8a84",
"name": "member",
"options": { "immutable": true }
}
],
"user": {
"domain": { "id": "default", "name": "Default" },
"id": "da0e3ae640584af98c015343b0552ec0",
"name": "client",
"password_expires_at": null
}
"OS-OAUTH2": {
"x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"
}
}
}
与当前 API 响应的区别是在 x5t#S256 字段中添加 OS-OAUTH2 字段,该字段包含与访问令牌对应的客户端证书主题 DN。仅当令牌由 OAuth2.0 访问令牌 API 颁发并且响应状态为 200 时,才会添加此字段。其他字段和错误响应与当前的 API 实现相同。
Keystone 中间件为了检查客户端证书的指纹与 mutual TLS 中证书的指纹匹配以及访问令牌是否未过期,会将包含在 API 请求中的访问令牌发送到此修改后的 Keystone Authentication and token management API。
如果令牌通过验证,Keystone 中间件将使用元数据更新请求标头。如果令牌无效或返回错误响应,则它会拒绝请求并返回 401 Unauthorized。
根据 RFC6749,RFC6750 [7] 中定义的“bearer”令牌类型用于将访问令牌字符串包含在 API 请求中。Keystone 中间件必须从带有 Authorization 标头的请求中获取访问令牌。以下是此类请求的示例。
GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer f69c9fb6947c47329b8955d629ac5722
备选方案¶
令牌有效性验证¶
我们可以通过添加新的令牌内省 API [8] 来避免修改现有的 Keystone 身份验证和令牌管理 API,如下所示。
POST /identity/v3/auth/OS-OAUTH2/introspect
请求
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
client_id=jFtpUlndpRGaAHuh9TsP3wtj&token=f69c9fb6947c47329b8955d629ac5722&token_type_hint=access_token
响应
HTTP/1.1 200 OK
Content-Type: application/json
{
"active": true,
"cnf":{
"x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"
}
"audit_ids": [
"mAjXQhiYRyKwkB4qygdLVg"
],
"catalog": [
{
"type": "identity",
"name": "keystone",
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://10.10.1.100/identity",
"publicURL": "http://10.10.1.100/identity"
}
]
}
],
"expires_at": "2015-11-07T02:58:43.578887Z",
"is_domain": false,
"issued_at": "2015-11-07T01:58:43.578929Z",
"methods": [
"password"
],
"project": {
"domain": { "id": "default", "name": "Default" },
"id": "f2796050af304441b5f1eabecb33e808",
"name": "service"
},
"roles": [
{
"description": null,
"domain_id": null,
"id": "d229bd3566fe4abe96a5d02c211e2f10",
"name": "admin",
"options": { "immutable": true }
},
{
"description": null,
"domain_id": null,
"id": "c9b1b27aeff440959db75bdc91dd8a84",
"name": "member",
"options": { "immutable": true }
}
],
"user": {
"domain": { "id": "default", "name": "Default" },
"id": "da0e3ae640584af98c015343b0552ec0",
"name": "client",
"password_expires_at": null
}
}
响应(已过期的令牌)
HTTP/1.1 200 OK
Content-Type: application/json
{
"active": false,
}
错误响应
HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Keystone uri="http://keysone.identity.host/identity/v3/users/{user_id}/application_credentials"
Cache-Control: no-store
Pragma: no-cache
{
"error": "invalid_client",
"error_description": "The client_id is not found or client_certificate is invalid."
}
Keystone 中间件使用包含在 API 请求中的访问令牌请求令牌内省。如果凭据有效,Keystone 将返回与令牌对应的元数据,例如客户端证书指纹、服务目录、用户 ID、令牌有效性等。否则,它将返回错误响应。
接收到此 API 请求的 Keystone 必须通过以下两个步骤获取令牌元数据
通过 Keystone API /v3/auth/tokens 验证并获取令牌信息
检索客户端证书主题 DN 的指纹
如果令牌通过验证且未过期,Keystone 将将所需的字段合并到 RFC7662: OAuth 2.0 Token Introspection 中的 JSON 对象中(即 active),并将这些字段视为 extension_field。响应中的字段被认为是 extension_field。客户端证书主题 DN 设置为 x5t#S256 字段。在令牌已过期且身份验证失败的情况下,Keystone 将返回带有指示 active: false 的 200 响应和 401 错误响应,分别。
Keystone 中间件仅当令牌有效时才使用元数据更新请求标头。如果令牌无效或返回错误响应,则它会拒绝请求并返回 401 Unauthorized。令牌的有效性由响应中 active 字段的值确定,即如果值为 true,则令牌有效;如果值为 false,则令牌无效。
另一种选择是使用包含客户端证书指纹作为字段的 JWT(参见 RFC8705: 3.1 JWT Certificate Thumbprint Confirmation Method [9])。在这种情况下,我们可以省略令牌内省 API。
创建与证书绑定的访问令牌¶
作为 fernet 令牌的替代方案,如果无法自由修改 fernet 令牌内容,我们可以使用 Keystone 已经支持的 JWT 令牌。在这种情况下,客户端证书的指纹或 credential Id(将用于从凭据表中检索证书)将作为 JWT 的一个字段包含在内(参见 RFC8705: 3.1 JWT Certificate Thumbprint Confirmation Method)。
安全影响¶
使用 mutual TLS 使 OAuth2.0 更安全,因此对 Keystone 安全性没有负面影响。
通知影响¶
无
其他最终用户影响¶
最终用户必须配置其客户端以使用本文档中描述的功能。因此,需要添加适当的用户文档。
性能影响¶
为了存储指纹,fernet 令牌的大小将略微增加。这可能会增加延迟、计算成本等。但是,通常,指纹是由 SHA256 等哈希函数创建的,其大小可以忽略不计。
其他部署者影响¶
Keystone 中间件配置¶
要使用 OAuth2.0 访问令牌,部署者必须通过更改例如 /etc/tacker/api-paste.ini 中的 [filter:authtoken] 在 OpenStack 服务中配置 Keystone 中间件。如果 paste.filter_factory 是 keystonemiddleware.oauth2_mtls_token:filter_factory,则 Keystone 中间件期望 API 请求使用 mutual TLS,并且请求中的访问令牌与客户端证书绑定。
[filter:authtoken]
paste.filter_factory = keystonemiddleware.oauth2_mtls_token:filter_factory
开发人员影响¶
无
实现¶
负责人¶
- 主要负责人
Hiromu Asahina (h-asahina) <hiromu.asahina.az@hco.ntt.co.jp>
- 其他贡献者
新見雄介 <niimi.yusuke@fujitsu.com>
山川圭一朗 <yamakawa.keiich@fujitsu.com>
工作项¶
将基于客户端证书的身份验证使用 mutual TLS 添加到 Keystone
添加创建与证书绑定的访问令牌的选项到 Keystone identity,并能够颁发与访问令牌绑定的证书
将客户端证书验证添加到 Keystone 中间件
将访问令牌的持有证明确认添加到 Keystone 中间件
将使用 mutual TLS 所需的配置参数(例如,客户端证书、CA 证书)添加到 keystoneauth
依赖项¶
无
文档影响¶
我们需要更新用户 API 文档和身份验证机制。
我们需要更新用户 API 文档和中间件架构。