This RabbitMQ authentication/authorisation backend plugin lets applications (clients) and users authenticate and authorize using JWT-encoded OAuth 2.0 access tokens.
This guide covers
This plugin does not communicate with any OAuth 2.0 provider. It decodes an access token provided by the client and authorises a user based on the data stored in the token.
The token can be any JWT token which contains the scope and aud fields. The way the token was issued (such as what grant type was used) is outside of the scope of this plugin.
To use this plugin, all RabbitMQ nodes must be
JWT Tokens presented to RabbitMQ for authentication must
The plugin needs a signing key to be configured in order to verify the token's signature. This is the signing key used by the OAuth 2.0 provider to sign the tokens. RabbitMQ supports two types of signing keys: symmetric and asymmetric.
The examples given below uses Cloud Foundry UAA as OAuth 2.0 provider.
To get the signing key from the OAuth 2.0 provider UAA, use the token_key endpoint or uaac (the uaac signing key command).
The following fields are required: kty, value, alg, and kid.
Assuming UAA reports the following signing key information:
uaac signing key kty: RSA e: AQAB use: sig kid: a-key-ID alg: RS256 value: -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2dP+vRn+Kj+S/oGd49kq 6+CKNAduCC1raLfTH7B3qjmZYm45yDl+XmgK9CNmHXkho9qvmhdksdzDVsdeDlhK IdcIWadhqDzdtn1hj/22iUwrhH0bd475hlKcsiZ+oy/sdgGgAzvmmTQmdMqEXqV2 B9q9KFBmo4Ahh/6+d4wM1rH9kxl0RvMAKLe+daoIHIjok8hCO4cKQQEw/ErBe4SF 2cr3wQwCfF1qVu4eAVNVfxfy/uEvG3Q7x005P3TcK+QcYgJxav3lictSi5dyWLgG QAvkknWitpRK8KVLypEj5WKej6CF8nq30utn15FQg0JkHoqzwiCqqeen8GIPteI7 VwIDAQAB -----END PUBLIC KEY----- n: ANnT_r0Z_io_kv6BnePZKuvgijQHbggta2i30x-wd6o5mWJuOcg5fl5oCvQjZh15IaPar5oXZLHcw1bHXg5YSiHXCFmnYag83bZ9YY_9tolMK4R9G3eO-YZSnLImfqMv7HYBoAM75pk0JnTKhF6ldgfavShQZqOAIYf-vneMDNax_ZMZdEbzACi3vnWqCByI6JPIQju HCkEBMPxKwXuEhdnK98EMAnxdalbuHgFTVX8X8v7hLxt0O8dNOT903CvkHGICcWr95YnLUouXcli4BkAL5JJ1oraUSvClS8qRI-Vino-ghfJ6t9LrZ9eRUINCZB6Ks8Igqqnnp_BiD7XiO1c
it will translate into the following configuration (in the advanced RabbitMQ config format):
[ %% ... %% backend configuration {rabbitmq_auth_backend_oauth2, [ {resource_server_id, <<"my_rabbit_server">>}, %% UAA signing key configuration {key_config, [ {signing_keys, #{ <<"a-key-ID">> => {pem, <<"-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2dP+vRn+Kj+S/oGd49kq 6+CKNAduCC1raLfTH7B3qjmZYm45yDl+XmgK9CNmHXkho9qvmhdksdzDVsdeDlhK IdcIWadhqDzdtn1hj/22iUwrhH0bd475hlKcsiZ+oy/sdgGgAzvmmTQmdMqEXqV2 B9q9KFBmo4Ahh/6+d4wM1rH9kxl0RvMAKLe+daoIHIjok8hCO4cKQQEw/ErBe4SF 2cr3wQwCfF1qVu4eAVNVfxfy/uEvG3Q7x005P3TcK+QcYgJxav3lictSi5dyWLgG QAvkknWitpRK8KVLypEj5WKej6CF8nq30utn15FQg0JkHoqzwiCqqeen8GIPteI7 VwIDAQAB -----END PUBLIC KEY-----">>} }} ]} ]} ].
If a symmetric key is used, the configuration will look like this:
[ {rabbitmq_auth_backend_oauth2, [ {resource_server_id, <<"my_rabbit_server">>}, {key_config, [ {signing_keys, #{ <<"a-key-ID">> => {map, #{<<"kty">> => <<"MAC">>, <<"alg">> => <<"HS256">>, <<"value">> => <<"my_signing_key">>}} }} ]} ]}, ].
The key set can also be retrieved dynamically from a URL serving a JWK Set. In that case, the configuration will look like this:
[ {rabbitmq_auth_backend_oauth2, [ {resource_server_id, <<"my_rabbit_server">>}, {key_config, [ {jwks_url, <<"https://my-jwt-issuer/jwks.json">>} ]} ]}, ].
NOTE: jwks_url takes precedence over signing_keys if both are provided.
Key | Documentation |
---|---|
auth_oauth2.resource_server_id | The Resource Server ID |
auth_oauth2.resource_server_type | The Resource Server Type |
auth_oauth2.additional_scopes_key | Configure the plugin to also look in other fields (maps to additional_rabbitmq_scopes in the old format). |
auth_oauth2.scope_prefix | Configure prefix for all scopes. Default value is auth_oauth2.resource_server_id followed by the dot . character. |
auth_oauth2.preferred_username_claims | List of JWT claims to look for username associated to the token separated by commas. |
auth_oauth2.default_key | ID of the default signing key. |
auth_oauth2.signing_keys | Paths to signing key files. |
auth_oauth2.jwks_url | The URL of key server. According to the JWT Specification key server URL must be https. |
auth_oauth2.https.cacertfile | Path to a file containing PEM-encoded CA certificates. The CA certificates are used during key server peer verification. |
auth_oauth2.https.depth | The maximum number of non-self-issued intermediate certificates that may follow the peer certificate in a valid certification path. Default is 10. |
auth_oauth2.https.peer_verification | Should peer verification be enabled. Available values: verify_none, verify_peer. Default is verify_none. It is recommended to configure verify_peer. Peer verification requires a certain amount of setup and is more secure. |
auth_oauth2.https.fail_if_no_peer_cert | Used together with auth_oauth2.https.peer_verification = verify_peer. When set to true, TLS connection will be rejected if client fails to provide a certificate. Default is false. |
auth_oauth2.https.hostname_verification | Enable wildcard-aware hostname verification for key server. Available values: wildcard, none. Default is none. |
auth_oauth2.algorithms | Restrict the usable algorithms. |
auth_oauth2.verify_aud | Verify token's aud. |
For example:
Configure with key files
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 auth_oauth2.algorithms.1 = HS256 auth_oauth2.algorithms.2 = RS256
Configure with key server
auth_oauth2.resource_server_id = new_resource_server_id auth_oauth2.jwks_url = https://my-jwt-issuer/jwks.json auth_oauth2.https.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem auth_oauth2.https.peer_verification = verify_peer auth_oauth2.https.depth = 5 auth_oauth2.https.fail_if_no_peer_cert = true auth_oauth2.https.hostname_verification = wildcard auth_oauth2.algorithms.1 = HS256 auth_oauth2.algorithms.2 = RS256
OAuth 2.0 tokens use scopes to communicate what set of permissions particular client has been granted. The scopes are free form strings.
By default, resource_server_id followed by the dot (.) character is the prefix used for scopes to avoid scope collisions (or unintended overlap). However, in some environments, it is not possible to use resource_server_id as the prefix for all scopes. For these environments, there is a new setting called scope_prefix which overrides the default scope prefix. Empty strings are allowed.
Given the below configuration, the scope associated to the permission read:*/* is api://read:*/*.
... auth_oauth2.scope_prefix = api:// ...
When RabbitMQ receives a JWT token, it validates it before accepting it.
The token must carry a digital signature and optionally a kid header attribute which identifies the key RabbitMQ should use to validate the signature.
RabbitMQ uses this field exp (exp) to validate the token if present. It contains the expiration time after which the JWT MUST NOT be accepted for processing.
The aud (Audience) identifies the recipients and/or resource_server of the JWT. By default, RabbitMQ uses this field to validate the token although you can deactivate it by setting verify_aud to false. When it set to true, this attribute must either match the resource_server_id setting or in case of a list, it must contain the resource_server_id.
Scopes are translated into permission grants to RabbitMQ resources for the provided token.
The current scope format is <permission>:<vhost_pattern>/<name_pattern>[/<routing_key_pattern>] where
Wildcard patterns are strings with optional wildcard symbols * that match any sequence of characters.
Wildcard patterns match as following:
There can be multiple wildcards in a pattern:
To use special characters like *, %, or / in a wildcard pattern, the pattern must be URL-encoded.
These are the typical permissions examples:
See the wildcard matching test suite and scopes test suite for more examples.
Scopes, by default, are prefixed with resource_server_id followed by the dot (.) character if scope_prefix is not configured. For example, if resource_server_id is "my_rabbit", a scope to enable read from any vhost will be my_rabbit.read:*/*.
If scope_prefix is configured then scopes are prefixed as follows: <scope_prefix><permission>. For example, if scope_prefix is api:// and the permission is read:*/* the scope would be api://read:*/*
The previous section explained, in detail, how permissions are mapped to scopes. This section explains more specifically what scopes you need in order to operate on Topic Exchanges.
To bind and/or unbind a queue to/from a Topic Exchange, you need to have the following scopes:
write permission on the queue and routing key -> rabbitmq.write:<vhost>/<queue>/<routingkey>
e.g. rabbitmq.write:*/*/*
read permission on the exchange and routing key -> rabbitmq.write:<vhost>/<exchange>/<routingkey>
e.g. rabbitmq.read:*/*/*
To publish to a Topic Exchange, you need to have the following scope:
e.g. rabbitmq.write:*/*/*
OAuth 2.0 authorisation backend supports variable expansion when checking permission on topics. It supports JWT claims whose value is a plain string, plus the vhost variable.
For example, a user connected with the token below to the vhost prod should have a write permission on all exchanges starting with x-prod-, and any routing key starting with u-bob-:
{ "sub" : "bob", "scope" : [ "rabbitmq.write:*/q-{vhost}-*/u-{sub}-*" ] }
By default the plugin will look for the scope key in the token, you can configure the plugin to also look in other fields using the extra_scopes_source setting. Values format accepted are scope as string or list
[ {rabbitmq_auth_backend_oauth2, [ {resource_server_id, <<"my_rabbit_server">>}, {extra_scopes_source, <<"my_custom_scope_key">>}, ... ]} ]}, ].
Token sample:
{ "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:*/*"], ... }
A client must present a valid access_token acquired from an OAuth 2.0 provider (such as UAA) as the password in order to authenticate with RabbitMQ.
To learn more about OAuth 2.0 clients, see the OAuth 2.0 client specification.
Users in RabbitMQ can have tags associated with them. Tags are used to control access to the management plugin.
In the OAuth context, tags can be added as part of the scope, using a format like <resource_server_id>.tag:<tag>. For example, if resource_server_id is "my_rabbit", a scope to grant access to the management plugin with the monitoring tag will be my_rabbit.tag:monitoring.
The username 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.
Example advanced.config configuration:
... {rabbitmq_auth_backend_oauth2, [ {resource_server_id, <<"rabbitmq">>}, {preferred_username_claims, [<<"user_name">>,<<"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.
On an existing connection the token can be refreshed by the update-secret AMQP 0.9.1 method. Please check your client whether it supports this method. (Eg. see documentation of the Java client.) Otherwise the client has to disconnect and reconnect to use a new token.
If the latest token expires on an existing connection, after a limited time the broker will refuse all operations (but it won't disconnect).
The Rich Authorization Request extension provides a way for OAuth clients to request fine-grained permissions during an authorization request. It moves away from the concept of scopes that are text labels and instead defines a more sophisticated permission model.
RabbitMQ supports JWT tokens compliant with the extension. Below is a sample example section of JWT token:
{ "authorization_details": [ { "type" : "rabbitmq", "locations": ["cluster:finance/vhost:production-*"], "actions": [ "read", "write", "configure" ] }, { "type" : "rabbitmq", "locations": ["cluster:finance", "cluster:inventory" ], "actions": ["administrator" ] } ] }
The token above contains two permissions under the attribute authorization_details. Both permissions are meant for RabbitMQ servers with resource_server_type set to rabbitmq. This field identifies RabbitMQ-specific permissions.
The first permission grants read, write and configure permissions to any queue and/or exchange on any virtual host whose name matches the pattern production-*, and that reside in clusters whose resource_server_id contains the string finance. The cluster attribute's value is also a regular expression. To match exactly the 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.
In order for a RabbitMQ node to accept a permission, its value must match that node's resource_server_type setting value. A JWT token may have permissions for multiple resource types.
The locations field can be either a string containing a single location or a Json array containing zero or many locations.
A location consists of a list of key-value pairs separated by forward slash / character. Here is the format:
cluster:<resource_server_id_pattern>[/vhost:<vhost_pattern>][/queue:<queue_name_pattern>|/exchange:<exchange_name_pattern][/routing-key:<routing_key_pattern>]
Any string separated by / which does not conform to <key>:<value> is ignored. For instance, if your locations start with a prefix, e.g. vrn/cluster:rabbitmq, the vrn pattern part is ignored.
The supported location's attributed are:
For more information about wildcard patterns, check the section Scope-to-Permission Translation.
The actions field can be either a string containing a single action or a Json array containing zero or many actions.
The supported actions map to either RabbitMQ permissions:
Or RabbitMQ user tags:
Rich Authorization Request permissions are translated into JWT token scopes that use the aforementioned convention using the following algorithm:
For each location found in the locations where the cluster attribute matches the current RabbitMQ server's resource_server_id:
For each location found in the locations field where the cluster attribute matches the current RabbitMQ node's resource_server_id, the plugin extracts the vhost, queue or exchange and routing_key attributes from the location. If the location does not have any of those attributes, the default value of * is assumed. Out of those values, the following scope suffix will be produced:
scope_suffix = <vhost>/<queue>|<exchange>/<routing-key>
For each action found in the actions field:
if the action is not a known user tag, the following scope is produced out of it:
scope = <resource_server_id>.<action>:<scope_suffix>
For known user tag actions, the following scope is produced:
scope = <resource_server_id>.<action>
The plugin produces permutations of all actions by all locations that match the node's configured resource_server_id.
In the following RAR example
{ "authorization_details": [ { "type" : "rabbitmq", "locations": ["cluster:finance/vhost:primary-*"], "actions": [ "read", "write", "configure" ] }, { "type" : "rabbitmq", "locations": ["cluster:finance", "cluster:inventory" ], "actions": ["administrator" ] } ] }
if RabbitMQ node's resource_server_id is equal to finance, the plugin will compute the following sets of scopes:
The RabbitMQ OAuth 2.0 Auth Backend Examples contains many example configuration files which can be used to set up several OAuth 2.0 providers, including UAA, Auth0, and Azure, and issue tokens, which can be used to access RabbitMQ resources.
(c) 2016-2022 VMware, Inc. or its affiliates.
Released under the Mozilla Public License 2.0, same as RabbitMQ.
If you have questions about the contents of this guide or any other topic related to RabbitMQ, don't hesitate to ask them on the RabbitMQ mailing list.
If you'd like to contribute an improvement to the site, its source is available on GitHub. Simply fork the repository and submit a pull request. Thank you!