OAuth 2.0 Authentication Backend
Overview
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
- How it works
- Usage
- Variables Configurable in rabbitmq.conf
- Resource Server ID and Scope Prefixes
- Token validation
- Scope-to-Permission Translation
- Topic Exchange scopes
- Using a different token field for the Scope
- Using Tokens with Clients
- Scope and Tags
- Token Expiration and Refresh
- Preferred username claims
- Rich Authorization Request
- Examples
How it works
The OAuth 2 plugin must be enabled (or pre-configured) before it can be used, like all other plugins:
rabbitmq-plugins enable rabbitmq_auth_backend_oauth2
Then it must be specified as one of the authN and authZ backends. It can be one of the backends or the only one backend, like in the example below:
# note that the module name begins with a "rabbit_", not "rabbitmq_", like in the name
# of the plugin
auth_backends.1 = rabbit_auth_backend_oauth2
Next, let's take a look at the workflows the OAuth 2 plugin supports.
Authorization Workflow
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.
Prerequisites
To use this plugin, all RabbitMQ nodes must be
- configured to use the rabbit_auth_backend_oauth2 backend.
- configured with a resource service ID (
resource_server_id
) that matches the scope prefix (e.g.rabbitmq
inrabbitmq.read:*/*
). - configured with a signing key used by RabbitMQ to validate the JWT token signatures.
JWT Tokens presented to RabbitMQ for authentication must
- be digitally signed with either a symmetric or asymmetric key.
- have a value in the
aud
field that matchesresource_server_id
value.
Authorization Flow
- Client requests an
access_token
from the OAuth 2.0 provider, - Token scope returned by OAuth 2.0 provider must include RabbitMQ resource scopes that follow a convention used by this plugin:
configure:%2F/foo
means "configure permissions for 'foo' in vhost '/'") (scope
field can be changed usingextra_scopes_source
in advanced.config file. - Client passes the token as password when connecting to a RabbitMQ node. The username field is ignored.
- The translated permissions are stored as part of the authenticated connection state and used the same way permissions from RabbitMQ's internal database would be used.
Usage
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.
Variables Configurable in rabbitmq.conf
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
Resource Server ID and scope prefix
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://
...
Token validation
When RabbitMQ receives a JWT token, it validates it before accepting it.
Must be digitally signed
The token must carry a digital signature and optionally a kid
header attribute which identifies the key RabbitMQ should
use to validate the signature.
Must not be expired
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.
Audience must have/match the resource_server_id
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
.
Scope-to-Permission Translation
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
<permission>
is an access permission (configure
,read
, orwrite
)<vhost_pattern>
is a wildcard pattern for vhosts token has access to.<name_pattern>
is a wildcard pattern for resource name<routing_key_pattern>
is a wildcard pattern for routing key in topic authorization
Wildcard patterns are strings with optional wildcard symbols *
that match
any sequence of characters.
Wildcard patterns match as following:
*
matches any stringfoo*
matches any string starting with afoo
*foo
matches any string ending with afoo
foo*bar
matches any string starting with afoo
and ending with abar
There can be multiple wildcards in a pattern:
start*middle*end
*before*after*
To use special characters like *
, %
, or /
in a wildcard pattern,
the pattern must be URL-encoded.
These are the typical permissions examples:
read:*/*
(read:*/*/*
) - read permissions to any resource on any vhostwrite:*/*
(write:*/*/*
) - write permissions to any resource on any vhostread:vhost1/*
(read:vhost1/*/*
) - read permissions to any resource on thevhost1
vhostread:vhost1/some*
- read permissions to all the resources, starting withsome
on thevhost1
vhostwrite:vhost1/some*/routing*
- topic write permissions to publish to an exchange starting withsome
with a routing key starting withrouting
read:*/*/*
andwrite:*/*/*
- queue binding permissions required to bind a queue on a topic exchange with any routing key
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:*/*
Topic Exchange scopes
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:
- write permission on the exchange and routing key ->
rabbitmq.write:<vhost>/<exchange>/<routingkey>
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}-*" ]
}
Using a different token field for the Scope
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:*/*"],
...
}
Using Tokens with Clients
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.
Scope and Tags
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
.
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.
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
.
Token Expiration and Refresh
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).
Rich Authorization Request
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
.
Type field
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.
Locations field
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:
cluster
: This is the only mandatory attribute. It is a wildcard pattern which must match RabbitMQ'sresource_server_id
otherwise the location is ignored.vhost
: This is the virtual host you are granting access to. It also a wildcard pattern. If not specified,*
will be used.queue
|exchange
: queue or exchange name pattern. The location grants the permission to a set of queues (or exchanges) that match it. One location can only specify eitherqueue
orexchange
but not both. If not specified,*
will be usedrouting-key
: this is the routing key pattern the location grants the permission to. If not specified,*
will be used
For more information about wildcard patterns, check the section Scope-to-Permission Translation.
Actions field
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:
configure
read
write
Or RabbitMQ user tags:
administrator
monitoring
management
policymaker
Rich-Permission to Scope translation
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 thecluster
attribute matches the current RabbitMQ node'sresource_server_id
, the plugin extracts thevhost
,queue
orexchange
androuting_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:
finance.read:primary-*/*/*
finance.write:primary-*/*/*
finance.configure:primary-*/*/*
finance.tag:administrator
Examples
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.