Merge pull request #9839 from kjellwinblad/kjell/fix/Authorization_header_log_leak_webhook/EMQX-8791
fix: Authorization header leak in log entries for webhook
This commit is contained in:
commit
2cf193e2fd
|
@ -609,7 +609,11 @@ do_redact(K, V, Checker) ->
|
||||||
|
|
||||||
-define(REDACT_VAL, "******").
|
-define(REDACT_VAL, "******").
|
||||||
redact_v(V) when is_binary(V) -> <<?REDACT_VAL>>;
|
redact_v(V) when is_binary(V) -> <<?REDACT_VAL>>;
|
||||||
redact_v(_V) -> ?REDACT_VAL.
|
%% The HOCON schema system may generate sensitive values with this format
|
||||||
|
redact_v([{str, Bin}]) when is_binary(Bin) ->
|
||||||
|
[{str, <<?REDACT_VAL>>}];
|
||||||
|
redact_v(_V) ->
|
||||||
|
?REDACT_VAL.
|
||||||
|
|
||||||
is_redacted(K, V) ->
|
is_redacted(K, V) ->
|
||||||
do_is_redacted(K, V, fun is_sensitive_key/1).
|
do_is_redacted(K, V, fun is_sensitive_key/1).
|
||||||
|
|
|
@ -209,7 +209,7 @@ on_start(
|
||||||
?SLOG(info, #{
|
?SLOG(info, #{
|
||||||
msg => "starting_http_connector",
|
msg => "starting_http_connector",
|
||||||
connector => InstId,
|
connector => InstId,
|
||||||
config => emqx_misc:redact(Config)
|
config => redact(Config)
|
||||||
}),
|
}),
|
||||||
{Transport, TransportOpts} =
|
{Transport, TransportOpts} =
|
||||||
case Scheme of
|
case Scheme of
|
||||||
|
@ -289,7 +289,11 @@ on_query(
|
||||||
?TRACE(
|
?TRACE(
|
||||||
"QUERY",
|
"QUERY",
|
||||||
"http_connector_received",
|
"http_connector_received",
|
||||||
#{request => Request, connector => InstId, state => State}
|
#{
|
||||||
|
request => redact(Request),
|
||||||
|
connector => InstId,
|
||||||
|
state => redact(State)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
NRequest = formalize_request(Method, BasePath, Request),
|
NRequest = formalize_request(Method, BasePath, Request),
|
||||||
Worker = resolve_pool_worker(State, KeyOrNum),
|
Worker = resolve_pool_worker(State, KeyOrNum),
|
||||||
|
@ -312,7 +316,7 @@ on_query(
|
||||||
{error, Reason} = Result ->
|
{error, Reason} = Result ->
|
||||||
?SLOG(error, #{
|
?SLOG(error, #{
|
||||||
msg => "http_connector_do_request_failed",
|
msg => "http_connector_do_request_failed",
|
||||||
request => NRequest,
|
request => redact(NRequest),
|
||||||
reason => Reason,
|
reason => Reason,
|
||||||
connector => InstId
|
connector => InstId
|
||||||
}),
|
}),
|
||||||
|
@ -324,7 +328,7 @@ on_query(
|
||||||
{ok, StatusCode, Headers} ->
|
{ok, StatusCode, Headers} ->
|
||||||
?SLOG(error, #{
|
?SLOG(error, #{
|
||||||
msg => "http connector do request, received error response",
|
msg => "http connector do request, received error response",
|
||||||
request => NRequest,
|
request => redact(NRequest),
|
||||||
connector => InstId,
|
connector => InstId,
|
||||||
status_code => StatusCode
|
status_code => StatusCode
|
||||||
}),
|
}),
|
||||||
|
@ -332,7 +336,7 @@ on_query(
|
||||||
{ok, StatusCode, Headers, Body} ->
|
{ok, StatusCode, Headers, Body} ->
|
||||||
?SLOG(error, #{
|
?SLOG(error, #{
|
||||||
msg => "http connector do request, received error response",
|
msg => "http connector do request, received error response",
|
||||||
request => NRequest,
|
request => redact(NRequest),
|
||||||
connector => InstId,
|
connector => InstId,
|
||||||
status_code => StatusCode
|
status_code => StatusCode
|
||||||
}),
|
}),
|
||||||
|
@ -369,7 +373,11 @@ on_query_async(
|
||||||
?TRACE(
|
?TRACE(
|
||||||
"QUERY_ASYNC",
|
"QUERY_ASYNC",
|
||||||
"http_connector_received",
|
"http_connector_received",
|
||||||
#{request => Request, connector => InstId, state => State}
|
#{
|
||||||
|
request => redact(Request),
|
||||||
|
connector => InstId,
|
||||||
|
state => redact(State)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
NRequest = formalize_request(Method, BasePath, Request),
|
NRequest = formalize_request(Method, BasePath, Request),
|
||||||
ok = ehttpc:request_async(
|
ok = ehttpc:request_async(
|
||||||
|
@ -409,7 +417,7 @@ do_get_status(PoolName, Timeout) ->
|
||||||
{error, Reason} = Error ->
|
{error, Reason} = Error ->
|
||||||
?SLOG(error, #{
|
?SLOG(error, #{
|
||||||
msg => "http_connector_get_status_failed",
|
msg => "http_connector_get_status_failed",
|
||||||
reason => Reason,
|
reason => redact(Reason),
|
||||||
worker => Worker
|
worker => Worker
|
||||||
}),
|
}),
|
||||||
Error
|
Error
|
||||||
|
@ -562,3 +570,63 @@ reply_delegator(ReplyFunAndArgs, Result) ->
|
||||||
_ ->
|
_ ->
|
||||||
emqx_resource:apply_reply_fun(ReplyFunAndArgs, Result)
|
emqx_resource:apply_reply_fun(ReplyFunAndArgs, Result)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% The HOCON schema system may generate sensitive keys with this format
|
||||||
|
is_sensitive_key([{str, StringKey}]) ->
|
||||||
|
is_sensitive_key(StringKey);
|
||||||
|
is_sensitive_key(Atom) when is_atom(Atom) ->
|
||||||
|
is_sensitive_key(erlang:atom_to_binary(Atom));
|
||||||
|
is_sensitive_key(Bin) when is_binary(Bin), (size(Bin) =:= 19 orelse size(Bin) =:= 13) ->
|
||||||
|
try
|
||||||
|
%% This is wrapped in a try-catch since we don't know that Bin is a
|
||||||
|
%% valid string so string:lowercase/1 might throw an exception.
|
||||||
|
%%
|
||||||
|
%% We want to convert this to lowercase since the http header fields
|
||||||
|
%% are case insensitive, which means that a user of the Webhook bridge
|
||||||
|
%% can write this field name in many different ways.
|
||||||
|
LowercaseBin = iolist_to_binary(string:lowercase(Bin)),
|
||||||
|
case LowercaseBin of
|
||||||
|
<<"authorization">> -> true;
|
||||||
|
<<"proxy-authorization">> -> true;
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
_:_ -> false
|
||||||
|
end;
|
||||||
|
is_sensitive_key(_) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
%% Function that will do a deep traversal of Data and remove sensitive
|
||||||
|
%% information (i.e., passwords)
|
||||||
|
redact(Data) ->
|
||||||
|
emqx_misc:redact(Data, fun is_sensitive_key/1).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
redact_test_() ->
|
||||||
|
TestData1 = [
|
||||||
|
{<<"content-type">>, <<"application/json">>},
|
||||||
|
{<<"Authorization">>, <<"Basic YWxhZGRpbjpvcGVuc2VzYW1l">>}
|
||||||
|
],
|
||||||
|
|
||||||
|
TestData2 = #{
|
||||||
|
headers =>
|
||||||
|
[
|
||||||
|
{[{str, <<"content-type">>}], [{str, <<"application/json">>}]},
|
||||||
|
{[{str, <<"Authorization">>}], [{str, <<"Basic YWxhZGRpbjpvcGVuc2VzYW1l">>}]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[
|
||||||
|
?_assert(is_sensitive_key(<<"Authorization">>)),
|
||||||
|
?_assert(is_sensitive_key(<<"AuthoriZation">>)),
|
||||||
|
?_assert(is_sensitive_key('AuthoriZation')),
|
||||||
|
?_assert(is_sensitive_key(<<"PrOxy-authoRizaTion">>)),
|
||||||
|
?_assert(is_sensitive_key('PrOxy-authoRizaTion')),
|
||||||
|
?_assertNot(is_sensitive_key(<<"Something">>)),
|
||||||
|
?_assertNot(is_sensitive_key(89)),
|
||||||
|
?_assertNotEqual(TestData1, redact(TestData1)),
|
||||||
|
?_assertNotEqual(TestData2, redact(TestData2))
|
||||||
|
].
|
||||||
|
|
||||||
|
-endif.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Make sure that the content of an Authorization header that users have specified for a webhook bridge is not printed to log files.
|
|
@ -0,0 +1 @@
|
||||||
|
确保用户为webhook-bridge指定的Authorization-HTTP-header的内容不会被打印到日志文件。
|
Loading…
Reference in New Issue