fix(bridges/actions api): correctly deobfuscate passwords when probing

Fixes https://emqx.atlassian.net/browse/EMQX-11533
This commit is contained in:
Thales Macedo Garitezi 2023-12-05 10:56:29 -03:00
parent 89732cb4e4
commit e42f4155d3
5 changed files with 131 additions and 3 deletions

View File

@ -550,8 +550,8 @@ schema("/action_types") ->
'/action_types'(get, _Request) ->
?OK(emqx_bridge_v2_schema:types()).
maybe_deobfuscate_bridge_probe(#{<<"type">> := BridgeType, <<"name">> := BridgeName} = Params) ->
case emqx_bridge:lookup(BridgeType, BridgeName) of
maybe_deobfuscate_bridge_probe(#{<<"type">> := ActionType, <<"name">> := BridgeName} = Params) ->
case emqx_bridge_v2:lookup(ActionType, BridgeName) of
{ok, #{raw_config := RawConf}} ->
%% TODO check if RawConf optained above is compatible with the commented out code below
%% RawConf = emqx:get_raw_config([bridges, BridgeType, BridgeName], #{}),

View File

@ -35,6 +35,9 @@
metrics_fields/0
]).
%% for testing only
-export([enterprise_api_schemas/1]).
%%======================================================================================
%% Hocon Schema Definitions
@ -57,7 +60,7 @@ api_schema(Method) ->
{<<"mqtt">>, emqx_bridge_mqtt_schema}
]
],
EE = enterprise_api_schemas(Method),
EE = ?MODULE:enterprise_api_schemas(Method),
hoconsc:union(bridge_api_union(Broker ++ EE)).
bridge_api_union(Refs) ->

View File

