Skip to content

Commit cf3fd1e

Browse files
Merge pull request #14359 from rabbitmq/verify-auth-plugin-is-enabled
rabbit_access_control: Check configured auth backends are enabled at boot time
2 parents 87c6615 + 23588b6 commit cf3fd1e

File tree

6 files changed

+606
-17
lines changed

6 files changed

+606
-17
lines changed

deps/rabbit/src/rabbit.erl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@
5252
{requires, pre_boot},
5353
{enables, external_infrastructure}]}).
5454

55+
-rabbit_boot_step({auth_backend_plugins_check,
56+
[{description, "check configured auth plugins are enabled"},
57+
{mfa, {rabbit_access_control,
58+
ensure_auth_backends_are_enabled,
59+
[]}},
60+
{requires, pre_boot},
61+
{enables, external_infrastructure}]}).
62+
5563
%% rabbit_alarm currently starts memory and disk space monitors
5664
-rabbit_boot_step({rabbit_alarm,
5765
[{description, "alarm handler"},

deps/rabbit/src/rabbit_access_control.erl

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
-include_lib("rabbit_common/include/rabbit.hrl").
1111
-include_lib("kernel/include/logger.hrl").
1212

13+
-export([ensure_auth_backends_are_enabled/0]).
1314
-export([check_user_pass_login/2, check_user_login/2, check_user_login/3, check_user_loopback/2,
1415
check_vhost_access/4, check_resource_access/4, check_topic_access/4,
1516
check_user_id/2]).
@@ -18,6 +19,141 @@
1819

1920
%%----------------------------------------------------------------------------
2021

22+
-spec ensure_auth_backends_are_enabled() -> Ret when
23+
Ret :: ok | {error, Reason},
24+
Reason :: string().
25+
26+
ensure_auth_backends_are_enabled() ->
27+
{ok, AuthBackends} = application:get_env(rabbit, auth_backends),
28+
ValidAuthBackends = filter_valid_auth_backend_configuration(
29+
AuthBackends, []),
30+
case ValidAuthBackends of
31+
AuthBackends ->
32+
ok;
33+
[_ | _] ->
34+
%% Some auth backend modules were filtered out because their
35+
%% corresponding plugin is either unavailable or disabled. We
36+
%% update the application environment variable so that
37+
%% authentication and authorization do not try to use them.
38+
?LOG_WARNING(
39+
"Some configured backends were dropped because their "
40+
"corresponding plugins are disabled. Please look at the "
41+
"info messages above to learn which plugin(s) should be "
42+
"enabled. Here is the list of auth backends kept after "
43+
"filering:~n~p", [ValidAuthBackends]),
44+
ok = application:set_env(rabbit, auth_backends, ValidAuthBackends),
45+
ok;
46+
[] ->
47+
%% None of the auth backend modules are usable. Log an error and
48+
%% abort the boot of RabbitMQ.
49+
?LOG_ERROR(
50+
"None of the configured auth backends are usable because "
51+
"their corresponding plugins were not enabled. Please look "
52+
"at the info messages above to learn which plugin(s) should "
53+
"be enabled."),
54+
{error,
55+
"Authentication/authorization backends require plugins to be "
56+
"enabled; see logs for details"}
57+
end.
58+
59+
filter_valid_auth_backend_configuration(
60+
[Mod | Rest], ValidAuthBackends)
61+
when is_atom(Mod) ->
62+
case is_auth_backend_module_enabled(Mod) of
63+
true ->
64+
ValidAuthBackends1 = [Mod | ValidAuthBackends],
65+
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends1);
66+
false ->
67+
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends)
68+
end;
69+
filter_valid_auth_backend_configuration(
70+
[{ModN, ModZ} = Mod | Rest], ValidAuthBackends)
71+
when is_atom(ModN) andalso is_atom(ModZ) ->
72+
%% Both auth backend modules must be usable to keep the entire pair.
73+
IsModNEnabled = is_auth_backend_module_enabled(ModN),
74+
IsModZEnabled = is_auth_backend_module_enabled(ModZ),
75+
case IsModNEnabled andalso IsModZEnabled of
76+
true ->
77+
ValidAuthBackends1 = [Mod | ValidAuthBackends],
78+
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends1);
79+
false ->
80+
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends)
81+
end;
82+
filter_valid_auth_backend_configuration(
83+
[{ModN, ModZs} | Rest], ValidAuthBackends)
84+
when is_atom(ModN) andalso is_list(ModZs) ->
85+
%% The authentication backend module and at least on of the authorization
86+
%% backend module must be usable to keep the entire pair.
87+
%%
88+
%% The list of authorization backend modules may be shorter than the
89+
%% configured one after the filtering.
90+
IsModNEnabled = is_auth_backend_module_enabled(ModN),
91+
EnabledModZs = lists:filter(fun is_auth_backend_module_enabled/1, ModZs),
92+
case IsModNEnabled andalso EnabledModZs =/= [] of
93+
true ->
94+
Mod1 = {ModN, EnabledModZs},
95+
ValidAuthBackends1 = [Mod1 | ValidAuthBackends],
96+
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends1);
97+
false ->
98+
filter_valid_auth_backend_configuration(Rest, ValidAuthBackends)
99+
end;
100+
filter_valid_auth_backend_configuration([], ValidAuthBackends) ->
101+
lists:reverse(ValidAuthBackends).
102+
103+
is_auth_backend_module_enabled(Mod) when is_atom(Mod) ->
104+
%% We check if the module is provided by the core of RabbitMQ or a plugin,
105+
%% and if that plugin is enabled.
106+
{ok, Modules} = application:get_key(rabbit, modules),
107+
case lists:member(Mod, Modules) of
108+
true ->
109+
true;
110+
false ->
111+
%% The module is not provided by RabbitMQ core. Let's query
112+
%% plugins then.
113+
case rabbit_plugins:which_plugin(Mod) of
114+
{ok, PluginName} ->
115+
%% FIXME: The definition of an "enabled plugin" in
116+
%% `rabbit_plugins' varies from funtion to function.
117+
%% Sometimes, it means the "rabbitmq-plugin enable
118+
%% <plugin>" was executed, sometimes it means the plugin
119+
%% is running.
120+
%%
121+
%% This function is a boot step and is executed before
122+
%% plugin are started. Therefore, we can't rely on
123+
%% `rabbit_plugins:is_enabled/1' because it uses the
124+
%% latter definition of "the plugin is running, regardless
125+
%% of if it is enabled or not".
126+
%%
127+
%% Therefore, we use `rabbit_plugins:enabled_plugins/0'
128+
%% which lists explicitly enabled plugins. Unfortunately,
129+
%% it won't include the implicitly enabled plugins (i.e,
130+
%% plugins that are dependencies of explicitly enabled
131+
%% plugins).
132+
EnabledPlugins = rabbit_plugins:enabled_plugins(),
133+
case lists:member(PluginName, EnabledPlugins) of
134+
true ->
135+
true;
136+
false ->
137+
?LOG_INFO(
138+
"The `~ts` auth backend module is configured. "
139+
"However, the `~ts` plugin must be enabled in "
140+
"order to use this auth backend. Until then "
141+
"it will be skipped during "
142+
"authentication/authorization",
143+
[Mod, PluginName]),
144+
false
145+
end;
146+
{error, no_provider} ->
147+
?LOG_INFO(
148+
"The `~ts` auth backend module is configured. "
149+
"However, no plugins available provide this "
150+
"module. Until then it will be skipped during "
151+
"authentication/authorization",
152+
[Mod]),
153+
false
154+
end
155+
end.
156+
21157
-spec check_user_pass_login
22158
(rabbit_types:username(), rabbit_types:password()) ->
23159
{'ok', rabbit_types:user()} |

deps/rabbit/src/rabbit_plugins.erl

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
-module(rabbit_plugins).
99
-include_lib("rabbit_common/include/rabbit.hrl").
1010
-include_lib("kernel/include/logger.hrl").
11-
-export([setup/0, active/0, read_enabled/1, list/1, list/2, dependencies/3, running_plugins/0]).
11+
-export([setup/0, active/0, read_enabled/1, list/0, list/1, list/2, dependencies/3, running_plugins/0]).
1212
-export([ensure/1]).
1313
-export([validate_plugins/1, format_invalid_plugins/1]).
1414
-export([is_strictly_plugin/1, strictly_plugins/2, strictly_plugins/1]).
1515
-export([plugins_dir/0, plugin_names/1, plugins_expand_dir/0, enabled_plugins_file/0]).
16-
-export([is_enabled/1, is_enabled_on_node/2]).
16+
-export([is_enabled/1, is_enabled_on_node/2, enabled_plugins/0]).
17+
-export([which_plugin/1]).
1718

1819
% Export for testing purpose.
1920
-export([is_version_supported/2, validate_plugins/2]).
@@ -130,7 +131,7 @@ setup() ->
130131
-spec active() -> [plugin_name()].
131132

132133
active() ->
133-
InstalledPlugins = plugin_names(list(plugins_dir())),
134+
InstalledPlugins = plugin_names(list()),
134135
[App || {App, _, _} <- rabbit_misc:which_applications(),
135136
lists:member(App, InstalledPlugins)].
136137

@@ -157,6 +158,13 @@ is_enabled_on_node(Name, Node) ->
157158
_Class:_Reason:_Stacktrace -> false
158159
end.
159160

161+
-spec list() -> [#plugin{}].
162+
%% @doc Get the list of plugins from the configured plugin path.
163+
164+
list() ->
165+
PluginsPath = plugins_dir(),
166+
list(PluginsPath).
167+
160168
%% @doc Get the list of plugins which are ready to be enabled.
161169

162170
-spec list(string()) -> [#plugin{}].
@@ -228,7 +236,7 @@ strictly_plugins(Plugins, AllPlugins) ->
228236
-spec strictly_plugins([plugin_name()]) -> [plugin_name()].
229237

230238
strictly_plugins(Plugins) ->
231-
AllPlugins = list(plugins_dir()),
239+
AllPlugins = list(),
232240
lists:filter(
233241
fun(Name) ->
234242
is_strictly_plugin(lists:keyfind(Name, #plugin.name, AllPlugins))
@@ -279,11 +287,61 @@ running_plugins() ->
279287
ActivePlugins = active(),
280288
{ok, [{App, Vsn} || {App, _ , Vsn} <- rabbit_misc:which_applications(), lists:member(App, ActivePlugins)]}.
281289

290+
-spec which_plugin(Module) -> Ret when
291+
Module :: module(),
292+
Ret :: {ok, PluginName} | {error, Reason},
293+
PluginName :: atom(),
294+
Reason :: no_provider.
295+
%% @doc Returns the name of the plugin that provides the given module.
296+
%%
297+
%% If no plugin provides the module, `{error, no_provider}' is returned.
298+
%%
299+
%% The returned plugin might not be enabled, thus using the given module might
300+
%% not work until the plugin is enabled.
301+
%%
302+
%% @returns An `{ok, PluginName}' tuple with the name of the plugin providing
303+
%% the module, or `{error, no_provider}'.
304+
305+
which_plugin(Module) ->
306+
Plugins = list(),
307+
which_plugin(Plugins, Module).
308+
309+
which_plugin([#plugin{name = Name} | Rest], Module) ->
310+
%% Get the list of modules belonging to this plugin.
311+
ModulesKey = case application:get_key(Name, modules) of
312+
{ok, _} = Ret ->
313+
Ret;
314+
undefined ->
315+
%% The plugin application might not be loaded. Load
316+
%% it temporarily and try again.
317+
case application:load(Name) of
318+
ok ->
319+
Ret = application:get_key(Name, modules),
320+
_ = application:unload(Name),
321+
Ret;
322+
{error, _Reason} ->
323+
undefined
324+
end
325+
end,
326+
case ModulesKey of
327+
{ok, Modules} ->
328+
case lists:member(Module, Modules) of
329+
true ->
330+
{ok, Name};
331+
false ->
332+
which_plugin(Rest, Module)
333+
end;
334+
undefined ->
335+
which_plugin(Rest, Module)
336+
end;
337+
which_plugin([], _Module) ->
338+
{error, no_provider}.
339+
282340
%%----------------------------------------------------------------------------
283341

284342
prepare_plugins(Enabled) ->
285343
ExpandDir = plugins_expand_dir(),
286-
AllPlugins = list(plugins_dir()),
344+
AllPlugins = list(),
287345
Wanted = dependencies(false, Enabled, AllPlugins),
288346
WantedPlugins = lookup_plugins(Wanted, AllPlugins),
289347
{ValidPlugins, Problems} = validate_plugins(WantedPlugins),

0 commit comments

Comments
 (0)