From 6373af3300b850c1d4c74ca8e5f8453c3da3eb18 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Pedron Date: Tue, 25 Nov 2014 19:21:27 +0100 Subject: Add more properties to the user_authentication_* notifications Until now, the only property was {name, Username}. The added properties are: o {connection_type, network | direct} o {error, Message} (only if the authentication failed) For network connections, the following informations are added as returned by rabbit_reader:infos/2: o auth_mechanism o host o name (the property is renamed to connection_name to avoid conflict with the username) o peer_cert_issuer o peer_cert_subject o peer_cert_validity o peer_host o peer_port o protocol o ssl o ssl_cipher o ssl_protocol o vhost The notification is sent by rabbit_reader:notify_auth_result/5 and rabbit_direct:notify_auth_result/4, not by rabbit_access_control:check_user_login/2 anymore. This fixes a bug where a "user_authentication_success" event would be sent by rabbit_access_control:check_user_login/2, even if rabbit_reader:auth_phase/2 rejects the user later because the connection isn't on the loopback interface. --- src/rabbit_access_control.erl | 31 ++++++++++++------------ src/rabbit_direct.erl | 20 ++++++++++++++-- src/rabbit_reader.erl | 55 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 83 insertions(+), 23 deletions(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index d1577432..41c54b07 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -31,10 +31,12 @@ -spec(check_user_pass_login/2 :: (rabbit_types:username(), rabbit_types:password()) - -> {'ok', rabbit_types:user()} | {'refused', string(), [any()]}). + -> {'ok', rabbit_types:user()} | + {'refused', rabbit_types:username(), string(), [any()]}). -spec(check_user_login/2 :: (rabbit_types:username(), [{atom(), any()}]) - -> {'ok', rabbit_types:user()} | {'refused', string(), [any()]}). + -> {'ok', rabbit_types:user()} | + {'refused', rabbit_types:username(), string(), [any()]}). -spec(check_user_loopback/2 :: (rabbit_types:username(), rabbit_net:socket() | inet:ip_address()) -> 'ok' | 'not_allowed'). @@ -55,7 +57,7 @@ check_user_pass_login(Username, Password) -> check_user_login(Username, AuthProps) -> {ok, Modules} = application:get_env(rabbit, auth_backends), R = lists:foldl( - fun ({ModN, ModZs0}, {refused, _, _}) -> + fun ({ModN, ModZs0}, {refused, _, _, _}) -> ModZs = case ModZs0 of A when is_atom(A) -> [A]; L when is_list(L) -> L @@ -69,7 +71,7 @@ check_user_login(Username, AuthProps) -> Else -> Else end; - (Mod, {refused, _, _}) -> + (Mod, {refused, _, _, _}) -> %% Same module for authN and authZ. Just take the result %% it gives us case try_authenticate(Mod, Username, AuthProps) of @@ -81,19 +83,17 @@ check_user_login(Username, AuthProps) -> (_, {ok, User}) -> %% We've successfully authenticated. Skip to the end... {ok, User} - end, {refused, "No modules checked '~s'", [Username]}, Modules), - rabbit_event:notify(case R of - {ok, _User} -> user_authentication_success; - _ -> user_authentication_failure - end, [{name, Username}]), + end, + {refused, Username, "No modules checked '~s'", [Username]}, Modules), R. try_authenticate(Module, Username, AuthProps) -> case Module:user_login_authentication(Username, AuthProps) of {ok, AuthUser} -> {ok, AuthUser}; - {error, E} -> {refused, "~s failed authenticating ~s: ~p~n", + {error, E} -> {refused, Username, + "~s failed authenticating ~s: ~p~n", [Module, Username, E]}; - {refused, F, A} -> {refused, F, A} + {refused, F, A} -> {refused, Username, F, A} end. try_authorize(Modules, Username) -> @@ -101,12 +101,13 @@ try_authorize(Modules, Username) -> fun (Module, {ok, ModsImpls}) -> case Module:user_login_authorization(Username) of {ok, Impl} -> {ok, [{Module, Impl} | ModsImpls]}; - {error, E} -> {refused, "~s failed authorizing ~s: ~p~n", + {error, E} -> {refused, Username, + "~s failed authorizing ~s: ~p~n", [Module, Username, E]}; - {refused, F, A} -> {refused, F, A} + {refused, F, A} -> {refused, Username, F, A} end; - (_, {refused, _, _} = Error) -> - Error + (_, {refused, F, A}) -> + {refused, Username, F, A} end, {ok, []}, Modules). user(#auth_user{username = Username, tags = Tags}, {ok, ModZImpls}) -> diff --git a/src/rabbit_direct.erl b/src/rabbit_direct.erl index f6140f09..9756dd49 100644 --- a/src/rabbit_direct.erl +++ b/src/rabbit_direct.erl @@ -83,14 +83,30 @@ connect({Username, Password}, VHost, Protocol, Pid, Infos) -> connect0(AuthFun, VHost, Protocol, Pid, Infos) -> case rabbit:is_running() of true -> case AuthFun() of - {ok, User} -> + {ok, User = #user{username = Username}} -> + notify_auth_result(Username, + user_authentication_success, "", []), connect1(User, VHost, Protocol, Pid, Infos); - {refused, _M, _A} -> + {refused, Username, Msg, Args} -> + notify_auth_result(Username, + user_authentication_failure, Msg, Args), {error, {auth_failure, "Refused"}} end; false -> {error, broker_not_found_on_node} end. +notify_auth_result(Username, AuthResult, Msg, Args) -> + EventProps0 = [{connection_type, direct}], + EventProps1 = case Username of + none -> [{name, ''} | EventProps0]; + _ -> [{name, Username} | EventProps0] + end, + EventProps = case Msg of + "" -> EventProps1; + _ -> [{error, lists:flatten(io_lib:format(Msg, Args))} | EventProps1] + end, + rabbit_event:notify(AuthResult, EventProps). + connect1(User, VHost, Protocol, Pid, Infos) -> try rabbit_access_control:check_vhost_access(User, VHost, undefined) of ok -> ok = pg_local:join(rabbit_direct, Pid), diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 2033dd14..a18d75d7 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -1046,9 +1046,15 @@ auth_phase(Response, auth_state = AuthState}, sock = Sock}) -> case AuthMechanism:handle_response(Response, AuthState) of + {refused, Username, Msg, Args} -> + auth_fail(Username, Msg, Args, Name, State); {refused, Msg, Args} -> - auth_fail(Msg, Args, Name, State); + %% Older auth mechanisms didn't return the username, even if + %% they reach a stage where they know it. + auth_fail(none, Msg, Args, Name, State); {protocol_error, Msg, Args} -> + notify_auth_result(none, user_authentication_failure, + Msg, Args, State), rabbit_misc:protocol_error(syntax_error, Msg, Args); {challenge, Challenge, AuthState1} -> Secure = #'connection.secure'{challenge = Challenge}, @@ -1057,9 +1063,12 @@ auth_phase(Response, auth_state = AuthState1}}; {ok, User = #user{username = Username}} -> case rabbit_access_control:check_user_loopback(Username, Sock) of - ok -> ok; - not_allowed -> auth_fail("user '~s' can only connect via " - "localhost", [Username], Name, State) + ok -> + notify_auth_result(Username, user_authentication_success, + "", [], State); + not_allowed -> + auth_fail(Username, "user '~s' can only connect via " + "localhost", [Username], Name, State) end, Tune = #'connection.tune'{frame_max = get_env(frame_max), channel_max = get_env(channel_max), @@ -1071,11 +1080,14 @@ auth_phase(Response, end. -ifdef(use_specs). --spec(auth_fail/4 :: (string(), [any()], binary(), #v1{}) -> no_return()). +-spec(auth_fail/5 :: + (rabbit_types:username() | none, string(), [any()], binary(), #v1{}) -> + no_return()). -endif. -auth_fail(Msg, Args, AuthName, +auth_fail(Username, Msg, Args, AuthName, State = #v1{connection = #connection{protocol = Protocol, capabilities = Capabilities}}) -> + notify_auth_result(Username, user_authentication_failure, Msg, Args, State), AmqpError = rabbit_misc:amqp_error( access_refused, "~s login refused: ~s", [AuthName, io_lib:format(Msg, Args)], none), @@ -1094,6 +1106,37 @@ auth_fail(Msg, Args, AuthName, end, rabbit_misc:protocol_error(AmqpError). +notify_auth_result(Username, AuthResult, Msg, Args, State) -> + EventProps0 = [{connection_type, network}], + EventProps1 = lists:foldl( + fun + (name, Acc) -> [{connection_name, i(name, State)} | Acc]; + (Item, Acc) -> [{Item, i(Item, State)} | Acc] + end, EventProps0, [ + peer_cert_validity, + peer_cert_subject, + peer_cert_issuer, + ssl_cipher, + ssl_protocol, + ssl, + auth_mechanism, + protocol, + peer_port, + peer_host, + name, + vhost, + host + ]), + EventProps2 = case Username of + none -> [{name, ''} | EventProps1]; + _ -> [{name, Username} | EventProps1] + end, + EventProps = case Msg of + "" -> EventProps2; + _ -> [{error, lists:flatten(io_lib:format(Msg, Args))} | EventProps2] + end, + rabbit_event:notify(AuthResult, EventProps). + %%-------------------------------------------------------------------------- infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items]. -- cgit v1.2.1