Menu

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

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

  1. configured to use the rabbit_auth_backend_oauth2 backend.
  2. configured with a resource service ID (resource_server_id) that matches the scope prefix (e.g. rabbitmq in rabbitmq.read:*/*).
  3. configured with a signing key used by RabbitMQ to validate the JWT token signatures.

JWT Tokens presented to RabbitMQ for authentication must

  1. be digitally signed with either a symmetric or asymmetric key.
  2. have a value in the aud field that matches resource_server_id value.

Authorization Flow

  1. Client requests an access_token from the OAuth 2.0 provider,
  2. 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 using extra_scopes_source in advanced.config file.
  3. Client passes the token as password when connecting to a RabbitMQ node. The username field is ignored.
  4. 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.additional_scopes_key Configure the plugin to also look in other fields (maps to additional_rabbitmq_scopes in the old format).
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.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 Prefixes

OAuth 2.0 (and thus UAA-provided) tokens use scopes to communicate what set of permissions particular client has been granted. The scopes are free form strings.

resource_server_id is a prefix used for scopes in UAA to avoid scope collisions (or unintended overlap). It is an empty string by default.

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, or write)
  • <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 an optional 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 string
  • foo* matches any string starting with a foo
  • *foo matches any string ending with a foo
  • foo*bar matches any string starting with a foo and ending with a bar

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 vhost
  • write:*/*(write:*/*/*) - write permissions to any resource on any vhost
  • read:vhost1/*(read:vhost1/*/*) - read permissions to any resource on the vhost1 vhost
  • read:vhost1/some* - read permissions to all the resources, starting with some on the vhost1 vhost
  • write:vhsot1/some*/routing* - topic write permissions to publish to an exchange starting with some with a routing key starting with routing

See the wildcard matching test suite and scopes test suite for more examples.

Scopes should be prefixed with resource_server_id. For example, if resource_server_id is "my_rabbit", a scope to enable read from any vhost will be my_rabbit.read:*/*.

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.

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's resource_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 either queue or exchange but not both. If not specified, * will be used
  • routing-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 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:

  • 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.

License and Copyright

(c) 2016-2022 VMware, Inc. or its affiliates.

Released under the Mozilla Public License 2.0, same as RabbitMQ.

Getting Help and Providing Feedback

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.

Help Us Improve the Docs <3

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!