OAuth 2.0 互信 TLS 客户端身份验证流程

概述

基于 RFC8705 的 OAuth 2.0 互信 TLS 客户端身份验证作为 Keystone 的扩展实现。用户可以使用 use_id 作为 client_id 来获取 OAuth 2.0 证书绑定访问令牌,并使用 TLS 证书。使用相同的 TLS 证书,证书绑定访问令牌可用于访问使用支持 OAuth 2.0 互信 TLS 客户端身份验证的 Keystone 中间件的 OpenStack API 的受保护资源。有关生成 OAuth 2.0 访问令牌的更多信息,请参阅 身份 API 参考

指南

按照本指南中的步骤启用 Keystone 身份服务器以支持 OAuth 2.0 互信 TLS 客户端身份验证。在本示例中,keystone.host 是 Keystone 身份服务器使用的域名。

创建私有/公有证书颁发机构 (CA)

为了使用互信 TLS,有必要创建一个私有/公有证书颁发机构 (CA) 作为根证书,用于签署客户端和 Keystone 证书。

  1. 生成 RSA 私钥。

$ openssl genrsa -out root_a.key 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.++++
.........................++++
e is 65537 (0x010001)
  1. 生成自签名证书。

$ openssl req -new -x509 -key root_a.key -out root_a.pem -days 365
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]: JP
State or Province Name (full name) [Some-State]: Tokyo
Locality Name (eg, city) []: Chiyoda-ku
Organization Name (eg, company) [Internet Widgits Pty Ltd]: IssuingORG
Organizational Unit Name (eg, section) []: CertDept
Common Name (e.g. server FQDN or YOUR name) []: root_a.openstack.host
Email Address []: root_a@issuing.org
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
  1. 如果您需要支持多个根证书,可以参考步骤 1 和步骤 2。由于服务器配置的需要,您需要将这些根证书合并到一个文件中。本指南使用了多个根证书,因此创建了另一个根证书,证书命名为 root_b,证书的 CN 为“root_b.openstack.host”。

$ cat root_a.pem >> multi_ca.pem
$ cat root_b.pem >> multi_ca.pem
$ cat multi_ca.pem
-----BEGIN CERTIFICATE-----
MIIF1TCCA72gAwIBAgIUN7d0MTiikDjDMLxUQ8SJcV97Nz8wDQYJKoZIhvcNAQEL
BQAwejELMAkGA1UEBhMCSlAxEDAOBgNVBAgMB2ppYW5nc3UxDzANBgNVBAcMBnN1
...
K/k00vZmrZXONglaf/OeMalhiRaOTsK2CzEvg6Xgu1zOjtNshm6qnSEXDYxzJue2
FPLDGEMKSCLb
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF1TCCA72gAwIBAgIUOiAEZWTheMS5wFA661G6bushkg4wDQYJKoZIhvcNAQEL
BQAwejELMAkGA1UEBhMCY24xEDAOBgNVBAgMB2ppYW5nc3UxDzANBgNVBAcMBnN1
...
UzvplIZcNZKzgOLLrSkk42/yqxdTZnc3BeBiVsA5T6aapNbY8D6ZpPU2cYYSxrfK
VpOanJoJy22J
-----END CERTIFICATE-----

启用 Keystone 以支持互信 TLS

以下部分描述了仅为 os-oauth2-api 启用互信 TLS 的步骤。

  1. 生成 RSA 私钥。

$ openssl genrsa -out keystone_priv.key 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.........................................+++++
.........................+++++
e is 65537 (0x010001)
  1. 创建一个证书签名请求。

$ openssl req -new -key keystone_priv.key -out keystone_csr.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]: JP
State or Province Name (full name) [Some-State]: Tokyo
Locality Name (eg, city) []: Chiyoda-ku
Organization Name (eg, company) [Internet Widgits Pty Ltd]: OpenstackORG
Organizational Unit Name (eg, section) []: DevDept
Common Name (e.g. server FQDN or YOUR name) []:keystone.host
Email Address []: dev@keystone.host
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
  1. 使用根证书生成自签名证书。

$ openssl x509 -req -in keystone_csr.csr \
-CA root_a.pem -CAkey root_a.key -CAcreateserial \
-out keystone_ca.pem -days 365 -sha384
Signature ok
subject=C = JP, ST = Tokyo, L = Chiyoda-ku, O = OpenstackORG, OU = DevDept, CN = keystone.host, emailAddress = dev@keystone.host
Getting CA Private Key
  1. 修改 Apache 配置文件并添加选项以实现 Keystone 服务的互信 TLS 支持。