@ -21,6 +21,7 @@
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("typerefl/include/types.hrl").
-include_lib("emqx/include/asserts.hrl").
-import(emqx_common_test_helpers, [on_exit/1]).
@ -108,6 +109,14 @@ setup_mocks() ->
fun(Method) -> [{bridge_type_bin(), hoconsc:ref(?MODULE, "api_" ++ Method)}] end
),
catch meck:new(emqx_bridge_schema, MeckOpts),
meck:expect(
emqx_bridge_schema,
enterprise_api_schemas,
1,
fun(Method) -> [{bridge_type_bin(), hoconsc:ref(?MODULE, "api_" ++ Method)}] end
),
ok.
con_mod() ->
@ -142,7 +151,9 @@ con_schema() ->
fields("connector") ->
[
{enable, hoconsc:mk(any(), #{})},
{password, emqx_schema_secret:mk(#{required => false})},
{resource_opts, hoconsc:mk(map(), #{})},
{on_start_fun, hoconsc:mk(binary(), #{})},
{ssl, hoconsc:ref(ssl)}
];
fields("api_post") ->
@ -458,6 +469,29 @@ enable_rule_http(RuleId) ->
Params = #{<<"enable">> => true},
update_rule_http(RuleId, Params).
probe_bridge_http_api_v1(Opts) ->
Name = maps:get(name, Opts),
Overrides = maps:get(overrides, Opts, #{}),
BridgeConfig0 = emqx_utils_maps:deep_merge(bridge_config(), Overrides),
BridgeConfig = maps:without([<<"connector">>], BridgeConfig0),
Params = BridgeConfig#{<<"type">> => bridge_type_bin(), <<"name">> => Name},
Path = emqx_mgmt_api_test_util:api_path(["bridges_probe"]),
ct:pal("probe bridge (http v1) (~p):\n ~p", [#{name => Name}, Params]),
Res = request(post, Path, Params),
ct:pal("probe bridge (http v1) (~p) result:\n ~p", [#{name => Name}, Res]),
Res.
probe_action_http_api_v2(Opts) ->
Name = maps:get(name, Opts),
Overrides = maps:get(overrides, Opts, #{}),
BridgeConfig = emqx_utils_maps:deep_merge(bridge_config(), Overrides),
Params = BridgeConfig#{<<"type">> => bridge_type_bin(), <<"name">> => Name},
Path = emqx_mgmt_api_test_util:api_path(["actions_probe"]),
ct:pal("probe action (http v2) (~p):\n ~p", [#{name => Name}, Params]),
Res = request(post, Path, Params),
ct:pal("probe action (http v2) (~p) result:\n ~p", [#{name => Name}, Res]),
Res.
%%------------------------------------------------------------------------------
%% Test cases
%%------------------------------------------------------------------------------
@ -825,3 +859,63 @@ t_create_with_bad_name(_Config) ->
}
}}} = create_bridge_http_api_v1(Opts),
ok.
t_obfuscated_secrets_probe(_Config) ->
Name = <<"bridgev2">>,
Me = self(),
ets:new(emqx_bridge_v2_SUITE:fun_table_name(), [named_table, public]),
OnStartFun = emqx_bridge_v2_SUITE:wrap_fun(fun(Conf) ->
Me ! {on_start, Conf},
{ok, Conf}
end),
OriginalPassword = <<"supersecret">>,
Overrides = #{<<"password">> => OriginalPassword, <<"on_start_fun">> => OnStartFun},
%% Using the real password, like when creating the bridge for the first time.
?assertMatch(
{ok, {{_, 204, _}, _, _}},
probe_bridge_http_api_v1(#{name => Name, overrides => Overrides})
),
%% Check that we still can probe created bridges that use passwords.
?assertMatch(
{ok, {{_, 201, _}, _, #{}}},
create_bridge_http_api_v1(#{name => Name, overrides => Overrides})
),
%% Password is obfuscated
?assertMatch(
{ok, {{_, 200, _}, _, #{<<"password">> := <<"******">>}}},
get_bridge_http_api_v1(Name)
),
%% still using the password
?assertMatch(
{ok, {{_, 204, _}, _, _}},
probe_bridge_http_api_v1(#{name => Name, overrides => Overrides})
),
%% now with obfuscated password (loading the UI again)
?assertMatch(
{ok, {{_, 204, _}, _, _}},
probe_bridge_http_api_v1(#{
name => Name,
overrides => Overrides#{<<"password">> => <<"******">>}
})
),
?assertMatch(
{ok, {{_, 204, _}, _, _}},
probe_action_http_api_v2(#{
name => Name,
overrides => Overrides#{<<"password">> => <<"******">>}
})
),
%% We have to check that the connector was started with real passwords during dry runs
StartConfs = [Conf || {on_start, Conf} <- ?drainMailbox()],
Passwords = lists:map(fun(#{password := P}) -> P end, StartConfs),
?assert(lists:all(fun is_function/1, Passwords), #{passwords => Passwords}),
UnwrappedPasswords = [F() || F <- Passwords],
?assertEqual(
[OriginalPassword],
lists:usort(UnwrappedPasswords),
#{passwords => UnwrappedPasswords}
),
ok.

View File

@ -759,6 +759,9 @@ do_is_redacted(K, ?REDACT_VAL, Fun) ->
Fun(K);
do_is_redacted(K, <<?REDACT_VAL>>, Fun) ->
Fun(K);
do_is_redacted(_K, V, _Fun) when is_function(V, 0) ->
%% already wrapped by `emqx_secret' or other module
true;
do_is_redacted(_K, _V, _Fun) ->
false.

View File

@ -0,0 +1,28 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_utils_tests).
-include_lib("eunit/include/eunit.hrl").
is_redacted_test_() ->
[
?_assertNot(emqx_utils:is_redacted(password, <<"secretpass">>)),
?_assertNot(emqx_utils:is_redacted(password, <<>>)),
?_assertNot(emqx_utils:is_redacted(password, undefined)),
?_assert(emqx_utils:is_redacted(password, <<"******">>)),
?_assert(emqx_utils:is_redacted(password, fun() -> <<"secretpass">> end)),
?_assert(emqx_utils:is_redacted(password, emqx_secret:wrap(<<"secretpass">>)))
].