From faffd6fa98c70da90812617aeeaba4069bd4c918 Mon Sep 17 00:00:00 2001 From: Marcial Rosales Date: Wed, 26 Apr 2023 12:40:38 +0200 Subject: Configure Oauth scope prefix separate from resource_server_id --- .../schema/rabbitmq_auth_backend_oauth2.schema | 24 ++++++++++++- .../src/rabbit_auth_backend_oauth2.erl | 17 ++++----- .../rabbitmq_auth_backend_oauth2.snippets | 2 ++ .../test/scope_SUITE.erl | 4 +-- .../test/unit_SUITE.erl | 42 ++++++++++++++++------ 5 files changed, 68 insertions(+), 21 deletions(-) diff --git a/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema b/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema index 6f8c048516..8ee313ba5e 100644 --- a/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema +++ b/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema @@ -5,8 +5,16 @@ %% %% ---------------------------------------------------------------------------- -%% A prefix used for scopes to avoid scope collisions (or unintended overlap). It is an empty string by default. +%% OAuth Resource identity. Usage: +%% - This is the identity of a RabbitMQ server/cluster used as the +%% recipient of JWT Tokens (see audience claim, https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3). +%% - This is also the resource identifier used by RabbitMQ server/cluster in the authorization and access token +%% requests (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-indicators-05#page-3) %% +%% Up to version 3.12, RabbitMQ's scopes followed this pattern : .. +%% Nowadays, there is a new setting called scope_prefix and RabbitMQ's scopes follow this pattern instead: +%% . Note that there is no dot in between. +%% The default value of this setting is `.`. %% {resource_server_id, <<"my_rabbit_server">>}, {mapping, @@ -19,6 +27,20 @@ fun(Conf) -> list_to_binary(cuttlefish:conf_get("auth_oauth2.resource_server_id", Conf)) end}. +%% A prefix used for scopes to avoid scope collisions (or unintended overlap). If not configured, +%% it is defaulted to `.` to maintain backward compatibility. Empty string is a permitted value. +%% +%% {scope_prefix, <<"api:/rabbitmq:">>}, + +{mapping, + "auth_oauth2.scope_prefix", + "rabbitmq_auth_backend_oauth2.scope_prefix", + [{datatype, string}]}. + +{translation, + "rabbitmq_auth_backend_oauth2.scope_prefix", + fun(Conf) -> list_to_binary(cuttlefish:conf_get("auth_oauth2.scope_prefix", Conf)) + end}. %% An identifier used for JWT Tokens compliant with Rich Authorization Request spec %% RabbitMq uses this field as discriminator to filter out permissions meant for RabbitMQ diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl index 1d4f7fadb7..78e1242549 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl @@ -33,6 +33,7 @@ -define(APP, rabbitmq_auth_backend_oauth2). -define(RESOURCE_SERVER_ID, resource_server_id). +-define(SCOPE_PREFIX, scope_prefix). %% a term defined for Rich Authorization Request tokens to identify a RabbitMQ permission -define(RESOURCE_SERVER_TYPE, resource_server_type). %% verify server_server_id aud field is on the aud field @@ -498,23 +499,23 @@ post_process_payload_in_rich_auth_request_format(#{<<"authorization_details">> : validate_payload(#{?SCOPE_JWT_FIELD := _Scope } = DecodedToken) -> ResourceServerEnv = application:get_env(?APP, ?RESOURCE_SERVER_ID, <<>>), ResourceServerId = rabbit_data_coercion:to_binary(ResourceServerEnv), - validate_payload(DecodedToken, ResourceServerId). + ScopePrefix = application:get_env(?APP, ?SCOPE_PREFIX, <>), + validate_payload(DecodedToken, ResourceServerId, ScopePrefix). -validate_payload(#{?SCOPE_JWT_FIELD := Scope, ?AUD_JWT_FIELD := Aud} = DecodedToken, ResourceServerId) -> +validate_payload(#{?SCOPE_JWT_FIELD := Scope, ?AUD_JWT_FIELD := Aud} = DecodedToken, ResourceServerId, ScopePrefix) -> case check_aud(Aud, ResourceServerId) of - ok -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ResourceServerId)}}; + ok -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}}; {error, Err} -> {refused, {invalid_aud, Err}} end; -validate_payload(#{?SCOPE_JWT_FIELD := Scope} = DecodedToken, ResourceServerId) -> +validate_payload(#{?SCOPE_JWT_FIELD := Scope} = DecodedToken, ResourceServerId, ScopePrefix) -> case application:get_env(?APP, ?VERIFY_AUD, true) of true -> {error, {badarg, {aud_field_is_missing}}}; - false -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ResourceServerId)}} + false -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}} end. filter_scopes(Scopes, <<"">>) -> Scopes; -filter_scopes(Scopes, ResourceServerId) -> - PrefixPattern = <>, - matching_scopes_without_prefix(Scopes, PrefixPattern). +filter_scopes(Scopes, ScopePrefix) -> + matching_scopes_without_prefix(Scopes, ScopePrefix). check_aud(_, <<>>) -> ok; check_aud(Aud, ResourceServerId) -> diff --git a/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets b/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets index 908733238b..21aca91815 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets +++ b/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets @@ -1,6 +1,7 @@ [ {oauth2_pem_config2, "auth_oauth2.resource_server_id = new_resource_server_id + auth_oauth2.scope_prefix = new_resource_server_id. auth_oauth2.resource_server_type = new_resource_server_type auth_oauth2.additional_scopes_key = my_custom_scope_key auth_oauth2.preferred_username_claims.1 = user_name @@ -22,6 +23,7 @@ [ {rabbitmq_auth_backend_oauth2, [ {resource_server_id,<<"new_resource_server_id">>}, + {scope_prefix,<<"new_resource_server_id.">>}, {resource_server_type,<<"new_resource_server_type">>}, {extra_scopes_source, <<"my_custom_scope_key">>}, {preferred_username_claims, [<<"user_name">>, <<"username">>, <<"email">>]}, diff --git a/deps/rabbitmq_auth_backend_oauth2/test/scope_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/scope_SUITE.erl index 84effae77c..73062ce08b 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/scope_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/scope_SUITE.erl @@ -20,7 +20,7 @@ all() -> permission_resource, permission_topic ]. - + variable_expansion(_Config) -> Scenarios = [ { "Emtpy Scopes", @@ -31,7 +31,7 @@ variable_expansion(_Config) -> }, { "No Scopes", #{ - <<"client_id">> => <<"some_client">> + <<"client_id">> => <<"some_client">> }, <<"default">>, [] }, { "Expand token's var and vhost", diff --git a/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl index c6a1b27b45..e9d775bf90 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl @@ -17,6 +17,7 @@ all() -> [ test_own_scope, test_validate_payload_resource_server_id_mismatch, + test_validate_payload_with_scope_prefix, test_validate_payload, test_validate_payload_when_verify_aud_false, test_successful_access_with_a_token, @@ -101,6 +102,7 @@ end_per_testcase(_, Config) -> -define(UTIL_MOD, rabbit_auth_backend_oauth2_test_util). -define(RESOURCE_SERVER_ID, <<"rabbitmq">>). -define(RESOURCE_SERVER_TYPE, <<"rabbitmq-type">>). +-define(DEFAULT_SCOPE_PREFIX, <<"rabbitmq.">>). test_post_process_token_payload(_) -> ArgumentsExpections = [ @@ -1175,16 +1177,16 @@ test_command_pem_no_kid(Config) -> test_own_scope(_) -> Examples = [ - {<<"foo">>, [<<"foo">>, <<"foo.bar">>, <<"bar.foo">>, + {<<"foo.">>, [<<"foo">>, <<"foo.bar">>, <<"bar.foo">>, <<"one.two">>, <<"foobar">>, <<"foo.other.third">>], [<<"bar">>, <<"other.third">>]}, - {<<"foo">>, [], []}, - {<<"foo">>, [<<"foo">>, <<"other.foo.bar">>], []}, + {<<"foo.">>, [], []}, + {<<"foo.">>, [<<"foo">>, <<"other.foo.bar">>], []}, {<<"">>, [<<"foo">>, <<"bar">>], [<<"foo">>, <<"bar">>]} ], lists:map( - fun({ResId, Src, Dest}) -> - Dest = rabbit_auth_backend_oauth2:filter_scopes(Src, ResId) + fun({ScopePrefix, Src, Dest}) -> + Dest = rabbit_auth_backend_oauth2:filter_scopes(Src, ScopePrefix) end, Examples). @@ -1198,10 +1200,30 @@ test_validate_payload_resource_server_id_mismatch(_) -> ?assertEqual({refused, {invalid_aud, {resource_id_not_found_in_aud, ?RESOURCE_SERVER_ID, [<<"foo">>,<<"bar">>]}}}, - rabbit_auth_backend_oauth2:validate_payload(NoKnownResourceServerId, ?RESOURCE_SERVER_ID)), + rabbit_auth_backend_oauth2:validate_payload(NoKnownResourceServerId, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)), ?assertEqual({refused, {invalid_aud, {resource_id_not_found_in_aud, ?RESOURCE_SERVER_ID, []}}}, - rabbit_auth_backend_oauth2:validate_payload(EmptyAud, ?RESOURCE_SERVER_ID)). + rabbit_auth_backend_oauth2:validate_payload(EmptyAud, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)). + +test_validate_payload_with_scope_prefix(_) -> + Scenarios = [ { <<>>, + #{<<"aud">> => [?RESOURCE_SERVER_ID], + <<"scope">> => [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ]}, + [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ] + }, + { <<"some-prefix::">>, + #{<<"aud">> => [?RESOURCE_SERVER_ID], + <<"scope">> => [<<"some-prefix::foo">>, <<"foo.bar">>, <<"some-prefix::other.third">> ]}, + [<<"foo">>, <<"other.third">>] + } + + ], + + lists:map(fun({ ScopePrefix, Token, ExpectedScopes}) -> + ?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID], <<"scope">> => ExpectedScopes } }, + rabbit_auth_backend_oauth2:validate_payload(Token, ?RESOURCE_SERVER_ID, ScopePrefix)) + end + , Scenarios). test_validate_payload(_) -> KnownResourceServerId = #{<<"aud">> => [?RESOURCE_SERVER_ID], @@ -1210,7 +1232,7 @@ test_validate_payload(_) -> <<"foobar">>, <<"rabbitmq.other.third">>]}, ?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID], <<"scope">> => [<<"bar">>, <<"other.third">>]}}, - rabbit_auth_backend_oauth2:validate_payload(KnownResourceServerId, ?RESOURCE_SERVER_ID)). + rabbit_auth_backend_oauth2:validate_payload(KnownResourceServerId, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)). test_validate_payload_when_verify_aud_false(_) -> WithoutAud = #{ @@ -1219,7 +1241,7 @@ test_validate_payload_when_verify_aud_false(_) -> <<"foobar">>, <<"rabbitmq.other.third">>]}, ?assertEqual({ok, #{ <<"scope">> => [<<"bar">>, <<"other.third">>]}}, - rabbit_auth_backend_oauth2:validate_payload(WithoutAud, ?RESOURCE_SERVER_ID)), + rabbit_auth_backend_oauth2:validate_payload(WithoutAud, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)), WithAudWithUnknownResourceId = #{ <<"aud">> => [<<"unknown">>], @@ -1228,7 +1250,7 @@ test_validate_payload_when_verify_aud_false(_) -> <<"foobar">>, <<"rabbitmq.other.third">>]}, ?assertEqual({ok, #{<<"aud">> => [<<"unknown">>], <<"scope">> => [<<"bar">>, <<"other.third">>]}}, - rabbit_auth_backend_oauth2:validate_payload(WithAudWithUnknownResourceId, ?RESOURCE_SERVER_ID)). + rabbit_auth_backend_oauth2:validate_payload(WithAudWithUnknownResourceId, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)). -- cgit v1.2.1