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