Skip to content

Commit d962e16

Browse files
dumbbellmergify[bot]
authored andcommitted
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. (cherry picked from commit 23588b6)
1 parent fea26a8 commit d962e16

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
@@ -9,6 +9,7 @@
99

1010
-include_lib("rabbit_common/include/rabbit.hrl").
1111

12+
-export([ensure_auth_backends_are_enabled/0]).
1213
-export([check_user_pass_login/2, check_user_login/2, check_user_login/3, check_user_loopback/2,
1314
check_vhost_access/4, check_resource_access/4, check_topic_access/4,
1415
check_user_id/2]).
@@ -17,6 +18,141 @@
1718

1819
%%----------------------------------------------------------------------------
1920

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

deps/rabbit/src/rabbit_plugins.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
-export([validate_plugins/1, format_invalid_plugins/1]).
1818
-export([is_strictly_plugin/1, strictly_plugins/2, strictly_plugins/1]).
1919
-export([plugins_dir/0, plugin_names/1, plugins_expand_dir/0, enabled_plugins_file/0]).
20-
-export([is_enabled/1, is_enabled_on_node/2]).
20+
-export([is_enabled/1, is_enabled_on_node/2, enabled_plugins/0]).
2121
-export([which_plugin/1]).
2222

2323
% Export for testing purpose.

0 commit comments

Comments
 (0)