Skip to content

Commit

Permalink
Merge pull request #2091 from rabbitmq/support-scope-aliases-in-cuttl…
Browse files Browse the repository at this point in the history
…efish-style

4.1: OAuth 2: support scope aliases in rabbitmq.conf
  • Loading branch information
michaelklishin authored Oct 11, 2024
2 parents d13f6b9 + 4ce0b26 commit 83bde59
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 129 deletions.
20 changes: 16 additions & 4 deletions docs/oauth2-examples-okta.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,14 @@ This is totally optional but it can save you time.

## Configure RabbitMQ to use Okta as OAuth 2.0 Authentication Backend

The configuration on Okta side is done. You now have to configure RabbitMQ to use the resources you just created. You took note of the following values:
The configuration on the Okta side is now done. The next step is to configure RabbitMQ
to use the resources created earlier.

- **okta_client_app_ID** associated to the okta app that you registered in okta for rabbitMQ.
- **okta-Issuer** associated to the **default Authorization server**.
- **okta-Metadata-URI** associated to the **default Authorization server**.
The following values will be necessary during the next steps:

* **okta_client_app_ID**: the Okta app registered above to be used with RabbitMQ
* **okta-Issuer**: the **default Authorization server**
* **okta-Metadata-URI**: the **default Authorization server**

Clone [rabbitmq.conf.tmpl](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/tree/next/conf/okta/rabbitmq.conf.tmpl) as `rabbitmq.conf` (in the same folder as `rabbitmq.conf.tmpl`).
There is a second configuration file, [advanced.config](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/tree/next/conf/okta/advanced.config),
Expand All @@ -205,6 +208,15 @@ or `{okta-issuer}/.well-known/openid-configuration`
4. Else you need to determine the path that follows the uri in `{okta-issuer}` and update
`auth_oauth2.discovery_endpoint_path` accordingly. For instance, if **okta-Metadata-URI** is `{okta-issuer}/some-other-endpoint`, you update `auth_oauth2.discovery_endpoint_path` with the value `some-other-endpoint`.

The mapping of the roles configured in Okta, such as `monitoring` and `admin`, are configured
at the bottom of the `rabbitmq.conf` file. For example:

```ini
#...
auth_oauth2.scope_aliases.admin = okta.read:*/* okta.write:*/* okta.configure:*/* okta.tag:administrator
auth_oauth2.scope_aliases.monitoring = okta.tag:management okta.read:*/
#...
```

### About OpenId Discovery Endpoint

Expand Down
121 changes: 32 additions & 89 deletions docs/oauth2-examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,116 +528,59 @@ make curl-with-token URL=http://localhost:15672/api/overview TOKEN=$(bin/jwt_tok

### Using Scope Aliases {#using-scope-aliases}

*Custom scopes*? are any scope whose format is not compliant with RabbitMQ format.
For instance, `api://rabbitmq:Read.All` is one of the custom scopes you will use in this use case.
This example demonstrates how to use custom scopes with RabbitMQ.

#### How to Configure RabbitMQ to Use a Custom Scope Mapping
**UAA** identity provider has been configured with two clients (`producer_with_roles`
and `consumer_with_roles`) with the following custom scopes:

See below a sample RabbitMQ configuration where you map `api://rabbitmq:Read.All`
custom scope to `rabbitmq.read:*/*` RabbitMQ scope.
`producer_with_roles` with
- `api://rabbitmq:producer`.

``` erl
{rabbitmq_auth_backend_oauth2, [
%%...,
{scope_aliases, #{
<<"api://rabbitmq:Read.All">> => [<<"rabbitmq.read:*/*">>],
...
},
%%...
]}
```

Additionally, you can map a custom scope to many RabbitMQ scopes. For instance below you
are mapping the role `api://rabbitmq:producer` to 3 RabbitMQ scopes which grants
`read`, `write` and `configure` access on any resource and on any vhost:

