From dc7b212c87aaa155a69b5ee8538adc71e776f7cb Mon Sep 17 00:00:00 2001 From: Marcial Rosales Date: Fri, 7 Feb 2025 17:02:50 +0100 Subject: [PATCH 1/4] Explain additional_scopes_key configuration --- docs/oauth2-examples-keycloak.md | 8 +++ docs/oauth2.md | 113 +++++++++++++++++++++++++++---- 2 files changed, 108 insertions(+), 13 deletions(-) diff --git a/docs/oauth2-examples-keycloak.md b/docs/oauth2-examples-keycloak.md index 596acb3c38..fe18355947 100644 --- a/docs/oauth2-examples-keycloak.md +++ b/docs/oauth2-examples-keycloak.md @@ -69,6 +69,14 @@ make start-rabbitmq RabbitMQ is deployed with TLS enabled and Keycloak is configured with the corresponding `redirect_url` which uses https. ::: +:::important +RabbitMQ is configured to read the scopes from the custom claim [extra_scope](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/blob/next/conf/keycloak/rabbitmq.conf#L11) and +by default from the standard claim `scope`. +However, if your scopes are deep in a map/list structure like `authorization.permissions.scopes` +or under `realm_access.roles` or `resource_access.account.roles`, you can configure +RabbitMQ to use those locations instead. See the section [Use a different token field for the scope](./oauth2#use-different-token-field) for more information. +::: + ## Access Management api To access the management api run the following command. It uses the client [mgt_api_client](https://keycloak:8443/admin/master/console/#/test/clients/c5be3c24-0c88-4672-a77a-79002fcc9a9d/settings) which has the scope [rabbitmq.tag:administrator](https://keycloak:8443/admin/master/console/#/test/client-scopes/f6e6dd62-22bf-4421-910e-e6070908764c/settings). diff --git a/docs/oauth2.md b/docs/oauth2.md index e2a91c653f..9c3885e6a1 100644 --- a/docs/oauth2.md +++ b/docs/oauth2.md @@ -144,7 +144,7 @@ In chronological order, here is the sequence of events that occur when a client |--------------------------------------------|----------- | `auth_oauth2.resource_server_id` | The [Resource Server ID](#resource-server-id) | `auth_oauth2.resource_server_type` | The Resource Server Type required when using [Rich Authorization Request](#rich-authorization-request) token format -| `auth_oauth2.additional_scopes_key` | Configure the plugin to look for scopes in other fields (maps to `additional_rabbitmq_scopes` in the old format). | +| `auth_oauth2.additional_scopes_key` | [Configure](#use-different-token-field) the plugin to look for scopes in other fields. | | `auth_oauth2.scope_prefix` | [Configure the prefix for all scopes](#scope-prefix). The default value is `auth_oauth2.resource_server_id` followed by the dot `.` character. | | `auth_oauth2.preferred_username_claims` | [List of the JWT claims](#preferred-username-claims) to look for the username associated with the token. | `auth_oauth2.default_key` | ID of the default signing key. @@ -252,9 +252,6 @@ The following configuration declares two signing keys and configures the kid of ```ini auth_oauth2.resource_server_id = new_resource_server_id -auth_oauth2.additional_scopes_key = my_custom_scope_key -auth_oauth2.preferred_username_claims.1 = username -auth_oauth2.preferred_username_claims.2 = user_name auth_oauth2.default_key = id1 auth_oauth2.signing_keys.id1 = test/config_schema_SUITE_data/certs/key.pem auth_oauth2.signing_keys.id2 = test/config_schema_SUITE_data/certs/cert.pem @@ -571,26 +568,116 @@ If a symmetric key is used, the configuration looks like this: ### Use a different token field for the scope {#use-different-token-field} -By default the plugin looks for the `scope` key in the token, you can configure the plugin to also look in other fields using the `extra_scopes_source` variable. Values format accepted are scope as **string** or **list** +The plugin always extracts the scopes from the `scope` claim. However, +you can also configure the plugin to look in other claims using the `auth_oauth2.additional_scopes_key` variable. -```ini -auth_oauth2.resource_server_id = my_rabbit_server -auth_oauth2.additional_scopes_key = my_custom_scope_key -``` +The scopes found in the `scope` claim must be of these two value types: +- **string separated by spaces** like `my_id.configure:*/* my_id.read:*/* my_id.write:*/*` +- **list** like `["my_id.configure:*/*", "my_id.read:*/*", "my_id.write:*/*"]` + +The scopes found in any claim listed in the `auth_oauth2.additional_scopes_key` variable can be +of several types in addition to the two value types supported by the `scope` claim mentioned earlier. + +#### Map of scopes indexed by resource_server_id {#map-of-scopes-indexed-by-resource-id} + +This is an example of a token where scopes are not yet prefixed with the `resource_server_id` +but are indexed by the `resource_server_id`: -Token sample: ```ini { "exp": 1618592626, "iat": 1618578226, "aud" : ["my_id"], ... - "scope_as_string": "my_id.configure:*/* my_id.read:*/* my_id.write:*/*", - "scope_as_list": ["my_id.configure:*/*", "my_id.read:*/*", "my_id.write:*/*"], - ... + "complex_claim_as_string": { + "rabbitmq": ["configure:*/* read:*/* write:*/*"] + }, + "complex_claim_as_list": { + "rabbitmq": ["configure:vhost1/*", "read:vhost1/*", "write:vhost1/*"] } + ... +} +``` + +With the following plugin configuration, the plugin reads the scopes from two +additional claims: `complex_claim_as_string` and `complex_claim_as_list`. +The plugin reads the scopes and adds the key value as prefix, for instance, +given the scope `configure:*/*` it produces `rabbitmq.configure:*/*`. + +```ini +auth_oauth2.resource_server_id = my_rabbit_server +auth_oauth2.additional_scopes_key = complex_claim_as_string complex_claim_as_list +``` + +#### Scopes nested deep in Maps and Lists + +This is the case for tokens issued by **Keycloak** Identity Provider but can be +applied to any token from any provider. + +This first token format stores scopes deep in maps and lists. +```json +{ + "authorization": { + "permissions": [ + { + "scopes": [ + "rabbitmq-resource.read:*/*" + ], + "rsid": "2c390fe4-02ad-41c7-98a2-cebb8c60ccf1", + "rsname": "allvhost" + }, + { + "scopes": [ + "rabbitmq-resource.write:vhost1/*" + ], + "rsid": "e7f12e94-4c34-43d8-b2b1-c516af644cee", + "rsname": "vhost1" + }, + { + "scopes": [ + "rabbitmq-resource.tag:administrator" + ], + "rsid": "12ac3d1c-28c2-4521-8e33-0952eff10bd9" + } + ] + }, + "scope": "email profile rabbitmq-resource.tag:monitoring", +} ``` +Given the following configuration: +```ini +auth_oauth2.resource_server_id = my_rabbit_server +auth_oauth2.additional_scopes_key = authorization.permissions.scopes +``` + +The plugin navigates the token structure following this logic: +1. It looks up the claim `authorization`. +2. It finds a map, it then looks for the next claim `permissions`. +3. This time, it finds a list of maps. It goes over all the items in the list. +4. For each map in the list, it looks up the next claim `scopes`. +5. The value can be a list of scopes or a comma-separated string of scopes or +a [map of scopes indexed by resource_server_id](#map-of-scopes-indexed-by-resource-id). + +Additionally, the plugin always reads the scopes from the official `scope` claim. + +With the above token and plugin's configuration, the list of scopes are following: +- `rabbitmq-resource.tag:monitoring` +- `rabbitmq-resource.read:*/*` +- `rabbitmq-resource.write:vhost1/*` +- `rabbitmq-resource.tag:administrator` + +In summary, the plugin is able to navigate the token to find the scopes +using the appropriate path. Each intermediary stage, for instance, after +finding `authorization` and/or `permissions` keys, the value can be another Map or +a List of Maps. The last stage, after finding the last `scopes` key, the value +can be any of any of the value types explained in the previous section. + +These are: +- **string separated by spaces** like `my_id.configure:*/* my_id.read:*/* my_id.write:*/*` +- **list** like `["my_id.configure:*/*", "my_id.read:*/*", "my_id.write:*/*"]` +- [Map of scopes indexed by resource server id](#map-of-scopes-indexed-by-resource-id) + ### Preferred username claims {#preferred-username-claims} The username associated with the token must be available to RabbitMQ so that this username is displayed in the RabbitMQ Management UI. From 072464d2307fc809f6fa02c922a2e1b138c8e373 Mon Sep 17 00:00:00 2001 From: Marcial Rosales Date: Mon, 10 Feb 2025 15:30:39 +0100 Subject: [PATCH 2/4] Explain keycloak jwt token formats --- docs/oauth2-examples-keycloak.md | 60 ++++++++++++++++++++++++++++++++ docs/oauth2.md | 49 ++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/docs/oauth2-examples-keycloak.md b/docs/oauth2-examples-keycloak.md index fe18355947..f35115be30 100644 --- a/docs/oauth2-examples-keycloak.md +++ b/docs/oauth2-examples-keycloak.md @@ -28,6 +28,66 @@ and Keycloak as Authorization Server using the following flows: * Access management HTTP API * Application authentication and authorization +## Keycloak JWT payloads + +Keycloak may issue two types of JWT payloads. + +One type of payload is found in a [Requesting Party Token](./oauth2#requesting-party-token). +RabbitMQ supports this type of token and it extracts the scopes from it. +You do not need to configure anything. + +A second type of payload is the following. The claim `roles` is not strictily +speaking part of Keycloak official claims. Instead, it is a custom claim configured +by the user via the Keycloak administration console. + +```json +{ + "realm_access": { + "roles": [ + "offline_access", + "uma_authorization", + "rabbitmq.tag:management", + ] + }, + "resource_access": { + "account": { + "roles": [ + "manage-account", + "manage-account-links", + "view-profile", + "rabbitmq.write:*/*" + ] + } + }, + "roles": "rabbitmq.read:*/*", + "scope": "profile email" +} +``` + +RabbitMQ does not read the scopes from this token unless you configure it to do so. +For instance, to configure RabbitMQ to extract the scopes from "roles" under "realm_access", +you add the following configuration variable: + +```json +auth_oauth2.additional_scopes_key = realm_access.roles +``` + +To configure RabbitMQ to also read from "resource_access", you modify the previous +configuration as follows: + +```json +auth_oauth2.additional_scopes_key = realm_access.roles resource_access.account.roles +``` + +And finally, if you also want to use the scopes in the claim `roles`, you modify +the previous configuration: + +```json +auth_oauth2.additional_scopes_key = roles realm_access.roles resource_access.account.roles +``` + +RabbitMQ reads the scopes from all those sources. + ## Prerequisites to follow this guide * Docker diff --git a/docs/oauth2.md b/docs/oauth2.md index 9c3885e6a1..f4456a075b 100644 --- a/docs/oauth2.md +++ b/docs/oauth2.md @@ -47,6 +47,7 @@ There's also a companion [troubleshooting guide for OAuth 2-specific problems](. * [Preferred username claims](#preferred-username-claims) * [Discovery Endpoint params](#discovery-endpoint-params) * [Rich Authorization Request](#rich-authorization-request) +* [Requesting Party Token](#requesting-party-token) ### [Advanced usage](#advanced-usage) @@ -719,6 +720,54 @@ This is the URL built to access the OpenId Discovery endpoint: https://myissuer.com/v2/.well-known/authorization-server?param1=value1¶m2=value2 ``` +### Requesting Party Token {#requesting-party-token} + +A **Requesting Party Token (RPT)** is a special OAuth 2.0 **access token** +issued by an **Authorization Server** in the [User-Managed Access (UMA) 2.0](https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html) framework. +It is used by a **Requesting Party** (such as an application or user) to access +a protected resource on a Resource Server like RabbitMQ, after being authorized +based on a resource owner policies. + +[Keycloak](./oauth2-examples-keycloak) is one of the Authorization Servers that issues this type of tokens. +An RPT is typically a JWT with permissions claims under a claim called `authorization`. +See the example below. The rest of the claims have been removed from the token for +brevity: + +```json +{ + "authorization": { + "permissions": [ + { + "scopes": [ + "rabbitmq-resource.read:*/*" + ], + "rsid": "2c390fe4-02ad-41c7-98a2-cebb8c60ccf1", + "rsname": "allvhost" + }, + { + "scopes": [ + "rabbitmq-resource:vhost1/*" + ], + "rsid": "e7f12e94-4c34-43d8-b2b1-c516af644cee", + "rsname": "vhost1" + }, + { + "rsid": "12ac3d1c-28c2-4521-8e33-0952eff10bd9", + "scopes": [ + "rabbitmq-resource.tag:administrator" + ] + } + ] + }, + "scope": "email profile", +} +``` + +RabbitMQ supports this token format. It reads all the scopes in all the `permissions` +claims. If the token also contains the standard `scope` claim, RabbitMQ adds it to the +list of scopes presented by the token. + + ### Rich Authorization Request {#rich-authorization-request} The [Rich Authorization Request](https://oauth.net/2/rich-authorization-requests/) extension provides a way for From 8debcad16923f48043cf3e96e99ee089a211a345 Mon Sep 17 00:00:00 2001 From: Marcial Rosales Date: Mon, 10 Feb 2025 15:37:04 +0100 Subject: [PATCH 3/4] Minor doc changes --- docs/oauth2-examples-keycloak.md | 14 +++++++++----- docs/oauth2.md | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/oauth2-examples-keycloak.md b/docs/oauth2-examples-keycloak.md index f35115be30..21f7f2c2c2 100644 --- a/docs/oauth2-examples-keycloak.md +++ b/docs/oauth2-examples-keycloak.md @@ -36,9 +36,7 @@ One type of payload is found in a [Requesting Party Token](./oauth2#requesting-p RabbitMQ supports this type of token and it extracts the scopes from it. You do not need to configure anything. -A second type of payload is the following. The claim `roles` is not strictily -speaking part of Keycloak official claims. Instead, it is a custom claim configured -by the user via the Keycloak administration console. +A second type of payload is the following. ```json { @@ -64,15 +62,21 @@ by the user via the Keycloak administration console. } ``` +:::info +The claim `roles` is not strictily +speaking part of Keycloak official claims. Instead, it is a custom claim configured +by the user via the Keycloak administration console. +::: + RabbitMQ does not read the scopes from this token unless you configure it to do so. -For instance, to configure RabbitMQ to extract the scopes from "roles" under "realm_access", +For instance, to configure RabbitMQ to extract the scopes from `roles` under `realm_access` claim, you add the following configuration variable: ```json auth_oauth2.additional_scopes_key = realm_access.roles ``` -To configure RabbitMQ to also read from "resource_access", you modify the previous +To configure RabbitMQ to also read from `resource_access` claim, you modify the previous configuration as follows: ```json diff --git a/docs/oauth2.md b/docs/oauth2.md index f4456a075b..e522119c5b 100644 --- a/docs/oauth2.md +++ b/docs/oauth2.md @@ -46,8 +46,8 @@ There's also a companion [troubleshooting guide for OAuth 2-specific problems](. * [Use a different token field for the scope](#use-different-token-field) * [Preferred username claims](#preferred-username-claims) * [Discovery Endpoint params](#discovery-endpoint-params) -* [Rich Authorization Request](#rich-authorization-request) * [Requesting Party Token](#requesting-party-token) +* [Rich Authorization Request](#rich-authorization-request) ### [Advanced usage](#advanced-usage) From 5383aefccc23481c71f2bbb67987d806e8f30fa1 Mon Sep 17 00:00:00 2001 From: RichardJJG Date: Thu, 20 Feb 2025 14:36:29 +0000 Subject: [PATCH 4/4] [TNZDOC-664] Language and formatting edits --- docs/oauth2-examples-keycloak.md | 153 ++++++++++++++++++------------- docs/oauth2.md | 87 +++++++++--------- 2 files changed, 132 insertions(+), 108 deletions(-) diff --git a/docs/oauth2-examples-keycloak.md b/docs/oauth2-examples-keycloak.md index 21f7f2c2c2..da70aa19e8 100644 --- a/docs/oauth2-examples-keycloak.md +++ b/docs/oauth2-examples-keycloak.md @@ -19,10 +19,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -# Use Keycloak as OAuth 2.0 server +## Use Keycloak as OAuth 2.0 server -This guide explains how to set up OAuth 2.0 for RabbitMQ -and Keycloak as Authorization Server using the following flows: +This guide explains how to set up OAuth 2.0 for RabbitMQ and Keycloak as Authorization Server using +the following flows: * Access [management UI](./management/) via a browser * Access management HTTP API @@ -30,15 +30,15 @@ and Keycloak as Authorization Server using the following flows: ## Keycloak JWT payloads -Keycloak may issue two types of JWT payloads. +Keycloak can issue two types of JWT payloads. -One type of payload is found in a [Requesting Party Token](./oauth2#requesting-party-token). -RabbitMQ supports this type of token and it extracts the scopes from it. -You do not need to configure anything. +One type of payload is found in a [Requesting Party Token](./oauth2#requesting-party-token). +RabbitMQ supports this type of token and it extracts the scopes from it. You do not need to +configure anything. -A second type of payload is the following. +The second type of payload is the following: -```json +```json { "realm_access": { "roles": [ @@ -63,87 +63,98 @@ A second type of payload is the following. ``` :::info -The claim `roles` is not strictily -speaking part of Keycloak official claims. Instead, it is a custom claim configured -by the user via the Keycloak administration console. +The claim `roles` is not, strictly speaking, part of Keycloak official claims. Instead, it is a +custom claim configured by the user from the Keycloak administration console. ::: -RabbitMQ does not read the scopes from this token unless you configure it to do so. -For instance, to configure RabbitMQ to extract the scopes from `roles` under `realm_access` claim, -you add the following configuration variable: +RabbitMQ does not read the scopes from this token unless you configure it to do so. For example, to +configure RabbitMQ to extract the scopes from `roles` under the `realm_access` claim, add the +following configuration variable: ```json auth_oauth2.additional_scopes_key = realm_access.roles ``` -To configure RabbitMQ to also read from `resource_access` claim, you modify the previous -configuration as follows: +To configure RabbitMQ to also read from `resource_access` claim, edit the previous configuration as +follows: ```json -auth_oauth2.additional_scopes_key = realm_access.roles resource_access.account.roles +auth_oauth2.additional_scopes_key = realm_access.roles resource_access.account.roles ``` -And finally, if you also want to use the scopes in the claim `roles`, you modify -the previous configuration: +And finally, if you also want to use the scopes in the claim `roles`, you edit the previous +configuration: ```json -auth_oauth2.additional_scopes_key = roles realm_access.roles resource_access.account.roles +auth_oauth2.additional_scopes_key = roles realm_access.roles resource_access.account.roles ``` -RabbitMQ reads the scopes from all those sources. +RabbitMQ reads the scopes from all those sources. ## Prerequisites to follow this guide * Docker * make -* A local clone of a [GitHub repository](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/tree/next) for branch `next` that contains all the configuration files and scripts used on this example +* A local clone of a + [GitHub repository](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/tree/next) for branch + `next` that contains all the configuration files and scripts used on this example * Add the following entry to `/etc/hosts`: -``` -localhost keycloak rabbitmq -``` + + ```console + localhost keycloak rabbitmq + ``` ## Deploy Keycloak -1. First, deploy **Keycloak**. It comes preconfigured with all the required scopes, users and clients. +1. First, deploy Keycloak. It comes preconfigured with all the required scopes, users, and clients. -2. Run the following command to start **Keycloak** server: +2. Start the Keycloak server by running: - ```bash - make start-keycloak - ``` + ```bash + make start-keycloak + ``` -There is a dedicated **Keycloak realm** called `Test` configured as follows: +There is a dedicated Keycloak realm called `Test` configured as follows: -* A [rsa](https://keycloak:8443/admin/master/console/#/test/realm-settings/keys) signing key. Use `admin`:`admin` - when prompted for credentials to access the Keycloak Administration page +* A [rsa](https://keycloak:8443/admin/master/console/#/test/realm-settings/keys) signing key. Use + `admin`:`admin` when prompted for credentials to access the Keycloak Administration page * A [rsa provider](https://keycloak:8443/admin/master/console/#/test/realm-settings/keys/providers) -* Three clients: `rabbitmq-client-code` for the rabbitmq management UI, `mgt_api_client` to access via the -management api and `producer` to access via AMQP protocol. - +* Three clients: `rabbitmq-client-code` for the RabbitMQ management UI, `mgt_api_client` to access + via the management API and `producer` to access via the AMQP protocol. ## Start RabbitMQ -Run the command below to start RabbitMQ configured with the **Keycloak** server we started in the previous section: This is the [rabbitmq.conf](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/blob/next/conf/keycloak/rabbitmq.conf) used for **Keycloak**. +Run the command below to start RabbitMQ configured with the `Keycloak` server we started in the +previous section: This is the +[rabbitmq.conf](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/blob/next/conf/keycloak/rabbitmq.conf) +used for Keycloak. + ```bash export MODE=keycloak make start-rabbitmq ``` :::info -RabbitMQ is deployed with TLS enabled and Keycloak is configured with the corresponding `redirect_url` which uses https. +RabbitMQ is deployed with TLS enabled and Keycloak is configured with the corresponding `redirect_url` +which uses HTTPS. ::: -:::important -RabbitMQ is configured to read the scopes from the custom claim [extra_scope](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/blob/next/conf/keycloak/rabbitmq.conf#L11) and -by default from the standard claim `scope`. -However, if your scopes are deep in a map/list structure like `authorization.permissions.scopes` -or under `realm_access.roles` or `resource_access.account.roles`, you can configure -RabbitMQ to use those locations instead. See the section [Use a different token field for the scope](./oauth2#use-different-token-field) for more information. +:::important +RabbitMQ is configured to read the scopes from the custom claim +[extra_scope](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/blob/next/conf/keycloak/rabbitmq.conf#L11) +and by default from the standard claim `scope`. +However, if your scopes are deep in a map/list structure such as `authorization.permissions.scopes`, +or under `realm_access.roles` or `resource_access.account.roles`, you can configure RabbitMQ to use +those locations instead. For more information, see the section +[Use a different token field for the scope](./oauth2#use-different-token-field). ::: -## Access Management api +## Access Management API -To access the management api run the following command. It uses the client [mgt_api_client](https://keycloak:8443/admin/master/console/#/test/clients/c5be3c24-0c88-4672-a77a-79002fcc9a9d/settings) which has the scope [rabbitmq.tag:administrator](https://keycloak:8443/admin/master/console/#/test/client-scopes/f6e6dd62-22bf-4421-910e-e6070908764c/settings). +To access the management api run the following command. It uses the client +[mgt_api_client](https://keycloak:8443/admin/master/console/#/test/clients/c5be3c24-0c88-4672-a77a-79002fcc9a9d/settings) +that has the scope +[rabbitmq.tag:administrator](https://keycloak:8443/admin/master/console/#/test/client-scopes/f6e6dd62-22bf-4421-910e-e6070908764c/settings). ```bash make curl-keycloak url=https://localhost:15671/api/overview client_id=mgt_api_client secret=LWOuYqJ8gjKg3D2U8CJZDuID3KiRZVDa realm=test @@ -151,23 +162,31 @@ make curl-keycloak url=https://localhost:15671/api/overview client_id=mgt_api_cl ## Application authentication and authorization with PerfTest -To test OAuth 2.0 authentication with AMQP protocol you are going to use RabbitMQ PerfTest tool which uses RabbitMQ Java Client. +To test OAuth 2.0 authentication with the AMQP protocol you use the RabbitMQ PerfTest tool, which +uses RabbitMQ Java Client. -First you obtain the token and pass it as a parameter to the make target `start-perftest-producer-with-token`. +First you obtain the token and pass it as a parameter to the make target +`start-perftest-producer-with-token`. ```bash make start-perftest-producer-with-token PRODUCER=producer TOKEN=$(bin/keycloak/token producer kbOFBXI9tANgKUq8vXHLhT6YhbivgXxn test) ``` -**NOTE**: Initializing an application with a token has one drawback: the application cannot use the connection beyond the lifespan of the token. See the next section where you demonstrate how to refresh the token. +:::info +Initializing an application with a token has one drawback: the application cannot use the connection +beyond the lifespan of the token. See the next section where you demonstrate how to refresh the token. +::: ## Application authentication and authorization with Pika -In the following information, OAuth 2.0 authentication is tested with the AMQP protocol and the Pika library. These tests specifically demonstrate how to refresh a token on a live AMQP connection. +In the following information, OAuth 2.0 authentication is tested with the AMQP protocol and the Pika +library. These tests specifically demonstrate how to refresh a token on a live AMQP connection. -The sample Python application [can be found on GitHub](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/tree/next/pika-client). +The sample Python application is +[in GitHub](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/tree/next/pika-client). To run this sample code proceed as follows: + ```bash python3 --version pip install pika @@ -183,16 +202,18 @@ source venv/bin/activate ``` ::: -Note: Ensure you install pika 1.3 +:::important +Ensure that you install pika 1.3. +::: ## Access [management UI](./management/) 1. Go to https://localhost:15671. -2. Click on the single button on the page which redirects to **Keycloak** to authenticate. -3. Enter `rabbit_admin` and `rabbit_admin` and you should be redirected back to RabbitMQ Management fully logged in. +2. Click on the single button on the page which redirects to Keycloak to authenticate. +3. Enter `rabbit_admin` and `rabbit_admin` and you should be redirected back to RabbitMQ Management + fully logged in. - -## Stop keycloak +## Stop Keycloak ```bash make stop-keycloak @@ -200,23 +221,23 @@ make stop-keycloak ## Notes about setting up Keycloak -### Configure Client +### Configure client For backend applications which uses **Client Credentials flow**, you can create a **Client** with: -* **Access Type** : `public` +* **Access Type**: `public` * Turn off `Standard Flow`, `Implicit Flow`, and `Direct Access Grants` * With **Service Accounts Enabled** on. If it is not enabled you do not have the tab `Credentials` * In the `Credentials` tab, you have the `client id` +### Configure client scopes -### Configure Client scopes - -*Default Client Scope* are scopes automatically granted to every token. Whereas *Optional Client Scope* are -scopes which are only granted if they are explicitly requested during the authorization/token request flow. - +*Default Client Scope* are scopes automatically granted to every token. Whereas +*Optional Client Scope* are scopes which are only granted if they are explicitly requested during +the authorization/token request flow. ### Include appropriate aud claim -You must configure a **Token Mapper** of type **Hardcoded claim** with the value of rabbitmq's *resource_server_id**. -You can configure **Token Mapper** either to a **Client scope** or to a **Client**. +You must configure a **Token Mapper** of type **Hardcoded claim** with the value of RabbitMQ's +`resource_server_id`. You can configure **Token Mapper** either to a **Client scope** or to a +**Client**. diff --git a/docs/oauth2.md b/docs/oauth2.md index e522119c5b..f40d363f85 100644 --- a/docs/oauth2.md +++ b/docs/oauth2.md @@ -58,8 +58,7 @@ There's also a companion [troubleshooting guide for OAuth 2-specific problems](. ### Examples for Specific Identity Providers - * How to [set up RabbitMQ with OAuth 2: examples](#examples) - +* How to [set up RabbitMQ with OAuth 2: examples](#examples) ## How it works {#how-it-works} @@ -569,19 +568,20 @@ If a symmetric key is used, the configuration looks like this: ### Use a different token field for the scope {#use-different-token-field} -The plugin always extracts the scopes from the `scope` claim. However, -you can also configure the plugin to look in other claims using the `auth_oauth2.additional_scopes_key` variable. +The plugin always extracts the scopes from the `scope` claim. However, you can also configure the +plugin to look in other claims using the `auth_oauth2.additional_scopes_key` variable. The scopes found in the `scope` claim must be of these two value types: + - **string separated by spaces** like `my_id.configure:*/* my_id.read:*/* my_id.write:*/*` - **list** like `["my_id.configure:*/*", "my_id.read:*/*", "my_id.write:*/*"]` -The scopes found in any claim listed in the `auth_oauth2.additional_scopes_key` variable can be +The scopes found in any claim listed in the `auth_oauth2.additional_scopes_key` variable can be of several types in addition to the two value types supported by the `scope` claim mentioned earlier. #### Map of scopes indexed by resource_server_id {#map-of-scopes-indexed-by-resource-id} -This is an example of a token where scopes are not yet prefixed with the `resource_server_id` +This is an example of a token where scopes are not yet prefixed with the `resource_server_id`, but are indexed by the `resource_server_id`: ```ini @@ -594,16 +594,15 @@ but are indexed by the `resource_server_id`: "rabbitmq": ["configure:*/* read:*/* write:*/*"] }, "complex_claim_as_list": { - "rabbitmq": ["configure:vhost1/*", "read:vhost1/*", "write:vhost1/*"] + "rabbitmq": ["configure:vhost1/*", "read:vhost1/*", "write:vhost1/*"] } ... } ``` -With the following plugin configuration, the plugin reads the scopes from two -additional claims: `complex_claim_as_string` and `complex_claim_as_list`. -The plugin reads the scopes and adds the key value as prefix, for instance, -given the scope `configure:*/*` it produces `rabbitmq.configure:*/*`. +With the following plugin configuration, the plugin reads the scopes from two additional claims: +`complex_claim_as_string` and `complex_claim_as_list`. The plugin reads the scopes and adds the key +value as prefix. For example, given the scope `configure:*/*` it produces `rabbitmq.configure:*/*`. ```ini auth_oauth2.resource_server_id = my_rabbit_server @@ -612,10 +611,11 @@ auth_oauth2.additional_scopes_key = complex_claim_as_string complex_claim_as_lis #### Scopes nested deep in Maps and Lists -This is the case for tokens issued by **Keycloak** Identity Provider but can be -applied to any token from any provider. +This is the case for tokens issued by the Keycloak Identity Provider, but can be applied to any +token from any provider. This first token format stores scopes deep in maps and lists. + ```json { "authorization": { @@ -638,7 +638,7 @@ This first token format stores scopes deep in maps and lists. "scopes": [ "rabbitmq-resource.tag:administrator" ], - "rsid": "12ac3d1c-28c2-4521-8e33-0952eff10bd9" + "rsid": "12ac3d1c-28c2-4521-8e33-0952eff10bd9" } ] }, @@ -647,41 +647,45 @@ This first token format stores scopes deep in maps and lists. ``` Given the following configuration: + ```ini auth_oauth2.resource_server_id = my_rabbit_server -auth_oauth2.additional_scopes_key = authorization.permissions.scopes +auth_oauth2.additional_scopes_key = authorization.permissions.scopes ``` The plugin navigates the token structure following this logic: + 1. It looks up the claim `authorization`. 2. It finds a map, it then looks for the next claim `permissions`. 3. This time, it finds a list of maps. It goes over all the items in the list. 4. For each map in the list, it looks up the next claim `scopes`. -5. The value can be a list of scopes or a comma-separated string of scopes or -a [map of scopes indexed by resource_server_id](#map-of-scopes-indexed-by-resource-id). +5. The value can be a list of scopes or a comma-separated string of scopes or a + [map of scopes indexed by resource_server_id](#map-of-scopes-indexed-by-resource-id). Additionally, the plugin always reads the scopes from the official `scope` claim. With the above token and plugin's configuration, the list of scopes are following: + - `rabbitmq-resource.tag:monitoring` - `rabbitmq-resource.read:*/*` - `rabbitmq-resource.write:vhost1/*` - `rabbitmq-resource.tag:administrator` -In summary, the plugin is able to navigate the token to find the scopes -using the appropriate path. Each intermediary stage, for instance, after -finding `authorization` and/or `permissions` keys, the value can be another Map or -a List of Maps. The last stage, after finding the last `scopes` key, the value -can be any of any of the value types explained in the previous section. +In summary, the plugin is able to navigate the token to find the scopes using the appropriate path. + +For example, in each intermediary stage after finding `authorization` and/or `permissions` keys, the +value can be another Map or a List of Maps. In the last stage, after finding the last `scopes` key, +the value can be any of any of the value types explained in the previous section. These are: -- **string separated by spaces** like `my_id.configure:*/* my_id.read:*/* my_id.write:*/*` -- **list** like `["my_id.configure:*/*", "my_id.read:*/*", "my_id.write:*/*"]` + +- **string separated by spaces** such as `my_id.configure:*/* my_id.read:*/* my_id.write:*/*` +- **list** such as `["my_id.configure:*/*", "my_id.read:*/*", "my_id.write:*/*"]` - [Map of scopes indexed by resource server id](#map-of-scopes-indexed-by-resource-id) ### Preferred username claims {#preferred-username-claims} -The username associated with the token must be available to RabbitMQ so that this username is displayed in the RabbitMQ Management UI. +The user name associated with the token must be available to RabbitMQ so that this username is displayed in the RabbitMQ Management UI. By default, RabbitMQ searches for the `sub` claim first, and if it is not found, RabbitMQ uses the `client_id`. Most authorization servers return the user's GUID in the `sub` claim instead of the user's username or email address, anything the user can relate to. When the `sub` claim does not carry a *user-friendly username*, you can configure one or several claims to extract the username from the token. @@ -698,7 +702,6 @@ auth_oauth2.preferred_username_claims.2 = email In the example configuration, RabbitMQ searches for the `user_name` claim first and if it is not found, RabbitMQ searches for the `email`. If these are not found, RabbitMQ uses its default lookup mechanism which first looks for `sub` and then `client_id`. - ### Discovery endpoint parameters {#discovery-endpoint-params} Some OAuth 2.0 providers requires certain query parameters in the OpenId Discovery endpoint. For instance, Microsoft Entra ID requires a query parameter called `appid` when the application uses custom signing keys. The discovery endpoint returns an OpenId configuration tailored for the application that matches the `appid`. @@ -716,22 +719,23 @@ auth_oauth2.discovery_endpoint_params.param2 = value2 ``` This is the URL built to access the OpenId Discovery endpoint: -``` + +```console https://myissuer.com/v2/.well-known/authorization-server?param1=value1¶m2=value2 ``` ### Requesting Party Token {#requesting-party-token} -A **Requesting Party Token (RPT)** is a special OAuth 2.0 **access token** -issued by an **Authorization Server** in the [User-Managed Access (UMA) 2.0](https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html) framework. -It is used by a **Requesting Party** (such as an application or user) to access -a protected resource on a Resource Server like RabbitMQ, after being authorized -based on a resource owner policies. +A **Requesting Party Token (RPT)** is a special OAuth 2.0 **access token** issued by an +**Authorization Server** in the +[User-Managed Access (UMA) 2.0](https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html) +framework. It is used by a **Requesting Party** (such as an application or user) to access a +protected resource on a Resource Server such as RabbitMQ, after being authorized based on +resource-owner policies. -[Keycloak](./oauth2-examples-keycloak) is one of the Authorization Servers that issues this type of tokens. -An RPT is typically a JWT with permissions claims under a claim called `authorization`. -See the example below. The rest of the claims have been removed from the token for -brevity: +[Keycloak](./oauth2-examples-keycloak) is one of the Authorization Servers that issues this type of +token. An RPT is typically a JWT with permissions claims under a claim called `authorization`. See +the example below. The rest of the claims have been removed from the token for brevity: ```json { @@ -752,7 +756,7 @@ brevity: "rsname": "vhost1" }, { - "rsid": "12ac3d1c-28c2-4521-8e33-0952eff10bd9", + "rsid": "12ac3d1c-28c2-4521-8e33-0952eff10bd9", "scopes": [ "rabbitmq-resource.tag:administrator" ] @@ -763,10 +767,9 @@ brevity: } ``` -RabbitMQ supports this token format. It reads all the scopes in all the `permissions` -claims. If the token also contains the standard `scope` claim, RabbitMQ adds it to the -list of scopes presented by the token. - +RabbitMQ supports this token format. It reads all the scopes in all the `permissions` claims. If the +token also contains the standard `scope` claim, RabbitMQ adds it to the list of scopes presented by +the token. ### Rich Authorization Request {#rich-authorization-request} @@ -807,7 +810,6 @@ string `finance`, use `^finance$`. The second permission grants the `administrator` user tag in two clusters, `finance` and `inventory`. Other supported user tags as `management`, `policymaker` and `monitoring`. - #### Type field In order for a RabbitMQ node to accept a permission, its value must match that @@ -820,6 +822,7 @@ The `locations` field can be either a string containing a single location or a J zero or many locations. A location consists of a list of key-value pairs separated by forward slash `/` character. Here is the format: + ```bash cluster:[/vhost:][/queue:|/exchange:][/routing-key:] ```