summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Klishin <klishinm@vmware.com>2023-05-16 19:03:54 +0400
committerGitHub <noreply@github.com>2023-05-16 19:03:54 +0400
commite290acbd7d6f989737c258d617f551c3fff58ada (patch)
tree504008906ed8b61ec4823b79fb85dec213b33b0d
parentc7d0427d621493f00a3aa0d6e7720f709f3769a6 (diff)
parent1cd84b36ec127d462f0ee1321636ff9f7dd0b22f (diff)
downloadrabbitmq-server-git-e290acbd7d6f989737c258d617f551c3fff58ada.tar.gz
Merge pull request #8001 from rabbitmq/configurable-oauth2-scope-prefix
Configure Oauth scope prefix
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema24
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl17
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets2
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/test/scope_SUITE.erl4
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl86
5 files changed, 112 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 : <resource_server_id>.<scope>.
+%% Nowadays, there is a new setting called scope_prefix and RabbitMQ's scopes follow this pattern instead:
+%% <scope_prefix><scope>. Note that there is no dot in between.
+%% The default value of this setting is `<resource_server_id>.`.
%% {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 `<resource_server_id>.` 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..eea7771782 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, <<ResourceServerId/binary, ".">>),
+ 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 = <<ResourceServerId/binary, ".">>,
- 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..3d47146a3a 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,
@@ -39,6 +40,7 @@ all() ->
test_post_process_token_payload_complex_claims,
test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field,
test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_field,
+ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_and_custom_scope_prefix,
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_field,
test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_source_field,
test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_scope_source_field,
@@ -101,6 +103,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 = [
@@ -719,6 +722,49 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field(
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
+
+test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_and_custom_scope_prefix(_) ->
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
+ application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<>>),
+ Alias = <<"client-alias-1">>,
+ application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
+ Alias => [
+ <<"configure:vhost/one">>,
+ <<"write:vhost/two">>,
+ <<"read:vhost/one">>,
+ <<"read:vhost/two">>,
+ <<"read:vhost/two/abc">>,
+ <<"tag:management">>,
+ <<"tag:custom">>
+ ]
+ }),
+
+ VHost = <<"vhost">>,
+ Username = <<"username">>,
+ Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(
+ ?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk),
+
+ {ok, #auth_user{username = Username, tags = [custom, management]} = AuthUser} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
+ assert_vhost_access_granted(AuthUser, VHost),
+ assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>),
+
+ assert_resource_access_granted(AuthUser, VHost, <<"one">>, configure),
+ assert_resource_access_granted(AuthUser, VHost, <<"one">>, read),
+ assert_resource_access_granted(AuthUser, VHost, <<"two">>, read),
+ assert_resource_access_granted(AuthUser, VHost, <<"two">>, write),
+ assert_resource_access_denied(AuthUser, VHost, <<"three">>, configure),
+ assert_resource_access_denied(AuthUser, VHost, <<"three">>, read),
+ assert_resource_access_denied(AuthUser, VHost, <<"three">>, write),
+
+ application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
+ application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
+ application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix),
+ application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
+
test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_field(_) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
@@ -1175,16 +1221,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 +1244,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 +1276,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 +1285,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 +1294,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)).