Add new configuration for rabbitmq_web_dispatch.auth_backends with a fallback to the core auth_backends

This commit is contained in:
Aaron Seo 2025-02-17 16:17:05 -08:00
parent 852f8243a5
commit b048ed55bb
No known key found for this signature in database
GPG Key ID: 7F5C877C31189F37
5 changed files with 239 additions and 2 deletions

View File

@ -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, _, _, _}) ->

View File

@ -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.
%%
%% {web_dispatch.auth_backends, [rabbit_auth_backend_internal]},
{translation, "rabbitmq_web_dispatch.auth_backends",
fun(Conf) ->
Settings = cuttlefish_variable:filter_by_prefix("web_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)}} || {["web_dispatch", "auth_backends", Num], V} <- Settings],
AuthNBackends = [{Num, {authn, BackendModule(V)}} || {["web_dispatch", "auth_backends", Num, "authn"], V} <- Settings],
AuthZBackends = [{Num, {authz, BackendModule(V)}} || {["web_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, "web_dispatch.auth_backends.$num", "rabbitmq_web_dispatch.auth_backends", [
{datatype, atom}
]}.
{mapping, "web_dispatch.auth_backends.$num.authn", "rabbitmq_web_dispatch.auth_backends",[
{datatype, atom}
]}.
{mapping, "web_dispatch.auth_backends.$num.authz", "rabbitmq_web_dispatch.auth_backends",[
{datatype, atom}
]}.
%{mapping, "management.test_config", "rabbitmq_management.test_config",
% [{datatype, {enum, [true, false]}}]}.

View File

@ -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};
_ -> rabbit_log:debug("rabbitmq_web_dispatch.auth_backends not configured,
falling back to rabbit.auth_backends"),
application:get_env(rabbit, auth_backends)
end.

View File

@ -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).

View File

@ -0,0 +1,64 @@
% vim:ft=erlang:
%
[{internal_auth_backend,
"web_dispatch.auth_backends.1 = internal",
[{rabbitmq_web_dispatch,[{auth_backends,[rabbit_auth_backend_internal]}]}],
[]},
{ldap_auth_backend,
"web_dispatch.auth_backends.1 = ldap",
[{rabbitmq_web_dispatch,[{auth_backends,[rabbit_auth_backend_ldap]}]}],
[]},
{http_auth_backend,
"web_dispatch.auth_backends.1 = http",
[{rabbitmq_web_dispatch,[{auth_backends,[rabbit_auth_backend_http]}]}],
[]},
{oauth2_auth_backend,
"web_dispatch.auth_backends.1 = oauth2",
[{rabbitmq_web_dispatch,[{auth_backends,[rabbit_auth_backend_oauth2]}]}],
[]},
{multiple_auth_backends,
"web_dispatch.auth_backends.1 = ldap
web_dispatch.auth_backends.2 = internal",
[{rabbitmq_web_dispatch,
[{auth_backends,
[rabbit_auth_backend_ldap,rabbit_auth_backend_internal]}]}],
[]},
{full_name_auth_backend,
"web_dispatch.auth_backends.1 = ldap
# uses module name instead of a short alias, \"http\"
web_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,
"web_dispatch.auth_backends.1.authn = internal
# uses module name because this backend is from a 3rd party
web_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,
"web_dispatch.auth_backends.1.authn = ldap
web_dispatch.auth_backends.1.authz = internal",
[{rabbitmq_web_dispatch,
[{auth_backends,
[{rabbit_auth_backend_ldap,rabbit_auth_backend_internal}]}]}],
[]},
{authn_authz_multiple_backends,
"web_dispatch.auth_backends.1.authn = ldap
web_dispatch.auth_backends.1.authz = internal
web_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,
"web_dispatch.auth_backends.1.authn = ldap",
[{rabbitmq_web_dispatch,
[{auth_backends,
[{rabbit_auth_backend_ldap,rabbit_auth_backend_ldap}]}]}],
[]}
].