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}, |                     {requires,    pre_boot}, | ||||||
|                     {enables,     external_infrastructure}]}). |                     {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_alarm currently starts memory and disk space monitors | ||||||
| -rabbit_boot_step({rabbit_alarm, | -rabbit_boot_step({rabbit_alarm, | ||||||
|                    [{description, "alarm handler"}, |                    [{description, "alarm handler"}, | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
| -include_lib("rabbit_common/include/rabbit.hrl"). | -include_lib("rabbit_common/include/rabbit.hrl"). | ||||||
| -include_lib("kernel/include/logger.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, | -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_vhost_access/4, check_resource_access/4, check_topic_access/4, | ||||||
|          check_user_id/2]). |          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 | -spec check_user_pass_login | ||||||
|         (rabbit_types:username(), rabbit_types:password()) -> |         (rabbit_types:username(), rabbit_types:password()) -> | ||||||
|             {'ok', rabbit_types:user()} | |             {'ok', rabbit_types:user()} | | ||||||
|  |  | ||||||
|  | @ -8,12 +8,13 @@ | ||||||
| -module(rabbit_plugins). | -module(rabbit_plugins). | ||||||
| -include_lib("rabbit_common/include/rabbit.hrl"). | -include_lib("rabbit_common/include/rabbit.hrl"). | ||||||
| -include_lib("kernel/include/logger.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([ensure/1]). | ||||||
| -export([validate_plugins/1, format_invalid_plugins/1]). | -export([validate_plugins/1, format_invalid_plugins/1]). | ||||||
| -export([is_strictly_plugin/1, strictly_plugins/2, strictly_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([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 for testing purpose. | ||||||
| -export([is_version_supported/2, validate_plugins/2]). | -export([is_version_supported/2, validate_plugins/2]). | ||||||
|  | @ -130,7 +131,7 @@ setup() -> | ||||||
| -spec active() -> [plugin_name()]. | -spec active() -> [plugin_name()]. | ||||||
| 
 | 
 | ||||||
| active() -> | active() -> | ||||||
|     InstalledPlugins = plugin_names(list(plugins_dir())), |     InstalledPlugins = plugin_names(list()), | ||||||
|     [App || {App, _, _} <- rabbit_misc:which_applications(), |     [App || {App, _, _} <- rabbit_misc:which_applications(), | ||||||
|             lists:member(App, InstalledPlugins)]. |             lists:member(App, InstalledPlugins)]. | ||||||
| 
 | 
 | ||||||
|  | @ -157,6 +158,13 @@ is_enabled_on_node(Name, Node) -> | ||||||
|         _Class:_Reason:_Stacktrace -> false |         _Class:_Reason:_Stacktrace -> false | ||||||
|     end. |     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. | %% @doc Get the list of plugins which are ready to be enabled. | ||||||
| 
 | 
 | ||||||
| -spec list(string()) -> [#plugin{}]. | -spec list(string()) -> [#plugin{}]. | ||||||
|  | @ -228,7 +236,7 @@ strictly_plugins(Plugins, AllPlugins) -> | ||||||
| -spec strictly_plugins([plugin_name()]) -> [plugin_name()]. | -spec strictly_plugins([plugin_name()]) -> [plugin_name()]. | ||||||
| 
 | 
 | ||||||
| strictly_plugins(Plugins) -> | strictly_plugins(Plugins) -> | ||||||
|     AllPlugins = list(plugins_dir()), |     AllPlugins = list(), | ||||||
|     lists:filter( |     lists:filter( | ||||||
|       fun(Name) -> |       fun(Name) -> | ||||||
|               is_strictly_plugin(lists:keyfind(Name, #plugin.name, AllPlugins)) |               is_strictly_plugin(lists:keyfind(Name, #plugin.name, AllPlugins)) | ||||||
|  | @ -279,11 +287,61 @@ running_plugins() -> | ||||||
|     ActivePlugins = active(), |     ActivePlugins = active(), | ||||||
|     {ok, [{App, Vsn} || {App, _ , Vsn} <- rabbit_misc:which_applications(), lists:member(App, ActivePlugins)]}. |     {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) -> | prepare_plugins(Enabled) -> | ||||||
|     ExpandDir = plugins_expand_dir(), |     ExpandDir = plugins_expand_dir(), | ||||||
|     AllPlugins = list(plugins_dir()), |     AllPlugins = list(), | ||||||
|     Wanted = dependencies(false, Enabled, AllPlugins), |     Wanted = dependencies(false, Enabled, AllPlugins), | ||||||
|     WantedPlugins = lookup_plugins(Wanted, AllPlugins), |     WantedPlugins = lookup_plugins(Wanted, AllPlugins), | ||||||
|     {ValidPlugins, Problems} = validate_plugins(WantedPlugins), |     {ValidPlugins, Problems} = validate_plugins(WantedPlugins), | ||||||
|  |  | ||||||
|  | @ -2,34 +2,54 @@ | ||||||
| %% License, v. 2.0. If a copy of the MPL was not distributed with this | %% 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/. | %% 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. | %% The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. | ||||||
| %% | %% | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| -module(rabbit_access_control_SUITE). | -module(rabbit_access_control_SUITE). | ||||||
| 
 | 
 | ||||||
| -compile(export_all). |  | ||||||
| 
 |  | ||||||
| -include_lib("eunit/include/eunit.hrl"). | -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"). | -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 | %%% Common Test callbacks | ||||||
| %%%=================================================================== | %%%=================================================================== | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| all() -> | all() -> | ||||||
|     [{group, tests}]. |     [{group, unit_tests}, | ||||||
| 
 |      {group, integration_tests}]. | ||||||
| %% replicate eunit like test resolution |  | ||||||
| all_tests() -> |  | ||||||
|     [F |  | ||||||
|      || {F, _} <- ?MODULE:module_info(functions), |  | ||||||
|         re:run(atom_to_list(F), "_test$") /= nomatch]. |  | ||||||
| 
 | 
 | ||||||
| groups() -> | 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) -> | init_per_suite(Config) -> | ||||||
|     Config. |     Config. | ||||||
|  | @ -37,20 +57,74 @@ init_per_suite(Config) -> | ||||||
| end_per_suite(_Config) -> | end_per_suite(_Config) -> | ||||||
|     ok. |     ok. | ||||||
| 
 | 
 | ||||||
|  | init_per_group(integration_tests, Config) -> | ||||||
|  |     rabbit_ct_helpers:log_environment(), | ||||||
|  |     rabbit_ct_helpers:run_setup_steps(Config); | ||||||
| init_per_group(_Group, Config) -> | init_per_group(_Group, Config) -> | ||||||
|     Config. |     Config. | ||||||
| 
 | 
 | ||||||
|  | end_per_group(integration_tests, Config) -> | ||||||
|  |     rabbit_ct_helpers:run_teardown_steps(Config); | ||||||
| end_per_group(_Group, _Config) -> | end_per_group(_Group, _Config) -> | ||||||
|     ok. |     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) -> | init_per_testcase(_TestCase, Config) -> | ||||||
|     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) -> | end_per_testcase(_TestCase, _Config) -> | ||||||
|     meck:unload(), |     meck:unload(), | ||||||
|     ok. |     ok. | ||||||
| 
 | 
 | ||||||
| expiry_timestamp_test(_) -> | %% ------------------------------------------------------------------- | ||||||
|  | %% Testcases. | ||||||
|  | %% ------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | expiry_timestamp(_) -> | ||||||
|     %% test rabbit_access_control:expiry_timestamp/1 returns the earliest expiry time |     %% test rabbit_access_control:expiry_timestamp/1 returns the earliest expiry time | ||||||
|     Now = os:system_time(seconds), |     Now = os:system_time(seconds), | ||||||
|     BeforeNow = Now - 60, |     BeforeNow = Now - 60, | ||||||
|  | @ -102,3 +176,265 @@ expiry_timestamp_test(_) -> | ||||||
|                                     {rabbit_expiry_backend, unused}]}, |                                     {rabbit_expiry_backend, unused}]}, | ||||||
|     ?assertEqual(Now, rabbit_access_control:expiry_timestamp(User7)), |     ?assertEqual(Now, rabbit_access_control:expiry_timestamp(User7)), | ||||||
|     ok. |     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