Merge pull request #14359 from rabbitmq/verify-auth-plugin-is-enabled
rabbit_access_control: Check configured auth backends are enabled at boot time
This commit is contained in:
commit
cf3fd1e7c7
|
|
@ -52,6 +52,14 @@
|
|||
{requires, pre_boot},
|
||||
{enables, external_infrastructure}]}).
|
||||
|
||||
-rabbit_boot_step({auth_backend_plugins_check,
|
||||
[{description, "check configured auth plugins are enabled"},
|
||||
{mfa, {rabbit_access_control,
|
||||
ensure_auth_backends_are_enabled,
|
||||
[]}},
|
||||
{requires, pre_boot},
|
||||
{enables, external_infrastructure}]}).
|
||||
|
||||
%% rabbit_alarm currently starts memory and disk space monitors
|
||||
-rabbit_boot_step({rabbit_alarm,
|
||||
[{description, "alarm handler"},
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
-include_lib("rabbit_common/include/rabbit.hrl").
|
||||
-include_lib("kernel/include/logger.hrl").
|
||||
|
||||
-export([ensure_auth_backends_are_enabled/0]).
|
||||
-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]).
|
||||
|
|
@ -18,6 +19,141 @@
|
|||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-spec ensure_auth_backends_are_enabled() -> Ret when
|
||||
Ret :: ok | {error, Reason},
|
||||
Reason :: string().
|
||||
|
||||
ensure_auth_backends_are_enabled() ->
|
||||
{ok, AuthBackends} = application:get_env(rabbit, auth_backends),
|
||||
ValidAuthBackends = filter_valid_auth_backend_configuration(
|
||||
AuthBackends, []),
|
||||
case ValidAuthBackends of
|
||||
AuthBackends ->
|
||||
ok;
|
||||
[_ | _] ->
|
||||
%% Some auth backend modules were filtered out because their
|
||||
%% corresponding plugin is either unavailable or disabled. We
|
||||
%% update the application environment variable so that
|
||||
%% authentication and authorization do not try to use them.
|
||||
?LOG_WARNING(
|
||||
"Some configured backends were dropped because their "
|
||||
"corresponding plugins are disabled. Please look at the "
|
||||
"info messages above to learn which plugin(s) should be "
|
||||
"enabled. Here is the list of auth backends kept after "
|
||||
"filering:~n~p", [ValidAuthBackends]),
|
||||
ok = application:set_env(rabbit, auth_backends, ValidAuthBackends),
|
||||
ok;
|
||||
[] ->
|
||||
%% None of the auth backend modules are usable. Log an error and
|
||||
%% abort the boot of RabbitMQ.
|
||||
?LOG_ERROR(
|
||||
"None of the configured auth backends are usable because "
|
||||
"their corresponding plugins were not enabled. Please look "
|
||||
"at the info messages above to learn which plugin(s) should "
|
||||
"be enabled."),
|
||||
{error,
|
||||
"Authentication/authorization backends require plugins to be "
|
||||
"enabled; see logs for details"}
|
||||
end.
|
||||
|
||||
filter_valid_auth_backend_configuration(
|
||||
[Mod | Rest], ValidAuthBackends)
|
||||
when is_atom(Mod) ->
|
||||
case is_auth_backend_module_enabled(Mod) of
|
||||
true ->
|
||||
ValidAuthBackends1 = [Mod | ValidAuthBackends],
|
||||
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends1);
|
||||
false ->
|
||||
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends)
|
||||
end;
|
||||
filter_valid_auth_backend_configuration(
|
||||
[{ModN, ModZ} = Mod | Rest], ValidAuthBackends)
|
||||
when is_atom(ModN) andalso is_atom(ModZ) ->
|
||||
%% Both auth backend modules must be usable to keep the entire pair.
|
||||
IsModNEnabled = is_auth_backend_module_enabled(ModN),
|
||||
IsModZEnabled = is_auth_backend_module_enabled(ModZ),
|
||||
case IsModNEnabled andalso IsModZEnabled of
|
||||
true ->
|
||||
ValidAuthBackends1 = [Mod | ValidAuthBackends],
|
||||
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends1);
|
||||
false ->
|
||||
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends)
|
||||
end;
|
||||
filter_valid_auth_backend_configuration(
|
||||
[{ModN, ModZs} | Rest], ValidAuthBackends)
|
||||
when is_atom(ModN) andalso is_list(ModZs) ->
|
||||
%% The authentication backend module and at least on of the authorization
|
||||
%% backend module must be usable to keep the entire pair.
|
||||
%%
|
||||
%% The list of authorization backend modules may be shorter than the
|
||||
%% configured one after the filtering.
|
||||
IsModNEnabled = is_auth_backend_module_enabled(ModN),
|
||||
EnabledModZs = lists:filter(fun is_auth_backend_module_enabled/1, ModZs),
|
||||
case IsModNEnabled andalso EnabledModZs =/= [] of
|
||||
true ->
|
||||
Mod1 = {ModN, EnabledModZs},
|
||||
ValidAuthBackends1 = [Mod1 | ValidAuthBackends],
|
||||
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends1);
|
||||
false ->
|
||||
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends)
|
||||
end;
|
||||
filter_valid_auth_backend_configuration([], ValidAuthBackends) ->
|
||||
lists:reverse(ValidAuthBackends).
|
||||
|
||||
is_auth_backend_module_enabled(Mod) when is_atom(Mod) ->
|
||||
%% We check if the module is provided by the core of RabbitMQ or a plugin,
|
||||
%% and if that plugin is enabled.
|
||||
{ok, Modules} = application:get_key(rabbit, modules),
|
||||
case lists:member(Mod, Modules) of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
%% The module is not provided by RabbitMQ core. Let's query
|
||||
%% plugins then.
|
||||
case rabbit_plugins:which_plugin(Mod) of
|
||||
{ok, PluginName} ->
|
||||
%% FIXME: The definition of an "enabled plugin" in
|
||||
%% `rabbit_plugins' varies from funtion to function.
|
||||
%% Sometimes, it means the "rabbitmq-plugin enable
|
||||
%% <plugin>" was executed, sometimes it means the plugin
|
||||
%% is running.
|
||||
%%
|
||||
%% This function is a boot step and is executed before
|
||||
%% plugin are started. Therefore, we can't rely on
|
||||
%% `rabbit_plugins:is_enabled/1' because it uses the
|
||||
%% latter definition of "the plugin is running, regardless
|
||||
%% of if it is enabled or not".
|
||||
%%
|
||||
%% Therefore, we use `rabbit_plugins:enabled_plugins/0'
|
||||
%% which lists explicitly enabled plugins. Unfortunately,
|
||||
%% it won't include the implicitly enabled plugins (i.e,
|
||||
%% plugins that are dependencies of explicitly enabled
|
||||
%% plugins).
|
||||
EnabledPlugins = rabbit_plugins:enabled_plugins(),
|
||||
case lists:member(PluginName, EnabledPlugins) of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
?LOG_INFO(
|
||||
"The `~ts` auth backend module is configured. "
|
||||
"However, the `~ts` plugin must be enabled in "
|
||||
"order to use this auth backend. Until then "
|
||||
"it will be skipped during "
|
||||
"authentication/authorization",
|
||||
[Mod, PluginName]),
|
||||
false
|
||||
end;
|
||||
{error, no_provider} ->
|
||||
?LOG_INFO(
|
||||
"The `~ts` auth backend module is configured. "
|
||||
"However, no plugins available provide this "
|
||||
"module. Until then it will be skipped during "
|
||||
"authentication/authorization",
|
||||
[Mod]),
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
-spec check_user_pass_login
|
||||
(rabbit_types:username(), rabbit_types:password()) ->
|
||||
{'ok', rabbit_types:user()} |
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@
|
|||
-module(rabbit_plugins).
|
||||
-include_lib("rabbit_common/include/rabbit.hrl").
|
||||
-include_lib("kernel/include/logger.hrl").
|
||||
-export([setup/0, active/0, read_enabled/1, list/1, list/2, dependencies/3, running_plugins/0]).
|
||||
-export([setup/0, active/0, read_enabled/1, list/0, list/1, list/2, dependencies/3, running_plugins/0]).
|
||||
-export([ensure/1]).
|
||||
-export([validate_plugins/1, format_invalid_plugins/1]).
|
||||
-export([is_strictly_plugin/1, strictly_plugins/2, strictly_plugins/1]).
|
||||
-export([plugins_dir/0, plugin_names/1, plugins_expand_dir/0, enabled_plugins_file/0]).
|
||||
-export([is_enabled/1, is_enabled_on_node/2]).
|
||||
-export([is_enabled/1, is_enabled_on_node/2, enabled_plugins/0]).
|
||||
-export([which_plugin/1]).
|
||||
|
||||
% Export for testing purpose.
|
||||
-export([is_version_supported/2, validate_plugins/2]).
|
||||
|
|
@ -130,7 +131,7 @@ setup() ->
|
|||
-spec active() -> [plugin_name()].
|
||||
|
||||
active() ->
|
||||
InstalledPlugins = plugin_names(list(plugins_dir())),
|
||||
InstalledPlugins = plugin_names(list()),
|
||||
[App || {App, _, _} <- rabbit_misc:which_applications(),
|
||||
lists:member(App, InstalledPlugins)].
|
||||
|
||||
|
|
@ -157,6 +158,13 @@ is_enabled_on_node(Name, Node) ->
|
|||
_Class:_Reason:_Stacktrace -> false
|
||||
end.
|
||||
|
||||
-spec list() -> [#plugin{}].
|
||||
%% @doc Get the list of plugins from the configured plugin path.
|
||||
|
||||
list() ->
|
||||
PluginsPath = plugins_dir(),
|
||||
list(PluginsPath).
|
||||
|
||||
%% @doc Get the list of plugins which are ready to be enabled.
|
||||
|
||||
-spec list(string()) -> [#plugin{}].
|
||||
|
|
@ -228,7 +236,7 @@ strictly_plugins(Plugins, AllPlugins) ->
|
|||
-spec strictly_plugins([plugin_name()]) -> [plugin_name()].
|
||||
|
||||
strictly_plugins(Plugins) ->
|
||||
AllPlugins = list(plugins_dir()),
|
||||
AllPlugins = list(),
|
||||
lists:filter(
|
||||
fun(Name) ->
|
||||
is_strictly_plugin(lists:keyfind(Name, #plugin.name, AllPlugins))
|
||||
|
|
@ -279,11 +287,61 @@ running_plugins() ->
|
|||
ActivePlugins = active(),
|
||||
{ok, [{App, Vsn} || {App, _ , Vsn} <- rabbit_misc:which_applications(), lists:member(App, ActivePlugins)]}.
|
||||
|
||||
-spec which_plugin(Module) -> Ret when
|
||||
Module :: module(),
|
||||
Ret :: {ok, PluginName} | {error, Reason},
|
||||
PluginName :: atom(),
|
||||
Reason :: no_provider.
|
||||
%% @doc Returns the name of the plugin that provides the given module.
|
||||
%%
|
||||
%% If no plugin provides the module, `{error, no_provider}' is returned.
|
||||
%%
|
||||
%% The returned plugin might not be enabled, thus using the given module might
|
||||
%% not work until the plugin is enabled.
|
||||
%%
|
||||
%% @returns An `{ok, PluginName}' tuple with the name of the plugin providing
|
||||
%% the module, or `{error, no_provider}'.
|
||||
|
||||
which_plugin(Module) ->
|
||||
Plugins = list(),
|
||||
which_plugin(Plugins, Module).
|
||||
|
||||
which_plugin([#plugin{name = Name} | Rest], Module) ->
|
||||
%% Get the list of modules belonging to this plugin.
|
||||
ModulesKey = case application:get_key(Name, modules) of
|
||||
{ok, _} = Ret ->
|
||||
Ret;
|
||||
undefined ->
|
||||
%% The plugin application might not be loaded. Load
|
||||
%% it temporarily and try again.
|
||||
case application:load(Name) of
|
||||
ok ->
|
||||
Ret = application:get_key(Name, modules),
|
||||
_ = application:unload(Name),
|
||||
Ret;
|
||||
{error, _Reason} ->
|
||||
undefined
|
||||
end
|
||||
end,
|
||||
case ModulesKey of
|
||||
{ok, Modules} ->
|
||||
case lists:member(Module, Modules) of
|
||||
true ->
|
||||
{ok, Name};
|
||||
false ->
|
||||
which_plugin(Rest, Module)
|
||||
end;
|
||||
undefined ->
|
||||
which_plugin(Rest, Module)
|
||||
end;
|
||||
which_plugin([], _Module) ->
|
||||
{error, no_provider}.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
prepare_plugins(Enabled) ->
|
||||
ExpandDir = plugins_expand_dir(),
|
||||
AllPlugins = list(plugins_dir()),
|
||||
AllPlugins = list(),
|
||||
Wanted = dependencies(false, Enabled, AllPlugins),
|
||||
WantedPlugins = lookup_plugins(Wanted, AllPlugins),
|
||||
{ValidPlugins, Problems} = validate_plugins(WantedPlugins),
|
||||
|
|
|
|||
|
|
@ -2,34 +2,54 @@
|
|||
%% 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) 2024 Broadcom. All Rights Reserved.
|
||||
%% Copyright (c) 2024-2025 Broadcom. All Rights Reserved.
|
||||
%% The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
|
||||
%%
|
||||
|
||||
|
||||
-module(rabbit_access_control_SUITE).
|
||||
|
||||
-compile(export_all).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("rabbitmq_ct_helpers/include/rabbit_assert.hrl").
|
||||
|
||||
-include_lib("rabbit_common/include/rabbit.hrl").
|
||||
|
||||
-export([all/0,
|
||||
groups/0,
|
||||
init_per_suite/1,
|
||||
end_per_suite/1,
|
||||
init_per_group/2,
|
||||
end_per_group/2,
|
||||
init_per_testcase/2,
|
||||
end_per_testcase/2,
|
||||
|
||||
expiry_timestamp/1,
|
||||
with_enabled_plugin/1,
|
||||
with_enabled_plugin_plus_internal/1,
|
||||
with_missing_plugin/1,
|
||||
with_missing_plugin_plus_internal/1,
|
||||
with_disabled_plugin/1,
|
||||
with_disabled_plugin_plus_internal/1
|
||||
]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Common Test callbacks
|
||||
%%%===================================================================
|
||||
|
||||
|
||||
all() ->
|
||||
[{group, tests}].
|
||||
|
||||
%% replicate eunit like test resolution
|
||||
all_tests() ->
|
||||
[F
|
||||
|| {F, _} <- ?MODULE:module_info(functions),
|
||||
re:run(atom_to_list(F), "_test$") /= nomatch].
|
||||
[{group, unit_tests},
|
||||
{group, integration_tests}].
|
||||
|
||||
groups() ->
|
||||
[{tests, [], all_tests()}].
|
||||
[{unit_tests, [], [expiry_timestamp]},
|
||||
{integration_tests, [], [with_enabled_plugin,
|
||||
with_enabled_plugin_plus_internal,
|
||||
with_missing_plugin,
|
||||
with_missing_plugin_plus_internal,
|
||||
with_disabled_plugin,
|
||||
with_disabled_plugin_plus_internal]}].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
Config.
|
||||
|
|
@ -37,20 +57,74 @@ init_per_suite(Config) ->
|
|||
end_per_suite(_Config) ->
|
||||
ok.
|
||||
|
||||
init_per_group(integration_tests, Config) ->
|
||||
rabbit_ct_helpers:log_environment(),
|
||||
rabbit_ct_helpers:run_setup_steps(Config);
|
||||
init_per_group(_Group, Config) ->
|
||||
Config.
|
||||
|
||||
end_per_group(integration_tests, Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(Config);
|
||||
end_per_group(_Group, _Config) ->
|
||||
ok.
|
||||
|
||||
init_per_testcase(Testcase, Config)
|
||||
when Testcase =:= with_missing_plugin orelse
|
||||
Testcase =:= with_missing_plugin_plus_internal ->
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
do_init_per_testcase(Testcase, Config, []);
|
||||
init_per_testcase(Testcase, Config)
|
||||
when Testcase =:= with_enabled_plugin orelse
|
||||
Testcase =:= with_enabled_plugin_plus_internal orelse
|
||||
Testcase =:= with_disabled_plugin orelse
|
||||
Testcase =:= with_disabled_plugin_plus_internal ->
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
do_init_per_testcase(Testcase, Config, [fun prepare_my_plugin/1]);
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
Config.
|
||||
|
||||
do_init_per_testcase(Testcase, Config, PrepSteps) ->
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
TestNumber = rabbit_ct_helpers:testcase_number(Config, ?MODULE, Testcase),
|
||||
ClusterSize = 1,
|
||||
Config1 = rabbit_ct_helpers:set_config(
|
||||
Config,
|
||||
[{rmq_nodename_suffix, Testcase},
|
||||
{rmq_nodes_count, ClusterSize},
|
||||
{tcp_ports_base, {skip_n_nodes, TestNumber * ClusterSize}},
|
||||
{start_rmq_with_plugins_disabled, true}]),
|
||||
Config2 = rabbit_ct_helpers:merge_app_env(
|
||||
Config1,
|
||||
{rabbit,
|
||||
[{log, [{file, [{level, debug}]}]}]}),
|
||||
Config3 = rabbit_ct_helpers:run_steps(
|
||||
Config2,
|
||||
PrepSteps ++
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()),
|
||||
Config3.
|
||||
|
||||
end_per_testcase(Testcase, Config)
|
||||
when Testcase =:= with_enabled_plugin orelse
|
||||
Testcase =:= with_enabled_plugin_plus_internal orelse
|
||||
Testcase =:= with_missing_plugin orelse
|
||||
Testcase =:= with_missing_plugin_plus_internal orelse
|
||||
Testcase =:= with_disabled_plugin orelse
|
||||
Testcase =:= with_disabled_plugin_plus_internal ->
|
||||
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);
|
||||
end_per_testcase(_TestCase, _Config) ->
|
||||
meck:unload(),
|
||||
ok.
|
||||
|
||||
expiry_timestamp_test(_) ->
|
||||
%% -------------------------------------------------------------------
|
||||
%% Testcases.
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
expiry_timestamp(_) ->
|
||||
%% test rabbit_access_control:expiry_timestamp/1 returns the earliest expiry time
|
||||
Now = os:system_time(seconds),
|
||||
BeforeNow = Now - 60,
|
||||
|
|
@ -102,3 +176,265 @@ expiry_timestamp_test(_) ->
|
|||
{rabbit_expiry_backend, unused}]},
|
||||
?assertEqual(Now, rabbit_access_control:expiry_timestamp(User7)),
|
||||
ok.
|
||||
|
||||
with_enabled_plugin(Config) ->
|
||||
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
|
||||
ok = rabbit_ct_broker_helpers:enable_plugin(Config, Node, my_auth_plugin),
|
||||
rabbit_ct_broker_helpers:stop_broker(Config, Node),
|
||||
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, os, unsetenv, ["RABBITMQ_ENABLED_PLUGINS"]),
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, os, unsetenv, ["LEAVE_PLUGINS_DISABLED"]),
|
||||
|
||||
AuthBackends = [my_auth_plugin],
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, application, set_env,
|
||||
[rabbit, auth_backends, AuthBackends, [{persistent, true}]]),
|
||||
|
||||
?assertEqual(
|
||||
ok,
|
||||
rabbit_ct_broker_helpers:start_broker(Config, Node)),
|
||||
|
||||
?assertMatch({error, {auth_failure, _}}, test_connection(Config, Node)),
|
||||
|
||||
ok.
|
||||
|
||||
with_enabled_plugin_plus_internal(Config) ->
|
||||
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
|
||||
ok = rabbit_ct_broker_helpers:enable_plugin(Config, Node, my_auth_plugin),
|
||||
rabbit_ct_broker_helpers:stop_broker(Config, Node),
|
||||
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, os, unsetenv, ["RABBITMQ_ENABLED_PLUGINS"]),
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, os, unsetenv, ["LEAVE_PLUGINS_DISABLED"]),
|
||||
|
||||
AuthBackends = [my_auth_plugin, rabbit_auth_backend_internal],
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, application, set_env,
|
||||
[rabbit, auth_backends, AuthBackends, [{persistent, true}]]),
|
||||
|
||||
?assertEqual(
|
||||
ok,
|
||||
rabbit_ct_broker_helpers:start_broker(Config, Node)),
|
||||
|
||||
?assertEqual(ok, test_connection(Config, Node)),
|
||||
|
||||
ok.
|
||||
|
||||
with_missing_plugin(Config) ->
|
||||
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
|
||||
rabbit_ct_broker_helpers:stop_broker(Config, Node),
|
||||
|
||||
AuthBackends = [my_auth_plugin],
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, application, set_env,
|
||||
[rabbit, auth_backends, AuthBackends, [{persistent, true}]]),
|
||||
|
||||
?assertThrow(
|
||||
{error,
|
||||
{rabbit,
|
||||
{{error,
|
||||
"Authentication/authorization backends require plugins to be "
|
||||
"enabled; see logs for details"},
|
||||
_}}},
|
||||
rabbit_ct_broker_helpers:start_broker(Config, Node)),
|
||||
|
||||
?awaitMatch(
|
||||
true,
|
||||
check_log(Config, Node, "no plugins available provide this module"),
|
||||
30000),
|
||||
|
||||
ok.
|
||||
|
||||
with_missing_plugin_plus_internal(Config) ->
|
||||
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
|
||||
rabbit_ct_broker_helpers:stop_broker(Config, Node),
|
||||
|
||||
AuthBackends = [my_auth_plugin, rabbit_auth_backend_internal],
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, application, set_env,
|
||||
[rabbit, auth_backends, AuthBackends, [{persistent, true}]]),
|
||||
|
||||
?assertEqual(
|
||||
ok,
|
||||
rabbit_ct_broker_helpers:start_broker(Config, Node)),
|
||||
|
||||
?awaitMatch(
|
||||
true,
|
||||
check_log(Config, Node, "no plugins available provide this module"),
|
||||
30000),
|
||||
|
||||
?assertEqual(ok, test_connection(Config, Node)),
|
||||
|
||||
ok.
|
||||
|
||||
with_disabled_plugin(Config) ->
|
||||
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
|
||||
rabbit_ct_broker_helpers:stop_broker(Config, Node),
|
||||
|
||||
AuthBackends = [my_auth_plugin],
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, application, set_env,
|
||||
[rabbit, auth_backends, AuthBackends, [{persistent, true}]]),
|
||||
|
||||
?assertThrow(
|
||||
{error,
|
||||
{rabbit,
|
||||
{{error,
|
||||
"Authentication/authorization backends require plugins to be "
|
||||
"enabled; see logs for details"},
|
||||
_}}},
|
||||
rabbit_ct_broker_helpers:start_broker(Config, Node)),
|
||||
|
||||
?awaitMatch(
|
||||
true,
|
||||
check_log(
|
||||
Config, Node,
|
||||
"the `my_auth_plugin` plugin must be enabled in order to use "
|
||||
"this auth backend"),
|
||||
30000),
|
||||
|
||||
ok.
|
||||
|
||||
with_disabled_plugin_plus_internal(Config) ->
|
||||
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
|
||||
rabbit_ct_broker_helpers:stop_broker(Config, Node),
|
||||
|
||||
AuthBackends = [my_auth_plugin, rabbit_auth_backend_internal],
|
||||
rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node, application, set_env,
|
||||
[rabbit, auth_backends, AuthBackends, [{persistent, true}]]),
|
||||
|
||||
?assertEqual(
|
||||
ok,
|
||||
rabbit_ct_broker_helpers:start_broker(Config, Node)),
|
||||
|
||||
?awaitMatch(
|
||||
true,
|
||||
check_log(
|
||||
Config, Node,
|
||||
"the `my_auth_plugin` plugin must be enabled in order to use "
|
||||
"this auth backend"),
|
||||
30000),
|
||||
|
||||
?assertEqual(ok, test_connection(Config, Node)),
|
||||
|
||||
ok.
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Internal helpers.
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
prepare_my_plugin(Config) ->
|
||||
case os:getenv("RABBITMQ_RUN") of
|
||||
false ->
|
||||
build_my_plugin(Config);
|
||||
_ ->
|
||||
MyPluginDir = filename:dirname(
|
||||
filename:dirname(
|
||||
code:where_is_file("my_auth_plugin.app"))),
|
||||
PluginsDir = filename:dirname(MyPluginDir),
|
||||
rabbit_ct_helpers:set_config(
|
||||
Config, [{rmq_plugins_dir, PluginsDir}])
|
||||
end.
|
||||
|
||||
build_my_plugin(Config) ->
|
||||
DataDir = filename:join(
|
||||
filename:dirname(filename:dirname(?config(data_dir, Config))),
|
||||
?MODULE_STRING ++ "_data"),
|
||||
PluginSrcDir = filename:join(DataDir, "my_auth_plugin"),
|
||||
PluginsDir = filename:join(PluginSrcDir, "plugins"),
|
||||
Config1 = rabbit_ct_helpers:set_config(Config,
|
||||
[{rmq_plugins_dir, PluginsDir}]),
|
||||
{MyPlugin, OtherPlugins} = list_my_plugin_plugins(PluginSrcDir),
|
||||
case MyPlugin of
|
||||
[] ->
|
||||
DepsDir = ?config(erlang_mk_depsdir, Config),
|
||||
Args = ["test-dist",
|
||||
{"DEPS_DIR=~ts", [DepsDir]},
|
||||
%% We clear ALL_DEPS_DIRS to make sure they are
|
||||
%% not recompiled when the plugin is built. `rabbit`
|
||||
%% was previously compiled with -DTEST and if it is
|
||||
%% recompiled because of this plugin, it will be
|
||||
%% recompiled without -DTEST: the testsuite depends
|
||||
%% on test code so we can't allow that.
|
||||
%%
|
||||
%% Note that we do not clear the DEPS variable: we need
|
||||
%% it to be correct because it is used to generate
|
||||
%% `my_auth_plugin.app` (and a RabbitMQ plugin must
|
||||
%% depend on `rabbit`).
|
||||
"ALL_DEPS_DIRS="],
|
||||
case rabbit_ct_helpers:make(Config1, PluginSrcDir, Args) of
|
||||
{ok, _} ->
|
||||
{_, OtherPlugins1} = list_my_plugin_plugins(PluginSrcDir),
|
||||
remove_other_plugins(PluginSrcDir, OtherPlugins1),
|
||||
update_cli_path(Config1, PluginSrcDir);
|
||||
{error, _} ->
|
||||
{skip,
|
||||
"Failed to compile the `my_auth_plugin` test plugin"}
|
||||
end;
|
||||
_ ->
|
||||
remove_other_plugins(PluginSrcDir, OtherPlugins),
|
||||
update_cli_path(Config1, PluginSrcDir)
|
||||
end.
|
||||
|
||||
update_cli_path(Config, PluginSrcDir) ->
|
||||
SbinDir = filename:join(PluginSrcDir, "sbin"),
|
||||
Rabbitmqctl = filename:join(SbinDir, "rabbitmqctl"),
|
||||
RabbitmqPlugins = filename:join(SbinDir, "rabbitmq-plugins"),
|
||||
RabbitmqQueues = filename:join(SbinDir, "rabbitmq-queues"),
|
||||
case filelib:is_regular(Rabbitmqctl) of
|
||||
true ->
|
||||
ct:pal(?LOW_IMPORTANCE,
|
||||
"Switching to CLI in e.g. ~ts", [Rabbitmqctl]),
|
||||
rabbit_ct_helpers:set_config(
|
||||
Config,
|
||||
[{rabbitmqctl_cmd, Rabbitmqctl},
|
||||
{rabbitmq_plugins_cmd, RabbitmqPlugins},
|
||||
{rabbitmq_queues_cmd, RabbitmqQueues}]);
|
||||
false ->
|
||||
Config
|
||||
end.
|
||||
|
||||
list_my_plugin_plugins(PluginSrcDir) ->
|
||||
Files = filelib:wildcard("plugins/*", PluginSrcDir),
|
||||
lists:partition(
|
||||
fun(Path) ->
|
||||
Filename = filename:basename(Path),
|
||||
re:run(Filename, "^my_auth_plugin-", [{capture, none}]) =:= match
|
||||
end, Files).
|
||||
|
||||
remove_other_plugins(PluginSrcDir, OtherPlugins) ->
|
||||
ok = rabbit_file:recursive_delete(
|
||||
[filename:join(PluginSrcDir, OtherPlugin)
|
||||
|| OtherPlugin <- OtherPlugins]).
|
||||
|
||||
check_log(Config, Node, Msg) ->
|
||||
LogLocations = rabbit_ct_broker_helpers:rpc(
|
||||
Config, Node,
|
||||
rabbit, log_locations, []),
|
||||
lists:any(
|
||||
fun(LogLocation) ->
|
||||
check_log1(LogLocation, Msg)
|
||||
end, LogLocations).
|
||||
|
||||
check_log1(LogLocation, Msg) ->
|
||||
case filelib:is_regular(LogLocation) of
|
||||
true ->
|
||||
{ok, Content} = file:read_file(LogLocation),
|
||||
ReOpts = [{capture, none}, multiline, unicode],
|
||||
match =:= re:run(Content, Msg, ReOpts);
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
test_connection(Config, Node) ->
|
||||
case rabbit_ct_client_helpers:open_unmanaged_connection(Config, Node) of
|
||||
Conn when is_pid(Conn) ->
|
||||
ok = rabbit_ct_client_helpers:close_connection(Conn),
|
||||
ok;
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
PROJECT = my_auth_plugin
|
||||
PROJECT_DESCRIPTION = Plugin to test access control
|
||||
PROJECT_VERSION = 1.0.0
|
||||
|
||||
define PROJECT_APP_EXTRA_KEYS
|
||||
{broker_version_requirements, []}
|
||||
endef
|
||||
|
||||
DEPS = rabbit_common rabbit
|
||||
|
||||
DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk
|
||||
DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk
|
||||
|
||||
include ../../../../../rabbitmq-components.mk
|
||||
include ../../../../../erlang.mk
|
||||
36
deps/rabbit/test/rabbit_access_control_SUITE_data/my_auth_plugin/src/my_auth_plugin.erl
vendored
Normal file
36
deps/rabbit/test/rabbit_access_control_SUITE_data/my_auth_plugin/src/my_auth_plugin.erl
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
%% 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) 2025 Broadcom. All Rights Reserved. The term “Broadcom”
|
||||
%% refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
|
||||
%%
|
||||
|
||||
-module(my_auth_plugin).
|
||||
|
||||
-behaviour(rabbit_authn_backend).
|
||||
-behaviour(rabbit_authz_backend).
|
||||
|
||||
-export([user_login_authentication/2, user_login_authorization/2,
|
||||
check_vhost_access/3, check_resource_access/4, check_topic_access/4]).
|
||||
-export([expiry_timestamp/1]).
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Implementation of rabbit_authn_backend.
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
user_login_authentication(_, _) ->
|
||||
{error, unknown_user}.
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Implementation of rabbit_authz_backend.
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
user_login_authorization(_, _) ->
|
||||
{error, unknown_user}.
|
||||
|
||||
check_vhost_access(_AuthUser, _VHostPath, _AuthzData) -> true.
|
||||
check_resource_access(_AuthUser, _Resource, _Permission, _Context) -> true.
|
||||
check_topic_access(_AuthUser, _Resource, _Permission, _Context) -> true.
|
||||
|
||||
expiry_timestamp(_AuthUser) -> never.
|
||||
Loading…
Reference in New Issue