Skip to content

Commit 5de5f89

Browse files
authored
Merge pull request #14408 from rabbitmq/mergify/bp/v4.1.x/pr-14359
rabbit_access_control: Check configured auth backends are enabled at boot time (backport #14359)
2 parents bc281d5 + f5a4a05 commit 5de5f89

File tree

6 files changed

+607
-17
lines changed

6 files changed

+607
-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: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
-module(rabbit_access_control).
99

1010
-include_lib("rabbit_common/include/rabbit.hrl").
11+
-include_lib("kernel/include/logger.hrl").
1112

13+
-export([ensure_auth_backends_are_enabled/0]).
1214
-export([check_user_pass_login/2, check_user_login/2, check_user_login/3, check_user_loopback/2,
1315
check_vhost_access/4, check_resource_access/4, check_topic_access/4,
1416
check_user_id/2]).
@@ -17,6 +19,141 @@
1719

1820
%%----------------------------------------------------------------------------
1921

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+
20157
-spec check_user_pass_login
21158
(rabbit_types:username(), rabbit_types:password()) ->
22159
{'ok', rabbit_types:user()} |

deps/rabbit/src/rabbit_plugins.erl

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88
-module(rabbit_plugins).
99
-include_lib("rabbit_common/include/rabbit.hrl").
10-
-export([setup/0, active/0, read_enabled/1, list/1, list/2, dependencies/3, running_plugins/0]).
10+
-export([setup/0, active/0, read_enabled/1, list/0, list/1, list/2, dependencies/3, running_plugins/0]).
1111
-export([ensure/1]).
1212
-export([validate_plugins/1, format_invalid_plugins/1]).
1313
-export([is_strictly_plugin/1, strictly_plugins/2, strictly_plugins/1]).
1414
-export([plugins_dir/0, plugin_names/1, plugins_expand_dir/0, enabled_plugins_file/0]).
15-
-export([is_enabled/1, is_enabled_on_node/2]).
15+
-export([is_enabled/1, is_enabled_on_node/2, enabled_plugins/0]).
16+
-export([which_plugin/1]).
1617

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

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

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

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

161169
-spec list(string()) -> [#plugin{}].
@@ -227,7 +235,7 @@ strictly_plugins(Plugins, AllPlugins) ->
227235
-spec strictly_plugins([plugin_name()]) -> [plugin_name()].
228236

229237
strictly_plugins(Plugins) ->
230-
AllPlugins = list(plugins_dir()),
238+
AllPlugins = list(),
231239
lists:filter(
232240
fun(Name) ->
233241
is_strictly_plugin(lists:keyfind(Name, #plugin.name, AllPlugins))
@@ -278,11 +286,61 @@ running_plugins() ->
278286
ActivePlugins = active(),
279287
{ok, [{App, Vsn} || {App, _ , Vsn} <- rabbit_misc:which_applications(), lists:member(App, ActivePlugins)]}.
280288

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

283341
prepare_plugins(Enabled) ->
284342
ExpandDir = plugins_expand_dir(),
285-
AllPlugins = list(plugins_dir()),
343+
AllPlugins = list(),
286344
Wanted = dependencies(false, Enabled, AllPlugins),
287345
WantedPlugins = lookup_plugins(Wanted, AllPlugins),
288346
{ValidPlugins, Problems} = validate_plugins(WantedPlugins),

0 commit comments

Comments
 (0)