Use the outcome from first authentication
stored in the #user.authz_backends to authenticate
subsequent attempts which occur when a session is
opened.
In particular, during the first authentication attempt
which occurs during the sasl handshake, the amqp 1.0
plugins reads and validates JWT token present in the
password field.
When a new AMQP 1.0 session is opened, the plugin creates
an internal AMQP connection which triggers a second/nth
authentication. For this second/nth authentication, the
plugin propagates as Authentication Credentials the outcome
from the first authentication which is stored in the
`#user.authz_backends`.
The Oauth2 backend first attempts to authenticate using
the password credentials else it uses the credential with the
key `rabbit_auth_backend_oauth2` which has a function which
returns the decoded token
This commit is contained in:
Marcial Rosales 2023-01-30 14:14:58 +01:00
parent b02c268632
commit 51e27f8a3f
6 changed files with 53 additions and 11 deletions

View File

@ -92,7 +92,11 @@ user_login_authentication(Username, AuthProps) ->
false
end
end);
false -> exit({unknown_auth_props, Username, AuthProps})
false ->
case proplists:get_value(rabbit_auth_backend_internal, AuthProps, undefined) of
undefined -> exit({unknown_auth_props, Username, AuthProps});
_ -> internal_check_user_login(Username, fun(_) -> true end)
end
end.
state_can_expire() -> false.

View File

@ -52,8 +52,8 @@ list() ->
auth_fun({none, _}, _VHost, _ExtraAuthProps) ->
fun () -> {ok, rabbit_auth_backend_dummy:user()} end;
auth_fun({Username, none}, _VHost, _ExtraAuthProps) ->
fun () -> rabbit_access_control:check_user_login(Username, []) end;
auth_fun({Username, none}, _VHost, ExtraAuthProps) ->
fun () -> rabbit_access_control:check_user_login(Username, [] ++ ExtraAuthProps) end;
auth_fun({Username, Password}, VHost, ExtraAuthProps) ->
fun () ->
@ -73,7 +73,8 @@ auth_fun({Username, Password}, VHost, ExtraAuthProps) ->
{'auth_failure', string()} | 'access_refused').
connect(Creds, VHost, Protocol, Pid, Infos) ->
ExtraAuthProps = extract_extra_auth_props(Creds, VHost, Pid, Infos),
ExtraAuthProps = append_authz_backends(extract_extra_auth_props(Creds, VHost, Pid, Infos), Infos),
AuthFun = auth_fun(Creds, VHost, ExtraAuthProps),
case rabbit_boot_state:has_reached_and_is_active(core_started) of
true ->
@ -114,6 +115,12 @@ extract_extra_auth_props(Creds, VHost, Pid, Infos) ->
maybe_call_connection_info_module(Protocol, Creds, VHost, Pid, Infos)
end.
append_authz_backends(AuthProps, Infos) ->
case proplists:get_value(authz_backends, Infos, undefined) of
undefined -> AuthProps;
Authz_backends -> AuthProps ++ Authz_backends
end.
extract_protocol(Infos) ->
case proplists:get_value(protocol, Infos, undefined) of
{Protocol, _Version} ->

View File

@ -710,6 +710,7 @@ send_to_new_1_0_session(Channel, Frame, State) ->
user = User},
proxy_socket = ProxySocket} = State,
%% Note: the equivalent, start_channel is in channel_sup_sup
case rabbit_amqp1_0_session_sup_sup:start_session(
%% NB subtract fixed frame header size
ChanSupSup, {amqp10_framing, Sock, Channel,

View File

@ -29,7 +29,7 @@
%%----------------------------------------------------------------------------
start_link({amqp10_framing, Sock, Channel, FrameMax, ReaderPid,
Username, VHost, Collector, ProxySocket}) ->
User, VHost, Collector, ProxySocket}) ->
{ok, SupPid} = supervisor:start_link(?MODULE, []),
{ok, WriterPid} =
supervisor:start_child(
@ -61,8 +61,8 @@ start_link({amqp10_framing, Sock, Channel, FrameMax, ReaderPid,
id => channel,
start =>
{rabbit_amqp1_0_session_process, start_link, [
{Channel, ReaderPid, WriterPid, Username, VHost, FrameMax,
adapter_info(SocketForAdapterInfo), Collector}
{Channel, ReaderPid, WriterPid, User, VHost, FrameMax,
adapter_info(User, SocketForAdapterInfo), Collector}
]},
restart => transient,
significant => true,
@ -86,5 +86,7 @@ init([]) ->
auto_shutdown => any_significant},
{ok, {SupFlags, []}}.
adapter_info(Sock) ->
amqp_connection:socket_adapter_info(Sock, {'AMQP', "1.0"}).
adapter_info(User, Sock) ->
AdapterInfo = amqp_connection:socket_adapter_info(Sock, {'AMQP', "1.0"}),
AdapterInfo#amqp_adapter_info{additional_info =
AdapterInfo#amqp_adapter_info.additional_info ++ [{authz_backends, User#user.authz_backends}]}.

View File

@ -167,7 +167,7 @@ validate_token_expiry(#{<<"exp">> := Exp}) when is_integer(Exp) ->
end;
validate_token_expiry(#{}) -> ok.
-spec check_token(binary()) ->
-spec check_token(binary() | map()) ->
{'ok', map()} |
{'error', term() }|
{'refused',
@ -175,6 +175,9 @@ validate_token_expiry(#{}) -> ok.
{'error', term()} |
{'invalid_aud', term()}}.
check_token(DecodedToken) when is_map(DecodedToken) ->
{ok, DecodedToken};
check_token(Token) ->
Settings = application:get_all_env(?APP),
case uaa_jwt:decode_and_verify(Token) of
@ -535,7 +538,14 @@ get_scopes(#{?SCOPE_JWT_FIELD := Scope}) -> Scope.
-spec token_from_context(map()) -> binary() | undefined.
token_from_context(AuthProps) ->
maps:get(password, AuthProps, undefined).
case maps:get(password, AuthProps, undefined) of
undefined ->
case maps:get(rabbit_auth_backend_oauth2, AuthProps, undefined) of
undefined -> undefined;
Impl -> Impl()
end;
Token -> Token
end.
%% Decoded tokens look like this:
%%

View File

@ -12,6 +12,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
all() ->
[
test_own_scope,
@ -19,6 +20,7 @@ all() ->
test_validate_payload,
test_validate_payload_when_verify_aud_false,
test_successful_access_with_a_token,
test_successful_access_with_a_parsed_token,
test_successful_access_with_a_token_that_has_tag_scopes,
test_unsuccessful_access_with_a_bogus_token,
test_restricted_vhost_access_with_a_valid_token,
@ -629,6 +631,22 @@ test_successful_access_with_a_token(_) ->
assert_topic_access_granted(User, VHost, <<"bar">>, read, #{routing_key => <<"#/foo">>}).
test_successful_access_with_a_parsed_token(_) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
VHost = <<"vhost">>,
Username = <<"username">>,
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
{ok, #auth_user{impl = Impl} } =
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
{ok, _ } =
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{rabbit_auth_backend_oauth2, Impl}]).
test_successful_access_with_a_token_that_has_tag_scopes(_) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],