diff options
6 files changed, 116 insertions, 77 deletions
diff --git a/deps/rabbit_common/mk/rabbitmq-dist.mk b/deps/rabbit_common/mk/rabbitmq-dist.mk index 978d3ddc65..35b0dda529 100644 --- a/deps/rabbit_common/mk/rabbitmq-dist.mk +++ b/deps/rabbit_common/mk/rabbitmq-dist.mk @@ -17,12 +17,6 @@ dist_verbose_0 = @echo " DIST " $@; dist_verbose_2 = set -x; dist_verbose = $(dist_verbose_$(V)) -MIX_ARCHIVES ?= $(HOME)/.mix/archives - -MIX_TASK_ARCHIVE_DEPS_VERSION = 1.0.0 -MIX_TASK_ARCHIVE_DEPS_SHA512 = 6947124c0848d0584416251fc31335d8c17ecd3dbcf8c4013a9c1f0df7a31d16b790dfc1cb45721624fb77610e31975b6804771fe5268797740002bf54c789b8 -mix_task_archive_deps = $(MIX_ARCHIVES)/mix_task_archive_deps-$(MIX_TASK_ARCHIVE_DEPS_VERSION) - # We take the version of an Erlang application from the .app file. This # macro is called like this: # @@ -45,12 +39,6 @@ $(shell awk ' }' $(1)) endef -define get_mix_project_version -$(shell cd $(1) && \ - $(MIX) do deps.get, deps.compile, compile >/dev/null && \ - $(MIX) run --no-start -e "IO.puts(Mix.Project.config[:version])") -endef - # Define the target to create an .ez plugin archive for an # Erlang.mk-based project. This macro is called like this: # @@ -91,40 +79,12 @@ ERLANGMK_DIST_EZS += $$(dist_$(1)_ez) endef -# Define the target to create an .ez plugin archive for a Mix-based -# project. This macro is called like this: -# -# $(call do_ez_target_mix,app_name,app_version,app_dir) - -define get_mix_project_dep_ezs -$(shell cd $(1) && \ - $(MIX) do deps.get, deps.compile, compile >/dev/null && \ - $(MIX) archive.build.all.list -e -o $(DIST_DIR) --skip "rabbit $(ERLANGMK_DIST_APPS)") -endef - -define do_ez_target_mix -dist_$(1)_ez_dir = $$(if $(2),$(DIST_DIR)/$(1)-$(2), \ - $$(if $$(VERSION),$(DIST_DIR)/$(1)-$$(VERSION),$(DIST_DIR)/$(1))) -dist_$(1)_ez = $$(dist_$(1)_ez_dir).ez - -$$(dist_$(1)_ez): APP = $(1) -$$(dist_$(1)_ez): VSN = $(2) -$$(dist_$(1)_ez): SRC_DIR = $(3) -$$(dist_$(1)_ez): EZ_DIR = $$(abspath $$(dist_$(1)_ez_dir)) -$$(dist_$(1)_ez): EZ = $$(dist_$(1)_ez) -$$(dist_$(1)_ez): $$(if $$(wildcard _build/dev/lib/$(1)/ebin $(3)/priv),\ - $$(filter-out %/dep_built,$$(call core_find,$$(wildcard _build/dev/lib/$(1)/ebin $(3)/priv),*)),) - -MIX_DIST_EZS += $$(dist_$(1)_ez) -EXTRA_DIST_EZS += $$(call get_mix_project_dep_ezs,$(3)) - -endef - # Real entry point: it tests the existence of an .app file to determine # if it is an Erlang application (and therefore if it should be provided # as an .ez plugin archive) and calls do_ez_target_erlangmk. If instead -# it finds a Mix configuration file, it calls do_ez_target_mix. It -# should be called as: +# it finds a Mix configuration file, it is skipped, as the only elixir +# applications in the directory are used by rabbitmq_cli and compiled +# with it. # # $(call ez_target,path_to_app) @@ -134,9 +94,7 @@ dist_$(1)_appfile = $$(dist_$(1)_appdir)/ebin/$(1).app dist_$(1)_mixfile = $$(dist_$(1)_appdir)/mix.exs $$(if $$(shell test -f $$(dist_$(1)_appfile) && echo OK), \ - $$(eval $$(call do_ez_target_erlangmk,$(1),$$(call get_app_version,$$(dist_$(1)_appfile)),$$(dist_$(1)_appdir))), \ - $$(if $$(shell test -f $$(dist_$(1)_mixfile) && [ "x$(1)" != "xrabbitmqctl" ] && [ "x$(1)" != "xrabbitmq_cli" ] && echo OK), \ - $$(eval $$(call do_ez_target_mix,$(1),$$(call get_mix_project_version,$$(dist_$(1)_appdir)),$$(dist_$(1)_appdir))))) + $$(eval $$(call do_ez_target_erlangmk,$(1),$$(call get_app_version,$$(dist_$(1)_appfile)),$$(dist_$(1)_appdir)))) endef @@ -202,16 +160,6 @@ ifneq ($(DIST_AS_EZS),) $(verbose) rm -rf $(EZ_DIR) $(EZ_DIR).manifest endif -$(MIX_DIST_EZS): $(mix_task_archive_deps) - $(verbose) cd $(SRC_DIR) && \ - $(MIX) do deps.get, deps.compile, compile, archive.build.all \ - -e -o $(abspath $(DIST_DIR)) --skip "rabbit $(ERLANGMK_DIST_APPS)" - -MIX_TASK_ARCHIVE_DEPS_URL = https://github.com/rabbitmq/mix_task_archive_deps/releases/download/$(MIX_TASK_ARCHIVE_DEPS_VERSION)/mix_task_archive_deps-$(MIX_TASK_ARCHIVE_DEPS_VERSION).ez - -$(mix_task_archive_deps): - $(gen_verbose) mix archive.install --force --sha512 $(MIX_TASK_ARCHIVE_DEPS_SHA512) $(MIX_TASK_ARCHIVE_DEPS_URL) - # We need to recurse because the top-level make instance is evaluated # before dependencies are downloaded. 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)). |