|
7 | 7 |
|
8 | 8 | -module(rabbit_amqp_reader). |
9 | 9 |
|
| 10 | +-include_lib("kernel/include/logger.hrl"). |
10 | 11 | -include_lib("rabbit_common/include/rabbit.hrl"). |
11 | 12 | -include_lib("amqp10_common/include/amqp10_types.hrl"). |
12 | 13 | -include("rabbit_amqp.hrl"). |
13 | 14 |
|
14 | 15 | -export([init/1, |
15 | 16 | info/2, |
16 | | - mainloop/2]). |
| 17 | + mainloop/2, |
| 18 | + set_credential/2]). |
17 | 19 |
|
18 | 20 | -export([system_continue/3, |
19 | 21 | system_terminate/4, |
|
53 | 55 | channel_max :: non_neg_integer(), |
54 | 56 | auth_mechanism :: sasl_init_unprocessed | {binary(), module()}, |
55 | 57 | auth_state :: term(), |
| 58 | + credential_timer :: undefined | reference(), |
56 | 59 | properties :: undefined | {map, list(tuple())} |
57 | 60 | }). |
58 | 61 |
|
@@ -139,6 +142,11 @@ server_properties() -> |
139 | 142 | Props = [{{symbol, <<"node">>}, {utf8, atom_to_binary(node())}} | Props1], |
140 | 143 | {map, Props}. |
141 | 144 |
|
| 145 | +-spec set_credential(pid(), binary()) -> ok. |
| 146 | +set_credential(Pid, Credential) -> |
| 147 | + Pid ! {set_credential, Credential}, |
| 148 | + ok. |
| 149 | + |
142 | 150 | %%-------------------------------------------------------------------------- |
143 | 151 |
|
144 | 152 | inet_op(F) -> rabbit_misc:throw_on_error(inet_error, F). |
@@ -243,6 +251,8 @@ handle_other({'$gen_cast', {force_event_refresh, _Ref}}, State) -> |
243 | 251 | State; |
244 | 252 | handle_other(terminate_connection, _State) -> |
245 | 253 | stop; |
| 254 | +handle_other({set_credential, Cred}, State) -> |
| 255 | + set_credential0(Cred, State); |
246 | 256 | handle_other(credential_expired, State) -> |
247 | 257 | Error = error_frame(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, "credential expired", []), |
248 | 258 | handle_exception(State, 0, Error); |
@@ -320,16 +330,14 @@ error_frame(Condition, Fmt, Args) -> |
320 | 330 |
|
321 | 331 | handle_exception(State = #v1{connection_state = closed}, Channel, |
322 | 332 | #'v1_0.error'{description = {utf8, Desc}}) -> |
323 | | - rabbit_log_connection:error( |
324 | | - "Error on AMQP 1.0 connection ~tp (~tp), channel number ~b:~n~tp", |
325 | | - [self(), closed, Channel, Desc]), |
| 333 | + ?LOG_ERROR("Error on AMQP 1.0 connection ~tp (~tp), channel number ~b:~n~tp", |
| 334 | + [self(), closed, Channel, Desc]), |
326 | 335 | State; |
327 | 336 | handle_exception(State = #v1{connection_state = CS}, Channel, |
328 | 337 | Error = #'v1_0.error'{description = {utf8, Desc}}) |
329 | 338 | when ?IS_RUNNING(State) orelse CS =:= closing -> |
330 | | - rabbit_log_connection:error( |
331 | | - "Error on AMQP 1.0 connection ~tp (~tp), channel number ~b:~n~tp", |
332 | | - [self(), CS, Channel, Desc]), |
| 339 | + ?LOG_ERROR("Error on AMQP 1.0 connection ~tp (~tp), channel number ~b:~n~tp", |
| 340 | + [self(), CS, Channel, Desc]), |
333 | 341 | close(Error, State); |
334 | 342 | handle_exception(State, _Channel, Error) -> |
335 | 343 | silent_close_delay(), |
@@ -416,21 +424,23 @@ handle_connection_frame( |
416 | 424 | }, |
417 | 425 | helper_sup = HelperSupPid, |
418 | 426 | sock = Sock} = State0) -> |
419 | | - logger:update_process_metadata(#{amqp_container => ContainerId}), |
420 | 427 | Vhost = vhost(Hostname), |
| 428 | + logger:update_process_metadata(#{amqp_container => ContainerId, |
| 429 | + vhost => Vhost, |
| 430 | + user => Username}), |
421 | 431 | ok = check_user_loopback(State0), |
422 | 432 | ok = check_vhost_exists(Vhost, State0), |
423 | 433 | ok = check_vhost_alive(Vhost), |
424 | 434 | ok = rabbit_access_control:check_vhost_access(User, Vhost, {socket, Sock}, #{}), |
425 | 435 | ok = check_vhost_connection_limit(Vhost, Username), |
426 | 436 | ok = check_user_connection_limit(Username), |
427 | | - ok = ensure_credential_expiry_timer(User), |
| 437 | + Timer = maybe_start_credential_expiry_timer(User), |
428 | 438 | rabbit_core_metrics:auth_attempt_succeeded(<<>>, Username, amqp10), |
429 | 439 | notify_auth(user_authentication_success, Username, State0), |
430 | | - rabbit_log_connection:info( |
431 | | - "Connection from AMQP 1.0 container '~ts': user '~ts' authenticated " |
432 | | - "using SASL mechanism ~s and granted access to vhost '~ts'", |
433 | | - [ContainerId, Username, Mechanism, Vhost]), |
| 440 | + ?LOG_INFO( |
| 441 | + "Connection from AMQP 1.0 container '~ts': user '~ts' authenticated " |
| 442 | + "using SASL mechanism ~s and granted access to vhost '~ts'", |
| 443 | + [ContainerId, Username, Mechanism, Vhost]), |
434 | 444 |
|
435 | 445 | OutgoingMaxFrameSize = case ClientMaxFrame of |
436 | 446 | undefined -> |
@@ -499,17 +509,18 @@ handle_connection_frame( |
499 | 509 | outgoing_max_frame_size = OutgoingMaxFrameSize, |
500 | 510 | channel_max = EffectiveChannelMax, |
501 | 511 | properties = Properties, |
502 | | - timeout = ReceiveTimeoutMillis}, |
| 512 | + timeout = ReceiveTimeoutMillis, |
| 513 | + credential_timer = Timer}, |
503 | 514 | heartbeater = Heartbeater}, |
504 | 515 | State = start_writer(State1), |
505 | 516 | HostnameVal = case Hostname of |
506 | 517 | undefined -> undefined; |
507 | 518 | null -> undefined; |
508 | 519 | {utf8, Val} -> Val |
509 | 520 | end, |
510 | | - rabbit_log:debug( |
511 | | - "AMQP 1.0 connection.open frame: hostname = ~ts, extracted vhost = ~ts, idle-time-out = ~p", |
512 | | - [HostnameVal, Vhost, IdleTimeout]), |
| 521 | + ?LOG_DEBUG( |
| 522 | + "AMQP 1.0 connection.open frame: hostname = ~ts, extracted vhost = ~ts, idle-time-out = ~p", |
| 523 | + [HostnameVal, Vhost, IdleTimeout]), |
513 | 524 |
|
514 | 525 | Infos = infos(?CONNECTION_EVENT_KEYS, State), |
515 | 526 | ok = rabbit_core_metrics:connection_created( |
@@ -768,16 +779,16 @@ notify_auth(EventType, Username, State) -> |
768 | 779 | rabbit_event:notify(EventType, EventProps). |
769 | 780 |
|
770 | 781 | track_channel(ChannelNum, SessionPid, #v1{tracked_channels = Channels} = State) -> |
771 | | - rabbit_log:debug("AMQP 1.0 created session process ~p for channel number ~b", |
772 | | - [SessionPid, ChannelNum]), |
| 782 | + ?LOG_DEBUG("AMQP 1.0 created session process ~p for channel number ~b", |
| 783 | + [SessionPid, ChannelNum]), |
773 | 784 | _Ref = erlang:monitor(process, SessionPid, [{tag, {'DOWN', ChannelNum}}]), |
774 | 785 | State#v1{tracked_channels = maps:put(ChannelNum, SessionPid, Channels)}. |
775 | 786 |
|
776 | 787 | untrack_channel(ChannelNum, SessionPid, #v1{tracked_channels = Channels0} = State) -> |
777 | 788 | case maps:take(ChannelNum, Channels0) of |
778 | 789 | {SessionPid, Channels} -> |
779 | | - rabbit_log:debug("AMQP 1.0 closed session process ~p with channel number ~b", |
780 | | - [SessionPid, ChannelNum]), |
| 790 | + ?LOG_DEBUG("AMQP 1.0 closed session process ~p with channel number ~b", |
| 791 | + [SessionPid, ChannelNum]), |
781 | 792 | State#v1{tracked_channels = Channels}; |
782 | 793 | _ -> |
783 | 794 | State |
@@ -871,39 +882,57 @@ check_user_connection_limit(Username) -> |
871 | 882 | end. |
872 | 883 |
|
873 | 884 |
|
874 | | -%% TODO Provide a means for the client to refresh the credential. |
875 | | -%% This could be either via: |
876 | | -%% 1. SASL (if multiple authentications are allowed on the same AMQP 1.0 connection), see |
877 | | -%% https://datatracker.ietf.org/doc/html/rfc4422#section-3.8 , or |
878 | | -%% 2. Claims Based Security (CBS) extension, see https://docs.oasis-open.org/amqp/amqp-cbs/v1.0/csd01/amqp-cbs-v1.0-csd01.html |
879 | | -%% and https://github.com/rabbitmq/rabbitmq-server/issues/9259 |
880 | | -%% 3. Simpler variation of 2. where a token is put to a special /token node. |
881 | | -%% |
882 | | -%% If the user does not refresh their credential on time (the only implementation currently), |
883 | | -%% close the entire connection as we must assume that vhost access could have been revoked. |
884 | | -%% |
885 | | -%% If the user refreshes their credential on time (to be implemented), the AMQP reader should |
886 | | -%% 1. rabbit_access_control:check_vhost_access/4 |
887 | | -%% 2. send a message to all its sessions which should then erase the permission caches and |
888 | | -%% re-check all link permissions (i.e. whether reading / writing to exchanges / queues is still allowed). |
889 | | -%% 3. cancel the current timer, and set a new timer |
890 | | -%% similary as done for Stream connections, see https://github.com/rabbitmq/rabbitmq-server/issues/10292 |
891 | | -ensure_credential_expiry_timer(User) -> |
| 885 | +set_credential0(Cred, |
| 886 | + State = #v1{connection = #v1_connection{ |
| 887 | + user = User0, |
| 888 | + vhost = Vhost, |
| 889 | + credential_timer = OldTimer} = Conn, |
| 890 | + tracked_channels = Chans, |
| 891 | + sock = Sock}) -> |
| 892 | + ?LOG_INFO("updating credential", []), |
| 893 | + case rabbit_access_control:update_state(User0, Cred) of |
| 894 | + {ok, User} -> |
| 895 | + try rabbit_access_control:check_vhost_access(User, Vhost, {socket, Sock}, #{}) of |
| 896 | + ok -> |
| 897 | + maps:foreach(fun(_ChanNum, Pid) -> |
| 898 | + rabbit_amqp_session:reset_authz(Pid, User) |
| 899 | + end, Chans), |
| 900 | + case OldTimer of |
| 901 | + undefined -> ok; |
| 902 | + Ref -> ok = erlang:cancel_timer(Ref, [{info, false}]) |
| 903 | + end, |
| 904 | + NewTimer = maybe_start_credential_expiry_timer(User), |
| 905 | + State#v1{connection = Conn#v1_connection{ |
| 906 | + user = User, |
| 907 | + credential_timer = NewTimer}} |
| 908 | + catch _:Reason -> |
| 909 | + Error = error_frame(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, |
| 910 | + "access to vhost ~s failed for new credential: ~p", |
| 911 | + [Vhost, Reason]), |
| 912 | + handle_exception(State, 0, Error) |
| 913 | + end; |
| 914 | + Err -> |
| 915 | + Error = error_frame(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, |
| 916 | + "credential update failed: ~p", |
| 917 | + [Err]), |
| 918 | + handle_exception(State, 0, Error) |
| 919 | + end. |
| 920 | + |
| 921 | +maybe_start_credential_expiry_timer(User) -> |
892 | 922 | case rabbit_access_control:expiry_timestamp(User) of |
893 | 923 | never -> |
894 | | - ok; |
| 924 | + undefined; |
895 | 925 | Ts when is_integer(Ts) -> |
896 | 926 | Time = (Ts - os:system_time(second)) * 1000, |
897 | | - rabbit_log:debug( |
898 | | - "Credential expires in ~b ms frow now (absolute timestamp = ~b seconds since epoch)", |
899 | | - [Time, Ts]), |
| 927 | + ?LOG_DEBUG( |
| 928 | + "credential expires in ~b ms frow now (absolute timestamp = ~b seconds since epoch)", |
| 929 | + [Time, Ts]), |
900 | 930 | case Time > 0 of |
901 | 931 | true -> |
902 | | - _TimerRef = erlang:send_after(Time, self(), credential_expired), |
903 | | - ok; |
| 932 | + erlang:send_after(Time, self(), credential_expired); |
904 | 933 | false -> |
905 | 934 | protocol_error(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, |
906 | | - "Credential expired ~b ms ago", [abs(Time)]) |
| 935 | + "credential expired ~b ms ago", [abs(Time)]) |
907 | 936 | end |
908 | 937 | end. |
909 | 938 |
|
|
0 commit comments