OAuth 2.0 Mutual-TLS Client Authentication Flow¶
Overview¶
OAuth 2.0 Mutual-TLS Client Authentication based on RFC8705 is implemented as an extension of Keystone. Users can use use_id as client_id to obtain the OAuth 2.0 Certificate-Bound access token with TLS certificates. With the same TLS certificates, the Certificate-Bound access token can then be used to access the protected resources of the OpenStack API, which uses Keystone middleware supporting the OAuth 2.0 Mutual-TLS Client Authentication. See the Identity API reference for more information on generating OAuth 2.0 access token.
Guide¶
Enable Keystone identity server to support OAuth 2.0 Mutual-TLS Client
Authentication by the following steps in this guide. In this example,
keystone.host
is the domain name used by the Keystone identity server.
Enable Keystone to support mutual TLS¶
The following parts describe steps to enable mutual TLS only for os-oauth2-api.
Generate an RSA private key.
$ openssl genrsa -out keystone_priv.key 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.........................................+++++
.........................+++++
e is 65537 (0x010001)
Create a certificate signing request.
$ 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 []:
Use the root certificate to generate a self-signed certificate.
$ 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
Modify the apache configuration file and add options to implement mutual TLS support for the Keystone service.
Note
Based on the server environment, this command may have to be run to enable SSL module in apache2 service when setting up HTTPS protocol for keystone server.
$ 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>
Restart apache service so that the modified configuration information takes effect.
$ 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 ===
Create mapping rules for validating TLS certificates¶
Because different root certificates have different ways of authenticating TLS certificates provided by client, the relevant mapping rules need to be set in the system.
Create a mapping rule file. The mapping used below supports both root certificates. When the CN name of the issuer of the client certificate is “root_a.openstack.host”, the client certificate must contain the 5 fields specified by Mapping, and these fields must match the user information in Keystone. When the CN name of the issuer of the client certificate is “root_b.openstack.host”, only 2 fields need to be included to keep the user information consistent with the keystone. When using Subject Distinguished Names, the
SSL_CLIENT_SUBJECT_DN_*
format must be used. When using Issuer Distinguished Names, theSSL_CLIENT_ISSUER_DN_*
format must be used. The*
part is the key of the attribute for Distinguished Names converted to uppercase. For more information about the attribute types for Distinguished Names, see the relevant RFC documentation such as: RFC1779, RFC2985, RFC4519, etc.
Note
The short forms of attribute keys can be found in RFC4514. For the key
Email Address
which is not listed in RFC4514, you can use
SSL_CLIENT_ISSUER_DN_EMAILADDRESS
and
SSL_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"
]
}
]
}
]
Use the file to create the mapping rule in keystone.
$ openstack mapping create --rules oauth2_mapping.json oauth2_mapping
If it already exists, use the file to update the mapping rule in keystone.
openstack mapping set --rules oauth2_mapping.json oauth2_mapping
Enable keystone to support OAuth 2.0 Mutual-TLS Client Authentication¶
Modify the relevant configuration to enable the os-oauth2-api to use TLS certificates for user authentication.
Modify
keystone.conf
to OAuth 2.0 Mutual-TLS Client Authentication.
$ vi /etc/keystone/keystone.conf
[oauth2]
oauth2_authn_method = certificate
oauth2_cert_dn_mapping_id=oauth2_mapping
Restart Keystone service so that the modified configuration information takes effect.
$ sudo systemctl restart devstack@keystone.service
Try to access the Keystone APIs¶
At last, try to access the Keystone APIs to confirm that the server is working properly.
1. Create an OAuth 2.0 Mutual-TLS Client Authentication user. Because some OpenStack APIs require project information, it is recommended to specify the project when creating a user.
$ 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 |
+---------------------+----------------------------------+
Existing users can set the project information through the command.
$ 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 |
+---------------------+----------------------------------+
Assign roles to the user.
$ 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 |
+----------------------------------+----------------------------------+-------+----------------------------------+--------+--------+-----------+
Generate an RSA private key for the user.
$ 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)
Create a certificate signing request based on the mapping rule of the root certificate and on the user information. Because the client certificate is subsequently signed with root_a, five fields are specified when the request is created. If root_b is used to issue the client certificate, only two fields are required when creating the request.
$ 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"
Use the root certificate to generate a self-signed certificate for the user.
$ 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
Through the HTTP protocol, access the Keystone token API to confirm that the X-Auth-Token can be obtained normally.
$ 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"}]}}
Obtain OAuth 2.0 Certificate-Bound access tokens through OAuth 2.0 Mutual-TLS Client Authentication.
$ 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"}
Confirm that the OAuth 2.0 Certificate-Bound access tokens contain information such as project, roles, thumbprint, etc.
$ 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="}}}