Skip to content

Commit d46f07c

Browse files
committed
Add SASL mechanism ANONYMOUS
## 1. Introduce new SASL mechanism ANONYMOUS ### What? Introduce a new `rabbit_auth_mechanism` implementation for SASL mechanism ANONYMOUS called `rabbit_auth_mechanism_anonymous`. ### Why? As described in AMQP section 5.3.3.1, ANONYMOUS should be used when the client doesn't need to authenticate. Introducing a new `rabbit_auth_mechanism` consolidates and simplifies how anonymous logins work across all RabbitMQ protocols that support SASL. This commit therefore allows AMQP 0.9.1, AMQP 1.0, stream clients to connect out of the box to RabbitMQ without providing any username or password. Today's AMQP 0.9.1 and stream protocol client libs hard code RabbitMQ default credentials `guest:guest` for example done in: * https://github.com/rabbitmq/rabbitmq-java-client/blob/0215e85643a9ae0800822869be0200024e2ab569/src/main/java/com/rabbitmq/client/ConnectionFactory.java#L58-L61 * https://github.com/rabbitmq/amqp091-go/blob/ddb7a2f0685689063e6d709b8e417dbf9d09469c/uri.go#L31-L32 Hard coding RabbitMQ specific default credentials in dozens of different client libraries is an anti-pattern in my opinion. Furthermore, there are various AMQP 1.0 and MQTT client libraries which we do not control or maintain and which still should work out of the box when a user is getting started with RabbitMQ (that is without providing `guest:guest` credentials). ### How? The old RabbitMQ 3.13 AMQP 1.0 plugin `default_user` [configuration](https://github.com/rabbitmq/rabbitmq-server/blob/146b4862d8e570b344c99c37d91246760e218b18/deps/rabbitmq_amqp1_0/Makefile#L6) is replaced with the following two new `rabbit` configurations: ``` {anonymous_login_user, <<"guest">>}, {anonymous_login_pass, <<"guest">>}, ``` We call it `anonymous_login_user` because this user will be used for anonymous logins. The subsequent commit uses the same setting for anonymous logins in MQTT. Hence, this user is orthogonal to the protocol used when the client connects. Setting `anonymous_login_pass` could have been left out. This commit decides to include it because our documentation has so far recommended: > It is highly recommended to pre-configure a new user with a generated username and password or delete the guest user > or at least change its password to reasonably secure generated value that won't be known to the public. By having the new module `rabbit_auth_mechanism_anonymous` internally authenticate with `anonymous_login_pass` instead of blindly allowing access without any password, we protect operators that relied on the sentence: > or at least change its password to reasonably secure generated value that won't be known to the public To ease the getting started experience, since RabbitMQ already deploys a guest user with full access to the default virtual host `/`, this commit also allows SASL mechanism ANONYMOUS in `rabbit` setting `auth_mechanisms`. In production, operators should disable SASL mechanism ANONYMOUS by setting `anonymous_login_user` to `none` (or by removing ANONYMOUS from the `auth_mechanisms` setting. This will be documented separately. Even if operators forget or don't read the docs, this new ANONYMOUS mechanism won't do any harm because it relies on the default user name `guest` and password `guest`, which is recommended against in production, and who by default can only connect from the local host. ## 2. Require SASL security layer in AMQP 1.0 ### What? An AMQP 1.0 client must use the SASL security layer. ### Why? This is in line with the mandatory usage of SASL in AMQP 0.9.1 and RabbitMQ stream protocol. Since (presumably) any AMQP 1.0 client knows how to authenticate with a username and password using SASL mechanism PLAIN, any AMQP 1.0 client also (presumably) implements the trivial SASL mechanism ANONYMOUS. Skipping SASL is not recommended in production anyway. By requiring SASL, configuration for operators becomes easier. Following the principle of least surprise, when an an operator configures `auth_mechanisms` to exclude `ANONYMOUS`, anonymous logins will be prohibited in SASL and also by disallowing skipping the SASL layer. ### How? This commit implements AMQP 1.0 figure 2.13. A follow-up commit needs to be pushed to `v3.13.x` which will use SASL mechanism `anon` instead of `none` in the Erlang AMQP 1.0 client such that AMQP 1.0 shovels running on 3.13 can connect to 4.0 RabbitMQ nodes.
1 parent cabe873 commit d46f07c

File tree

18 files changed

+363
-283
lines changed

18 files changed

+363
-283
lines changed

deps/amqp10_client/src/amqp10_client.erl

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,8 @@ parse_result(Map) ->
429429
throw(plain_sasl_missing_userinfo);
430430
_ ->
431431
case UserInfo of
432-
[] -> none;
433-
undefined -> none;
432+
[] -> anon;
433+
undefined -> anon;
434434
U -> parse_usertoken(U)
435435
end
436436
end,
@@ -456,11 +456,6 @@ parse_result(Map) ->
456456
Ret0#{tls_opts => {secure_port, TlsOpts}}
457457
end.
458458

