Skip to content

Commit 23588b6

Browse files
committed
rabbit_access_control: Check configured auth backends are enabled at boot time
[Why] If a user configures an auth backend module, but doesn't enabled the plugin that provides it, it will get a crash and a stacktrace when authentication is performed. The error is not helpful to understand what the problem is. [How] We add a boot step that go through the configured auth backends and query the core of RabbitMQ and the plugins. If an auth backend is provided by a plugin, the plugin must be enabled to consider the auth backend to be valid. In the end, at least one auth backend must be valid, otherwise the boot is aborted. If only some of the configured auth backends were filtered out, but there are still some valid auth backends, we store the filtered list in the application environment variable so that authentication/authorization doesn't try to use them later. We also report invalid auth backends in the logs: * Info message for a single invalid auth backend: [info] <0.213.0> The `rabbit_auth_backend_ldap` auth backend module is configured. However, the `rabbitmq_auth_backend_ldap` plugin must be enabled in order to use this auth backend. Until then it will be skipped during authentication/authorization * Warning message when some auth backends were filtered out: [warning] <0.213.0> 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: [warning] <0.213.0> [rabbit_auth_backend_internal] * Error message when no auth backends are valid: [error] <0.213.0> 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. V2: In fact, `rabbit_plugins:is_enabled/1` indicates if a plugin is running, not if it is enabled... The new check runs as a boot step and thus is executed before plugins are started. Therefore we can't use this API. Instead, we use `rabbit_plugins:enabled_plugins/0' which lists explicitly enabled plugins. The drawback is that in the auth backend is enabled implicitly because it is a dependency of another explicitly enabled plugin, the check will still consider it is disabled and thus abort the boot. Fixes #13783.
1 parent a8bef77 commit 23588b6

File tree

6 files changed

+544
-13
lines changed

6 files changed

+544
-13
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
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]).
1717
-export([which_plugin/1]).
1818

1919
% Export for testing purpose.

0 commit comments

Comments
 (0)