注意

根据服务器环境,在为 Keystone 服务器设置 HTTPS 协议时,可能需要运行此命令以启用 Apache2 服务中的 SSL 模块。

$ sudo a2enmod ssl
$ sudo vi /etc/apache2/sites-enabled/keystone-wsgi-public.conf
ProxyPass "/identity" "unix:/var/run/uwsgi/keystone-wsgi-public.socket|uwsgi://uwsgi-uds-keystone-wsgi-public" retry=0
<IfModule mod_ssl.c>
<VirtualHost _default_:443>
  ServerAdmin webmaster@localhost
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  SSLEngine on
  SSLCertificateFile      /etc/ssl/certs/keystone_ca.pem
  SSLCertificateKeyFile   /etc/ssl/private/keystone_priv.key
  SSLCACertificateFile    /etc/ssl/certs/multi_ca.pem
  <Location /identity/v3/OS-OAUTH2/token>
    SSLVerifyClient require
    SSLOptions +ExportCertData
    SSLOptions +StdEnvVars
    SSLRequireSSL
  </Location>
</VirtualHost>
</IfModule>
  1. 重新启动 Apache 服务,以使修改后的配置信息生效。

$ systemctl restart apache2.service
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to restart 'apache2.service'.
Authenticating as: Ubuntu (ubuntu)
Password:
==== AUTHENTICATION COMPLETE ===

创建用于验证 TLS 证书的映射规则

由于不同的根证书对客户端提供的 TLS 证书的身份验证方式不同,因此需要在系统中设置相关的映射规则。

  1. 创建一个映射规则文件。以下使用的映射支持两个根证书。当客户端证书颁发者的 CN 名称为“root_a.openstack.host”时,客户端证书必须包含 Mapping 中指定的 5 个字段,并且这些字段必须与 Keystone 中的用户信息匹配。当客户端证书颁发者的 CN 名称为“root_b.openstack.host”时,只需要包含 2 个字段即可保持用户信息与 Keystone 一致。在使用主体 Distinguished Names 时,必须使用 SSL_CLIENT_SUBJECT_DN_* 格式。在使用颁发者 Distinguished Names 时,必须使用 SSL_CLIENT_ISSUER_DN_* 格式。* 部分是 Distinguished Names 属性的键,转换为大写。有关 Distinguished Names 的属性类型,请参阅相关的 RFC 文档,例如:RFC1779RFC2985RFC4519 等。

注意

属性键的简写形式可以在 RFC4514 中找到。对于未在 RFC4514 中列出的键 Email Address,可以使用 SSL_CLIENT_ISSUER_DN_EMAILADDRESSSSL_CLIENT_SUBJECT_DN_EMAILADDRESS

$ vi oauth2_mapping.json
[
  {
    "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"
       ]
      }
    ]
  }
]
  1. 使用该文件在 Keystone 中创建映射规则。

$ openstack mapping create --rules oauth2_mapping.json oauth2_mapping
  1. 如果它已经存在,请使用该文件更新 Keystone 中的映射规则。

openstack mapping set --rules oauth2_mapping.json oauth2_mapping

启用 Keystone 以支持 OAuth 2.0 互信 TLS 客户端身份验证

修改相关配置以启用 os-oauth2-api 使用 TLS 证书进行用户身份验证。

  1. 修改 keystone.conf 以进行 OAuth 2.0 互信 TLS 客户端身份验证。

$ vi /etc/keystone/keystone.conf
[oauth2]
oauth2_authn_method = certificate
oauth2_cert_dn_mapping_id=oauth2_mapping
  1. 重新启动 Keystone 服务,以使修改后的配置信息生效。

$ sudo systemctl restart devstack@keystone.service

尝试访问 Keystone API

最后,尝试访问 Keystone API 以确认服务器正常工作。

1. 创建 OAuth 2.0 互信 TLS 客户端身份验证用户。由于某些 OpenStack API 需要项目信息,因此建议在创建用户时指定项目。

$ openstack user create --domain default --email test@demo.com --project demo --project-domain default client01
+---------------------+----------------------------------+
| Field               | Value                            |
+---------------------+----------------------------------+
| default_project_id  | c5c07949e53a41da816f3c052b37dfe8 |
| domain_id           | default                          |
| email               | test@demo.com                    |
| enabled             | True                             |
| id                  | 88319190aca54383a38b96eb0e75266e |
| name                | client01                         |
| description         | None                             |
| password_expires_at | None                             |
+---------------------+----------------------------------+
  1. 现有用户可以通过该命令设置项目信息。

