rabbitmq / rabbitmq-management

RabbitMQ Management UI and HTTP API
https://www.rabbitmq.com/management.html
Other
372 stars 165 forks source link

Support IdentityServer4 OAuth2 token provider #754

Closed papugamichal closed 3 years ago

papugamichal commented 4 years ago

Proposed Changes

Please consider to add support for scenarios where Rabbit Management backend handle both UAA and IdentityServer4 (IS4) OAuth2 token providers.

Proposed solution uses OIDC (OpenId-Connect library) client to connect both Rabbit Management UI javascript backend and IdentityServer. In this case OIDC was configured to handle signin/signout redirections, store and management of returned JWT token in browser memory and automatically handle to refresh token when it's about to expire soon.

First attempt to integrate Management UI with IdentityServer assumed to obligatory use modified rabbitmq-auth-backend-oauth2 pluggin but now it is optional. I will provide manual for both scenarios. This implementation add few more options to _rabbitmqmanagement secrion, but with respect to UAA implementation there are shared parameters for both providers.

Below example config has arrows for new parameters used in IS4 case:

{rabbitmq_management, [
       {enable_oauth2, true},
       {uaa_client_id, "RabbitMq.ManagementUI"},
       {uaa_location, "http://localhost:50000"},
 --->  {oauth2_scopes, "read write add delete"}, <---
 --->  {oauth2_implementation, identityserver},  <---  %% uaa or identityserver
]},

_oauth2scopes parameter was configured as string _oauth2implementation parameter was configured as enum [uaa , identityserver] with defeault uaa value when its not configured

Options like: _enableoauth2, _uaa_clientid and _uaalocation are shared by UAA and IdentityServer implementations. Their meainng in both cases are the same.

Why do I need _oauth2scopes parameter? Analyzed UAA implementation shows that Singular in rpFrame.js has hard coded "openid" scope with whom is requesting to UAA server. I have considered to make Singular use _oauth2scopes as well, but I am not entirely sure about Singular origin, why he's code looks like obfuscated in Rabbit repo and any other but potentailly consequences regardled to UAA.

rpFrame.js