459-
460-
parse_usertoken(undefined) ->
461-
none;
462-
parse_usertoken("") ->
463-
none;
464459
parse_usertoken(U) ->
465460
[User, Pass] = string:tokens(U, ":"),
466461
{plain,
@@ -532,7 +527,7 @@ parse_uri_test_() ->
532527
[?_assertEqual({ok, #{address => "my_host",
533528
port => 9876,
534529
hostname => <<"my_host">>,
535-
sasl => none}}, parse_uri("amqp://my_host:9876")),
530+
sasl => anon}}, parse_uri("amqp://my_host:9876")),
536531
%% port defaults
537532
?_assertMatch({ok, #{port := 5671}}, parse_uri("amqps://my_host")),
538533
?_assertMatch({ok, #{port := 5672}}, parse_uri("amqp://my_host")),

deps/amqp10_client/test/system_SUITE.erl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ stop_amqp10_client_app(Config) ->
103103
%% -------------------------------------------------------------------
104104

105105
init_per_group(rabbitmq, Config0) ->
106-
Config = rabbit_ct_helpers:set_config(Config0,
107-
{sasl, {plain, <<"guest">>, <<"guest">>}}),
106+
Config = rabbit_ct_helpers:set_config(Config0, {sasl, anon}),
108107
Config1 = rabbit_ct_helpers:merge_app_env(Config,
109108
[{rabbit,
110109
[{max_message_size, 134217728}]}]),
@@ -115,7 +114,7 @@ init_per_group(rabbitmq_strict, Config0) ->
115114
{sasl, {plain, <<"guest">>, <<"guest">>}}),
116115
Config1 = rabbit_ct_helpers:merge_app_env(Config,
117116
[{rabbit,
118-
[{amqp1_0_default_user, none},
117+
[{anonymous_login_user, none},
119118
{max_message_size, 134217728}]}]),
120119
rabbit_ct_helpers:run_steps(Config1, rabbit_ct_broker_helpers:setup_steps());
121120

deps/rabbit/BUILD.bazel

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,16 @@ _APP_ENV = """[
5858
{default_user_tags, [administrator]},
5959
{default_vhost, <<"/">>},
6060
{default_permissions, [<<".*">>, <<".*">>, <<".*">>]},
61-
{amqp1_0_default_user, <<"guest">>},
62-
{amqp1_0_default_vhost, <<"/">>},
6361
{loopback_users, [<<"guest">>]},
6462
{password_hashing_module, rabbit_password_hashing_sha256},
6563
{server_properties, []},
6664
{collect_statistics, none},
6765
{collect_statistics_interval, 5000},
6866
{mnesia_table_loading_retry_timeout, 30000},
6967
{mnesia_table_loading_retry_limit, 10},
70-
{auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
68+
{anonymous_login_user, <<"guest">>},
69+
{anonymous_login_pass, <<"guest">>},
70+
{auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'ANONYMOUS']},
7171
{auth_backends, [rabbit_auth_backend_internal]},
7272
{delegate_count, 16},
7373
{trace_vhosts, []},

deps/rabbit/Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,19 @@ define PROJECT_ENV
3838
{default_user_tags, [administrator]},
3939
{default_vhost, <<"/">>},
4040
{default_permissions, [<<".*">>, <<".*">>, <<".*">>]},
41-
{amqp1_0_default_user, <<"guest">>},
42-
{amqp1_0_default_vhost, <<"/">>},
4341
{loopback_users, [<<"guest">>]},
4442
{password_hashing_module, rabbit_password_hashing_sha256},
4543
{server_properties, []},
4644
{collect_statistics, none},
4745
{collect_statistics_interval, 5000},
4846
{mnesia_table_loading_retry_timeout, 30000},
4947
{mnesia_table_loading_retry_limit, 10},
50-
{auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
48+
%% The identity to act as for anonymous logins.
49+
{anonymous_login_user, <<"guest">>},
50+
{anonymous_login_pass, <<"guest">>},
51+
%% "The server mechanisms are ordered in decreasing level of preference."
52+
%% AMQP §5.3.3.1
53+
{auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'ANONYMOUS']},
5154
{auth_backends, [rabbit_auth_backend_internal]},
5255
{delegate_count, 16},
5356
{trace_vhosts, []},

deps/rabbit/app.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def all_beam_files(name = "all_beam_files"):
5858
"src/rabbit_amqqueue_sup_sup.erl",
5959
"src/rabbit_auth_backend_internal.erl",
6060
"src/rabbit_auth_mechanism_amqplain.erl",
61+
"src/rabbit_auth_mechanism_anonymous.erl",
6162
"src/rabbit_auth_mechanism_cr_demo.erl",
6263
"src/rabbit_auth_mechanism_plain.erl",
6364
"src/rabbit_autoheal.erl",
@@ -313,6 +314,7 @@ def all_test_beam_files(name = "all_test_beam_files"):
313314
"src/rabbit_amqqueue_sup_sup.erl",
314315
"src/rabbit_auth_backend_internal.erl",
315316
"src/rabbit_auth_mechanism_amqplain.erl",
317+
"src/rabbit_auth_mechanism_anonymous.erl",
316318
"src/rabbit_auth_mechanism_cr_demo.erl",
317319
"src/rabbit_auth_mechanism_plain.erl",
318320
"src/rabbit_autoheal.erl",
@@ -586,6 +588,7 @@ def all_srcs(name = "all_srcs"):
586588
"src/rabbit_amqqueue_sup_sup.erl",
587589
"src/rabbit_auth_backend_internal.erl",
588590
"src/rabbit_auth_mechanism_amqplain.erl",
591+
"src/rabbit_auth_mechanism_anonymous.erl",
589592
"src/rabbit_auth_mechanism_cr_demo.erl",
590593
"src/rabbit_auth_mechanism_plain.erl",
591594
"src/rabbit_autoheal.erl",

deps/rabbit/priv/schema/rabbit.schema

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -444,13 +444,12 @@ end}.
444444
%% ===========================================================================
445445

446446
%% Choose the available SASL mechanism(s) to expose.
447-
%% The two default (built in) mechanisms are 'PLAIN' and
448-
%% 'AMQPLAIN'. Additional mechanisms can be added via
449-
%% plugins.
447+
%% The three default (built in) mechanisms are 'PLAIN', 'AMQPLAIN' and 'ANONYMOUS'.
448+
%% Additional mechanisms can be added via plugins.
450449
%%
451450
%% See https://www.rabbitmq.com/authentication.html for more details.
452451
%%
453-
%% {auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
452+
%% {auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'ANONYMOUS']},
454453

455454
{mapping, "auth_mechanisms.$name", "rabbit.auth_mechanisms", [
456455
{datatype, atom}]}.
@@ -735,6 +734,30 @@ end}.
735734
end
736735
end}.
737736

737+
%% Connections that skip SASL layer or use SASL mechanism ANONYMOUS will use this identity.
738+
%% Setting this to a username will allow (anonymous) clients to connect and act as this
739+
%% given user. For production environments, set this value to 'none'.
740+
{mapping, "anonymous_login_user", "rabbit.anonymous_login_user",
741+
[{datatype, [{enum, [none]}, string]}]}.
742+
743+
{translation, "rabbit.anonymous_login_user",
744+
fun(Conf) ->
745+
case cuttlefish:conf_get("anonymous_login_user", Conf) of
746+
none -> none;
747+
User -> list_to_binary(User)
748+
end
749+
end}.
750+
751+
{mapping, "anonymous_login_pass", "rabbit.anonymous_login_pass", [
752+
{datatype, [tagged_binary, binary]}
753+
]}.
754+
755+
{translation, "rabbit.anonymous_login_pass",
756+
fun(Conf) ->
757+
rabbit_cuttlefish:optionally_tagged_binary("anonymous_login_pass", Conf)
758+
end}.
759+
760+
738761
%%
739762
%% Default Policies
740763
%% ====================
@@ -2649,32 +2672,6 @@ end}.
26492672
end
26502673
}.
26512674

2652-
% ===============================
2653-
% AMQP 1.0
2654-
% ===============================
2655-
2656-
%% Connections that skip SASL layer or use SASL mechanism ANONYMOUS will connect as this account.
2657-
%% Setting this to a username will allow clients to connect without authenticating.
2658-
%% For production environments, set this value to 'none'.
2659-
{mapping, "amqp1_0.default_user", "rabbit.amqp1_0_default_user",
2660-
[{datatype, [{enum, [none]}, string]}]}.
2661-
2662-
{mapping, "amqp1_0.default_vhost", "rabbit.amqp1_0_default_vhost",
2663-
[{datatype, string}]}.
2664-
2665-
{translation, "rabbit.amqp1_0_default_user",
2666-
fun(Conf) ->
2667-
case cuttlefish:conf_get("amqp1_0.default_user", Conf) of
2668-
none -> none;
2669-
User -> list_to_binary(User)
2670-
end
2671-
end}.
2672-
2673-
{translation , "rabbit.amqp1_0_default_vhost",
2674-
fun(Conf) ->
2675-
list_to_binary(cuttlefish:conf_get("amqp1_0.default_vhost", Conf))
2676-
end}.
2677-
26782675
{mapping, "stream.replication.port_range.min", "osiris.port_range", [
26792676
{datatype, [integer]},
26802677
{validators, ["non_zero_positive_integer"]}

0 commit comments

Comments
 (0)