$ openstack user show client02
+---------------------+----------------------------------+
| Field               | Value                            |
+---------------------+----------------------------------+
| default_project_id  | None                             |
| domain_id           | default                          |
| email               | test@demo.com                    |
| enabled             | True                             |
| id                  | dc8682953ad9443dbda5291d6f675def |
| name                | client02                         |
| description         | None                             |
| password_expires_at | None                             |
+---------------------+----------------------------------+
$ openstack user set dc8682953ad9443dbda5291d6f675def --project demo --project-domain default
$ openstack user show client02
+---------------------+----------------------------------+
| Field               | Value                            |
+---------------------+----------------------------------+
| default_project_id  | c5c07949e53a41da816f3c052b37dfe8 |
| domain_id           | default                          |
| email               | test@demo.com                    |
| enabled             | True                             |
| id                  | dc8682953ad9443dbda5291d6f675def |
| name                | client02                         |
| description         | None                             |
| password_expires_at | None                             |
+---------------------+----------------------------------+
  1. 为用户分配角色。

$ openstack role add --project demo --user client01 admin
$ openstack role assignment list --project demo --user client01
+----------------------------------+----------------------------------+-------+----------------------------------+--------+--------+-----------+
| Role                             | User                             | Group | Project                          | Domain | System | Inherited |
+----------------------------------+----------------------------------+-------+----------------------------------+--------+--------+-----------+
| 1684856368de4c31a7b6e8fefd6654ff | 88319190aca54383a38b96eb0e75266e |       | c5c07949e53a41da816f3c052b37dfe8 |        |        | False     |
+----------------------------------+----------------------------------+-------+----------------------------------+--------+--------+-----------+
$ openstack role add --project demo --user client02 admin
$ openstack role assignment list --project demo --user client02
+----------------------------------+----------------------------------+-------+----------------------------------+--------+--------+-----------+
| Role                             | User                             | Group | Project                          | Domain | System | Inherited |
+----------------------------------+----------------------------------+-------+----------------------------------+--------+--------+-----------+
| 1684856368de4c31a7b6e8fefd6654ff | dc8682953ad9443dbda5291d6f675def |       | c5c07949e53a41da816f3c052b37dfe8 |        |        | False     |
+----------------------------------+----------------------------------+-------+----------------------------------+--------+--------+-----------+
  1. 为用户生成 RSA 私钥。

$ openssl genrsa -out client01_priv.key 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.........................................+++++
.........................+++++
e is 65537 (0x010001)
$ openssl genrsa -out client02_priv.key 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.........................................+++++
.........................+++++
e is 65537 (0x010001)
  1. 基于根证书的映射规则和用户信息创建证书签名请求。由于客户端证书随后将使用 root_a 签名,因此在创建请求时指定五个字段。如果使用 root_b 颁发客户端证书,则在创建请求时只需要两个字段。

$ openssl req -new -key client01_priv.key -out client01.csr \
-subj "/UID=88319190aca54383a38b96eb0e75266e/O=Default/DC=default/emailAddress=test@demo.com/CN=client01"
$ openssl req -new -key client02_priv.key -out client02.csr \
-subj "/UID=dc8682953ad9443dbda5291d6f675def/DC=default/CN=client02"
  1. 使用根证书为用户生成自签名证书。

$ openssl x509 -req -in client01.csr \
-CA root_a.pem -CAkey root_a.key -CAcreateserial -out \
client01.pem -days 180 -sha256
Signature ok
subject=UID = 88319190aca54383a38b96eb0e75266e, O = Default, DC = default, emailAddress = test@demo.com, CN = client01
Getting CA Private Key
$ openssl x509 -req -in client02.csr \
-CA root_b.pem -CAkey root_b.key -CAcreateserial -out \
client02.pem -days 180 -sha256
Signature ok
subject=UID = dc8682953ad9443dbda5291d6f675def, DC = default, CN = client02
Getting CA Private Key
  1. 通过 HTTP 协议访问 Keystone 令牌 API,以确认可以正常获取 X-Auth-Token。

$ curl -si -X POST http://keystone.local/identity/v3/auth/tokens?nocatalog \
-d '{"auth":{"identity":{"methods":["password"],"password": {"user":{"domain":{"name":"Default"},"name":"username","password":"test_pwd"}}},"scope":{"project":{"domain":{"name":"Default"},"name":"admin"}}}}' \
-H 'Content-type:application/json'
HTTP/1.1 201 CREATED
Date: Tue, 24 Dec 2024 16:21:22 GMT
Server: Apache/2.4.52 (Ubuntu)
Content-Type: application/json
Content-Length: 711
X-Subject-Token: gAAAAABnat-...
Vary: X-Auth-Token
x-openstack-request-id: req-37d6a755-a633-4ab1-aa1b-980553804546
Connection: close

