rabbitmq / rabbitmq-server

Open source RabbitMQ: core server and tier 1 (built-in) plugins
https://www.rabbitmq.com/
Other
12.28k stars 3.91k forks source link

Drop built-in support for keycloak token format (potential breaking change) #12324

Open MarcialRosales opened 1 month ago

MarcialRosales commented 1 month ago

Is your feature request related to a problem? Please describe.

RabbitMQ has been supporting a token format used by Keycloak which carried the tokens in a map whose key is permissions and this map is the value of the token's claim called authorization.

This is an adhoc token layout that it is not necessary to be supported in code but instead via configuration. Should Keycloak ever changed this layout, users can adjust their configuration accordingly without having to make a code change in RabbitMQ.

JWT Keycloak format 1: (not supported in code)

{
  "jti": "865a14bc-d1d7-4b81-8d14-94c4af2bd61f",
  "exp": 1586670977,
  "nbf": 0,
  "iat": 1586670677,
  "iss": "http://localhost:8090/auth/realms/wstutorial",
  "aud": "account",
  "sub": "e4713c5f-d662-4156-93ee-5110ec6007bd",
  "typ": "Bearer",
  "azp": "demo-app",
  "auth_time": 0,
  "session_state": "3aa0f14a-2178-41b1-bff8-fb6364ac1865",
  "acr": "1",
  "realm_access": {
    "roles": [
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "profile email",
  "email_verified": false,
  "preferred_username": "johndoe"
}

Keycloak JWT format 2: (supported in code)

{
  "authorization": {
    "permissions": [
      {
        "scopes": [
          "rabbitmq-resource.read:*/*"
        ],
        "rsid": "2c390fe4-02ad-41c7-98a2-cebb8c60ccf1",
        "rsname": "allvhost"
      },
      {
        "scopes": [
          "rabbitmq-resource-read"
        ],
        "rsid": "e7f12e94-4c34-43d8-b2b1-c516af644cee",
        "rsname": "vhost1"
      },
      {
        "rsid": "12ac3d1c-28c2-4521-8e33-0952eff10bd9",
        "rsname": "Default Resource"
      }
    ]
  },
  "scope": "email profile",
}

Describe the solution you'd like

Users of keycloak who depend on the token format 2 will have to modify their RabbitMQ configuration to tell RabbitMQ where to get the scope from.

auth_oauth2.additional_scopes_key = authorization.permissions.scope 

With the above configuration, RabbitMQ navigates the map structure. First it looks up the claim authorization from the token. It may return a map or a list of maps. If it is a map, it looks up the next keyword in the map. It finds the keyword permissions. It returns this time a list rather than a map. RabbitMQ iterates over the list and if the list's element is a map and has the next keyword, scope, it extracts them and it continues iterating the list.

Users who depend on the token format 1 will have to modify their RabbitMQ configuration to the following:

auth_oauth2.additional_scopes_key = resource_access.account.roles  real_access.roles

This format is simpler than the previous format as it only combines map objects as opposed to maps and lists.

If the user is already using auth_oauth2.additional_scopes_key, e.g. auth_oauth2.additional_scopes_key = roles the user can use any of these two configuration layouts:

auth_oauth2.additional_scopes_key = authorization.permissions.scope roles

or

auth_oauth2.additional_scopes_key.1 = authorization.permissions.scope 
auth_oauth2.additional_scopes_key.2 = roles

Describe alternatives you've considered

No response

Additional context

No response

michaelklishin commented 1 month ago

This sounds really complex for a vendor-specific feature. If Keycloak does change their format, treating "Keycloak v1" and "Keycloak v2" as two different IDPs without this extensive configuration would be much easier.

MarcialRosales commented 1 month ago

we already have additional_scopes_key . It is just a matter of separating the key by dots and navigating the maps. I have rejected in the past requests where the scopes were underneath another map ..similar to how Keycloak format works. I had not invested in this type of flexibility until today when I am refactoring and simplifying the implementation of oauth2 backend. I had forgotten that we had a custom look-up of scopes for keycloak.

With this change we can forget entirely where the scopes are located, pretty much.

It is a much bigger change the feature i am currently working on to support vendors like Azure/Entra and/or OAuth0 and who knows what others in the future.