Merge pull request #8554 from savonarola/authn-logging
Add more tracing for authentication
This commit is contained in:
commit
2f470b8878
|
@ -28,7 +28,8 @@
|
||||||
* Improve the dashboard listener startup log, the listener name is no longer spliced with port information,
|
* Improve the dashboard listener startup log, the listener name is no longer spliced with port information,
|
||||||
and the colon(:) is no longer displayed when IP is not specified. [#8480](https://github.com/emqx/emqx/pull/8480)
|
and the colon(:) is no longer displayed when IP is not specified. [#8480](https://github.com/emqx/emqx/pull/8480)
|
||||||
* Remove `/configs/listeners` API, use `/listeners/` instead. [#8485](https://github.com/emqx/emqx/pull/8485)
|
* Remove `/configs/listeners` API, use `/listeners/` instead. [#8485](https://github.com/emqx/emqx/pull/8485)
|
||||||
* Optimize performance of builtin database operations in processes with long message queue [8439](https://github.com/emqx/emqx/pull/8439)
|
* Optimize performance of builtin database operations in processes with long message queue [#8439](https://github.com/emqx/emqx/pull/8439)
|
||||||
|
* Improve authentication tracing. [#8554](https://github.com/emqx/emqx/pull/8554)
|
||||||
|
|
||||||
# 5.0.3
|
# 5.0.3
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,19 @@
|
||||||
-ifndef(EMQX_AUTHENTICATION_HRL).
|
-ifndef(EMQX_AUTHENTICATION_HRL).
|
||||||
-define(EMQX_AUTHENTICATION_HRL, true).
|
-define(EMQX_AUTHENTICATION_HRL, true).
|
||||||
|
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
|
-define(AUTHN_TRACE_TAG, "AUTHN").
|
||||||
|
|
||||||
|
-define(TRACE_AUTHN_PROVIDER(Msg), ?TRACE_AUTHN_PROVIDER(Msg, #{})).
|
||||||
|
-define(TRACE_AUTHN_PROVIDER(Msg, Meta), ?TRACE_AUTHN_PROVIDER(debug, Msg, Meta)).
|
||||||
|
-define(TRACE_AUTHN_PROVIDER(Level, Msg, Meta),
|
||||||
|
?TRACE_AUTHN(Level, Msg, (Meta)#{provider => ?MODULE})
|
||||||
|
).
|
||||||
|
|
||||||
|
-define(TRACE_AUTHN(Msg, Meta), ?TRACE_AUTHN(debug, Msg, Meta)).
|
||||||
|
-define(TRACE_AUTHN(Level, Msg, Meta), ?TRACE(Level, ?AUTHN_TRACE_TAG, Msg, Meta)).
|
||||||
|
|
||||||
%% config root name all auth providers have to agree on.
|
%% config root name all auth providers have to agree on.
|
||||||
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, "authentication").
|
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, "authentication").
|
||||||
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication).
|
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication).
|
||||||
|
|
|
@ -42,17 +42,21 @@
|
||||||
|
|
||||||
-define(TRACE_FILTER, emqx_trace_filter).
|
-define(TRACE_FILTER, emqx_trace_filter).
|
||||||
|
|
||||||
|
-define(TRACE(Tag, Msg, Meta), ?TRACE(debug, Tag, Msg, Meta)).
|
||||||
|
|
||||||
%% Only evaluate when necessary
|
%% Only evaluate when necessary
|
||||||
%% Always debug the trace events.
|
-define(TRACE(Level, Tag, Msg, Meta), begin
|
||||||
-define(TRACE(Tag, Msg, Meta), begin
|
case persistent_term:get(?TRACE_FILTER, []) of
|
||||||
case persistent_term:get(?TRACE_FILTER, undefined) of
|
|
||||||
undefined -> ok;
|
|
||||||
[] -> ok;
|
[] -> ok;
|
||||||
List -> emqx_trace:log(List, Msg, Meta#{trace_tag => Tag})
|
%% We can't bind filter list to a variablebecause we pollute the calling scope with it.
|
||||||
|
%% We also don't want to wrap the macro body in a fun
|
||||||
|
%% beacause this adds overhead to the happy path.
|
||||||
|
%% So evaluate `persistent_term:get` twice.
|
||||||
|
_ -> emqx_trace:log(persistent_term:get(?TRACE_FILTER, []), Msg, (Meta)#{trace_tag => Tag})
|
||||||
end,
|
end,
|
||||||
?SLOG(
|
?SLOG(
|
||||||
debug,
|
Level,
|
||||||
(emqx_trace_formatter:format_meta(Meta))#{msg => Msg, tag => Tag},
|
(emqx_trace_formatter:format_meta_map(Meta))#{msg => Msg, tag => Tag},
|
||||||
#{is_trace => false}
|
#{is_trace => false}
|
||||||
)
|
)
|
||||||
end).
|
end).
|
||||||
|
|
|
@ -101,6 +101,14 @@
|
||||||
|
|
||||||
-define(CHAINS_TAB, emqx_authn_chains).
|
-define(CHAINS_TAB, emqx_authn_chains).
|
||||||
|
|
||||||
|
-define(TRACE_RESULT(Label, Result, Reason), begin
|
||||||
|
?TRACE_AUTHN(Label, #{
|
||||||
|
result => (Result),
|
||||||
|
reason => (Reason)
|
||||||
|
}),
|
||||||
|
Result
|
||||||
|
end).
|
||||||
|
|
||||||
-type chain_name() :: atom().
|
-type chain_name() :: atom().
|
||||||
-type authenticator_id() :: binary().
|
-type authenticator_id() :: binary().
|
||||||
-type position() :: front | rear | {before, authenticator_id()} | {'after', authenticator_id()}.
|
-type position() :: front | rear | {before, authenticator_id()} | {'after', authenticator_id()}.
|
||||||
|
@ -216,14 +224,14 @@ when
|
||||||
|
|
||||||
authenticate(#{enable_authn := false}, _AuthResult) ->
|
authenticate(#{enable_authn := false}, _AuthResult) ->
|
||||||
inc_authenticate_metric('authentication.success.anonymous'),
|
inc_authenticate_metric('authentication.success.anonymous'),
|
||||||
ignore;
|
?TRACE_RESULT("authentication_result", ignore, enable_authn_false);
|
||||||
authenticate(#{listener := Listener, protocol := Protocol} = Credential, _AuthResult) ->
|
authenticate(#{listener := Listener, protocol := Protocol} = Credential, _AuthResult) ->
|
||||||
case get_authenticators(Listener, global_chain(Protocol)) of
|
case get_authenticators(Listener, global_chain(Protocol)) of
|
||||||
{ok, ChainName, Authenticators} ->
|
{ok, ChainName, Authenticators} ->
|
||||||
case get_enabled(Authenticators) of
|
case get_enabled(Authenticators) of
|
||||||
[] ->
|
[] ->
|
||||||
inc_authenticate_metric('authentication.success.anonymous'),
|
inc_authenticate_metric('authentication.success.anonymous'),
|
||||||
ignore;
|
?TRACE_RESULT("authentication_result", ignore, empty_chain);
|
||||||
NAuthenticators ->
|
NAuthenticators ->
|
||||||
Result = do_authenticate(ChainName, NAuthenticators, Credential),
|
Result = do_authenticate(ChainName, NAuthenticators, Credential),
|
||||||
|
|
||||||
|
@ -235,11 +243,11 @@ authenticate(#{listener := Listener, protocol := Protocol} = Credential, _AuthRe
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
Result
|
?TRACE_RESULT("authentication_result", Result, chain_result)
|
||||||
end;
|
end;
|
||||||
none ->
|
none ->
|
||||||
inc_authenticate_metric('authentication.success.anonymous'),
|
inc_authenticate_metric('authentication.success.anonymous'),
|
||||||
ignore
|
?TRACE_RESULT("authentication_result", ignore, no_chain)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_authenticators(Listener, Global) ->
|
get_authenticators(Listener, Global) ->
|
||||||
|
@ -626,11 +634,11 @@ handle_create_authenticator(Chain, Config, Providers) ->
|
||||||
do_authenticate(_ChainName, [], _) ->
|
do_authenticate(_ChainName, [], _) ->
|
||||||
{stop, {error, not_authorized}};
|
{stop, {error, not_authorized}};
|
||||||
do_authenticate(
|
do_authenticate(
|
||||||
ChainName, [#authenticator{id = ID, provider = Provider, state = State} | More], Credential
|
ChainName, [#authenticator{id = ID} = Authenticator | More], Credential
|
||||||
) ->
|
) ->
|
||||||
MetricsID = metrics_id(ChainName, ID),
|
MetricsID = metrics_id(ChainName, ID),
|
||||||
emqx_metrics_worker:inc(authn_metrics, MetricsID, total),
|
emqx_metrics_worker:inc(authn_metrics, MetricsID, total),
|
||||||
try Provider:authenticate(Credential, State) of
|
try authenticate_with_provider(Authenticator, Credential) of
|
||||||
ignore ->
|
ignore ->
|
||||||
ok = emqx_metrics_worker:inc(authn_metrics, MetricsID, nomatch),
|
ok = emqx_metrics_worker:inc(authn_metrics, MetricsID, nomatch),
|
||||||
do_authenticate(ChainName, More, Credential);
|
do_authenticate(ChainName, More, Credential);
|
||||||
|
@ -651,8 +659,7 @@ do_authenticate(
|
||||||
{stop, Result}
|
{stop, Result}
|
||||||
catch
|
catch
|
||||||
Class:Reason:Stacktrace ->
|
Class:Reason:Stacktrace ->
|
||||||
?SLOG(warning, #{
|
?TRACE_AUTHN(warning, "authenticator_error", #{
|
||||||
msg => "unexpected_error_in_authentication",
|
|
||||||
exception => Class,
|
exception => Class,
|
||||||
reason => Reason,
|
reason => Reason,
|
||||||
stacktrace => Stacktrace,
|
stacktrace => Stacktrace,
|
||||||
|
@ -662,6 +669,14 @@ do_authenticate(
|
||||||
do_authenticate(ChainName, More, Credential)
|
do_authenticate(ChainName, More, Credential)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
authenticate_with_provider(#authenticator{id = ID, provider = Provider, state = State}, Credential) ->
|
||||||
|
AuthnResult = Provider:authenticate(Credential, State),
|
||||||
|
?TRACE_AUTHN("authenticator_result", #{
|
||||||
|
authenticator => ID,
|
||||||
|
result => AuthnResult
|
||||||
|
}),
|
||||||
|
AuthnResult.
|
||||||
|
|
||||||
reply(Reply, State) ->
|
reply(Reply, State) ->
|
||||||
{reply, Reply, State}.
|
{reply, Reply, State}.
|
||||||
|
|
||||||
|
|
|
@ -92,15 +92,16 @@ unsubscribe(<<"$SYS/", _/binary>>, _SubOpts) ->
|
||||||
unsubscribe(Topic, SubOpts) ->
|
unsubscribe(Topic, SubOpts) ->
|
||||||
?TRACE("UNSUBSCRIBE", "unsubscribe", #{topic => Topic, sub_opts => SubOpts}).
|
?TRACE("UNSUBSCRIBE", "unsubscribe", #{topic => Topic, sub_opts => SubOpts}).
|
||||||
|
|
||||||
log(List, Msg, Meta0) ->
|
log(List, Msg, Meta) ->
|
||||||
Meta =
|
Log = #{level => debug, meta => enrich_meta(Meta), msg => Msg},
|
||||||
case logger:get_process_metadata() of
|
|
||||||
undefined -> Meta0;
|
|
||||||
ProcMeta -> maps:merge(ProcMeta, Meta0)
|
|
||||||
end,
|
|
||||||
Log = #{level => debug, meta => Meta, msg => Msg},
|
|
||||||
log_filter(List, Log).
|
log_filter(List, Log).
|
||||||
|
|
||||||
|
enrich_meta(Meta) ->
|
||||||
|
case logger:get_process_metadata() of
|
||||||
|
undefined -> Meta;
|
||||||
|
ProcMeta -> maps:merge(ProcMeta, Meta)
|
||||||
|
end.
|
||||||
|
|
||||||
log_filter([], _Log) ->
|
log_filter([], _Log) ->
|
||||||
ok;
|
ok;
|
||||||
log_filter([{Id, FilterFun, Filter, Name} | Rest], Log0) ->
|
log_filter([{Id, FilterFun, Filter, Name} | Rest], Log0) ->
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
-module(emqx_trace_formatter).
|
-module(emqx_trace_formatter).
|
||||||
|
|
||||||
-export([format/2]).
|
-export([format/2]).
|
||||||
-export([format_meta/1]).
|
-export([format_meta_map/1]).
|
||||||
|
|
||||||
%%%-----------------------------------------------------------------
|
%%%-----------------------------------------------------------------
|
||||||
%%% API
|
%%% API
|
||||||
|
@ -31,32 +31,39 @@ format(
|
||||||
ClientId = to_iolist(maps:get(clientid, Meta, "")),
|
ClientId = to_iolist(maps:get(clientid, Meta, "")),
|
||||||
Peername = maps:get(peername, Meta, ""),
|
Peername = maps:get(peername, Meta, ""),
|
||||||
MetaBin = format_meta(Meta, PEncode),
|
MetaBin = format_meta(Meta, PEncode),
|
||||||
[Time, " [", Tag, "] ", ClientId, "@", Peername, " msg: ", Msg, MetaBin, "\n"];
|
[Time, " [", Tag, "] ", ClientId, "@", Peername, " msg: ", Msg, ", ", MetaBin, "\n"];
|
||||||
format(Event, Config) ->
|
format(Event, Config) ->
|
||||||
emqx_logger_textfmt:format(Event, Config).
|
emqx_logger_textfmt:format(Event, Config).
|
||||||
|
|
||||||
format_meta(Meta) ->
|
format_meta_map(Meta) ->
|
||||||
Encode = emqx_trace_handler:payload_encode(),
|
Encode = emqx_trace_handler:payload_encode(),
|
||||||
do_format_meta(Meta, Encode).
|
format_meta_map(Meta, Encode).
|
||||||
|
|
||||||
format_meta(Meta0, Encode) ->
|
format_meta_map(Meta, Encode) ->
|
||||||
Meta1 = #{packet := Packet0, payload := Payload0} = do_format_meta(Meta0, Encode),
|
format_meta_map(Meta, Encode, [{packet, fun format_packet/2}, {payload, fun format_payload/2}]).
|
||||||
Packet = enrich(", packet: ", Packet0),
|
|
||||||
Payload = enrich(", payload: ", Payload0),
|
format_meta_map(Meta, _Encode, []) ->
|
||||||
Meta2 = maps:without([msg, clientid, peername, packet, payload, trace_tag], Meta1),
|
Meta;
|
||||||
case Meta2 =:= #{} of
|
format_meta_map(Meta, Encode, [{Name, FormatFun} | Rest]) ->
|
||||||
true -> [Packet, Payload];
|
case Meta of
|
||||||
false -> [Packet, ", ", map_to_iolist(Meta2), Payload]
|
#{Name := Value} ->
|
||||||
|
NewMeta = Meta#{Name => FormatFun(Value, Encode)},
|
||||||
|
format_meta_map(NewMeta, Encode, Rest);
|
||||||
|
#{} ->
|
||||||
|
format_meta_map(Meta, Encode, Rest)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
enrich(_, "") -> "";
|
format_meta(Meta0, Encode) ->
|
||||||
enrich(Key, IoData) -> [Key, IoData].
|
Meta1 = maps:without([msg, clientid, peername, trace_tag], Meta0),
|
||||||
|
Meta2 = format_meta_map(Meta1, Encode),
|
||||||
|
kvs_to_iolist(lists:sort(fun compare_meta_kvs/2, maps:to_list(Meta2))).
|
||||||
|
|
||||||
do_format_meta(Meta, Encode) ->
|
%% packet always goes first; payload always goes last
|
||||||
Meta#{
|
compare_meta_kvs(KV1, KV2) -> weight(KV1) =< weight(KV2).
|
||||||
packet => format_packet(maps:get(packet, Meta, undefined), Encode),
|
|
||||||
payload => format_payload(maps:get(payload, Meta, undefined), Encode)
|
weight({packet, _}) -> {0, packet};
|
||||||
}.
|
weight({payload, _}) -> {2, payload};
|
||||||
|
weight({K, _}) -> {1, K}.
|
||||||
|
|
||||||
format_packet(undefined, _) -> "";
|
format_packet(undefined, _) -> "";
|
||||||
format_packet(Packet, Encode) -> emqx_packet:format(Packet, Encode).
|
format_packet(Packet, Encode) -> emqx_packet:format(Packet, Encode).
|
||||||
|
@ -69,14 +76,14 @@ format_payload(_, hidden) -> "******".
|
||||||
to_iolist(Atom) when is_atom(Atom) -> atom_to_list(Atom);
|
to_iolist(Atom) when is_atom(Atom) -> atom_to_list(Atom);
|
||||||
to_iolist(Int) when is_integer(Int) -> integer_to_list(Int);
|
to_iolist(Int) when is_integer(Int) -> integer_to_list(Int);
|
||||||
to_iolist(Float) when is_float(Float) -> float_to_list(Float, [{decimals, 2}]);
|
to_iolist(Float) when is_float(Float) -> float_to_list(Float, [{decimals, 2}]);
|
||||||
to_iolist(SubMap) when is_map(SubMap) -> ["[", map_to_iolist(SubMap), "]"];
|
to_iolist(SubMap) when is_map(SubMap) -> ["[", kvs_to_iolist(maps:to_list(SubMap)), "]"];
|
||||||
to_iolist(Char) -> emqx_logger_textfmt:try_format_unicode(Char).
|
to_iolist(Char) -> emqx_logger_textfmt:try_format_unicode(Char).
|
||||||
|
|
||||||
map_to_iolist(Map) ->
|
kvs_to_iolist(KVs) ->
|
||||||
lists:join(
|
lists:join(
|
||||||
",",
|
", ",
|
||||||
lists:map(
|
lists:map(
|
||||||
fun({K, V}) -> [to_iolist(K), ": ", to_iolist(V)] end,
|
fun({K, V}) -> [to_iolist(K), ": ", to_iolist(V)] end,
|
||||||
maps:to_list(Map)
|
KVs
|
||||||
)
|
)
|
||||||
).
|
).
|
||||||
|
|
|
@ -36,6 +36,6 @@
|
||||||
|
|
||||||
-type authenticator_id() :: binary().
|
-type authenticator_id() :: binary().
|
||||||
|
|
||||||
-endif.
|
|
||||||
|
|
||||||
-define(RESOURCE_GROUP, <<"emqx_authn">>).
|
-define(RESOURCE_GROUP, <<"emqx_authn">>).
|
||||||
|
|
||||||
|
-endif.
|
||||||
|
|
|
@ -33,7 +33,8 @@
|
||||||
bin/1,
|
bin/1,
|
||||||
ensure_apps_started/1,
|
ensure_apps_started/1,
|
||||||
cleanup_resources/0,
|
cleanup_resources/0,
|
||||||
make_resource_id/1
|
make_resource_id/1,
|
||||||
|
without_password/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(AUTHN_PLACEHOLDERS, [
|
-define(AUTHN_PLACEHOLDERS, [
|
||||||
|
@ -199,10 +200,23 @@ make_resource_id(Name) ->
|
||||||
NameBin = bin(Name),
|
NameBin = bin(Name),
|
||||||
emqx_resource:generate_id(NameBin).
|
emqx_resource:generate_id(NameBin).
|
||||||
|
|
||||||
|
without_password(Credential) ->
|
||||||
|
without_password(Credential, [password, <<"password">>]).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
without_password(Credential, []) ->
|
||||||
|
Credential;
|
||||||
|
without_password(Credential, [Name | Rest]) ->
|
||||||
|
case maps:is_key(Name, Credential) of
|
||||||
|
true ->
|
||||||
|
without_password(Credential#{Name => <<"[password]">>}, Rest);
|
||||||
|
false ->
|
||||||
|
without_password(Credential, Rest)
|
||||||
|
end.
|
||||||
|
|
||||||
handle_var({var, Name}, undefined) ->
|
handle_var({var, Name}, undefined) ->
|
||||||
error({cannot_get_variable, Name});
|
error({cannot_get_variable, Name});
|
||||||
handle_var({var, <<"peerhost">>}, PeerHost) ->
|
handle_var({var, <<"peerhost">>}, PeerHost) ->
|
||||||
|
|
|
@ -331,7 +331,10 @@ check_client_first_message(Bin, _Cache, #{iteration_count := IterationCount} = S
|
||||||
{continue, ServerFirstMessage, Cache};
|
{continue, ServerFirstMessage, Cache};
|
||||||
ignore ->
|
ignore ->
|
||||||
ignore;
|
ignore;
|
||||||
{error, _Reason} ->
|
{error, Reason} ->
|
||||||
|
?TRACE_AUTHN_PROVIDER("check_client_first_message_error", #{
|
||||||
|
reason => Reason
|
||||||
|
}),
|
||||||
{error, not_authorized}
|
{error, not_authorized}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -344,7 +347,10 @@ check_client_final_message(Bin, #{is_superuser := IsSuperuser} = Cache, #{algori
|
||||||
of
|
of
|
||||||
{ok, ServerFinalMessage} ->
|
{ok, ServerFinalMessage} ->
|
||||||
{ok, #{is_superuser => IsSuperuser}, ServerFinalMessage};
|
{ok, #{is_superuser => IsSuperuser}, ServerFinalMessage};
|
||||||
{error, _Reason} ->
|
{error, Reason} ->
|
||||||
|
?TRACE_AUTHN_PROVIDER("check_client_final_message_error", #{
|
||||||
|
reason => Reason
|
||||||
|
}),
|
||||||
{error, not_authorized}
|
{error, not_authorized}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -188,23 +188,22 @@ authenticate(
|
||||||
} = State
|
} = State
|
||||||
) ->
|
) ->
|
||||||
Request = generate_request(Credential, State),
|
Request = generate_request(Credential, State),
|
||||||
case emqx_resource:query(ResourceId, {Method, Request, RequestTimeout}) of
|
Response = emqx_resource:query(ResourceId, {Method, Request, RequestTimeout}),
|
||||||
|
?TRACE_AUTHN_PROVIDER("http_response", #{
|
||||||
|
request => request_for_log(Credential, State),
|
||||||
|
response => response_for_log(Response),
|
||||||
|
resource => ResourceId
|
||||||
|
}),
|
||||||
|
case Response of
|
||||||
{ok, 204, _Headers} ->
|
{ok, 204, _Headers} ->
|
||||||
{ok, #{is_superuser => false}};
|
{ok, #{is_superuser => false}};
|
||||||
{ok, 200, Headers, Body} ->
|
{ok, 200, Headers, Body} ->
|
||||||
handle_response(Headers, Body);
|
handle_response(Headers, Body);
|
||||||
{ok, _StatusCode, _Headers} = Response ->
|
{ok, _StatusCode, _Headers} = Response ->
|
||||||
log_response(ResourceId, Response),
|
|
||||||
ignore;
|
ignore;
|
||||||
{ok, _StatusCode, _Headers, _Body} = Response ->
|
{ok, _StatusCode, _Headers, _Body} = Response ->
|
||||||
log_response(ResourceId, Response),
|
|
||||||
ignore;
|
ignore;
|
||||||
{error, Reason} ->
|
{error, _Reason} ->
|
||||||
?SLOG(error, #{
|
|
||||||
msg => "http_server_query_failed",
|
|
||||||
resource => ResourceId,
|
|
||||||
reason => Reason
|
|
||||||
}),
|
|
||||||
ignore
|
ignore
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -296,7 +295,8 @@ parse_config(
|
||||||
cow_qs:parse_qs(to_bin(Query))
|
cow_qs:parse_qs(to_bin(Query))
|
||||||
),
|
),
|
||||||
body_template => emqx_authn_utils:parse_deep(maps:get(body, Config, #{})),
|
body_template => emqx_authn_utils:parse_deep(maps:get(body, Config, #{})),
|
||||||
request_timeout => RequestTimeout
|
request_timeout => RequestTimeout,
|
||||||
|
url => RawUrl
|
||||||
},
|
},
|
||||||
{Config#{base_url => BaseUrl, pool_type => random}, State}.
|
{Config#{base_url => BaseUrl, pool_type => random}, State}.
|
||||||
|
|
||||||
|
@ -379,11 +379,6 @@ parse_body(<<"application/x-www-form-urlencoded", _/binary>>, Body) ->
|
||||||
parse_body(ContentType, _) ->
|
parse_body(ContentType, _) ->
|
||||||
{error, {unsupported_content_type, ContentType}}.
|
{error, {unsupported_content_type, ContentType}}.
|
||||||
|
|
||||||
may_append_body(Output, {ok, _, _, Body}) ->
|
|
||||||
Output#{body => Body};
|
|
||||||
may_append_body(Output, {ok, _, _}) ->
|
|
||||||
Output.
|
|
||||||
|
|
||||||
uri_encode(T) ->
|
uri_encode(T) ->
|
||||||
emqx_http_lib:uri_encode(to_list(T)).
|
emqx_http_lib:uri_encode(to_list(T)).
|
||||||
|
|
||||||
|
@ -391,26 +386,33 @@ encode_path(Path) ->
|
||||||
Parts = string:split(Path, "/", all),
|
Parts = string:split(Path, "/", all),
|
||||||
lists:flatten(["/" ++ Part || Part <- lists:map(fun uri_encode/1, Parts)]).
|
lists:flatten(["/" ++ Part || Part <- lists:map(fun uri_encode/1, Parts)]).
|
||||||
|
|
||||||
log_response(ResourceId, Other) ->
|
request_for_log(Credential, #{url := Url} = State) ->
|
||||||
Output = may_append_body(#{resource => ResourceId}, Other),
|
SafeCredential = emqx_authn_utils:without_password(Credential),
|
||||||
case erlang:element(2, Other) of
|
case generate_request(SafeCredential, State) of
|
||||||
Code5xx when Code5xx >= 500 andalso Code5xx < 600 ->
|
{PathQuery, Headers} ->
|
||||||
?SLOG(error, Output#{
|
#{
|
||||||
msg => "http_server_error",
|
method => post,
|
||||||
code => Code5xx
|
base_url => Url,
|
||||||
});
|
path_query => PathQuery,
|
||||||
Code4xx when Code4xx >= 400 andalso Code4xx < 500 ->
|
headers => Headers
|
||||||
?SLOG(warning, Output#{
|
};
|
||||||
msg => "refused_by_http_server",
|
{PathQuery, Headers, Body} ->
|
||||||
code => Code4xx
|
#{
|
||||||
});
|
method => post,
|
||||||
OtherCode ->
|
base_url => Url,
|
||||||
?SLOG(error, Output#{
|
path_query => PathQuery,
|
||||||
msg => "undesired_response_code",
|
headers => Headers,
|
||||||
code => OtherCode
|
mody => Body
|
||||||
})
|
}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
response_for_log({ok, StatusCode, Headers}) ->
|
||||||
|
#{status => StatusCode, headers => Headers};
|
||||||
|
response_for_log({ok, StatusCode, Headers, Body}) ->
|
||||||
|
#{status => StatusCode, headers => Headers, body => Body};
|
||||||
|
response_for_log({error, Error}) ->
|
||||||
|
#{error => Error}.
|
||||||
|
|
||||||
to_list(A) when is_atom(A) ->
|
to_list(A) when is_atom(A) ->
|
||||||
atom_to_list(A);
|
atom_to_list(A);
|
||||||
to_list(B) when is_binary(B) ->
|
to_list(B) when is_binary(B) ->
|
||||||
|
|
|
@ -227,8 +227,7 @@ authenticate(
|
||||||
) ->
|
) ->
|
||||||
case emqx_resource:query(ResourceId, get_jwks) of
|
case emqx_resource:query(ResourceId, get_jwks) of
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(error, #{
|
?TRACE_AUTHN_PROVIDER(error, "get_jwks_failed", #{
|
||||||
msg => "get_jwks_failed",
|
|
||||||
resource => ResourceId,
|
resource => ResourceId,
|
||||||
reason => Reason
|
reason => Reason
|
||||||
}),
|
}),
|
||||||
|
@ -350,10 +349,17 @@ verify(undefined, _, _, _) ->
|
||||||
ignore;
|
ignore;
|
||||||
verify(JWT, JWKs, VerifyClaims, AclClaimName) ->
|
verify(JWT, JWKs, VerifyClaims, AclClaimName) ->
|
||||||
case do_verify(JWT, JWKs, VerifyClaims) of
|
case do_verify(JWT, JWKs, VerifyClaims) of
|
||||||
{ok, Extra} -> {ok, acl(Extra, AclClaimName)};
|
{ok, Extra} ->
|
||||||
{error, {missing_claim, _}} -> {error, bad_username_or_password};
|
{ok, acl(Extra, AclClaimName)};
|
||||||
{error, invalid_signature} -> ignore;
|
{error, {missing_claim, Claim}} ->
|
||||||
{error, {claims, _}} -> {error, bad_username_or_password}
|
?TRACE_AUTHN_PROVIDER("missing_jwt_claim", #{jwt => JWT, claim => Claim}),
|
||||||
|
{error, bad_username_or_password};
|
||||||
|
{error, invalid_signature} ->
|
||||||
|
?TRACE_AUTHN_PROVIDER("invalid_jwt_signature", #{jwks => JWKs, jwt => JWT}),
|
||||||
|
ignore;
|
||||||
|
{error, {claims, Claims}} ->
|
||||||
|
?TRACE_AUTHN_PROVIDER("invalid_jwt_claims", #{jwt => JWT, claims => Claims}),
|
||||||
|
{error, bad_username_or_password}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
acl(Claims, AclClaimName) ->
|
acl(Claims, AclClaimName) ->
|
||||||
|
@ -371,11 +377,11 @@ acl(Claims, AclClaimName) ->
|
||||||
end,
|
end,
|
||||||
maps:merge(emqx_authn_utils:is_superuser(Claims), Acl).
|
maps:merge(emqx_authn_utils:is_superuser(Claims), Acl).
|
||||||
|
|
||||||
do_verify(_JWS, [], _VerifyClaims) ->
|
do_verify(_JWT, [], _VerifyClaims) ->
|
||||||
{error, invalid_signature};
|
{error, invalid_signature};
|
||||||
do_verify(JWS, [JWK | More], VerifyClaims) ->
|
do_verify(JWT, [JWK | More], VerifyClaims) ->
|
||||||
try jose_jws:verify(JWK, JWS) of
|
try jose_jws:verify(JWK, JWT) of
|
||||||
{true, Payload, _JWS} ->
|
{true, Payload, _JWT} ->
|
||||||
Claims0 = emqx_json:decode(Payload, [return_maps]),
|
Claims0 = emqx_json:decode(Payload, [return_maps]),
|
||||||
Claims = try_convert_to_int(Claims0, [<<"exp">>, <<"iat">>, <<"nbf">>]),
|
Claims = try_convert_to_int(Claims0, [<<"exp">>, <<"iat">>, <<"nbf">>]),
|
||||||
case verify_claims(Claims, VerifyClaims) of
|
case verify_claims(Claims, VerifyClaims) of
|
||||||
|
@ -385,11 +391,11 @@ do_verify(JWS, [JWK | More], VerifyClaims) ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end;
|
end;
|
||||||
{false, _, _} ->
|
{false, _, _} ->
|
||||||
do_verify(JWS, More, VerifyClaims)
|
do_verify(JWT, More, VerifyClaims)
|
||||||
catch
|
catch
|
||||||
_:_Reason ->
|
_:Reason ->
|
||||||
?TRACE("JWT", "authn_jwt_invalid_signature", #{jwk => JWK, jws => JWS}),
|
?TRACE_AUTHN_PROVIDER("jwt_verify_error", #{jwk => JWK, jwt => JWT, reason => Reason}),
|
||||||
{error, invalid_signature}
|
do_verify(JWT, More, VerifyClaims)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
verify_claims(Claims, VerifyClaims0) ->
|
verify_claims(Claims, VerifyClaims0) ->
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
-module(emqx_authn_mnesia).
|
-module(emqx_authn_mnesia).
|
||||||
|
|
||||||
-include("emqx_authn.hrl").
|
-include("emqx_authn.hrl").
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
-include_lib("stdlib/include/ms_transform.hrl").
|
-include_lib("stdlib/include/ms_transform.hrl").
|
||||||
-include_lib("hocon/include/hoconsc.hrl").
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
|
||||||
|
@ -158,6 +159,7 @@ authenticate(
|
||||||
UserID = get_user_identity(Credential, Type),
|
UserID = get_user_identity(Credential, Type),
|
||||||
case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of
|
case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of
|
||||||
[] ->
|
[] ->
|
||||||
|
?TRACE_AUTHN_PROVIDER("user_not_found"),
|
||||||
ignore;
|
ignore;
|
||||||
[#user_info{password_hash = PasswordHash, salt = Salt, is_superuser = IsSuperuser}] ->
|
[#user_info{password_hash = PasswordHash, salt = Salt, is_superuser = IsSuperuser}] ->
|
||||||
case
|
case
|
||||||
|
@ -165,8 +167,10 @@ authenticate(
|
||||||
Algorithm, Salt, PasswordHash, Password
|
Algorithm, Salt, PasswordHash, Password
|
||||||
)
|
)
|
||||||
of
|
of
|
||||||
true -> {ok, #{is_superuser => IsSuperuser}};
|
true ->
|
||||||
false -> {error, bad_username_or_password}
|
{ok, #{is_superuser => IsSuperuser}};
|
||||||
|
false ->
|
||||||
|
{error, bad_username_or_password}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -167,8 +167,7 @@ authenticate(
|
||||||
undefined ->
|
undefined ->
|
||||||
ignore;
|
ignore;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(error, #{
|
?TRACE_AUTHN_PROVIDER(error, "mongodb_query_failed", #{
|
||||||
msg => "mongodb_query_failed",
|
|
||||||
resource => ResourceId,
|
resource => ResourceId,
|
||||||
collection => Collection,
|
collection => Collection,
|
||||||
filter => Filter,
|
filter => Filter,
|
||||||
|
@ -180,11 +179,11 @@ authenticate(
|
||||||
ok ->
|
ok ->
|
||||||
{ok, is_superuser(Doc, State)};
|
{ok, is_superuser(Doc, State)};
|
||||||
{error, {cannot_find_password_hash_field, PasswordHashField}} ->
|
{error, {cannot_find_password_hash_field, PasswordHashField}} ->
|
||||||
?SLOG(error, #{
|
?TRACE_AUTHN_PROVIDER(error, "cannot_find_password_hash_field", #{
|
||||||
msg => "cannot_find_password_hash_field",
|
|
||||||
resource => ResourceId,
|
resource => ResourceId,
|
||||||
collection => Collection,
|
collection => Collection,
|
||||||
filter => Filter,
|
filter => Filter,
|
||||||
|
document => Doc,
|
||||||
password_hash_field => PasswordHashField
|
password_hash_field => PasswordHashField
|
||||||
}),
|
}),
|
||||||
ignore;
|
ignore;
|
||||||
|
|
|
@ -130,8 +130,7 @@ authenticate(
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end;
|
end;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(error, #{
|
?TRACE_AUTHN_PROVIDER(error, "mysql_query_failed", #{
|
||||||
msg => "mysql_query_failed",
|
|
||||||
resource => ResourceId,
|
resource => ResourceId,
|
||||||
tmpl_token => TmplToken,
|
tmpl_token => TmplToken,
|
||||||
params => Params,
|
params => Params,
|
||||||
|
|
|
@ -133,8 +133,7 @@ authenticate(
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end;
|
end;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(error, #{
|
?TRACE_AUTHN_PROVIDER(error, "postgresql_query_failed", #{
|
||||||
msg => "postgresql_query_failed",
|
|
||||||
resource => ResourceId,
|
resource => ResourceId,
|
||||||
params => Params,
|
params => Params,
|
||||||
reason => Reason
|
reason => Reason
|
||||||
|
|
|
@ -128,13 +128,14 @@ authenticate(#{auth_method := _}, _) ->
|
||||||
authenticate(
|
authenticate(
|
||||||
#{password := Password} = Credential,
|
#{password := Password} = Credential,
|
||||||
#{
|
#{
|
||||||
cmd := {Command, KeyTemplate, Fields},
|
cmd := {CommandName, KeyTemplate, Fields},
|
||||||
resource_id := ResourceId,
|
resource_id := ResourceId,
|
||||||
password_hash_algorithm := Algorithm
|
password_hash_algorithm := Algorithm
|
||||||
}
|
}
|
||||||
) ->
|
) ->
|
||||||
NKey = emqx_authn_utils:render_str(KeyTemplate, Credential),
|
NKey = emqx_authn_utils:render_str(KeyTemplate, Credential),
|
||||||
case emqx_resource:query(ResourceId, {cmd, [Command, NKey | Fields]}) of
|
Command = [CommandName, NKey | Fields],
|
||||||
|
case emqx_resource:query(ResourceId, {cmd, Command}) of
|
||||||
{ok, []} ->
|
{ok, []} ->
|
||||||
ignore;
|
ignore;
|
||||||
{ok, Values} ->
|
{ok, Values} ->
|
||||||
|
@ -150,8 +151,7 @@ authenticate(
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end;
|
end;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(error, #{
|
?TRACE_AUTHN_PROVIDER(error, "redis_query_failed", #{
|
||||||
msg => "redis_query_failed",
|
|
||||||
resource => ResourceId,
|
resource => ResourceId,
|
||||||
cmd => Command,
|
cmd => Command,
|
||||||
keys => NKey,
|
keys => NKey,
|
||||||
|
|
Loading…
Reference in New Issue