{"token": {"methods": ["password"], "user": {"domain": {"id": "default", "name": "Default"}, "id": "3414be74f5df43549088db1d63d33a61", "name": "admin", "password_expires_at": null}, "audit_ids": ["wc7QBA2CSMilYrgxzsDLOw"], "expires_at": "2024-12-24T17:21:22.000000Z", "issued_at": "2024-12-24T16:21:22.000000Z", "project": {"domain": {"id": "default", "name": "Default"}, "id": "6a7d3fa72f7a42b39938e9b3c845d206", "name": "admin"}, "is_domain": false, "roles": [{"id": "241e9736dbd0449eb22b7f23289ca6f8", "name": "manager"}, {"id": "73eb16a56be74f6783f0f26a8cd0df36", "name": "member"}, {"id": "1684856368de4c31a7b6e8fefd6654ff", "name": "admin"}, {"id": "b36ac7b62c204727930f41df609a236a", "name": "reader"}]}}
  1. 通过 OAuth 2.0 互信 TLS 客户端身份验证获取 OAuth 2.0 证书绑定访问令牌。

$ curl -si -X POST https://keystone.local/identity/v3/OS-OAUTH2/token \
-H "application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=88319190aca54383a38b96eb0e75266e" \
--cacert root_a.pem \
--key client01_priv.key --cert client01.pem
HTTP/1.1 200 OK
Date: Tue, 24 Dec 2024 16:19:14 GMT
Server: Apache/2.4.52 (Ubuntu)
Content-Type: application/json
Content-Length: 307
Vary: X-Auth-Token
x-openstack-request-id: req-8ec8dff2-8c34-4799-aa4b-a855566111dc
Connection: close

{"access_token":"gAAAAABnat8...","expires_in":3600,"token_type":"Bearer"}

$ curl -si -X POST https://keystone.local/identity/v3/OS-OAUTH2/token \
-H "application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=dc8682953ad9443dbda5291d6f675def" \
--cacert root_a.pem \
--key client02_priv.key --cert client02.pem
HTTP/1.1 200 OK
Date: Tue, 24 Dec 2024 16:27:24 GMT
Server: Apache/2.4.52 (Ubuntu)
Content-Type: application/json
Content-Length: 307
Vary: X-Auth-Token
x-openstack-request-id: req-9bf1ad0f-32b7-4e86-8f30-01292bbb49a5
Connection: close

{"access_token":"gAAAAABnauD...","expires_in":3600,"token_type":"Bearer"}
  1. 确认 OAuth 2.0 证书绑定访问令牌包含项目、角色、指纹等信息。

$ curl -si -X GET http://keystone.local/identity/v3/auth/tokens?nocatalog -H "X-Auth-Token:$x_auth_token" -H "X-Subject-Token:$access_token"
HTTP/1.1 200 OK
Date: Tue, 24 Dec 2024 16:45:43 GMT
Server: Apache/2.4.52 (Ubuntu)
Content-Type: application/json
Content-Length: 805
X-Subject-Token: gAAAAABnauU...
Vary: X-Auth-Token
x-openstack-request-id: req-fca9bf15-80cc-42c6-9153-769b77ec1b00
Connection: close

{"token": {"methods": ["oauth2_credential"], "user": {"domain": {"id": "default", "name": "Default"}, "id": "88319190aca54383a38b96eb0e75266e", "name": "client01", "password_expires_at": null}, "audit_ids": ["yeIlaD7ETe6tJPN7QoJ2Bg"], "expires_at": "2024-12-24T17:45:39.000000Z", "issued_at": "2024-12-24T16:45:39.000000Z", "project": {"domain": {"id": "default", "name": "Default"}, "id": "c5c07949e53a41da816f3c052b37dfe8", "name": "demo"}, "is_domain": false, "roles": [{"id": "241e9736dbd0449eb22b7f23289ca6f8", "name": "manager"}, {"id": "73eb16a56be74f6783f0f26a8cd0df36", "name": "member"}, {"id": "1684856368de4c31a7b6e8fefd6654ff", "name": "admin"}, {"id": "b36ac7b62c204727930f41df609a236a", "name": "reader"}], "oauth2_credential": {"x5t#S256": "9gRzUFm9Qu5nwFsKr9nCwPZhNTXP4dlvG73GBj5UmwY="}}}