|
13 | 13 |
|
14 | 14 | -export([init/1, |
15 | 15 | info/2, |
16 | | - mainloop/2]). |
| 16 | + mainloop/2, |
| 17 | + set_credential/2]). |
17 | 18 |
|
18 | 19 | -export([system_continue/3, |
19 | 20 | system_terminate/4, |
|
53 | 54 | channel_max :: non_neg_integer(), |
54 | 55 | auth_mechanism :: sasl_init_unprocessed | {binary(), module()}, |
55 | 56 | auth_state :: term(), |
| 57 | + credential_timer :: undefined | reference(), |
56 | 58 | properties :: undefined | {map, list(tuple())} |
57 | 59 | }). |
58 | 60 |
|
@@ -139,6 +141,11 @@ server_properties() -> |
139 | 141 | Props = [{{symbol, <<"node">>}, {utf8, atom_to_binary(node())}} | Props1], |
140 | 142 | {map, Props}. |
141 | 143 |
|
| 144 | +-spec set_credential(pid(), binary()) -> ok. |
| 145 | +set_credential(Pid, Credential) -> |
| 146 | + Pid ! {set_credential, Credential}, |
| 147 | + ok. |
| 148 | + |
142 | 149 | %%-------------------------------------------------------------------------- |
143 | 150 |
|
144 | 151 | inet_op(F) -> rabbit_misc:throw_on_error(inet_error, F). |
@@ -243,6 +250,8 @@ handle_other({'$gen_cast', {force_event_refresh, _Ref}}, State) -> |
243 | 250 | State; |
244 | 251 | handle_other(terminate_connection, _State) -> |
245 | 252 | stop; |
| 253 | +handle_other({set_credential, Cred}, State) -> |
| 254 | + set_credential0(Cred, State); |
246 | 255 | handle_other(credential_expired, State) -> |
247 | 256 | Error = error_frame(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, "credential expired", []), |
248 | 257 | handle_exception(State, 0, Error); |
@@ -416,15 +425,17 @@ handle_connection_frame( |
416 | 425 | }, |
417 | 426 | helper_sup = HelperSupPid, |
418 | 427 | sock = Sock} = State0) -> |
419 | | - logger:update_process_metadata(#{amqp_container => ContainerId}), |
420 | 428 | Vhost = vhost(Hostname), |
| 429 | + logger:update_process_metadata(#{amqp_container => ContainerId, |
| 430 | + vhost => Vhost, |
| 431 | + user => Username}), |
421 | 432 | ok = check_user_loopback(State0), |
422 | 433 | ok = check_vhost_exists(Vhost, State0), |
423 | 434 | ok = check_vhost_alive(Vhost), |
424 | 435 | ok = rabbit_access_control:check_vhost_access(User, Vhost, {socket, Sock}, #{}), |
425 | 436 | ok = check_vhost_connection_limit(Vhost, Username), |
426 | 437 | ok = check_user_connection_limit(Username), |
427 | | - ok = ensure_credential_expiry_timer(User), |
| 438 | + Timer = maybe_start_credential_expiry_timer(User), |
428 | 439 | rabbit_core_metrics:auth_attempt_succeeded(<<>>, Username, amqp10), |
429 | 440 | notify_auth(user_authentication_success, Username, State0), |
430 | 441 | rabbit_log_connection:info( |
@@ -499,7 +510,8 @@ handle_connection_frame( |
499 | 510 | outgoing_max_frame_size = OutgoingMaxFrameSize, |
500 | 511 | channel_max = EffectiveChannelMax, |
501 | 512 | properties = Properties, |
502 | | - timeout = ReceiveTimeoutMillis}, |
| 513 | + timeout = ReceiveTimeoutMillis, |
| 514 | + credential_timer = Timer}, |
503 | 515 | heartbeater = Heartbeater}, |
504 | 516 | State = start_writer(State1), |
505 | 517 | HostnameVal = case Hostname of |
@@ -871,39 +883,57 @@ check_user_connection_limit(Username) -> |
871 | 883 | end. |
872 | 884 |
|
873 | 885 |
|
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) -> |
| 886 | +set_credential0(Cred, |
| 887 | + State = #v1{connection = #v1_connection{ |
| 888 | + user = User0, |
| 889 | + vhost = Vhost, |
| 890 | + credential_timer = OldTimer} = Conn, |
| 891 | + tracked_channels = Chans, |
| 892 | + sock = Sock}) -> |
| 893 | + rabbit_log:info("updating credential", []), |
| 894 | + case rabbit_access_control:update_state(User0, Cred) of |
| 895 | + {ok, User} -> |
| 896 | + try rabbit_access_control:check_vhost_access(User, Vhost, {socket, Sock}, #{}) of |
| 897 | + ok -> |
| 898 | + maps:foreach(fun(_ChanNum, Pid) -> |
| 899 | + rabbit_amqp_session:reset_authz(Pid, User) |
| 900 | + end, Chans), |
| 901 | + case OldTimer of |
| 902 | + undefined -> ok; |
| 903 | + Ref -> erlang:cancel_timer(Ref, [{info, false}]) |
| 904 | + end, |
| 905 | + NewTimer = maybe_start_credential_expiry_timer(User), |
| 906 | + State#v1{connection = Conn#v1_connection{ |
| 907 | + user = User, |
| 908 | + credential_timer = NewTimer}} |
| 909 | + catch _:Reason -> |
| 910 | + Error = error_frame(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, |
| 911 | + "access to vhost ~s failed for new credential: ~p", |
| 912 | + [Vhost, Reason]), |
| 913 | + handle_exception(State, 0, Error) |
| 914 | + end; |
| 915 | + Err -> |
| 916 | + Error = error_frame(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, |
| 917 | + "credential update failed: ~p", |
| 918 | + [Err]), |
| 919 | + handle_exception(State, 0, Error) |
| 920 | + end. |
| 921 | + |
| 922 | +maybe_start_credential_expiry_timer(User) -> |
892 | 923 | case rabbit_access_control:expiry_timestamp(User) of |
893 | 924 | never -> |
894 | | - ok; |
| 925 | + undefined; |
895 | 926 | Ts when is_integer(Ts) -> |
896 | 927 | Time = (Ts - os:system_time(second)) * 1000, |
897 | 928 | rabbit_log:debug( |
898 | | - "Credential expires in ~b ms frow now (absolute timestamp = ~b seconds since epoch)", |
| 929 | + "credential expires in ~b ms frow now (absolute timestamp = ~b seconds since epoch)", |
899 | 930 | [Time, Ts]), |
900 | 931 | case Time > 0 of |
901 | 932 | true -> |
902 | | - _TimerRef = erlang:send_after(Time, self(), credential_expired), |
903 | | - ok; |
| 933 | + erlang:send_after(Time, self(), credential_expired); |
904 | 934 | false -> |
905 | 935 | protocol_error(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, |
906 | | - "Credential expired ~b ms ago", [abs(Time)]) |
| 936 | + "credential expired ~b ms ago", [abs(Time)]) |
907 | 937 | end |
908 | 938 | end. |
909 | 939 |
|
|
0 commit comments