t=S("openid",h,g);
function S(t,n,r){return a.uaaLocation+"/oauth/authorize?response_type=token&scope="+encodeURIComponent(t)+"&client_id="+a.clientId+"&prompt=none&redirect_uri=.....

I found that when IdentityServer is requested without any scope assigned to IS4's ApiResource (ApiResource = resource_server_id) it returns token where aud (audience) section hasn't have included information about configured _resource_serverid - in another words it has no information that this token is valid for configured _resource_serverid.

eyJhbGciOiJSUzI1NiIsImtpZCI6Ik9YTndkX1RMZGJJY09leDZiTExTdFEiLCJ0eXAiOiJKV1QifQ.eyJuYmYiOjE1NzU4MTE4NDIsImV4cCI6MTYwNzM2ODc2OCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwMCIsImF1ZCI6IkdpdEh1Yi5SYWJiaXRNcS5NYW5hZ2VtZW50V2Vic2l0ZS5Mb2NhbCIsIm5vbmNlIjoiZmI1NWFjODRjYzEzNDMxZDg5YTFiMGY0YTAyNTE4NTQiLCJpYXQiOjE1NzU4MTE4NDIsImF0X2hhc2giOiJnYnY3RVB4WGJ4d0Q1bU9lVFAtLVFRIiwic19oYXNoIjoiV2dtb05aT3VoUWNsQWJNaW10YlllUSIsInNpZCI6IlBHVnNCM2lDTnFlTkhuYlJ2RGVKb0EiLCJzdWIiOiIxMTEiLCJhdXRoX3RpbWUiOjE1NzU4MTE4MDUsImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.pYBEUUe3ViAY-LDVwB5QP1fW0VFuxH017i16zvLjjDaZKJZVFLxu0EEc0SeNUD-kaifPPzB9d0KlCspptewsoGpFRbPfCFrTUOVl2G18QvW2ENUKMIxtGB_rMQLfeAEn9lYARwr1-W4i-ej_ZC2M00BlfUXiPyEYDGDRziE2z3kYmHODDlhZcrmLsu_BF0GQcaQzUj4HA9-ntw5OwkM9F1md9OhYtAzjIjTqIgkX8h9cw7u_cp_wAIlaQikxNeDQP3KO-IrwW39bkX6z0i3uk8oLOOC9h3bcd01BLMnzfor_UlN8VCZkEDTXF2S_0ywbliNq76w_t2Nnzju_B-Z2Rw

When such token is provided authorization backend responses (to log) with message:

2019-12-08 14:30:11.233 [warning] <0.874.0> 
HTTP access denied: Authentication using an OAuth 2/JWT token failed: 
{invalid_aud, {resource_id_not_found_in_aud,<<"rabbitmq">>, [<<"http://localhost:50000/resources">>]}}

How to tests it? I prepared dedicated solution with preconfigured IdentityServer4. README.md file contains manual how to setup and use it.

Case where we do need use any ComplexClaims: (please note that any user will have the same perrmision set)

advanced.config

{rabbitmq_management, [
    {enable_uaa, true},
    {uaa_client_id, "GitHub.RabbitMq.ManagementWebsite.Local"},
    {uaa_location, "http://localhost:50000"},
    {oauth2_scopes, "openid rabbitmq.configure:*/* rabbitmq.read:*/* rabbitmq.write:*/* rabbitmq.tag:monitoring"},
    {oauth2_implementation, identityserver}
  ]},

{rabbitmq_auth_backend_oauth2, [
    {resource_server_id, <<"rabbitmq">>},
    {key_config, [
        {signing_keys, #{
            <<"OXNwd_TLdbIcOex6bLLStQ">> => {map, #{    
                <<"kty">> => <<"RSA">>,
                <<"alg">> => <<"RS256">>,
                <<"e">> => <<"AQAB">>,
                <<"n">> => <<"sfq4sO8d2EPTxQqb56JTHNZ0OYr00qXfhxubmJUoiUTWjmTxtD48hTP2j-vgG4qw0eKeZyV8qZpA0q5w9HLk5xXNghwf5KMUDGiNhFd_5URBpVBcZ0fDSX2FwDTyRbF0XrxiYofROQeVOxbMIXHD0vCzTrzoypD6AA6wWI1ej4t-Zn5mpCG4DijIe-Ki0MJ4D58yY6YmuoERPhFNW1DmSKSohhex89anfrBfkIoyaBg9KNMtQW7oky5sPDH0GYqEWeXf4jqv4ODL-FtJhi74inUc2knXgznlkC15r-X_ubPv0ngvxetUELJFReWqyQDELwk_CBLZtxUiCkMZNl6JgQ">>
                }}
        }}
    ]}
]}

Case where we use ComplexClaims: (please note that each user may have his own permisions set included in _extra_scopessource JWT section)

advanced.config

{rabbitmq_management, [
    {enable_uaa, true},
    {uaa_client_id, "GitHub.RabbitMq.ManagementWebsite.Local"},
    {uaa_location, "http://localhost:50000"},
    {oauth2_scopes, "openid rabbitmq.managementwebsite"},
    {oauth2_implementation, identityserver}
  ]},

