diff --git a/deps/rabbit/src/rabbit_access_control.erl b/deps/rabbit/src/rabbit_access_control.erl index c58ac30d7562..4ff752c4538c 100644 --- a/deps/rabbit/src/rabbit_access_control.erl +++ b/deps/rabbit/src/rabbit_access_control.erl @@ -9,7 +9,7 @@ -include_lib("rabbit_common/include/rabbit.hrl"). --export([check_user_pass_login/2, check_user_login/2, check_user_loopback/2, +-export([check_user_pass_login/2, check_user_login/2, check_user_login/3, check_user_loopback/2, check_vhost_access/4, check_resource_access/4, check_topic_access/4, check_user_id/2]). @@ -33,6 +33,14 @@ check_user_pass_login(Username, Password) -> check_user_login(Username, AuthProps) -> %% extra auth properties like MQTT client id are in AuthProps {ok, Modules} = application:get_env(rabbit, auth_backends), + check_user_login(Username, AuthProps, Modules). + +-spec check_user_login + (rabbit_types:username(), [{atom(), any()}], term()) -> + {'ok', rabbit_types:user()} | + {'refused', rabbit_types:username(), string(), [any()]}. + +check_user_login(Username, AuthProps, Modules) -> try lists:foldl( fun (rabbit_auth_backend_cache=ModN, {refused, _, _, _}) -> diff --git a/deps/rabbitmq_web_dispatch/priv/schema/rabbitmq_web_dispatch.schema b/deps/rabbitmq_web_dispatch/priv/schema/rabbitmq_web_dispatch.schema new file mode 100644 index 000000000000..f9f2705fea09 --- /dev/null +++ b/deps/rabbitmq_web_dispatch/priv/schema/rabbitmq_web_dispatch.schema @@ -0,0 +1,100 @@ +% vim:ft=erlang: +%% ---------------------------------------------------------------------------- +%% RabbitMQ Web Dispatch +%% +%% ---------------------------------------------------------------------------- + +%% =========================================================================== +%% Auth Backends + +%% Select an authentication backend to use for the management plugin. RabbitMQ provides an +%% internal backend in the core. +%% +%% {http_dispatch.auth_backends, [rabbit_auth_backend_internal]}, + +{translation, "rabbitmq_web_dispatch.auth_backends", +fun(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix("http_dispatch.auth_backends", Conf), + BackendModule = fun + (internal) -> rabbit_auth_backend_internal; + (ldap) -> rabbit_auth_backend_ldap; + (http) -> rabbit_auth_backend_http; + (oauth) -> rabbit_auth_backend_oauth2; + (oauth2) -> rabbit_auth_backend_oauth2; + (cache) -> rabbit_auth_backend_cache; + (amqp) -> rabbit_auth_backend_amqp; + (dummy) -> rabbit_auth_backend_dummy; + (Other) when is_atom(Other) -> Other; + (_) -> cuttlefish:invalid("Unknown/unsupported auth backend") + end, + AuthBackends = [{Num, {default, BackendModule(V)}} || {["http_dispatch", "auth_backends", Num], V} <- Settings], + AuthNBackends = [{Num, {authn, BackendModule(V)}} || {["http_dispatch", "auth_backends", Num, "authn"], V} <- Settings], + AuthZBackends = [{Num, {authz, BackendModule(V)}} || {["http_dispatch", "auth_backends", Num, "authz"], V} <- Settings], + Backends = lists:foldl( + fun({NumStr, {Type, V}}, Acc) -> + Num = case catch list_to_integer(NumStr) of + N when is_integer(N) -> N; + Err -> + cuttlefish:invalid( + iolist_to_binary(io_lib:format( + "Auth backend position in the chain should be an integer ~p", [Err]))) + end, + NewVal = case dict:find(Num, Acc) of + {ok, {AuthN, AuthZ}} -> + case {Type, AuthN, AuthZ} of + {authn, undefined, _} -> + {V, AuthZ}; + {authz, _, undefined} -> + {AuthN, V}; + _ -> + cuttlefish:invalid( + iolist_to_binary( + io_lib:format( + "Auth backend already defined for the ~pth ~p backend", + [Num, Type]))) + end; + error -> + case Type of + authn -> {V, undefined}; + authz -> {undefined, V}; + default -> {V, V} + end + end, + dict:store(Num, NewVal, Acc) + end, + dict:new(), + AuthBackends ++ AuthNBackends ++ AuthZBackends), + lists:map( + fun + ({Num, {undefined, AuthZ}}) -> + cuttlefish:warn( + io_lib:format( + "Auth backend undefined for the ~pth authz backend. Using ~p", + [Num, AuthZ])), + {AuthZ, AuthZ}; + ({Num, {AuthN, undefined}}) -> + cuttlefish:warn( + io_lib:format( + "Authz backend undefined for the ~pth authn backend. Using ~p", + [Num, AuthN])), + {AuthN, AuthN}; + ({_Num, {Auth, Auth}}) -> Auth; + ({_Num, {AuthN, AuthZ}}) -> {AuthN, AuthZ} + end, + lists:keysort(1, dict:to_list(Backends))) +end}. + +{mapping, "http_dispatch.auth_backends.$num", "rabbitmq_web_dispatch.auth_backends", [ + {datatype, atom} +]}. + +{mapping, "http_dispatch.auth_backends.$num.authn", "rabbitmq_web_dispatch.auth_backends",[ + {datatype, atom} +]}. + +{mapping, "http_dispatch.auth_backends.$num.authz", "rabbitmq_web_dispatch.auth_backends",[ + {datatype, atom} +]}. + +%{mapping, "management.test_config", "rabbitmq_management.test_config", +% [{datatype, {enum, [true, false]}}]}. diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_access_control.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_access_control.erl index 339b0fa6e286..c4561c27d400 100644 --- a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_access_control.erl +++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_access_control.erl @@ -141,7 +141,10 @@ is_authorized(ReqData, Context, Username, Password, ErrorMsg, Fun, AuthConfig, R _ -> [] end, {IP, _} = cowboy_req:peer(ReqData), - case rabbit_access_control:check_user_login(Username, AuthProps) of + + {ok, AuthBackends} = get_auth_backends(), + + case rabbit_access_control:check_user_login(Username, AuthProps, AuthBackends) of {ok, User = #user{username = ResolvedUsername, tags = Tags}} -> case rabbit_access_control:check_user_loopback(ResolvedUsername, IP) of ok -> @@ -359,3 +362,11 @@ log_access_control_result(NotOK) -> is_basic_auth_disabled(#auth_settings{basic_auth_enabled = Enabled}) -> not Enabled. + +get_auth_backends() -> + case application:get_env(rabbitmq_web_dispatch, auth_backends) of + {ok, Backends} -> + {ok, Backends}; + undefined -> + application:get_env(rabbit, auth_backends) + end. diff --git a/deps/rabbitmq_web_dispatch/test/config_schema_SUITE.erl b/deps/rabbitmq_web_dispatch/test/config_schema_SUITE.erl new file mode 100644 index 000000000000..e40730983fa7 --- /dev/null +++ b/deps/rabbitmq_web_dispatch/test/config_schema_SUITE.erl @@ -0,0 +1,54 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(config_schema_SUITE). + +-compile(export_all). + +all() -> + [ + run_snippets + ]. + +%% ------------------------------------------------------------------- +%% Testsuite setup/teardown. +%% ------------------------------------------------------------------- + +init_per_suite(Config) -> + rabbit_ct_helpers:log_environment(), + Config1 = rabbit_ct_helpers:run_setup_steps(Config), + rabbit_ct_config_schema:init_schemas(rabbitmq_web_dispatch, Config1). + + +end_per_suite(Config) -> + rabbit_ct_helpers:run_teardown_steps(Config). + +init_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_started(Config, Testcase), + Config1 = rabbit_ct_helpers:set_config(Config, [ + {rmq_nodename_suffix, Testcase} + ]), + rabbit_ct_helpers:run_steps(Config1, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps()). + +end_per_testcase(Testcase, Config) -> + Config1 = rabbit_ct_helpers:run_steps(Config, + rabbit_ct_client_helpers:teardown_steps() ++ + rabbit_ct_broker_helpers:teardown_steps()), + rabbit_ct_helpers:testcase_finished(Config1, Testcase). + +%% ------------------------------------------------------------------- +%% Testcases. +%% ------------------------------------------------------------------- + +run_snippets(Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + ?MODULE, run_snippets1, [Config]). + +run_snippets1(Config) -> + rabbit_ct_config_schema:run_snippets(Config). diff --git a/deps/rabbitmq_web_dispatch/test/config_schema_SUITE_data/rabbitmq_web_dispatch.snippets b/deps/rabbitmq_web_dispatch/test/config_schema_SUITE_data/rabbitmq_web_dispatch.snippets new file mode 100644 index 000000000000..8997912dd288 --- /dev/null +++ b/deps/rabbitmq_web_dispatch/test/config_schema_SUITE_data/rabbitmq_web_dispatch.snippets @@ -0,0 +1,64 @@ +% vim:ft=erlang: +% + +[{internal_auth_backend, + "http_dispatch.auth_backends.1 = internal", + [{rabbitmq_web_dispatch,[{auth_backends,[rabbit_auth_backend_internal]}]}], + []}, + {ldap_auth_backend, + "http_dispatch.auth_backends.1 = ldap", + [{rabbitmq_web_dispatch,[{auth_backends,[rabbit_auth_backend_ldap]}]}], + []}, + {http_auth_backend, + "http_dispatch.auth_backends.1 = http", + [{rabbitmq_web_dispatch,[{auth_backends,[rabbit_auth_backend_http]}]}], + []}, + {oauth2_auth_backend, + "http_dispatch.auth_backends.1 = oauth2", + [{rabbitmq_web_dispatch,[{auth_backends,[rabbit_auth_backend_oauth2]}]}], + []}, + {multiple_auth_backends, + "http_dispatch.auth_backends.1 = ldap +http_dispatch.auth_backends.2 = internal", + [{rabbitmq_web_dispatch, + [{auth_backends, + [rabbit_auth_backend_ldap,rabbit_auth_backend_internal]}]}], + []}, + {full_name_auth_backend, + "http_dispatch.auth_backends.1 = ldap +# uses module name instead of a short alias, \"http\" +http_dispatch.auth_backends.2 = rabbit_auth_backend_http", + [{rabbitmq_web_dispatch, + [{auth_backends,[rabbit_auth_backend_ldap,rabbit_auth_backend_http]}]}], + []}, + {third_party_auth_backend, + "http_dispatch.auth_backends.1.authn = internal +# uses module name because this backend is from a 3rd party +http_dispatch.auth_backends.1.authz = rabbit_auth_backend_ip_range", + [{rabbitmq_web_dispatch, + [{auth_backends, + [{rabbit_auth_backend_internal,rabbit_auth_backend_ip_range}]}]}], + []}, + {authn_authz_backend, + "http_dispatch.auth_backends.1.authn = ldap +http_dispatch.auth_backends.1.authz = internal", + [{rabbitmq_web_dispatch, + [{auth_backends, + [{rabbit_auth_backend_ldap,rabbit_auth_backend_internal}]}]}], + []}, + {authn_authz_multiple_backends, + "http_dispatch.auth_backends.1.authn = ldap +http_dispatch.auth_backends.1.authz = internal +http_dispatch.auth_backends.2 = internal", + [{rabbitmq_web_dispatch, + [{auth_backends, + [{rabbit_auth_backend_ldap,rabbit_auth_backend_internal}, + rabbit_auth_backend_internal]}]}], + []}, + {authn_backend_only, + "http_dispatch.auth_backends.1.authn = ldap", + [{rabbitmq_web_dispatch, + [{auth_backends, + [{rabbit_auth_backend_ldap,rabbit_auth_backend_ldap}]}]}], + []} +].