``` erl
{rabbitmq_auth_backend_oauth2, [
%% ...,

{scope_aliases, #{
<<"api://rabbitmq:producer">> => [
<<"rabbitmq.read:*/*">>,
<<"rabbitmq.write:*/*">>,
<<"rabbitmq.configure:*/*">>
]
}},
%% ...
]}
```

#### Scopes Aliases in JWT Tokens
`consumer_with_roles` with
- `api://rabbitmq:Read.All`.
- `api://rabbitmq:Write.All`.
- `api://rabbitmq:Configure.All`.
- `api://rabbitmq:Administrator`.

If you do not configure RabbitMQ OAuth 2.0 plugin with `extra_scopes_source`, RabbitMQ
expects the `scope` token's field to carry *custom scopes*. For instance, below you have a sample JWT
token where the custom scopes are in the `scope` field :
For more information about scope aliases, check out
the [section](./oauth2#scope-aliases) that explains it in more detail.

```javascript
{
"sub": "producer",
"scope": [
"api://rabbitmq:producer",
"api://rabbitmq:Administrator"
],
"aud": [
"rabbitmq"
]
}
```
#### How to Configure Scope Aliases

Now, let's say you do configure RabbitMQ OAuth 2.0 plugin with `extra_scopes_source` as shown below:
This is the configuration required to map those custom scopes to RabbitMQ scopes.

:::tip
Since RabbitMQ 4.1, it is possible to configure **scope aliases** using the [ini-like](./configure#config-file) configuration style. Earlier versions only supported
the legacy Erlang-style.
:::

```ini
# ...
auth_oauth2.resource_server_id = rabbitmq
auth_oauth2.additional_scopes_key = roles
# ...
```

With this configuration, RabbitMQ expects *custom scopes* in the field `roles` and
the `scope` field is ignored.
auth_oauth2.scope_aliases.1.alias = api://rabbitmq:Read.All
auth_oauth2.scope_aliases.1.scope = rabbitmq.read:*/*

```javascript
{
"sub": "rabbitmq-client-code",
"roles": "api://rabbitmq:Administrator.All",
"aud": [
"rabbitmq"
]
}
```
auth_oauth2.scope_aliases.2.alias = api://rabbitmq:Write.All
auth_oauth2.scope_aliases.2.scope = rabbitmq.write:*/*

#### UAA Configuration
auth_oauth2.scope_aliases.3.alias = api://rabbitmq:Configure.All
auth_oauth2.scope_aliases.3.scope = rabbitmq.configure:*/*

To demonstrate this new capability you have configured UAA with two Oauth 2.0 clients. One
called `producer_with_roles` with the *custom scope* `api://rabbitmq:producer` and `consumer_with_roles` with
`api://rabbitmq:Read:All,api://rabbitmq:Configure:All,api://rabbitmq:Write:All`.
> You are granting configure and write permissions to the consumer because you have configured perf-test to declare
resources regardless whether it is a producer or consumer application.
auth_oauth2.scope_aliases.3.alias = api://rabbitmq:Administrator
auth_oauth2.scope_aliases.3.scope = rabbitmq.tag:administrator

These two uaac commands declare the two OAuth 2.0 clients above. You are adding an extra scope called `rabbitmq.*` so
that UAA populates the JWT claim `aud` with the value `rabbitmq`. RabbitMQ expects `aud` to match the value you
configure RabbitMQ with in the `resource_server_id` field.
auth_oauth2.scope_aliases.4.alias = api://rabbitmq:producer
auth_oauth2.scope_aliases.4.scope = rabbitmq.read:*/* rabbitmq.write:*/* rabbitmq.configure:*/* rabbitmq.tag:management

```bash
uaac client add producer_with_roles --name producer_with_roles \
--authorities "rabbitmq.*,api://rabbitmq:producer,api://rabbitmq:Administrator" \
--authorized_grant_types client_credentials \
--secret producer_with_roles_secret
uaac client add consumer_with_roles --name consumer_with_roles \
--authorities "rabbitmq.* api://rabbitmq:read:All" \
--authorized_grant_types client_credentials \
--secret consumer_with_roles_secret
```


#### RabbitMQ Configuration

In the OAuth 2.0 tutorial repository, there are two RabbitMQ configuration files ready to be used, for UAA:

- [conf/uaa/advanced-scope-aliases.config](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/blob/next/conf/uaa/advanced-scope-aliases.config): configures a set of scope aliases.
- [conf/uaa/rabbitmq.conf](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/blob/next/conf/uaa/rabbitmq.conf) : configure the rest of oauth2 configuration in addition to configuring `extra_scope` as another claim from where to read scopes from
* [conf/uaa/advanced-scope-aliases.config](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/blob/next/conf/uaa/advanced-scope-aliases.config):
configures a set of scope aliases.
* [conf/uaa/rabbitmq.conf](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/blob/next/conf/uaa/rabbitmq.conf):
configure the rest of oauth2 configuration in addition to configuring `extra_scope` as another claim from where to read scopes from


#### Demo 1: Launch RabbitMQ with custom scopes in scope field
Expand Down
70 changes: 59 additions & 11 deletions docs/oauth2.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ auth_backends.1 = rabbit_auth_backend_oauth2

Next, let's take a look at the workflows the OAuth 2 plugin supports.

### Prerequisites {#prerequisites}
## Prerequisites {#prerequisites}

To use the OAuth 2 plugin, all RabbitMQ nodes must be

Expand Down Expand Up @@ -116,7 +116,7 @@ auth_oauth2.discovery_endpoint_params.appid = some-app-id

More detail is included in the next section about what happens during the authentication and how to configure OAuth 2.0 beyond the basic configuration shown previously.

### Authorization Flow {#authorization-flow}
## Authorization Flow {#authorization-flow}

This plugin does not communicate with any OAuth 2.0 provider in order to authenticate user and grants access. Instead, it decodes an access token provided by the client and authorises a user based on the scopes found in the token.

Expand All @@ -138,7 +138,7 @@ In chronological order, here is the sequence of events that occur when a client
5. RabbitMQ validates that the token has the **audience** claim and whose value matches the `resource_server_id` (this operation can be deactivated by setting `auth_oauth2.verify_aud` to `false`).
6. RabbitMQ translates the **scopes** found in the token into RabbitMQ **permissions** (the same permissions used in the RabbitMQ's internal database).

### Variables configurable in rabbitmq.conf {#variables-configurable}
## Variables Configurable in rabbitmq.conf {#variables-configurable}

| Key | Documentation
|--------------------------------------------|-----------
Expand All @@ -150,8 +150,6 @@ In chronological order, here is the sequence of events that occur when a client
| `auth_oauth2.default_key` | ID of the default signing key.
| `auth_oauth2.signing_keys` | Paths to the [signing key files](#signing-key-files).
| `auth_oauth2.issuer` | The [issuer URL](#configure-issuer) of the authorization server that is used to either discover endpoints such as `jwks_uri` and/or where to redirect RabbitMQ management users to login and get a token.
| `auth_oauth2.discovery_endpoint_path` | The path used for the [OpenId discovery endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata). The endpoint URI is built using `auth_oauth2.issuer`, this path or else the default path `.well-known/openid-configuration` followed by query parameters configured in the following variable
| `auth_oauth2.discovery_endpoint_params` | [List of HTTP query parameters](#discovery-endpoint-params) sent to the OpenId discovery endpoint.
| `auth_oauth2.jwks_uri` | The URL of the [JWKS endpoint](#jwks-endpoint). According to the [JWT Specification](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.2), the endpoint URL must be https. Optional if you set `auth_oauth2.issuer`. If this URL is set, it overrides the `jwks_uri` discovered via the discovery endpoint.
| `auth_oauth2.jwks_url` | This variable is **deprecated** and you should use instead `auth_oauth2.jwks_uri`. In RabbitMQ 4.2.0, this variable will be removed. In the meantime, RabbitMQ supports it until you change your configuration.
| `auth_oauth2.token_endpoint` | The URL of the OAuth 2.0 token endpoint. Optional if you set `auth_oauth2.issuer`. If this URL is set, it overrides the `token_endpoint` discovered via the discovery endpoint.
Expand All @@ -168,7 +166,7 @@ In chronological order, here is the sequence of events that occur when a client
| `auth_oauth2.default_oauth_provider` | ID of the OAuth 2.0 provider used for the `auth_oauth2.resource_servers`, that did not specify any (via the variable `oauth_provider_id`) or when `auth_oauth2.jwks_uri` and `auth_oauth2.issuer` are both missing.


#### Resource Server ID {#resource-server-id}
## Resource Server ID {#resource-server-id}

A RabbitMQ cluster must have at least one resource server identifier configured. If it has just one resource, this is configured in the `auth_oauth2.resource_server_id` variable and it is **mandatory**.
If the RabbitMQ cluster has more than one OAuth resource then they are configured under `auth_oauth2.resource_servers.<index>` and in this case `auth_oauth2.resource_server_id` variable is not mandatory.
Expand All @@ -177,7 +175,7 @@ RabbitMQ uess the resource server identity for these two purposes:
- To validate the token's audience (`aud`) whose value must contain the resource server identifier. This validation can be disabled though.
- To initiate the OAuth 2.0 Authorization Code flow in the Management UI. This is the flow used to authenticate a user and to get its access token. RabbitMQ must include the resource server identifier in the request's attribute called `resource`.

#### Scope prefix {#scope-prefix}
## Scope Prefix {#scope-prefix}

OAuth 2.0 tokens use scopes to communicate what set of permissions particular client are granted. The scopes are free form strings.

Expand All @@ -198,7 +196,56 @@ auth_oauth2.scope_prefix = ''
...
```

#### Signing keys files {#signing-key-files}
## Scope Aliases {#scope-aliases}

:::important

Scope aliases are necessary when scopes in the RabbitMQ format cannot be
configured on the identity provider (IDP) side

:::

A scope alias is a mapping between a custom JWT token scope and a set of RabbitMQ-specific scopes. A custom
scope can also be defined as any scope which is not recogonized by RabbitMQ's OAuth 2 subsystem.

Scope aliases are necessary when scopes in the RabbitMQ format cannot be
configured on the identity provider (IDP) side. Instead, a set of names is configured
on the IDP side, and mapped to a set of scoped that RabbitMQ can parse and use.

For instance, let's consider an identity provider with the following two roles:

* `admin`
* `developer`

These roles should be mapped to the following RabbitMQ scopes:

* `admin` to `rabbitmq.tag:administrator rabbitmq.read:*/`
* `developer` to `rabbitmq.tag:management rabbitmq.read:*/* rabbitmq.write:*/* rabbitmq.configure:*/*`

The following `rabbitmq.conf` example performs the aforementioned mapping using scope aliases. The mapping can be one-to-one or one-to-many:

```ìni
# ...
# the "admin" role above
auth_oauth2.scope_aliases.admin = rabbitmq.tag:administrator rabbitmq.read:*/
# the "developer" role above
auth_oauth2.scope_aliases.developer = rabbitmq.tag:management rabbitmq.read:*/* rabbitmq.write:*/* rabbitmq.configure:*/*
# ...
```

Sometimes an alias may have to use special characters and symbols including the separator character, `.`.
In those cases, configure the scope aliases as follows:

```ìni
# ...
auth_oauth2.scope_aliases.1.alias = api://admin
auth_oauth2.scope_aliases.1.scope = rabbitmq.tag:administrator rabbitmq.read:*/
auth_oauth2.scope_aliases.2.alias = api://developer.All
auth_oauth2.scope_aliases.2.scope = rabbitmq.tag:management rabbitmq.read:*/* rabbitmq.write:*/* rabbitmq.configure:*/*
# ...
```

## Signing Keys Files {#signing-key-files}

The following configuration declares two signing keys and configures the kid of the default signing key. For more information check the section [Configure Signing keys](#configure-signing-keys).

Expand All @@ -214,7 +261,7 @@ auth_oauth2.algorithms.1 = HS256
auth_oauth2.algorithms.2 = RS256
```

#### JWKS endpoint {#jwks-endpoint}
## JWKS endpoint {#jwks-endpoint}

The following configuration sets the JWKS endpoint from which RabbitMQ downloads the signing keys using the configured CA certificate and TLS variables.

Expand All @@ -231,7 +278,7 @@ auth_oauth2.algorithms.2 = RS256
```


#### Multiple Resource Servers configuration {#multiple-resource-servers-configuration}
## Multiple Resource Servers Сonfiguration {#multiple-resource-servers-configuration}

Each `auth_oauth2.resource_servers.<id/index>.` entry has the following variables shown in the table below. Except for the variables `id` and `oauth_provider_id`, if a resource does not configure a variable, RabbitMQ uses the variable configured at the root level. For instance, if the resource `auth_oauth2.resource_servers.prod` does not configure `preferred_username_claims` variable, RabbitMQ uses the value configured in `auth_oauth2.preferred_username_claims` for the resource `prod`.

Expand All @@ -241,6 +288,7 @@ Each `auth_oauth2.resource_servers.<id/index>.` entry has the following variable
| `resource_server_type` | The Resource Server Type required when using [Rich Authorization Request](#rich-authorization-request) token format.
| `additional_scopes_key` | Configure the plugin to look for scopes in other fields (maps to `additional_rabbitmq_scopes` in the old format).
| `scope_prefix` | [Configure the prefix for all scopes](#scope-prefix). The default value is `auth_oauth2.resource_server_id` followed by the dot `.` character.
| `scope_aliases` | [Configure scope aliases](#scope-aliases)
| `preferred_username_claims` | [List of the JWT claims](#preferred-username-claims) to look for the username associated with the token separated by commas.
| `oauth_provider_id` | The identifier of the OAuth Provider associated to this resource. RabbitMQ uses the signing keys issued by this OAuth Provider to validate tokens whose audience matches this resource's id.

Expand All @@ -259,7 +307,7 @@ auth_oauth2.resource_servers.2.id = dev

See the advanced usage section called [Multiple Resource Servers](#multiple-resource-servers) for more information on how to configure them.

#### Multiple OAuth Providers configuration {#multiple-oauth-providers-configuration}
## Multiple OAuth Providers Сonfiguration {#multiple-oauth-providers-configuration}

Each `auth_oauth2.oauth_providers.{id/index}` entry has the following sub-keys.

Expand Down
9 changes: 5 additions & 4 deletions versioned_docs/version-3.13/oauth2-examples-okta.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,15 @@ Once you've added the user to the appropriate groups and apps, they should have

## Configure RabbitMQ to use Okta as OAuth 2.0 Authentication Backend

The configuration on Okta side is done. You now have to configure RabbitMQ to use the resources you just created.
The configuration on the Okta side is now done. The next step is to configure RabbitMQ
to use the resources created earlier.

[rabbitmq.conf](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/tree/main/conf/okta/rabbitmq.conf) is a RabbitMQ configuration to **enable okta as OAuth 2.0 authentication backend** for the RabbitMQ OAuth2 and Management plugins. And [advanced.config](https://github.com/rabbitmq/rabbitmq-oauth2-tutorial/tree/main/conf/okta/advanced.config) is the RabbitMQ advanced configuration that maps RabbitMQ scopes to the permissions previously configured in Okta.

Update it with the following values (you should have noted these in the previous steps):
Update it with the following values from the earlier steps:

- **okta-domain-name** associated to your okta domain name.
- **okta_client_app_ID** associated to the okta app that you registered in okta for rabbitMQ.
* **okta-domain-name**: the Okta domain name
* **okta_client_app_ID**: the Okta app registered above to be used with RabbitMQ


## Start RabbitMQ
Expand Down
Loading

0 comments on commit 83bde59

Please sign in to comment.