{rabbitmq_auth_backend_oauth2, [
    {resource_server_id, <<"rabbitmq">>},
    {extra_scopes_source, <<"rabbit_resources">>},
    {key_config, [
        {signing_keys, #{
            <<"OXNwd_TLdbIcOex6bLLStQ">> => {map, #{    
                <<"kty">> => <<"RSA">>,
                <<"alg">> => <<"RS256">>,
                <<"e">> => <<"AQAB">>,
                <<"n">> => <<"sfq4sO8d2EPTxQqb56JTHNZ0OYr00qXfhxubmJUoiUTWjmTxtD48hTP2j-vgG4qw0eKeZyV8qZpA0q5w9HLk5xXNghwf5KMUDGiNhFd_5URBpVBcZ0fDSX2FwDTyRbF0XrxiYofROQeVOxbMIXHD0vCzTrzoypD6AA6wWI1ej4t-Zn5mpCG4DijIe-Ki0MJ4D58yY6YmuoERPhFNW1DmSKSohhex89anfrBfkIoyaBg9KNMtQW7oky5sPDH0GYqEWeXf4jqv4ODL-FtJhi74inUc2knXgznlkC15r-X_ubPv0ngvxetUELJFReWqyQDELwk_CBLZtxUiCkMZNl6JgQ">>
                }}
        }}
    ]}
]}

Types of Changes

What types of changes does your code introduce to this project?

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask on the mailing list. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

michaelklishin commented 4 years ago

How can we QA this?

I'd like to do some renaming of config keys and it would be great to support the current (UAA-specific) key names as aliases. I'd be happy to handle that.

michaelklishin commented 4 years ago

rabbitmq.managementwebsite is one name that we must change and our team potentially would not have control over this (unlike, say, the configuration keys). "Management website" is not a term any of our documentation uses. Moreover, the "website" part is redundant. I'm not sure where does this IdentityServer uses that scope name but it should probably be rabbitmq.management_ui. I'd prefer rabbitmq.management (it would also match the name of the permission tag the UI uses) but that is not specific enough.

papugamichal commented 4 years ago

I will provide solution with IdentityServer for testing purposes.

What are benefits of keeping UAA naming? Are there - in other plugins - dependencies to it? That was a deliberate change, because I though with IdentityServer support I do not want to have in code properties names related to specific OAuth2 provider. I really want to know your point of view and pro and cons to keep UAA naming convention.

Update: thanks for your response. It's looks like we have posted in the same time.

papugamichal commented 4 years ago

rabbitmq.managementwebsite is one name that we must change and our team potentially would not have control over this (unlike, say, the configuration keys).

I should point out that i tried to create code as flexible as possible, thus "rabbitmq.managementwebsite" it's only example. As "rabbitmq" equals to "resouce_server_id" parameter value, that "managementwebsite" can be any other value, but it has to mach value defined by "{role_picklock, <<"managementwebsite">>}" developed in https://github.com/rabbitmq/rabbitmq-auth-backend-oauth2/pull/39.

michaelklishin commented 4 years ago

OK, I'm just making sure this value is not relied on elsewhere.

papugamichal commented 4 years ago

@michaelklishin ok, its reasonable to keep variable UAA naming convention because using uaa jwt to decode incoming token. I reverted original naming.

In separate repo I prepared dedicated solution with preconfigured IdentityServer4 for Rabbit Management. Its based on IdentityServer4.Demo solution which its quite simple but i wanted to keep it even simpler so removed unnecessarily parts and keep only configuration for Rabbit. README.md file contains manual how to setup and use it.

Below advanced.config file was preconfigured to working with IdentityServer:

