diff --git a/deps/amqp10_client/src/amqp10_client.erl b/deps/amqp10_client/src/amqp10_client.erl index a5de8bc88aa7..c5ebc7ba123f 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 302754d4fad3..9125222062eb 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 6d42d7b9f511..a027b25c826c 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 92d2b27aa80f..aa1c78bbac40 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 659ef70eb8c3..3cb3ca4c2bc5 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/docs/rabbitmq.conf.example b/deps/rabbit/docs/rabbitmq.conf.example index da8cbe36a63a..2a3f3f590d4f 100644 --- a/deps/rabbit/docs/rabbitmq.conf.example +++ b/deps/rabbit/docs/rabbitmq.conf.example @@ -232,6 +232,7 @@ ## # auth_mechanisms.1 = PLAIN # auth_mechanisms.2 = AMQPLAIN +# auth_mechanisms.3 = ANONYMOUS ## The rabbitmq-auth-mechanism-ssl plugin makes it possible to ## authenticate a user based on the client's x509 (TLS) certificate. @@ -905,14 +906,8 @@ ## # mqtt.proxy_protocol = false -## Set the default user name and password used for anonymous connections (when client -## provides no credentials). Anonymous connections are highly discouraged! -## -# mqtt.default_user = guest -# mqtt.default_pass = guest - ## Enable anonymous connections. If this is set to false, clients MUST provide -## credentials in order to connect. See also the mqtt.default_user/mqtt.default_pass +## credentials in order to connect. See also the anonymous_login_user/anonymous_login_pass ## keys. Anonymous connections are highly discouraged! ## # mqtt.allow_anonymous = true diff --git a/deps/rabbit/priv/schema/rabbit.schema b/deps/rabbit/priv/schema/rabbit.schema index 4d10cb206aad..324d0f30fe63 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,22 @@ 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]}, binary]}]}. + +{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 +2664,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 3ad7dba7ce71..04f4f5dd1a7b 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)]. - -%% Begin 1-0 - -auth_phase_1_0(Response, - State = #v1{sock = Sock, - connection = Connection = - #v1_connection{auth_mechanism = {Name, AuthMechanism}, - auth_state = AuthState}}) -> + {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). + +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 000000000000..60ec1d05c421 --- /dev/null +++ b/deps/rabbit/src/rabbit_auth_mechanism_anonymous.erl @@ -0,0 +1,54 @@ +%% 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]). +-export([credentials/0]). + +-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 31e235227500..d0881b4acc84 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 9b805502741d..2e5ca40121b6 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 0ff70bf0c520..c717cd886d60 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]). - ok = rpc(Config, application, set_env, [App, Par, Default]), - Reason. +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 287b933239ae..3f322dfbb029 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 1a1b416e90e9..a74b249ea02b 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 3bab2d7bb416..4f8e2b44235b 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 b5f1a5696110..402704fbfe89 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_mqtt/BUILD.bazel b/deps/rabbitmq_mqtt/BUILD.bazel index 9d7d3e88e43b..b9280b4dbbd4 100644 --- a/deps/rabbitmq_mqtt/BUILD.bazel +++ b/deps/rabbitmq_mqtt/BUILD.bazel @@ -26,10 +26,7 @@ APP_DESCRIPTION = "RabbitMQ MQTT Adapter" APP_MODULE = "rabbit_mqtt" APP_ENV = """[ - {default_user, <<"guest">>}, - {default_pass, <<"guest">>}, {ssl_cert_login,false}, - %% To satisfy an unfortunate expectation from popular MQTT clients. {allow_anonymous, true}, {vhost, <<"/">>}, {exchange, <<"amq.topic">>}, diff --git a/deps/rabbitmq_mqtt/Makefile b/deps/rabbitmq_mqtt/Makefile index eb1d6b657356..64bfb24e5116 100644 --- a/deps/rabbitmq_mqtt/Makefile +++ b/deps/rabbitmq_mqtt/Makefile @@ -4,10 +4,7 @@ PROJECT_MOD = rabbit_mqtt define PROJECT_ENV [ - {default_user, <<"guest">>}, - {default_pass, <<"guest">>}, {ssl_cert_login,false}, - %% To satisfy an unfortunate expectation from popular MQTT clients. {allow_anonymous, true}, {vhost, <<"/">>}, {exchange, <<"amq.topic">>}, diff --git a/deps/rabbitmq_mqtt/priv/schema/rabbitmq_mqtt.schema b/deps/rabbitmq_mqtt/priv/schema/rabbitmq_mqtt.schema index cef29eeb4eaf..80f1d83295f9 100644 --- a/deps/rabbitmq_mqtt/priv/schema/rabbitmq_mqtt.schema +++ b/deps/rabbitmq_mqtt/priv/schema/rabbitmq_mqtt.schema @@ -6,35 +6,8 @@ %% ---------------------------------------------------------------------------- % {rabbitmq_mqtt, -% [%% Set the default user name and password. Will be used as the default login -%% if a connecting client provides no other login details. -%% -%% Please note that setting this will allow clients to connect without -%% authenticating! -%% -%% {default_user, <<"guest">>}, -%% {default_pass, <<"guest">>}, - -{mapping, "mqtt.default_user", "rabbitmq_mqtt.default_user", [ - {datatype, string} -]}. - -{mapping, "mqtt.default_pass", "rabbitmq_mqtt.default_pass", [ - {datatype, string} -]}. - -{translation, "rabbitmq_mqtt.default_user", -fun(Conf) -> - list_to_binary(cuttlefish:conf_get("mqtt.default_user", Conf)) -end}. - -{translation, "rabbitmq_mqtt.default_pass", -fun(Conf) -> - list_to_binary(cuttlefish:conf_get("mqtt.default_pass", Conf)) -end}. - -%% Enable anonymous access. If this is set to false, clients MUST provide -%% login information in order to connect. See the default_user/default_pass +% [%% Enable anonymous access. If this is set to false, clients MUST provide +%% login information in order to connect. See the anonymous_login_user/anonymous_login_pass %% configuration elements for managing logins without authentication. %% %% {allow_anonymous, true}, diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl index 9917af58b1cc..f9983f47c0df 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl @@ -183,7 +183,7 @@ process_connect( maybe ok ?= check_extended_auth(ConnectProps), {ok, ClientId} ?= ensure_client_id(ClientId0, CleanStart, ProtoVer), - {ok, {Username1, Password}} ?= check_credentials(Username0, Password0, SslLoginName, PeerIp), + {ok, Username1, Password} ?= check_credentials(Username0, Password0, SslLoginName, PeerIp), {VHostPickedUsing, {VHost, Username2}} = get_vhost(Username1, SslLoginName, Port), ?LOG_DEBUG("MQTT connection ~s picked vhost using ~s", [ConnName0, VHostPickedUsing]), @@ -626,6 +626,8 @@ check_extended_auth(_) -> check_credentials(Username, Password, SslLoginName, PeerIp) -> case creds(Username, Password, SslLoginName) of + {ok, _, _} = Ok -> + Ok; nocreds -> ?LOG_ERROR("MQTT login failed: no credentials provided"), auth_attempt_failed(PeerIp, <<>>), @@ -637,9 +639,7 @@ check_credentials(Username, Password, SslLoginName, PeerIp) -> {invalid_creds, {User, _Pass}} when is_binary(User) -> ?LOG_ERROR("MQTT login failed for user '~s': no password provided", [User]), auth_attempt_failed(PeerIp, User), - {error, ?RC_BAD_USER_NAME_OR_PASSWORD}; - {UserBin, PassBin} -> - {ok, {UserBin, PassBin}} + {error, ?RC_BAD_USER_NAME_OR_PASSWORD} end. -spec ensure_client_id(client_id(), boolean(), protocol_version()) -> @@ -1201,29 +1201,37 @@ get_vhost_from_port_mapping(Port, Mapping) -> Res. creds(User, Pass, SSLLoginName) -> - DefaultUser = rabbit_mqtt_util:env(default_user), - DefaultPass = rabbit_mqtt_util:env(default_pass), - {ok, Anon} = application:get_env(?APP_NAME, allow_anonymous), - {ok, TLSAuth} = application:get_env(?APP_NAME, ssl_cert_login), - HaveDefaultCreds = Anon =:= true andalso - is_binary(DefaultUser) andalso - is_binary(DefaultPass), - CredentialsProvided = User =/= undefined orelse Pass =/= undefined, - CorrectCredentials = is_binary(User) andalso is_binary(Pass) andalso Pass =/= <<>>, + ValidCredentials = is_binary(User) andalso is_binary(Pass) andalso Pass =/= <<>>, + {ok, TLSAuth} = application:get_env(?APP_NAME, ssl_cert_login), SSLLoginProvided = TLSAuth =:= true andalso SSLLoginName =/= none, - case {CredentialsProvided, CorrectCredentials, SSLLoginProvided, HaveDefaultCreds} of - %% Username and password take priority - {true, true, _, _} -> {User, Pass}; - %% Either username or password is provided - {true, false, _, _} -> {invalid_creds, {User, Pass}}; - %% rabbitmq_mqtt.ssl_cert_login is true. SSL user name provided. - %% Authenticating using username only. - {false, false, true, _} -> {SSLLoginName, none}; - %% Anonymous connection uses default credentials - {false, false, false, true} -> {DefaultUser, DefaultPass}; - _ -> nocreds + case {CredentialsProvided, ValidCredentials, SSLLoginProvided} of + {true, true, _} -> + %% Username and password take priority + {ok, User, Pass}; + {true, false, _} -> + %% Either username or password is provided + {invalid_creds, {User, Pass}}; + {false, false, true} -> + %% rabbitmq_mqtt.ssl_cert_login is true. SSL user name provided. + %% Authenticating using username only. + {ok, SSLLoginName, none}; + {false, false, false} -> + {ok, AllowAnon} = application:get_env(?APP_NAME, allow_anonymous), + case AllowAnon of + true -> + case rabbit_auth_mechanism_anonymous:credentials() of + {ok, _, _} = Ok -> + Ok; + error -> + nocreds + end; + false -> + nocreds + end; + _ -> + nocreds end. -spec auth_attempt_failed(inet:ip_address(), binary()) -> ok. diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_util.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_util.erl index e47d5e443eae..b8c65cb7e54c 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_util.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_util.erl @@ -141,10 +141,10 @@ env(Key) -> undefined -> undefined end. -coerce_env_value(default_pass, Val) -> rabbit_data_coercion:to_binary(Val); -coerce_env_value(default_user, Val) -> rabbit_data_coercion:to_binary(Val); -coerce_env_value(vhost, Val) -> rabbit_data_coercion:to_binary(Val); -coerce_env_value(_, Val) -> Val. +coerce_env_value(vhost, Val) -> + rabbit_data_coercion:to_binary(Val); +coerce_env_value(_, Val) -> + Val. -spec table_lookup(rabbit_framing:amqp_table() | undefined, binary()) -> tuple() | undefined. diff --git a/deps/rabbitmq_mqtt/test/auth_SUITE.erl b/deps/rabbitmq_mqtt/test/auth_SUITE.erl index b7c6f33f405d..a1434b336ff6 100644 --- a/deps/rabbitmq_mqtt/test/auth_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/auth_SUITE.erl @@ -123,15 +123,20 @@ init_per_group(authz, Config0) -> User = <<"mqtt-user">>, Password = <<"mqtt-password">>, VHost = <<"mqtt-vhost">>, - MqttConfig = {rabbitmq_mqtt, [{default_user, User} - ,{default_pass, Password} - ,{allow_anonymous, true} - ,{vhost, VHost} - ,{exchange, <<"amq.topic">>} - ]}, - Config = rabbit_ct_helpers:run_setup_steps(rabbit_ct_helpers:merge_app_env(Config0, MqttConfig), - rabbit_ct_broker_helpers:setup_steps() ++ - rabbit_ct_client_helpers:setup_steps()), + Env = [{rabbitmq_mqtt, + [{allow_anonymous, true}, + {vhost, VHost}, + {exchange, <<"amq.topic">>} + ]}, + {rabbit, + [{anonymous_login_user, User}, + {anonymous_login_pass, Password} + ]}], + Config1 = rabbit_ct_helpers:merge_app_env(Config0, Env), + Config = rabbit_ct_helpers:run_setup_steps( + Config1, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps()), rabbit_ct_broker_helpers:add_user(Config, User, Password), rabbit_ct_broker_helpers:add_vhost(Config, VHost), [Log|_] = rpc(Config, 0, rabbit, log_locations, []), @@ -412,7 +417,6 @@ anonymous_auth_success(Config) -> anonymous_auth_failure(Config) -> expect_authentication_failure(fun connect_anonymous/1, Config). - ssl_user_auth_success(Config) -> expect_successful_connection(fun connect_ssl/1, Config). diff --git a/deps/rabbitmq_mqtt/test/config_schema_SUITE_data/rabbitmq_mqtt.snippets b/deps/rabbitmq_mqtt/test/config_schema_SUITE_data/rabbitmq_mqtt.snippets index df1a3f3a57f5..7feb71a6b92e 100644 --- a/deps/rabbitmq_mqtt/test/config_schema_SUITE_data/rabbitmq_mqtt.snippets +++ b/deps/rabbitmq_mqtt/test/config_schema_SUITE_data/rabbitmq_mqtt.snippets @@ -1,7 +1,5 @@ [{defaults, "listeners.tcp.default = 5672 - mqtt.default_user = guest - mqtt.default_pass = guest mqtt.allow_anonymous = true mqtt.vhost = / mqtt.exchange = amq.topic @@ -20,9 +18,7 @@ mqtt.topic_alias_maximum = 16", [{rabbit,[{tcp_listeners,[5672]}]}, {rabbitmq_mqtt, - [{default_user,<<"guest">>}, - {default_pass,<<"guest">>}, - {allow_anonymous,true}, + [{allow_anonymous,true}, {vhost,<<"/">>}, {exchange,<<"amq.topic">>}, {max_session_expiry_interval_seconds,86400}, @@ -101,8 +97,6 @@ [rabbitmq_mqtt]}, {proxy_protocol, "listeners.tcp.default = 5672 - mqtt.default_user = guest - mqtt.default_pass = guest mqtt.allow_anonymous = true mqtt.vhost = / mqtt.exchange = amq.topic @@ -111,9 +105,7 @@ mqtt.proxy_protocol = true", [{rabbit,[{tcp_listeners,[5672]}]}, {rabbitmq_mqtt, - [{default_user,<<"guest">>}, - {default_pass,<<"guest">>}, - {allow_anonymous,true}, + [{allow_anonymous,true}, {vhost,<<"/">>}, {exchange,<<"amq.topic">>}, {max_session_expiry_interval_seconds,infinity}, @@ -121,9 +113,7 @@ {proxy_protocol,true}]}], [rabbitmq_mqtt]}, {prefetch_retained_msg_store, - "mqtt.default_user = guest - mqtt.default_pass = guest - mqtt.allow_anonymous = true + "mqtt.allow_anonymous = true mqtt.vhost = / mqtt.exchange = amq.topic mqtt.max_session_expiry_interval_seconds = 1800 @@ -136,9 +126,7 @@ mqtt.listeners.ssl = none mqtt.listeners.tcp.default = 1883", [{rabbitmq_mqtt, - [{default_user,<<"guest">>}, - {default_pass,<<"guest">>}, - {allow_anonymous,true}, + [{allow_anonymous,true}, {vhost,<<"/">>}, {exchange,<<"amq.topic">>}, {max_session_expiry_interval_seconds,1800}, diff --git a/deps/rabbitmq_mqtt/test/rabbitmq_mqtt.app b/deps/rabbitmq_mqtt/test/rabbitmq_mqtt.app index c4083ec5fc81..287c59cfe230 100644 --- a/deps/rabbitmq_mqtt/test/rabbitmq_mqtt.app +++ b/deps/rabbitmq_mqtt/test/rabbitmq_mqtt.app @@ -4,9 +4,7 @@ {modules, []}, {registered, []}, {mod, {rabbit_mqtt, []}}, - {env, [{default_user, "guest_user"}, - {default_pass, "guest_pass"}, - {ssl_cert_login,false}, + {env, [{ssl_cert_login,false}, {allow_anonymous, true}, {vhost, "/"}, {exchange, "amq.topic"}, diff --git a/deps/rabbitmq_mqtt/test/util_SUITE.erl b/deps/rabbitmq_mqtt/test/util_SUITE.erl index a4a343c1eb94..3b16c8e68824 100644 --- a/deps/rabbitmq_mqtt/test/util_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/util_SUITE.erl @@ -18,8 +18,6 @@ groups() -> [ {tests, [parallel], [ coerce_vhost, - coerce_default_user, - coerce_default_pass, mqtt_amqp_topic_translation ] } @@ -36,12 +34,6 @@ end_per_suite(Config) -> coerce_vhost(_) -> ?assertEqual(<<"/">>, rabbit_mqtt_util:env(vhost)). -coerce_default_user(_) -> - ?assertEqual(<<"guest_user">>, rabbit_mqtt_util:env(default_user)). - -coerce_default_pass(_) -> - ?assertEqual(<<"guest_pass">>, rabbit_mqtt_util:env(default_pass)). - mqtt_amqp_topic_translation(_) -> ok = application:set_env(rabbitmq_mqtt, sparkplug, true), ok = rabbit_mqtt_util:init_sparkplug(), diff --git a/deps/rabbitmq_shovel/test/amqp10_inter_cluster_SUITE.erl b/deps/rabbitmq_shovel/test/amqp10_inter_cluster_SUITE.erl index 6e25ff2dfdfa..f7c25a8af8f1 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 7152396aa49a..06792b4e739d 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 d3110c5f5cd9..7d07fc31fa64 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 diff --git a/release-notes/4.0.0.md b/release-notes/4.0.0.md index e29e3ad08c67..a379c799ffda 100644 --- a/release-notes/4.0.0.md +++ b/release-notes/4.0.0.md @@ -25,14 +25,14 @@ Some key improvements in this release are listed below. See Compatibility Notes below to learn about **breaking or potentially breaking changes** in this release. -## Release Artifacts - -RabbitMQ releases are distributed via [GitHub](https://github.com/rabbitmq/rabbitmq-server/releases). -[Debian](https://rabbitmq.com/install-debian.html) and [RPM packages](https://rabbitmq.com/install-rpm.html) are available via Cloudsmith mirrors. - -[Community Docker image](https://hub.docker.com/_/rabbitmq/), [Chocolatey package](https://community.chocolatey.org/packages/rabbitmq), and the [Homebrew formula](https://www.rabbitmq.com/docs/install-homebrew) -are other installation options. They are updated with a delay. +## Breaking Changes and Compatibility Notes +* RabbitMQ 3.13 [rabbitmq.conf](https://www.rabbitmq.com/docs/configure#config-file) settings `mqtt.default_user`, `mqtt.default_password`, and `amqp1_0.default_user` are unsupported in RabbitMQ 4.0 + Instead, set the new RabbitMQ 4.0 settings `anonymous_login_user` and `anonymous_login_pass` (both values default to `guest`). + For production scenarios, [disallow anonymous logins](https://www.rabbitmq.com/docs/next/production-checklist#anonymous-login) +* RabbitMQ 3.13 `rabbitmq.conf` setting `rabbitmq_amqp1_0.default_vhost` is unsupported in RabbitMQ 4.0. + Instead `default_vhost` will be used to determine the default vhost an AMQP 1.0 client connects to(i.e. when the AMQP 1.0 client does not define the vhost in the `hostname` field of the `open` frame) +* RabbitMQ Shovels will be able connect to a RabbitMQ 4.0 node via AMQP 1.0 only when the Shovel runs on a RabbitMQ node >= `3.13.7` ## Erlang/OTP Compatibility Notes @@ -42,6 +42,15 @@ This release [requires Erlang 26.2](https://www.rabbitmq.com/docs/which-erlang). what package repositories and tools can be used to provision latest patch versions of Erlang 26.x. +## Release Artifacts + +RabbitMQ releases are distributed via [GitHub](https://github.com/rabbitmq/rabbitmq-server/releases). +[Debian](https://rabbitmq.com/install-debian.html) and [RPM packages](https://rabbitmq.com/install-rpm.html) are available via Cloudsmith mirrors. + +[Community Docker image](https://hub.docker.com/_/rabbitmq/), [Chocolatey package](https://community.chocolatey.org/packages/rabbitmq), and the [Homebrew formula](https://www.rabbitmq.com/docs/install-homebrew) +are other installation options. They are updated with a delay. + + ## Upgrading to 4.0 ### Documentation guides on upgrades @@ -81,7 +90,6 @@ TBD TBD - ### Dependency Changes * Ra was [upgraded to `2.13.5`](https://github.com/rabbitmq/ra/releases)