diff --git a/deps/amqp10_client/src/amqp10_client.erl b/deps/amqp10_client/src/amqp10_client.erl index a5de8bc88a..c5ebc7ba12 100644 --- a/deps/amqp10_client/src/amqp10_client.erl +++ b/deps/amqp10_client/src/amqp10_client.erl @@ -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")), diff --git a/deps/amqp10_client/test/system_SUITE.erl b/deps/amqp10_client/test/system_SUITE.erl index 302754d4fa..9125222062 100644 --- a/deps/amqp10_client/test/system_SUITE.erl +++ b/deps/amqp10_client/test/system_SUITE.erl @@ -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()); diff --git a/deps/rabbit/BUILD.bazel b/deps/rabbit/BUILD.bazel index 6d42d7b9f5..a027b25c82 100644 --- a/deps/rabbit/BUILD.bazel +++ b/deps/rabbit/BUILD.bazel @@ -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, []}, diff --git a/deps/rabbit/Makefile b/deps/rabbit/Makefile index 92d2b27aa8..aa1c78bbac 100644 --- a/deps/rabbit/Makefile +++ b/deps/rabbit/Makefile @@ -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, []}, diff --git a/deps/rabbit/app.bzl b/deps/rabbit/app.bzl index 659ef70eb8..3cb3ca4c2b 100644 --- a/deps/rabbit/app.bzl +++ b/deps/rabbit/app.bzl @@ -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", diff --git a/deps/rabbit/priv/schema/rabbit.schema b/deps/rabbit/priv/schema/rabbit.schema index 4d10cb206a..2331699751 100644 --- a/deps/rabbit/priv/schema/rabbit.schema +++ b/deps/rabbit/priv/schema/rabbit.schema @@ -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"]} diff --git a/deps/rabbit/src/rabbit_amqp_reader.erl b/deps/rabbit/src/rabbit_amqp_reader.erl index 3ad7dba7ce..04f4f5dd1a 100644 --- a/deps/rabbit/src/rabbit_amqp_reader.erl +++ b/deps/rabbit/src/rabbit_amqp_reader.erl @@ -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, - connection = Connection, - sock = Sock}) -> +handle_sasl_frame(#'v1_0.sasl_init'{mechanism = {symbol, Mechanism}, + 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{ - auth_mechanism = {Mechanism, AuthMechanism}, - auth_state = AuthMechanism:init(Sock)}, - connection_state = securing}, - auth_phase_1_0(Response, State); + AuthState = AuthMechanism:init(Sock), + State = State0#v1{ + connection = Connection#v1_connection{ + auth_mechanism = {Mechanism, AuthMechanism}, + auth_state = AuthState}, + connection_state = securing}, + 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 = <>, 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,18 +680,25 @@ 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, - State = #v1{sock = Sock, - connection = Connection = - #v1_connection{auth_mechanism = {Name, AuthMechanism}, - auth_state = AuthState}}) -> +auth_phase( + Response, + State = #v1{sock = Sock, + connection = Conn = #v1_connection{auth_mechanism = {Name, AuthMechanism}, + auth_state = AuthState}}) -> case AuthMechanism:handle_response(Response, AuthState) of {refused, Username, Msg, Args} -> %% We don't trust the client at this point - force them to wait @@ -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}) -> diff --git a/deps/rabbit/src/rabbit_auth_mechanism_anonymous.erl b/deps/rabbit/src/rabbit_auth_mechanism_anonymous.erl new file mode 100644 index 0000000000..016d36545a --- /dev/null +++ b/deps/rabbit/src/rabbit_auth_mechanism_anonymous.erl @@ -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. diff --git a/deps/rabbit/src/rabbit_auth_mechanism_plain.erl b/deps/rabbit/src/rabbit_auth_mechanism_plain.erl index 31e2352275..d0881b4acc 100644 --- a/deps/rabbit/src/rabbit_auth_mechanism_plain.erl +++ b/deps/rabbit/src/rabbit_auth_mechanism_plain.erl @@ -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 - end; - error -> error + {ok, User, Response1} -> + case extract_elem(Response1) of + {ok, Pass, <<>>} -> + {ok, User, Pass}; + _ -> + error + end; + error -> + error end. extract_elem(<<0:8, Rest/binary>>) -> diff --git a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl index 9b80550274..2e5ca40121 100644 --- a/deps/rabbit/src/rabbit_reader.erl +++ b/deps/rabbit/src/rabbit_reader.erl @@ -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) -> - {rabbit_amqp_reader, init, - [Mode, pack_for_1_0(Buf, BufLen, State0)]} - end, - State#v1{connection_state = {become, F}}. +become_10(State) -> + Fun = fun(_Deb, Buf, BufLen, State0) -> + {rabbit_amqp_reader, init, + [pack_for_1_0(Buf, BufLen, State0)]} + end, + State#v1{connection_state = {become, Fun}}. pack_for_1_0(Buf, BufLen, #v1{sock = Sock, recv_len = RecvLen, diff --git a/deps/rabbit/test/amqp_auth_SUITE.erl b/deps/rabbit/test/amqp_auth_SUITE.erl index 0ff70bf0c5..c717cd886d 100644 --- a/deps/rabbit/test/amqp_auth_SUITE.erl +++ b/deps/rabbit/test/amqp_auth_SUITE.erl @@ -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 - after 5000 -> ct:fail(missing_closed) - end, + 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">>), diff --git a/deps/rabbit/test/amqp_system_SUITE_data/fsharp-tests/Program.fs b/deps/rabbit/test/amqp_system_SUITE_data/fsharp-tests/Program.fs index 287b933239..3f322dfbb0 100755 --- a/deps/rabbit/test/amqp_system_SUITE_data/fsharp-tests/Program.fs +++ b/deps/rabbit/test/amqp_system_SUITE_data/fsharp-tests/Program.fs @@ -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 _ -> () diff --git a/deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets b/deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets index 1a1b416e90..a74b249ea0 100644 --- a/deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets +++ b/deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets @@ -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 diff --git a/deps/rabbit/test/unit_access_control_SUITE.erl b/deps/rabbit/test/unit_access_control_SUITE.erl index 3bab2d7bb4..4f8e2b4423 100644 --- a/deps/rabbit/test/unit_access_control_SUITE.erl +++ b/deps/rabbit/test/unit_access_control_SUITE.erl @@ -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), diff --git a/deps/rabbitmq_auth_mechanism_ssl/test/system_SUITE.erl b/deps/rabbitmq_auth_mechanism_ssl/test/system_SUITE.erl index b5f1a56961..402704fbfe 100644 --- a/deps/rabbitmq_auth_mechanism_ssl/test/system_SUITE.erl +++ b/deps/rabbitmq_auth_mechanism_ssl/test/system_SUITE.erl @@ -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), diff --git a/deps/rabbitmq_shovel/test/amqp10_inter_cluster_SUITE.erl b/deps/rabbitmq_shovel/test/amqp10_inter_cluster_SUITE.erl index 6e25ff2dfd..f7c25a8af8 100644 --- a/deps/rabbitmq_shovel/test/amqp10_inter_cluster_SUITE.erl +++ b/deps/rabbitmq_shovel/test/amqp10_inter_cluster_SUITE.erl @@ -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). diff --git a/deps/rabbitmq_stream/test/rabbit_stream_SUITE.erl b/deps/rabbitmq_stream/test/rabbit_stream_SUITE.erl index 7152396aa4..06792b4e73 100644 --- a/deps/rabbitmq_stream/test/rabbit_stream_SUITE.erl +++ b/deps/rabbitmq_stream/test/rabbit_stream_SUITE.erl @@ -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), diff --git a/moduleindex.yaml b/moduleindex.yaml index d3110c5f5c..7d07fc31fa 100755 --- a/moduleindex.yaml +++ b/moduleindex.yaml @@ -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