[
{rabbit, [
    {auth_backends, [rabbit_auth_backend_oauth2]},
    {log, [
        {file, [
            {level, debug}
        ]}
    ]}
]},

{rabbitmq_management, [
  {enable_uaa, true},
  {uaa_client_id, "GitHub.RabbitMq.ManagementWebsite.Local"},
  {uaa_location, "http://localhost:50000"},
  {oauth2_scopes, "openid rabbitmq.managementwebsite"},
  {oauth2_implementation, identityserver},
  {tcp_config, [
    {port, 15672},
    {cowboy_opts, [{max_header_value_length, 10000}]}
    ]}
]},

{rabbitmq_auth_backend_oauth2, [
    {resource_server_id, <<"rabbitmq">>},
    {key_config, [
        {signing_keys, #{
            <<"OXNwd_TLdbIcOex6bLLStQ">> => {map, #{    
                <<"kty">> => <<"RSA">>,
                <<"alg">> => <<"RS256">>,
                <<"e">> => <<"AQAB">>,
                <<"n">> => <<"sfq4sO8d2EPTxQqb56JTHNZ0OYr00qXfhxubmJUoiUTWjmTxtD48hTP2j-vgG4qw0eKeZyV8qZpA0q5w9HLk5xXNghwf5KMUDGiNhFd_5URBpVBcZ0fDSX2FwDTyRbF0XrxiYofROQeVOxbMIXHD0vCzTrzoypD6AA6wWI1ej4t-Zn5mpCG4DijIe-Ki0MJ4D58yY6YmuoERPhFNW1DmSKSohhex89anfrBfkIoyaBg9KNMtQW7oky5sPDH0GYqEWeXf4jqv4ODL-FtJhi74inUc2knXgznlkC15r-X_ubPv0ngvxetUELJFReWqyQDELwk_CBLZtxUiCkMZNl6JgQ">>
                }}
        }}
    ]},
    {role_picklock, <<"managementwebsite">>},
    {role_scope_map, #{
            <<"RabbitManagement_Management">> => [<<"tag:management">>, <<"configure:*/*">>],
            <<"RabbitManagement_Policymaker">> => [<<"tag:policymaker">>, <<"configure:*/*">>],
            <<"RabbitManagement_Monitoring">> => [<<"tag:monitoring">>, <<"configure:*/*">>],
            <<"RabbitManagement_Administrator">> => [<<"tag:administrator">>, <<"configure:*/*">>, <<"write:*/*">>, <<"read:*/*">>]}
    }
]}
].

Before first use, please check values of "signing_key_id" (in my example: OXNwd_TLdbIcOex6bLLStQ), and "n" with "kid" and "n" values returned by http://localhost:50000/.well-known/openid-configuration/jwks endpoint.

In this example i keep set "debug" logging level, because sometimes its can provice some useful information.

Last thing to QA this PR, you have to build rabbitmq_auth_backend_oauth2 plugin for ManagementUI to be able to decode incomming tokens.

Please, let me know if you have any troubles with IdentityServer. Is if sufficient to QA this PR?

papugamichal commented 4 years ago

@michaelklishin Michael, any update? How can I help with handling this PR?

michaelklishin commented 4 years ago

There doesn't see to be any progress on this PR => closing.

papugamichal commented 4 years ago

@michaelklishin thank you for your support so far. I have been planning to get back and finish this feature in next couple of days. Please, be in touch :)

michaelklishin commented 4 years ago

@papugamichal OK, fair enough

papugamichal commented 4 years ago

I do not understand why we need to bundle a regular, minimized and two "slim" versions of oidc-client at the same time.

Good point. Cleaned up.

papugamichal commented 4 years ago

I'd really like to see the IdentityServer-specific /callback to be named accordingly.

OK. I think that /silent.html also should be renamed to be more precise about it existence purpose.

My proposition to rename: /callback.html to /afterSignInCallback.html or /afterSignInRecall.html /silent.html to /silenTokenRenewCallback.html or /silenTokenRenewRecall.html

What do you think?

michaelklishin commented 4 years ago

I'd use /postSigninCallback.html and /silentTokenRenewal.html.

papugamichal commented 4 years ago

@michaelklishin Michael, do you see what else should be done around this implementation? Edit: by miss click I closed this PR... sorry, reopen.

papugamichal commented 4 years ago

@michaelklishin i would like to ask you to review this PR once again. Do you have any other remarks?

michaelklishin commented 4 years ago

FWIW this has not been forgotten.

dcorbacho commented 3 years ago

Moved to https://github.com/rabbitmq/rabbitmq-server/pull/2628