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: *0215e85643/src/main/java/com/rabbitmq/client/ConnectionFactory.java (L58-L61)
*ddb7a2f068/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](146b4862d8/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.
This commit is contained in:
parent
cabe873348
commit
d46f07c0a4
|
@ -429,8 +429,8 @@ parse_result(Map) ->
|
|||
throw(plain_sasl_missing_userinfo);
|
||||
_ ->
|
||||
case UserInfo of
|
||||
[] -> none;
|
||||
undefined -> none;
|
||||
[] -> anon;
|
||||
undefined -> anon;
|
||||
U -> parse_usertoken(U)
|
||||
end
|
||||
end,
|
||||
|
@ -456,11 +456,6 @@ parse_result(Map) ->
|
|||
Ret0#{tls_opts => {secure_port, TlsOpts}}
|
||||
end.
|
||||
|
||||
|
||||
parse_usertoken(undefined) ->
|
||||
none;
|
||||
parse_usertoken("") ->
|
||||
none;
|
||||
parse_usertoken(U) ->
|
||||
[User, Pass] = string:tokens(U, ":"),
|
||||
{plain,
|
||||
|
@ -532,7 +527,7 @@ parse_uri_test_() ->
|
|||
[?_assertEqual({ok, #{address => "my_host",
|
||||
port => 9876,
|
||||
hostname => <<"my_host">>,
|
||||
sasl => none}}, parse_uri("amqp://my_host:9876")),
|
||||
sasl => anon}}, parse_uri("amqp://my_host:9876")),
|
||||
%% port defaults
|
||||
?_assertMatch({ok, #{port := 5671}}, parse_uri("amqps://my_host")),
|
||||
?_assertMatch({ok, #{port := 5672}}, parse_uri("amqp://my_host")),
|
||||
|
|
|
@ -103,8 +103,7 @@ stop_amqp10_client_app(Config) ->
|
|||
%% -------------------------------------------------------------------
|
||||
|
||||
init_per_group(rabbitmq, Config0) ->
|
||||
Config = rabbit_ct_helpers:set_config(Config0,
|
||||
{sasl, {plain, <<"guest">>, <<"guest">>}}),
|
||||
Config = rabbit_ct_helpers:set_config(Config0, {sasl, anon}),
|
||||
Config1 = rabbit_ct_helpers:merge_app_env(Config,
|
||||
[{rabbit,
|
||||
[{max_message_size, 134217728}]}]),
|
||||
|
@ -115,7 +114,7 @@ init_per_group(rabbitmq_strict, Config0) ->
|
|||
{sasl, {plain, <<"guest">>, <<"guest">>}}),
|
||||
Config1 = rabbit_ct_helpers:merge_app_env(Config,
|
||||
[{rabbit,
|
||||
[{amqp1_0_default_user, none},
|
||||
[{anonymous_login_user, none},
|
||||
{max_message_size, 134217728}]}]),
|
||||
rabbit_ct_helpers:run_steps(Config1, rabbit_ct_broker_helpers:setup_steps());
|
||||
|
||||
|
|
|
@ -58,8 +58,6 @@ _APP_ENV = """[
|
|||
{default_user_tags, [administrator]},
|
||||
{default_vhost, <<"/">>},
|
||||
{default_permissions, [<<".*">>, <<".*">>, <<".*">>]},
|
||||
{amqp1_0_default_user, <<"guest">>},
|
||||
{amqp1_0_default_vhost, <<"/">>},
|
||||
{loopback_users, [<<"guest">>]},
|
||||
{password_hashing_module, rabbit_password_hashing_sha256},
|
||||
{server_properties, []},
|
||||
|
@ -67,7 +65,9 @@ _APP_ENV = """[
|
|||
{collect_statistics_interval, 5000},
|
||||
{mnesia_table_loading_retry_timeout, 30000},
|
||||
{mnesia_table_loading_retry_limit, 10},
|
||||
{auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
|
||||
{anonymous_login_user, <<"guest">>},
|
||||
{anonymous_login_pass, <<"guest">>},
|
||||
{auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'ANONYMOUS']},
|
||||
{auth_backends, [rabbit_auth_backend_internal]},
|
||||
{delegate_count, 16},
|
||||
{trace_vhosts, []},
|
||||
|
|
|
@ -38,8 +38,6 @@ define PROJECT_ENV
|
|||
{default_user_tags, [administrator]},
|
||||
{default_vhost, <<"/">>},
|
||||
{default_permissions, [<<".*">>, <<".*">>, <<".*">>]},
|
||||
{amqp1_0_default_user, <<"guest">>},
|
||||
{amqp1_0_default_vhost, <<"/">>},
|
||||
{loopback_users, [<<"guest">>]},
|
||||
{password_hashing_module, rabbit_password_hashing_sha256},
|
||||
{server_properties, []},
|
||||
|
@ -47,7 +45,12 @@ define PROJECT_ENV
|
|||
{collect_statistics_interval, 5000},
|
||||
{mnesia_table_loading_retry_timeout, 30000},
|
||||
{mnesia_table_loading_retry_limit, 10},
|
||||
{auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
|
||||
%% The identity to act as for anonymous logins.
|
||||
{anonymous_login_user, <<"guest">>},
|
||||
{anonymous_login_pass, <<"guest">>},
|
||||
%% "The server mechanisms are ordered in decreasing level of preference."
|
||||
%% AMQP §5.3.3.1
|
||||
{auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'ANONYMOUS']},
|
||||
{auth_backends, [rabbit_auth_backend_internal]},
|
||||
{delegate_count, 16},
|
||||
{trace_vhosts, []},
|
||||
|
|
|
@ -58,6 +58,7 @@ def all_beam_files(name = "all_beam_files"):
|
|||
"src/rabbit_amqqueue_sup_sup.erl",
|
||||
"src/rabbit_auth_backend_internal.erl",
|
||||
"src/rabbit_auth_mechanism_amqplain.erl",
|
||||
"src/rabbit_auth_mechanism_anonymous.erl",
|
||||
"src/rabbit_auth_mechanism_cr_demo.erl",
|
||||
"src/rabbit_auth_mechanism_plain.erl",
|
||||
"src/rabbit_autoheal.erl",
|
||||
|
@ -313,6 +314,7 @@ def all_test_beam_files(name = "all_test_beam_files"):
|
|||
"src/rabbit_amqqueue_sup_sup.erl",
|
||||
"src/rabbit_auth_backend_internal.erl",
|
||||
"src/rabbit_auth_mechanism_amqplain.erl",
|
||||
"src/rabbit_auth_mechanism_anonymous.erl",
|
||||
"src/rabbit_auth_mechanism_cr_demo.erl",
|
||||
"src/rabbit_auth_mechanism_plain.erl",
|
||||
"src/rabbit_autoheal.erl",
|
||||
|
@ -586,6 +588,7 @@ def all_srcs(name = "all_srcs"):
|
|||
"src/rabbit_amqqueue_sup_sup.erl",
|
||||
"src/rabbit_auth_backend_internal.erl",
|
||||
"src/rabbit_auth_mechanism_amqplain.erl",
|
||||
"src/rabbit_auth_mechanism_anonymous.erl",
|
||||
"src/rabbit_auth_mechanism_cr_demo.erl",
|
||||
"src/rabbit_auth_mechanism_plain.erl",
|
||||
"src/rabbit_autoheal.erl",
|
||||
|
|
|
@ -444,13 +444,12 @@ end}.
|
|||
%% ===========================================================================
|
||||
|
||||
%% Choose the available SASL mechanism(s) to expose.
|
||||
%% The two default (built in) mechanisms are 'PLAIN' and
|
||||
%% 'AMQPLAIN'. Additional mechanisms can be added via
|
||||
%% plugins.
|
||||
%% The three default (built in) mechanisms are 'PLAIN', 'AMQPLAIN' and 'ANONYMOUS'.
|
||||
%% Additional mechanisms can be added via plugins.
|
||||
%%
|
||||
%% See https://www.rabbitmq.com/authentication.html for more details.
|
||||
%%
|
||||
%% {auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
|
||||
%% {auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'ANONYMOUS']},
|
||||
|
||||
{mapping, "auth_mechanisms.$name", "rabbit.auth_mechanisms", [
|
||||
{datatype, atom}]}.
|
||||
|
@ -735,6 +734,30 @@ end}.
|
|||
end
|
||||
end}.
|
||||
|
||||
%% Connections that skip SASL layer or use SASL mechanism ANONYMOUS will use this identity.
|
||||
%% Setting this to a username will allow (anonymous) clients to connect and act as this
|
||||
%% given user. For production environments, set this value to 'none'.
|
||||
{mapping, "anonymous_login_user", "rabbit.anonymous_login_user",
|
||||
[{datatype, [{enum, [none]}, string]}]}.
|
||||
|
||||
{translation, "rabbit.anonymous_login_user",
|
||||
fun(Conf) ->
|
||||
case cuttlefish:conf_get("anonymous_login_user", Conf) of
|
||||
none -> none;
|
||||
User -> list_to_binary(User)
|
||||
end
|
||||
end}.
|
||||
|
||||
{mapping, "anonymous_login_pass", "rabbit.anonymous_login_pass", [
|
||||
{datatype, [tagged_binary, binary]}
|
||||
]}.
|
||||
|
||||
{translation, "rabbit.anonymous_login_pass",
|
||||
fun(Conf) ->
|
||||
rabbit_cuttlefish:optionally_tagged_binary("anonymous_login_pass", Conf)
|
||||
end}.
|
||||
|
||||
|
||||
%%
|
||||
%% Default Policies
|
||||
%% ====================
|
||||
|
@ -2649,32 +2672,6 @@ end}.
|
|||
end
|
||||
}.
|
||||
|
||||
% ===============================
|
||||
% AMQP 1.0
|
||||
% ===============================
|
||||
|
||||
%% Connections that skip SASL layer or use SASL mechanism ANONYMOUS will connect as this account.
|
||||
%% Setting this to a username will allow clients to connect without authenticating.
|
||||
%% For production environments, set this value to 'none'.
|
||||
{mapping, "amqp1_0.default_user", "rabbit.amqp1_0_default_user",
|
||||
[{datatype, [{enum, [none]}, string]}]}.
|
||||
|
||||
{mapping, "amqp1_0.default_vhost", "rabbit.amqp1_0_default_vhost",
|
||||
[{datatype, string}]}.
|
||||
|
||||
{translation, "rabbit.amqp1_0_default_user",
|
||||
fun(Conf) ->
|
||||
case cuttlefish:conf_get("amqp1_0.default_user", Conf) of
|
||||
none -> none;
|
||||
User -> list_to_binary(User)
|
||||
end
|
||||
end}.
|
||||
|
||||
{translation , "rabbit.amqp1_0_default_vhost",
|
||||
fun(Conf) ->
|
||||
list_to_binary(cuttlefish:conf_get("amqp1_0.default_vhost", Conf))
|
||||
end}.
|
||||
|
||||
{mapping, "stream.replication.port_range.min", "osiris.port_range", [
|
||||
{datatype, [integer]},
|
||||
{validators, ["non_zero_positive_integer"]}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
-include_lib("amqp10_common/include/amqp10_types.hrl").
|
||||
-include("rabbit_amqp.hrl").
|
||||
|
||||
-export([init/2,
|
||||
-export([init/1,
|
||||
info/2,
|
||||
mainloop/2]).
|
||||
|
||||
|
@ -45,12 +45,12 @@
|
|||
%% client port
|
||||
peer_port :: inet:port_number(),
|
||||
connected_at :: integer(),
|
||||
user :: rabbit_types:option(rabbit_types:user()),
|
||||
user :: unauthenticated | rabbit_types:user(),
|
||||
timeout :: non_neg_integer(),
|
||||
incoming_max_frame_size :: pos_integer(),
|
||||
outgoing_max_frame_size :: unlimited | pos_integer(),
|
||||
channel_max :: non_neg_integer(),
|
||||
auth_mechanism :: none | anonymous | {binary(), module()},
|
||||
auth_mechanism :: none | {binary(), module()},
|
||||
auth_state :: term(),
|
||||
properties :: undefined | {map, list(tuple())}
|
||||
}).
|
||||
|
@ -65,7 +65,9 @@
|
|||
sock :: rabbit_net:socket(),
|
||||
proxy_socket :: undefined | {rabbit_proxy_socket, any(), any()},
|
||||
connection :: #v1_connection{},
|
||||
connection_state :: pre_init | starting | waiting_amqp0100 | securing | running | closing | closed,
|
||||
connection_state :: received_amqp3100 | waiting_sasl_init | securing |
|
||||
waiting_amqp0100 | waiting_open | running |
|
||||
closing | closed,
|
||||
callback :: handshake |
|
||||
{frame_header, protocol()} |
|
||||
{frame_body, protocol(), DataOffset :: pos_integer(), channel_number()},
|
||||
|
@ -92,7 +94,7 @@ unpack_from_0_9_1(
|
|||
callback = handshake,
|
||||
recv_len = RecvLen,
|
||||
pending_recv = PendingRecv,
|
||||
connection_state = pre_init,
|
||||
connection_state = received_amqp3100,
|
||||
heartbeater = none,
|
||||
helper_sup = SupPid,
|
||||
buf = Buf,
|
||||
|
@ -108,7 +110,7 @@ unpack_from_0_9_1(
|
|||
port = Port,
|
||||
peer_port = PeerPort,
|
||||
connected_at = ConnectedAt,
|
||||
user = none,
|
||||
user = unauthenticated,
|
||||
timeout = HandshakeTimeout,
|
||||
incoming_max_frame_size = ?INITIAL_MAX_FRAME_SIZE,
|
||||
outgoing_max_frame_size = ?INITIAL_MAX_FRAME_SIZE,
|
||||
|
@ -148,15 +150,19 @@ recvloop(Deb, State = #v1{sock = Sock, recv_len = RecvLen, buf_len = BufLen})
|
|||
{error, Reason} ->
|
||||
throw({inet_error, Reason})
|
||||
end;
|
||||
recvloop(Deb, State = #v1{recv_len = RecvLen, buf = Buf, buf_len = BufLen}) ->
|
||||
recvloop(Deb, State0 = #v1{callback = Callback,
|
||||
recv_len = RecvLen,
|
||||
buf = Buf,
|
||||
buf_len = BufLen}) ->
|
||||
Bin = case Buf of
|
||||
[B] -> B;
|
||||
_ -> list_to_binary(lists:reverse(Buf))
|
||||
end,
|
||||
{Data, Rest} = split_binary(Bin, RecvLen),
|
||||
recvloop(Deb, handle_input(State#v1.callback, Data,
|
||||
State#v1{buf = [Rest],
|
||||
buf_len = BufLen - RecvLen})).
|
||||
State1 = State0#v1{buf = [Rest],
|
||||
buf_len = BufLen - RecvLen},
|
||||
State = handle_input(Callback, Data, State1),
|
||||
recvloop(Deb, State).
|
||||
|
||||
-spec mainloop([sys:dbg_opt()], state()) ->
|
||||
no_return() | ok.
|
||||
|
@ -240,7 +246,8 @@ handle_other(Other, _State) ->
|
|||
exit({unexpected_message, Other}).
|
||||
|
||||
switch_callback(State, Callback, Length) ->
|
||||
State#v1{callback = Callback, recv_len = Length}.
|
||||
State#v1{callback = Callback,
|
||||
recv_len = Length}.
|
||||
|
||||
terminate(Reason, State)
|
||||
when ?IS_RUNNING(State) ->
|
||||
|
@ -387,9 +394,12 @@ handle_connection_frame(
|
|||
idle_time_out = IdleTimeout,
|
||||
hostname = Hostname,
|
||||
properties = Properties},
|
||||
#v1{connection_state = starting,
|
||||
connection = Connection = #v1_connection{name = ConnectionName,
|
||||
user = User = #user{username = Username}},
|
||||
#v1{connection_state = waiting_open,
|
||||
connection = Connection = #v1_connection{
|
||||
name = ConnectionName,
|
||||
user = User = #user{username = Username},
|
||||
auth_mechanism = {Mechanism, _Mod}
|
||||
},
|
||||
helper_sup = HelperSupPid,
|
||||
sock = Sock} = State0) ->
|
||||
logger:update_process_metadata(#{amqp_container => ContainerId}),
|
||||
|
@ -404,9 +414,9 @@ handle_connection_frame(
|
|||
rabbit_core_metrics:auth_attempt_succeeded(<<>>, Username, amqp10),
|
||||
notify_auth(user_authentication_success, Username, State0),
|
||||
rabbit_log_connection:info(
|
||||
"Connection from AMQP 1.0 container '~ts': user '~ts' "
|
||||
"authenticated and granted access to vhost '~ts'",
|
||||
[ContainerId, Username, Vhost]),
|
||||
"Connection from AMQP 1.0 container '~ts': user '~ts' authenticated "
|
||||
"using SASL mechanism ~s and granted access to vhost '~ts'",
|
||||
[ContainerId, Username, Mechanism, Vhost]),
|
||||
|
||||
OutgoingMaxFrameSize = case ClientMaxFrame of
|
||||
undefined ->
|
||||
|
@ -539,50 +549,53 @@ handle_session_frame(Channel, Body, #v1{tracked_channels = Channels} = State) ->
|
|||
end
|
||||
end.
|
||||
|
||||
%% TODO: write a proper ANONYMOUS plugin and unify with STOMP
|
||||
handle_sasl_frame(#'v1_0.sasl_init'{mechanism = {symbol, <<"ANONYMOUS">>},
|
||||
hostname = _Hostname},
|
||||
#v1{connection_state = starting,
|
||||
connection = Connection,
|
||||
sock = Sock} = State0) ->
|
||||
case default_user() of
|
||||
none ->
|
||||
silent_close_delay(),
|
||||
Outcome = #'v1_0.sasl_outcome'{code = ?V_1_0_SASL_CODE_SYS_PERM},
|
||||
ok = send_on_channel0(Sock, Outcome, rabbit_amqp_sasl),
|
||||
throw(banned_unauthenticated_connection);
|
||||
_ ->
|
||||
%% We only need to send the frame, again start_connection
|
||||
%% will set up the default user.
|
||||
Outcome = #'v1_0.sasl_outcome'{code = ?V_1_0_SASL_CODE_OK},
|
||||
ok = send_on_channel0(Sock, Outcome, rabbit_amqp_sasl),
|
||||
State = State0#v1{connection_state = waiting_amqp0100,
|
||||
connection = Connection#v1_connection{auth_mechanism = anonymous}},
|
||||
switch_callback(State, handshake, 8)
|
||||
end;
|
||||
handle_sasl_frame(#'v1_0.sasl_init'{mechanism = {symbol, Mechanism},
|
||||
initial_response = {binary, Response},
|
||||
hostname = _Hostname},
|
||||
State0 = #v1{connection_state = starting,
|
||||
initial_response = Response,
|
||||
hostname = _},
|
||||
State0 = #v1{connection_state = waiting_sasl_init,
|
||||
connection = Connection,
|
||||
sock = Sock}) ->
|
||||
ResponseBin = case Response of
|
||||
undefined -> <<>>;
|
||||
{binary, Bin} -> Bin
|
||||
end,
|
||||
AuthMechanism = auth_mechanism_to_module(Mechanism, Sock),
|
||||
State = State0#v1{connection =
|
||||
Connection#v1_connection{
|
||||
AuthState = AuthMechanism:init(Sock),
|
||||
State = State0#v1{
|
||||
connection = Connection#v1_connection{
|
||||
auth_mechanism = {Mechanism, AuthMechanism},
|
||||
auth_state = AuthMechanism:init(Sock)},
|
||||
auth_state = AuthState},
|
||||
connection_state = securing},
|
||||
auth_phase_1_0(Response, State);
|
||||
auth_phase(ResponseBin, State);
|
||||
handle_sasl_frame(#'v1_0.sasl_response'{response = {binary, Response}},
|
||||
State = #v1{connection_state = securing}) ->
|
||||
auth_phase_1_0(Response, State);
|
||||
auth_phase(Response, State);
|
||||
handle_sasl_frame(Performative, State) ->
|
||||
throw({unexpected_1_0_sasl_frame, Performative, State}).
|
||||
|
||||
handle_input(handshake, <<"AMQP", 0, 1, 0, 0>>,
|
||||
#v1{connection_state = waiting_amqp0100} = State) ->
|
||||
start_connection(amqp, State);
|
||||
|
||||
handle_input(handshake, <<"AMQP",0,1,0,0>>,
|
||||
#v1{connection_state = waiting_amqp0100,
|
||||
sock = Sock,
|
||||
connection = Connection = #v1_connection{user = #user{}},
|
||||
helper_sup = HelperSup
|
||||
} = State0) ->
|
||||
%% Client already got successfully authenticated by SASL.
|
||||
send_handshake(Sock, <<"AMQP",0,1,0,0>>),
|
||||
ChildSpec = #{id => session_sup,
|
||||
start => {rabbit_amqp_session_sup, start_link, [self()]},
|
||||
restart => transient,
|
||||
significant => true,
|
||||
shutdown => infinity,
|
||||
type => supervisor},
|
||||
{ok, SessionSupPid} = supervisor:start_child(HelperSup, ChildSpec),
|
||||
State = State0#v1{
|
||||
session_sup = SessionSupPid,
|
||||
%% "After establishing or accepting a TCP connection and sending
|
||||
%% the protocol header, each peer MUST send an open frame before
|
||||
%% sending any other frames." [2.4.1]
|
||||
connection_state = waiting_open,
|
||||
connection = Connection#v1_connection{timeout = ?NORMAL_TIMEOUT}},
|
||||
switch_callback(State, {frame_header, amqp}, 8);
|
||||
handle_input({frame_header, Mode},
|
||||
Header = <<Size:32, DOff:8, Type:8, Channel:16>>,
|
||||
State) when DOff >= 2 ->
|
||||
|
@ -618,75 +631,27 @@ handle_input({frame_body, Mode, DOff, Channel},
|
|||
handle_input(Callback, Data, _State) ->
|
||||
throw({bad_input, Callback, Data}).
|
||||
|
||||
-spec init(protocol(), tuple()) -> no_return().
|
||||
init(Mode, PackedState) ->
|
||||
-spec init(tuple()) -> no_return().
|
||||
init(PackedState) ->
|
||||
{ok, HandshakeTimeout} = application:get_env(rabbit, handshake_timeout),
|
||||
{parent, Parent} = erlang:process_info(self(), parent),
|
||||
ok = rabbit_connection_sup:remove_connection_helper_sup(Parent, helper_sup_amqp_091),
|
||||
State0 = unpack_from_0_9_1(PackedState, Parent, HandshakeTimeout),
|
||||
State = start_connection(Mode, State0),
|
||||
State = advertise_sasl_mechanism(State0),
|
||||
%% By invoking recvloop here we become 1.0.
|
||||
recvloop(sys:debug_options([]), State).
|
||||
|
||||
start_connection(Mode = sasl, State = #v1{sock = Sock}) ->
|
||||
advertise_sasl_mechanism(State0 = #v1{connection_state = received_amqp3100,
|
||||
connection = Connection,
|
||||
sock = Sock}) ->
|
||||
send_handshake(Sock, <<"AMQP",3,1,0,0>>),
|
||||
%% "The server mechanisms are ordered in decreasing level of preference." [5.3.3.1]
|
||||
Ms0 = [{symbol, atom_to_binary(M)} || M <- auth_mechanisms(Sock)],
|
||||
Ms1 = case default_user() of
|
||||
none -> Ms0;
|
||||
_ -> Ms0 ++ [{symbol, <<"ANONYMOUS">>}]
|
||||
end,
|
||||
Ms2 = {array, symbol, Ms1},
|
||||
Ms = #'v1_0.sasl_mechanisms'{sasl_server_mechanisms = Ms2},
|
||||
Ms1 = {array, symbol, Ms0},
|
||||
Ms = #'v1_0.sasl_mechanisms'{sasl_server_mechanisms = Ms1},
|
||||
ok = send_on_channel0(Sock, Ms, rabbit_amqp_sasl),
|
||||
start_connection0(Mode, State);
|
||||
|
||||
start_connection(Mode = amqp,
|
||||
State = #v1{sock = Sock,
|
||||
connection = C = #v1_connection{user = User}}) ->
|
||||
case User of
|
||||
none ->
|
||||
%% Client either skipped SASL layer or used SASL mechansim ANONYMOUS.
|
||||
case default_user() of
|
||||
none ->
|
||||
send_handshake(Sock, <<"AMQP",3,1,0,0>>),
|
||||
throw(banned_unauthenticated_connection);
|
||||
NoAuthUsername ->
|
||||
case rabbit_access_control:check_user_login(NoAuthUsername, []) of
|
||||
{ok, NoAuthUser} ->
|
||||
State1 = State#v1{connection = C#v1_connection{user = NoAuthUser}},
|
||||
send_handshake(Sock, <<"AMQP",0,1,0,0>>),
|
||||
start_connection0(Mode, State1);
|
||||
{refused, _, _, _} ->
|
||||
send_handshake(Sock, <<"AMQP",3,1,0,0>>),
|
||||
throw(amqp1_0_default_user_missing)
|
||||
end
|
||||
end;
|
||||
#user{} ->
|
||||
%% Client already got successfully authenticated by SASL.
|
||||
send_handshake(Sock, <<"AMQP",0,1,0,0>>),
|
||||
start_connection0(Mode, State)
|
||||
end.
|
||||
|
||||
start_connection0(Mode, State0 = #v1{connection = Connection,
|
||||
helper_sup = HelperSup}) ->
|
||||
SessionSup = case Mode of
|
||||
sasl ->
|
||||
undefined;
|
||||
amqp ->
|
||||
ChildSpec = #{id => session_sup,
|
||||
start => {rabbit_amqp_session_sup, start_link, [self()]},
|
||||
restart => transient,
|
||||
significant => true,
|
||||
shutdown => infinity,
|
||||
type => supervisor},
|
||||
{ok, Pid} = supervisor:start_child(HelperSup, ChildSpec),
|
||||
Pid
|
||||
end,
|
||||
State = State0#v1{session_sup = SessionSup,
|
||||
connection_state = starting,
|
||||
State = State0#v1{connection_state = waiting_sasl_init,
|
||||
connection = Connection#v1_connection{timeout = ?NORMAL_TIMEOUT}},
|
||||
switch_callback(State, {frame_header, Mode}, 8).
|
||||
switch_callback(State, {frame_header, sasl}, 8).
|
||||
|
||||
send_handshake(Sock, Handshake) ->
|
||||
ok = inet_op(fun () -> rabbit_net:send(Sock, Handshake) end).
|
||||
|
@ -715,17 +680,24 @@ auth_mechanism_to_module(TypeBin, Sock) ->
|
|||
end
|
||||
end.
|
||||
|
||||
%% Returns mechanisms ordered in decreasing level of preference (as configured).
|
||||
auth_mechanisms(Sock) ->
|
||||
{ok, Configured} = application:get_env(rabbit, auth_mechanisms),
|
||||
[Name || {Name, Module} <- rabbit_registry:lookup_all(auth_mechanism),
|
||||
Module:should_offer(Sock), lists:member(Name, Configured)].
|
||||
{ok, ConfiguredMechs} = application:get_env(rabbit, auth_mechanisms),
|
||||
RegisteredMechs = rabbit_registry:lookup_all(auth_mechanism),
|
||||
lists:filter(
|
||||
fun(Mech) ->
|
||||
case proplists:lookup(Mech, RegisteredMechs) of
|
||||
{Mech, Mod} ->
|
||||
Mod:should_offer(Sock);
|
||||
none ->
|
||||
false
|
||||
end
|
||||
end, ConfiguredMechs).
|
||||
|
||||
%% Begin 1-0
|
||||
|
||||
auth_phase_1_0(Response,
|
||||
auth_phase(
|
||||
Response,
|
||||
State = #v1{sock = Sock,
|
||||
connection = Connection =
|
||||
#v1_connection{auth_mechanism = {Name, AuthMechanism},
|
||||
connection = Conn = #v1_connection{auth_mechanism = {Name, AuthMechanism},
|
||||
auth_state = AuthState}}) ->
|
||||
case AuthMechanism:handle_response(Response, AuthState) of
|
||||
{refused, Username, Msg, Args} ->
|
||||
|
@ -745,16 +717,16 @@ auth_phase_1_0(Response,
|
|||
{challenge, Challenge, AuthState1} ->
|
||||
Secure = #'v1_0.sasl_challenge'{challenge = {binary, Challenge}},
|
||||
ok = send_on_channel0(Sock, Secure, rabbit_amqp_sasl),
|
||||
State#v1{connection = Connection#v1_connection{auth_state = AuthState1}};
|
||||
State#v1{connection = Conn#v1_connection{auth_state = AuthState1}};
|
||||
{ok, User} ->
|
||||
Outcome = #'v1_0.sasl_outcome'{code = ?V_1_0_SASL_CODE_OK},
|
||||
ok = send_on_channel0(Sock, Outcome, rabbit_amqp_sasl),
|
||||
State1 = State#v1{connection_state = waiting_amqp0100,
|
||||
connection = Connection#v1_connection{user = User}},
|
||||
connection = Conn#v1_connection{user = User,
|
||||
auth_state = none}},
|
||||
switch_callback(State1, handshake, 8)
|
||||
end.
|
||||
|
||||
|
||||
auth_fail(Username, State) ->
|
||||
rabbit_core_metrics:auth_attempt_failed(<<>>, Username, amqp10),
|
||||
notify_auth(user_authentication_failure, Username, State).
|
||||
|
@ -822,8 +794,7 @@ send_to_new_session(
|
|||
vhost({utf8, <<"vhost:", VHost/binary>>}) ->
|
||||
VHost;
|
||||
vhost(_) ->
|
||||
application:get_env(rabbit, amqp1_0_default_vhost,
|
||||
application:get_env(rabbit, default_vhost, <<"/">>)).
|
||||
application:get_env(rabbit, default_vhost, <<"/">>).
|
||||
|
||||
check_user_loopback(#v1{connection = #v1_connection{user = #user{username = Username}},
|
||||
sock = Socket} = State) ->
|
||||
|
@ -917,15 +888,6 @@ ensure_credential_expiry_timer(User) ->
|
|||
end
|
||||
end.
|
||||
|
||||
-spec default_user() -> none | rabbit_types:username().
|
||||
default_user() ->
|
||||
case application:get_env(rabbit, amqp1_0_default_user) of
|
||||
{ok, none} ->
|
||||
none;
|
||||
{ok, Username} when is_binary(Username) ->
|
||||
Username
|
||||
end.
|
||||
|
||||
%% We don't trust the client at this point - force them to wait
|
||||
%% for a bit so they can't DOS us with repeated failed logins etc.
|
||||
silent_close_delay() ->
|
||||
|
@ -978,12 +940,11 @@ i(frame_max, #v1{connection = #v1_connection{outgoing_max_frame_size = Val}}) ->
|
|||
end;
|
||||
i(timeout, #v1{connection = #v1_connection{timeout = Millis}}) ->
|
||||
Millis div 1000;
|
||||
i(user,
|
||||
#v1{connection = #v1_connection{user = #user{username = Val}}}) ->
|
||||
Val;
|
||||
i(user,
|
||||
#v1{connection = #v1_connection{user = none}}) ->
|
||||
'';
|
||||
i(user, #v1{connection = #v1_connection{user = User}}) ->
|
||||
case User of
|
||||
#user{username = Val} -> Val;
|
||||
unauthenticated -> ''
|
||||
end;
|
||||
i(state, S) ->
|
||||
i(connection_state, S);
|
||||
i(connection_state, #v1{connection_state = Val}) ->
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
%% This Source Code Form is subject to the terms of the Mozilla Public
|
||||
%% License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
%%
|
||||
%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
|
||||
%%
|
||||
|
||||
-module(rabbit_auth_mechanism_anonymous).
|
||||
-behaviour(rabbit_auth_mechanism).
|
||||
|
||||
-export([description/0, should_offer/1, init/1, handle_response/2]).
|
||||
|
||||
-define(STATE, []).
|
||||
|
||||
-rabbit_boot_step(
|
||||
{?MODULE,
|
||||
[{description, "auth mechanism anonymous"},
|
||||
{mfa, {rabbit_registry, register, [auth_mechanism, <<"ANONYMOUS">>, ?MODULE]}},
|
||||
{requires, rabbit_registry},
|
||||
{enables, kernel_ready}]}).
|
||||
|
||||
description() ->
|
||||
[{description, <<"SASL ANONYMOUS authentication mechanism">>}].
|
||||
|
||||
should_offer(_Sock) ->
|
||||
case credentials() of
|
||||
{ok, _, _} ->
|
||||
true;
|
||||
error ->
|
||||
false
|
||||
end.
|
||||
|
||||
init(_Sock) ->
|
||||
?STATE.
|
||||
|
||||
handle_response(_Response, ?STATE) ->
|
||||
{ok, User, Pass} = credentials(),
|
||||
rabbit_access_control:check_user_pass_login(User, Pass).
|
||||
|
||||
-spec credentials() ->
|
||||
{ok, rabbit_types:username(), rabbit_types:password()} | error.
|
||||
credentials() ->
|
||||
case application:get_env(rabbit, anonymous_login_user) of
|
||||
{ok, User} when is_binary(User) ->
|
||||
case application:get_env(rabbit, anonymous_login_pass) of
|
||||
{ok, Pass} when is_binary(Pass) ->
|
||||
{ok, User, Pass};
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
|
@ -39,11 +39,15 @@ handle_response(Response, _State) ->
|
|||
|
||||
extract_user_pass(Response) ->
|
||||
case extract_elem(Response) of
|
||||
{ok, User, Response1} -> case extract_elem(Response1) of
|
||||
{ok, Pass, <<>>} -> {ok, User, Pass};
|
||||
_ -> error
|
||||
{ok, User, Response1} ->
|
||||
case extract_elem(Response1) of
|
||||
{ok, Pass, <<>>} ->
|
||||
{ok, User, Pass};
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
error -> error
|
||||
error ->
|
||||
error
|
||||
end.
|
||||
|
||||
extract_elem(<<0:8, Rest/binary>>) ->
|
||||
|
|
|
@ -60,6 +60,8 @@
|
|||
%% from connection storms and DoS.
|
||||
-define(SILENT_CLOSE_DELAY, 3).
|
||||
-define(CHANNEL_MIN, 1).
|
||||
%% AMQP 1.0 §5.3
|
||||
-define(PROTOCOL_ID_SASL, 3).
|
||||
|
||||
%%--------------------------------------------------------------------------
|
||||
|
||||
|
@ -432,6 +434,12 @@ log_connection_exception(Severity, Name, {handshake_error, tuning, _Channel,
|
|||
log_connection_exception_with_severity(Severity,
|
||||
"closing AMQP connection ~tp (~ts):~nfailed to negotiate connection parameters: ~ts",
|
||||
[self(), Name, Explanation]);
|
||||
log_connection_exception(Severity, Name, {sasl_required, ProtocolId}) ->
|
||||
log_connection_exception_with_severity(
|
||||
Severity,
|
||||
"closing AMQP 1.0 connection (~ts): RabbitMQ requires SASL "
|
||||
"security layer (expected protocol ID 3, but client sent protocol ID ~b)",
|
||||
[Name, ProtocolId]);
|
||||
%% old exception structure
|
||||
log_connection_exception(Severity, Name, connection_closed_abruptly) ->
|
||||
log_connection_exception_with_severity(Severity,
|
||||
|
@ -1086,8 +1094,11 @@ handle_input(Callback, Data, _State) ->
|
|||
throw({bad_input, Callback, Data}).
|
||||
|
||||
%% AMQP 1.0 §2.2
|
||||
version_negotiation({Id, 1, 0, 0}, State) ->
|
||||
become_10(Id, State);
|
||||
version_negotiation({?PROTOCOL_ID_SASL, 1, 0, 0}, State) ->
|
||||
become_10(State);
|
||||
version_negotiation({ProtocolId, 1, 0, 0}, #v1{sock = Sock}) ->
|
||||
%% AMQP 1.0 figure 2.13: We require SASL security layer.
|
||||
refuse_connection(Sock, {sasl_required, ProtocolId});
|
||||
version_negotiation({0, 0, 9, 1}, State) ->
|
||||
start_091_connection({0, 9, 1}, rabbit_framing_amqp_0_9_1, State);
|
||||
version_negotiation({1, 1, 0, 9}, State) ->
|
||||
|
@ -1126,14 +1137,13 @@ start_091_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision},
|
|||
|
||||
-spec refuse_connection(rabbit_net:socket(), any()) -> no_return().
|
||||
refuse_connection(Sock, Exception) ->
|
||||
refuse_connection(Sock, Exception, {0, 1, 0, 0}).
|
||||
refuse_connection(Sock, Exception, {?PROTOCOL_ID_SASL, 1, 0, 0}).
|
||||
|
||||
-spec refuse_connection(_, _, _) -> no_return().
|
||||
refuse_connection(Sock, Exception, {A, B, C, D}) ->
|
||||
ok = inet_op(fun () -> rabbit_net:send(Sock, <<"AMQP",A,B,C,D>>) end),
|
||||
throw(Exception).
|
||||
|
||||
|
||||
ensure_stats_timer(State = #v1{connection_state = running}) ->
|
||||
rabbit_event:ensure_stats_timer(State, #v1.stats_timer, emit_stats);
|
||||
ensure_stats_timer(State) ->
|
||||
|
@ -1626,21 +1636,12 @@ emit_stats(State) ->
|
|||
State1 = rabbit_event:reset_stats_timer(State, #v1.stats_timer),
|
||||
ensure_stats_timer(State1).
|
||||
|
||||
%% 1.0 stub
|
||||
-spec become_10(non_neg_integer(), #v1{}) -> no_return().
|
||||
become_10(Id, State = #v1{sock = Sock}) ->
|
||||
Mode = case Id of
|
||||
0 -> amqp;
|
||||
3 -> sasl;
|
||||
_ -> refuse_connection(
|
||||
Sock, {unsupported_amqp1_0_protocol_id, Id},
|
||||
{3, 1, 0, 0})
|
||||
end,
|
||||
F = fun (_Deb, Buf, BufLen, State0) ->
|
||||
become_10(State) ->
|
||||
Fun = fun(_Deb, Buf, BufLen, State0) ->
|
||||
{rabbit_amqp_reader, init,
|
||||
[Mode, pack_for_1_0(Buf, BufLen, State0)]}
|
||||
[pack_for_1_0(Buf, BufLen, State0)]}
|
||||
end,
|
||||
State#v1{connection_state = {become, F}}.
|
||||
State#v1{connection_state = {become, Fun}}.
|
||||
|
||||
pack_for_1_0(Buf, BufLen, #v1{sock = Sock,
|
||||
recv_len = RecvLen,
|
||||
|
|
|
@ -58,11 +58,10 @@ groups() ->
|
|||
%% authn
|
||||
authn_failure_event,
|
||||
sasl_anonymous_success,
|
||||
sasl_none_success,
|
||||
sasl_plain_success,
|
||||
sasl_anonymous_failure,
|
||||
sasl_none_failure,
|
||||
sasl_plain_failure,
|
||||
sasl_none_failure,
|
||||
vhost_absent,
|
||||
|
||||
%% limits
|
||||
|
@ -609,10 +608,6 @@ sasl_anonymous_success(Config) ->
|
|||
Mechanism = anon,
|
||||
ok = sasl_success(Mechanism, Config).
|
||||
|
||||
sasl_none_success(Config) ->
|
||||
Mechanism = none,
|
||||
ok = sasl_success(Mechanism, Config).
|
||||
|
||||
sasl_plain_success(Config) ->
|
||||
Mechanism = {plain, <<"guest">>, <<"guest">>},
|
||||
ok = sasl_success(Mechanism, Config).
|
||||
|
@ -627,38 +622,40 @@ sasl_success(Mechanism, Config) ->
|
|||
ok = amqp10_client:close_connection(Connection).
|
||||
|
||||
sasl_anonymous_failure(Config) ->
|
||||
Mechanism = anon,
|
||||
?assertEqual(
|
||||
{sasl_not_supported, Mechanism},
|
||||
sasl_failure(Mechanism, Config)
|
||||
).
|
||||
|
||||
sasl_none_failure(Config) ->
|
||||
Mechanism = none,
|
||||
sasl_failure(Mechanism, Config).
|
||||
|
||||
sasl_plain_failure(Config) ->
|
||||
Mechanism = {plain, <<"guest">>, <<"wrong password">>},
|
||||
?assertEqual(
|
||||
sasl_auth_failure,
|
||||
sasl_failure(Mechanism, Config)
|
||||
).
|
||||
|
||||
sasl_failure(Mechanism, Config) ->
|
||||
App = rabbit,
|
||||
Par = amqp1_0_default_user,
|
||||
Par = anonymous_login_user,
|
||||
{ok, Default} = rpc(Config, application, get_env, [App, Par]),
|
||||
%% Prohibit anonymous login.
|
||||
ok = rpc(Config, application, set_env, [App, Par, none]),
|
||||
|
||||
Mechanism = anon,
|
||||
OpnConf0 = connection_config(Config, <<"/">>),
|
||||
OpnConf = OpnConf0#{sasl := Mechanism},
|
||||
{ok, Connection} = amqp10_client:open_connection(OpnConf),
|
||||
Reason = receive {amqp10_event, {connection, Connection, {closed, Reason0}}} -> Reason0
|
||||
receive {amqp10_event, {connection, Connection, {closed, Reason}}} ->
|
||||
?assertEqual({sasl_not_supported, Mechanism}, Reason)
|
||||
after 5000 -> ct:fail(missing_closed)
|
||||
end,
|
||||
|
||||
ok = rpc(Config, application, set_env, [App, Par, Default]),
|
||||
Reason.
|
||||
ok = rpc(Config, application, set_env, [App, Par, Default]).
|
||||
|
||||
sasl_plain_failure(Config) ->
|
||||
OpnConf0 = connection_config(Config, <<"/">>),
|
||||
OpnConf = OpnConf0#{sasl := {plain, <<"guest">>, <<"wrong password">>}},
|
||||
{ok, Connection} = amqp10_client:open_connection(OpnConf),
|
||||
receive {amqp10_event, {connection, Connection, {closed, Reason}}} ->
|
||||
?assertEqual(sasl_auth_failure, Reason)
|
||||
after 5000 -> ct:fail(missing_closed)
|
||||
end.
|
||||
|
||||
%% Skipping SASL is disallowed in RabbitMQ.
|
||||
sasl_none_failure(Config) ->
|
||||
OpnConf0 = connection_config(Config, <<"/">>),
|
||||
OpnConf = OpnConf0#{sasl := none},
|
||||
{ok, Connection} = amqp10_client:open_connection(OpnConf),
|
||||
receive {amqp10_event, {connection, Connection, {closed, _Reason}}} -> ok
|
||||
after 5000 -> ct:fail(missing_closed)
|
||||
end.
|
||||
|
||||
vhost_absent(Config) ->
|
||||
OpnConf = connection_config(Config, <<"this vhost does not exist">>),
|
||||
|
|
|
@ -48,8 +48,13 @@ module AmqpClient =
|
|||
let s = Session c
|
||||
{ Conn = c; Session = s }
|
||||
|
||||
let connectWithOpen uri opn =
|
||||
let c = Connection(Address uri, null, opn, null)
|
||||
let connectAnon uri =
|
||||
let c = Connection(Address uri, SaslProfile.Anonymous, null, null)
|
||||
let s = Session c
|
||||
{ Conn = c; Session = s }
|
||||
|
||||
let connectAnonWithOpen uri opn =
|
||||
let c = Connection(Address uri, SaslProfile.Anonymous, opn, null)
|
||||
let s = Session c
|
||||
{ Conn = c; Session = s }
|
||||
|
||||
|
@ -114,7 +119,7 @@ module Test =
|
|||
]
|
||||
|
||||
let testOutcome uri (attach: Attach) (cond: string) =
|
||||
use ac = connect uri
|
||||
use ac = connectAnon uri
|
||||
let trySet (mre: AutoResetEvent) =
|
||||
try mre.Set() |> ignore with _ -> ()
|
||||
|
||||
|
@ -135,7 +140,7 @@ module Test =
|
|||
let no_routes_is_released uri =
|
||||
// tests that a message sent to an exchange that resolves no routes for the
|
||||
// binding key returns the Released outcome, rather than Accepted
|
||||
use ac = connect uri
|
||||
use ac = connectAnon uri
|
||||
let address = "/exchanges/no_routes_is_released"
|
||||
let sender = SenderLink(ac.Session, "released-sender", address)
|
||||
let trySet (mre: AutoResetEvent) =
|
||||
|
@ -160,7 +165,7 @@ module Test =
|
|||
()
|
||||
|
||||
let roundtrip uri =
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let sender, receiver = senderReceiver c "test" "/queues/roundtrip"
|
||||
for body in sampleTypes do
|
||||
let corr = "correlation"
|
||||
|
@ -175,7 +180,7 @@ module Test =
|
|||
()
|
||||
|
||||
let streams uri =
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let name = "streams-test"
|
||||
let address = "/queues/streams"
|
||||
let sender = SenderLink(c.Session, name + "-sender" , address)
|
||||
|
@ -216,7 +221,7 @@ module Test =
|
|||
open RabbitMQ.Client
|
||||
|
||||
let roundtrip_to_amqp_091 uri =
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let q = "roundtrip_to_amqp_091"
|
||||
let target = "/queues/roundtrip_to_amqp_091"
|
||||
let corr = "correlation"
|
||||
|
@ -282,7 +287,7 @@ module Test =
|
|||
let opn = Open(ContainerId = Guid.NewGuid().ToString(),
|
||||
HostName = addr.Host, ChannelMax = 256us,
|
||||
MaxFrameSize = frameSize)
|
||||
use c = connectWithOpen uri opn
|
||||
use c = connectAnonWithOpen uri opn
|
||||
let sender, receiver = senderReceiver c "test" "/queues/fragmentation"
|
||||
let m = new Message(String.replicate size "a")
|
||||
sender.Send m
|
||||
|
@ -290,7 +295,7 @@ module Test =
|
|||
assertEqual (m.Body) (m'.Body)
|
||||
|
||||
let message_annotations uri =
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let sender, receiver = senderReceiver c "test" "/queues/message_annotations"
|
||||
let ann = MessageAnnotations()
|
||||
let k1 = Symbol "key1"
|
||||
|
@ -309,7 +314,7 @@ module Test =
|
|||
assertTrue (m.MessageAnnotations.[k2] = m'.MessageAnnotations.[k2])
|
||||
|
||||
let footer uri =
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let sender, receiver = senderReceiver c "test" "/queues/footer"
|
||||
let footer = Footer()
|
||||
let k1 = Symbol "key1"
|
||||
|
@ -327,7 +332,7 @@ module Test =
|
|||
assertTrue (m.Footer.[k2] = m'.Footer.[k2])
|
||||
|
||||
let data_types uri =
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let sender, receiver = senderReceiver c "test" "/queues/data_types"
|
||||
let aSeq = amqpSequence sampleTypes
|
||||
(new Message(aSeq)) |> sender.Send
|
||||
|
@ -337,7 +342,7 @@ module Test =
|
|||
List.exists ((=) a) sampleTypes |> assertTrue
|
||||
|
||||
let reject uri =
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let sender, receiver = senderReceiver c "test" "/queues/reject"
|
||||
new Message "testing reject" |> sender.Send
|
||||
let m = receiver.Receive()
|
||||
|
@ -345,7 +350,7 @@ module Test =
|
|||
assertEqual null (receiver.Receive(TimeSpan.FromMilliseconds 100.))
|
||||
|
||||
let redelivery uri =
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let sender, receiver = senderReceiver c "test" "/queues/redelivery"
|
||||
new Message "testing redelivery" |> sender.Send
|
||||
let m = receiver.Receive()
|
||||
|
@ -363,7 +368,7 @@ module Test =
|
|||
session.Close()
|
||||
|
||||
let released uri =
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let sender, receiver = senderReceiver c "test" "/queues/released"
|
||||
new Message "testing released" |> sender.Send
|
||||
let m = receiver.Receive()
|
||||
|
@ -392,7 +397,7 @@ module Test =
|
|||
"/queues/autodel_q", "/queues/autodel_q", ""] do
|
||||
|
||||
let rnd = Random()
|
||||
use c = connect uri
|
||||
use c = connectAnon uri
|
||||
let sender = SenderLink(c.Session, "test-sender", target)
|
||||
let receiver = ReceiverLink(c.Session, "test-receiver", source)
|
||||
receiver.SetCredit(100, true)
|
||||
|
@ -411,7 +416,7 @@ module Test =
|
|||
for dest, cond in
|
||||
["/exchanges/missing", "amqp:not-found"
|
||||
"/fruit/orange", "amqp:invalid-field"] do
|
||||
use ac = connect uri
|
||||
use ac = connectAnon uri
|
||||
let trySet (mre: AutoResetEvent) =
|
||||
try mre.Set() |> ignore with _ -> ()
|
||||
|
||||
|
|
|
@ -220,6 +220,8 @@ ssl_options.fail_if_no_peer_cert = true",
|
|||
{default_user_settings,
|
||||
"default_user = guest
|
||||
default_pass = guest
|
||||
anonymous_login_user = guest
|
||||
anonymous_login_pass = guest
|
||||
default_user_tags.administrator = true
|
||||
default_permissions.configure = .*
|
||||
default_permissions.read = .*
|
||||
|
@ -227,9 +229,16 @@ default_permissions.write = .*",
|
|||
[{rabbit,
|
||||
[{default_user,<<"guest">>},
|
||||
{default_pass,<<"guest">>},
|
||||
{anonymous_login_user,<<"guest">>},
|
||||
{anonymous_login_pass,<<"guest">>},
|
||||
{default_user_tags,[administrator]},
|
||||
{default_permissions,[<<".*">>,<<".*">>,<<".*">>]}]}],
|
||||
[]},
|
||||
{anonymous_login_user,
|
||||
"anonymous_login_user = none",
|
||||
[{rabbit,
|
||||
[{anonymous_login_user, none}]}],
|
||||
[]},
|
||||
{cluster_formation,
|
||||
"cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
|
||||
cluster_formation.classic_config.nodes.peer1 = rabbit@hostname1
|
||||
|
|
|
@ -282,31 +282,36 @@ version_negotiation(Config) ->
|
|||
ok = rabbit_ct_broker_helpers:rpc(Config, ?MODULE, version_negotiation1, [Config]).
|
||||
|
||||
version_negotiation1(Config) ->
|
||||
H = ?config(rmq_hostname, Config),
|
||||
P = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
|
||||
Hostname = ?config(rmq_hostname, Config),
|
||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
|
||||
|
||||
[?assertEqual(<<"AMQP",0,1,0,0>>, version_negotiation2(H, P, Vsn)) ||
|
||||
[?assertEqual(<<"AMQP",3,1,0,0>>,
|
||||
version_negotiation2(Hostname, Port, Vsn)) ||
|
||||
Vsn <- [<<"AMQP",0,1,0,0>>,
|
||||
<<"AMQP",0,1,0,1>>,
|
||||
<<"AMQP",0,1,1,0>>,
|
||||
<<"AMQP",0,9,1,0>>,
|
||||
<<"AMQP",0,0,8,0>>,
|
||||
<<"XXXX",0,1,0,0>>,
|
||||
<<"XXXX",0,0,9,1>>]],
|
||||
|
||||
[?assertEqual(<<"AMQP",3,1,0,0>>, version_negotiation2(H, P, Vsn)) ||
|
||||
Vsn <- [<<"AMQP",1,1,0,0>>,
|
||||
<<"AMQP",1,1,0,0>>,
|
||||
<<"AMQP",2,1,0,0>>,
|
||||
<<"AMQP",3,1,0,0>>,
|
||||
<<"AMQP",3,1,0,1>>,
|
||||
<<"AMQP",3,1,0,1>>,
|
||||
<<"AMQP",4,1,0,0>>,
|
||||
<<"AMQP",9,1,0,0>>]],
|
||||
<<"AMQP",9,1,0,0>>,
|
||||
<<"XXXX",0,1,0,0>>,
|
||||
<<"XXXX",0,0,9,1>>
|
||||
]],
|
||||
|
||||
[?assertEqual(<<"AMQP",0,0,9,1>>, version_negotiation2(H, P, Vsn)) ||
|
||||
[?assertEqual(<<"AMQP",0,0,9,1>>,
|
||||
version_negotiation2(Hostname, Port, Vsn)) ||
|
||||
Vsn <- [<<"AMQP",0,0,9,2>>,
|
||||
<<"AMQP",0,0,10,0>>,
|
||||
<<"AMQP",0,0,10,1>>]],
|
||||
ok.
|
||||
|
||||
version_negotiation2(H, P, Header) ->
|
||||
{ok, C} = gen_tcp:connect(H, P, [binary, {active, false}]),
|
||||
version_negotiation2(Hostname, Port, Header) ->
|
||||
{ok, C} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]),
|
||||
ok = gen_tcp:send(C, Header),
|
||||
{ok, ServerVersion} = gen_tcp:recv(C, 8, 100),
|
||||
ok = gen_tcp:close(C),
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
all() ->
|
||||
[{group, tests}].
|
||||
[{group, external_enforced}].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{tests, [shuffle],
|
||||
[amqp]
|
||||
{external_enforced, [shuffle],
|
||||
[external_succeeds,
|
||||
anonymous_fails]
|
||||
}
|
||||
].
|
||||
|
||||
|
@ -37,6 +38,7 @@ init_per_group(_Group, Config0) ->
|
|||
Config0,
|
||||
{rabbit,
|
||||
[
|
||||
%% Enforce EXTERNAL disallowing other mechanisms.
|
||||
{auth_mechanisms, ['EXTERNAL']},
|
||||
{ssl_cert_login_from, common_name}
|
||||
]}),
|
||||
|
@ -68,7 +70,7 @@ end_per_testcase(Testcase, Config) ->
|
|||
ok = clear_permissions(Config),
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||
|
||||
amqp(Config) ->
|
||||
external_succeeds(Config) ->
|
||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp_tls),
|
||||
Host = ?config(rmq_hostname, Config),
|
||||
Vhost = ?config(test_vhost, Config),
|
||||
|
@ -90,6 +92,24 @@ amqp(Config) ->
|
|||
end,
|
||||
ok = amqp10_client:close_connection(Connection).
|
||||
|
||||
anonymous_fails(Config) ->
|
||||
Mechansim = anon,
|
||||
OpnConf0 = connection_config(Config, <<"/">>),
|
||||
OpnConf = OpnConf0#{sasl => Mechansim},
|
||||
{ok, Connection} = amqp10_client:open_connection(OpnConf),
|
||||
receive {amqp10_event, {connection, Connection, {closed, Reason}}} ->
|
||||
?assertEqual({sasl_not_supported, Mechansim}, Reason)
|
||||
after 5000 -> ct:fail(missing_closed)
|
||||
end.
|
||||
|
||||
connection_config(Config, Vhost) ->
|
||||
Host = ?config(rmq_hostname, Config),
|
||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
|
||||
#{address => Host,
|
||||
port => Port,
|
||||
container_id => <<"my container">>,
|
||||
hostname => <<"vhost:", Vhost/binary>>}.
|
||||
|
||||
set_permissions(Config, ConfigurePerm, WritePerm, ReadPerm) ->
|
||||
ok = rabbit_ct_broker_helpers:set_permissions(Config,
|
||||
?config(test_user, Config),
|
||||
|
|
|
@ -72,13 +72,25 @@ end_per_testcase(Testcase, Config) ->
|
|||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||
|
||||
old_to_new_on_old(Config) ->
|
||||
ok = shovel(?OLD, ?NEW, ?OLD, Config).
|
||||
case rabbit_ct_helpers:is_mixed_versions() of
|
||||
true ->
|
||||
{skip, "TODO: Unskip when lower version is >= 3.13.7 "
|
||||
"because AMQP 1.0 client must use SASL when connecting to 4.0"};
|
||||
false ->
|
||||
ok = shovel(?OLD, ?NEW, ?OLD, Config)
|
||||
end.
|
||||
|
||||
old_to_new_on_new(Config) ->
|
||||
ok = shovel(?OLD, ?NEW, ?NEW, Config).
|
||||
|
||||
new_to_old_on_old(Config) ->
|
||||
ok = shovel(?NEW, ?OLD, ?OLD, Config).
|
||||
case rabbit_ct_helpers:is_mixed_versions() of
|
||||
true ->
|
||||
{skip, "TODO: Unskip when lower version is >= 3.13.7 "
|
||||
"because AMQP 1.0 client must use SASL when connecting to 4.0"};
|
||||
false ->
|
||||
ok = shovel(?NEW, ?OLD, ?OLD, Config)
|
||||
end.
|
||||
|
||||
new_to_old_on_new(Config) ->
|
||||
ok = shovel(?NEW, ?OLD, ?NEW, Config).
|
||||
|
|
|
@ -63,7 +63,8 @@ groups() ->
|
|||
offset_lag_calculation,
|
||||
test_super_stream_duplicate_partitions,
|
||||
authentication_error_should_close_with_delay,
|
||||
unauthorized_vhost_access_should_close_with_delay
|
||||
unauthorized_vhost_access_should_close_with_delay,
|
||||
sasl_anonymous
|
||||
]},
|
||||
%% Run `test_global_counters` on its own so the global metrics are
|
||||
%% initialised to 0 for each testcase
|
||||
|
@ -249,6 +250,16 @@ test_stream(Config) ->
|
|||
test_server(gen_tcp, Stream, Config),
|
||||
ok.
|
||||
|
||||
sasl_anonymous(Config) ->
|
||||
Port = get_port(gen_tcp, Config),
|
||||
Opts = get_opts(gen_tcp),
|
||||
{ok, S} = gen_tcp:connect("localhost", Port, Opts),
|
||||
C0 = rabbit_stream_core:init(0),
|
||||
C1 = test_peer_properties(gen_tcp, S, C0),
|
||||
C2 = sasl_handshake(gen_tcp, S, C1),
|
||||
C3 = test_anonymous_sasl_authenticate(gen_tcp, S, C2),
|
||||
_C = tune(gen_tcp, S, C3).
|
||||
|
||||
test_update_secret(Config) ->
|
||||
Transport = gen_tcp,
|
||||
{S, C0} = connect_and_authenticate(Transport, Config),
|
||||
|
@ -1150,17 +1161,20 @@ test_authenticate(Transport, S, C0, Username, Password) ->
|
|||
sasl_handshake(Transport, S, C0) ->
|
||||
SaslHandshakeFrame = request(sasl_handshake),
|
||||
ok = Transport:send(S, SaslHandshakeFrame),
|
||||
Plain = <<"PLAIN">>,
|
||||
AmqPlain = <<"AMQPLAIN">>,
|
||||
{Cmd, C1} = receive_commands(Transport, S, C0),
|
||||
case Cmd of
|
||||
{response, _, {sasl_handshake, ?RESPONSE_CODE_OK, Mechanisms}} ->
|
||||
?assertEqual([AmqPlain, Plain], lists:sort(Mechanisms));
|
||||
?assertEqual([<<"AMQPLAIN">>, <<"ANONYMOUS">>, <<"PLAIN">>],
|
||||
lists:sort(Mechanisms));
|
||||
_ ->
|
||||
ct:fail("invalid cmd ~tp", [Cmd])
|
||||
end,
|
||||
C1.
|
||||
|
||||
test_anonymous_sasl_authenticate(Transport, S, C) ->
|
||||
Res = sasl_authenticate(Transport, S, C, <<"ANONYMOUS">>, <<>>),
|
||||
expect_successful_authentication(Res).
|
||||
|
||||
test_plain_sasl_authenticate(Transport, S, C1, Username) ->
|
||||
test_plain_sasl_authenticate(Transport, S, C1, Username, Username).
|
||||
|
||||
|
@ -1175,6 +1189,7 @@ expect_successful_authentication({SaslAuth, C2} = _SaslReponse) ->
|
|||
?assertEqual({response, 2, {sasl_authenticate, ?RESPONSE_CODE_OK}},
|
||||
SaslAuth),
|
||||
C2.
|
||||
|
||||
expect_unsuccessful_authentication({SaslAuth, C2} = _SaslReponse, ExpectedError) ->
|
||||
?assertEqual({response, 2, {sasl_authenticate, ExpectedError}},
|
||||
SaslAuth),
|
||||
|
|
|
@ -556,6 +556,7 @@ rabbit:
|
|||
- rabbit_amqqueue_sup_sup
|
||||
- rabbit_auth_backend_internal
|
||||
- rabbit_auth_mechanism_amqplain
|
||||
- rabbit_auth_mechanism_anonymous
|
||||
- rabbit_auth_mechanism_cr_demo
|
||||
- rabbit_auth_mechanism_plain
|
||||
- rabbit_autoheal
|
||||
|
|
Loading…
Reference in New Issue