Merge remote-tracking branch 'ce/release-53' into audit-log-fix-2

This commit is contained in:
JianBo He 2023-09-26 19:05:47 +08:00
commit a73c3b8e1e
19 changed files with 268 additions and 144 deletions

View File

@ -52,7 +52,7 @@
%% (e.g. in `init_per_suite/1` / `init_per_group/2`), providing the appspecs %% (e.g. in `init_per_suite/1` / `init_per_group/2`), providing the appspecs
%% and unique work dir for the testrun (e.g. `work_dir/1`). Save the result %% and unique work dir for the testrun (e.g. `work_dir/1`). Save the result
%% in a context. %% in a context.
%% 3. Call `emqx_cth_sutie:stop/1` to stop the applications after the testrun %% 3. Call `emqx_cth_suite:stop/1` to stop the applications after the testrun
%% finishes (e.g. in `end_per_suite/1` / `end_per_group/2`), providing the %% finishes (e.g. in `end_per_suite/1` / `end_per_group/2`), providing the
%% result from step 2. %% result from step 2.
-module(emqx_cth_suite). -module(emqx_cth_suite).
@ -245,6 +245,9 @@ spec_fmt(ffun, {_, X}) -> X.
maybe_configure_app(_App, #{config := false}) -> maybe_configure_app(_App, #{config := false}) ->
ok; ok;
maybe_configure_app(_App, AppConfig = #{schema_mod := SchemaModule}) when is_atom(SchemaModule) ->
#{config := Config} = AppConfig,
configure_app(SchemaModule, Config);
maybe_configure_app(App, #{config := Config}) -> maybe_configure_app(App, #{config := Config}) ->
case app_schema(App) of case app_schema(App) of
{ok, SchemaModule} -> {ok, SchemaModule} ->

View File

@ -43,6 +43,9 @@
]). ]).
-export([conf_get/2, conf_get/3, keys/2, filter/1]). -export([conf_get/2, conf_get/3, keys/2, filter/1]).
%% internal exports for `emqx_enterprise_schema' only.
-export([ensure_unicode_path/2, convert_rotation/2, log_handler_common_confs/2]).
%% Static apps which merge their configs into the merged emqx.conf %% Static apps which merge their configs into the merged emqx.conf
%% The list can not be made a dynamic read at run-time as it is used %% The list can not be made a dynamic read at run-time as it is used
%% by nodetool to generate app.<time>.config before EMQX is started %% by nodetool to generate app.<time>.config before EMQX is started
@ -962,15 +965,6 @@ fields("log") ->
aliases => [file_handlers], aliases => [file_handlers],
importance => ?IMPORTANCE_HIGH importance => ?IMPORTANCE_HIGH
} }
)},
{"audit",
sc(
?R_REF("log_audit_handler"),
#{
desc => ?DESC("log_audit_handler"),
importance => ?IMPORTANCE_HIGH,
default => #{<<"enable">> => true, <<"level">> => <<"info">>}
}
)} )}
]; ];
fields("console_handler") -> fields("console_handler") ->
@ -1012,59 +1006,6 @@ fields("log_file_handler") ->
} }
)} )}
] ++ log_handler_common_confs(file, #{}); ] ++ log_handler_common_confs(file, #{});
fields("log_audit_handler") ->
[
{"level",
sc(
log_level(),
#{
default => info,
desc => ?DESC("audit_handler_level"),
importance => ?IMPORTANCE_HIDDEN
}
)},
{"path",
sc(
file(),
#{
desc => ?DESC("audit_file_handler_path"),
default => <<"${EMQX_LOG_DIR}/audit.log">>,
importance => ?IMPORTANCE_HIGH,
converter => fun(Path, Opts) ->
emqx_schema:naive_env_interpolation(ensure_unicode_path(Path, Opts))
end
}
)},
{"rotation_count",
sc(
range(1, 128),
#{
default => 10,
converter => fun convert_rotation/2,
desc => ?DESC("log_rotation_count"),
importance => ?IMPORTANCE_MEDIUM
}
)},
{"rotation_size",
sc(
hoconsc:union([infinity, emqx_schema:bytesize()]),
#{
default => <<"50MB">>,
desc => ?DESC("log_file_handler_max_size"),
importance => ?IMPORTANCE_MEDIUM
}
)}
] ++
%% Only support json
lists:keydelete(
"level",
1,
lists:keydelete(
"formatter",
1,
log_handler_common_confs(file, #{})
)
);
fields("log_overload_kill") -> fields("log_overload_kill") ->
[ [
{"enable", {"enable",
@ -1155,8 +1096,6 @@ desc("console_handler") ->
?DESC("desc_console_handler"); ?DESC("desc_console_handler");
desc("log_file_handler") -> desc("log_file_handler") ->
?DESC("desc_log_file_handler"); ?DESC("desc_log_file_handler");
desc("log_audit_handler") ->
?DESC("desc_audit_log_handler");
desc("log_rotation") -> desc("log_rotation") ->
?DESC("desc_log_rotation"); ?DESC("desc_log_rotation");
desc("log_overload_kill") -> desc("log_overload_kill") ->

View File

@ -78,16 +78,7 @@ t_log_conf(_Conf) ->
<<"time_offset">> => <<"system">> <<"time_offset">> => <<"system">>
}, },
<<"file">> => <<"file">> =>
#{<<"default">> => FileExpect}, #{<<"default">> => FileExpect}
<<"audit">> =>
#{
<<"enable">> => true,
<<"level">> => <<"info">>,
<<"path">> => <<"log/audit.log">>,
<<"rotation_count">> => 10,
<<"rotation_size">> => <<"50MB">>,
<<"time_offset">> => <<"system">>
}
}, },
?assertEqual(ExpectLog1, emqx_conf:get_raw([<<"log">>])), ?assertEqual(ExpectLog1, emqx_conf:get_raw([<<"log">>])),
UpdateLog0 = emqx_utils_maps:deep_remove([<<"file">>, <<"default">>], ExpectLog1), UpdateLog0 = emqx_utils_maps:deep_remove([<<"file">>, <<"default">>], ExpectLog1),

View File

@ -181,23 +181,8 @@ validate_log(Conf) ->
}}, }},
FileHandler FileHandler
), ),
AuditHandler = lists:keyfind(emqx_audit, 2, FileHandlers), %% audit is an EE-only feature
%% default is enable and log level is info. ?assertNot(lists:keyfind(emqx_audit, 2, FileHandlers)),
?assertMatch(
{handler, emqx_audit, logger_disk_log_h, #{
config := #{
type := wrap,
file := "log/audit.log",
max_no_bytes := _,
max_no_files := _
},
filesync_repeat_interval := no_repeat,
filters := [{filter_audit, {_, stop}}],
formatter := _,
level := info
}},
AuditHandler
),
ConsoleHandler = lists:keyfind(logger_std_h, 3, Loggers), ConsoleHandler = lists:keyfind(logger_std_h, 3, Loggers),
?assertEqual( ?assertEqual(
{handler, console, logger_std_h, #{ {handler, console, logger_std_h, #{

View File

@ -24,6 +24,7 @@
-define(ROLE_SUPERUSER, <<"administrator">>). -define(ROLE_SUPERUSER, <<"administrator">>).
-define(ROLE_DEFAULT, ?ROLE_SUPERUSER). -define(ROLE_DEFAULT, ?ROLE_SUPERUSER).
-define(BACKEND_LOCAL, local).
-define(SSO_USERNAME(Backend, Name), {Backend, Name}). -define(SSO_USERNAME(Backend, Name), {Backend, Name}).
-type dashboard_sso_backend() :: atom(). -type dashboard_sso_backend() :: atom().

View File

@ -230,7 +230,7 @@ remove_user(Username) ->
-spec update_user(dashboard_username(), dashboard_user_role(), binary()) -> -spec update_user(dashboard_username(), dashboard_user_role(), binary()) ->
{ok, map()} | {error, term()}. {ok, map()} | {error, term()}.
update_user(Username, Role, Desc) when is_binary(Username) -> update_user(Username, Role, Desc) ->
case legal_role(Role) of case legal_role(Role) of
ok -> ok ->
case case
@ -427,7 +427,7 @@ flatten_username(#{username := ?SSO_USERNAME(Backend, Name)} = Data) ->
backend => Backend backend => Backend
}; };
flatten_username(#{username := Username} = Data) when is_binary(Username) -> flatten_username(#{username := Username} = Data) when is_binary(Username) ->
Data#{backend => local}. Data#{backend => ?BACKEND_LOCAL}.
-spec add_sso_user(dashboard_sso_backend(), binary(), dashboard_user_role(), binary()) -> -spec add_sso_user(dashboard_sso_backend(), binary(), dashboard_user_role(), binary()) ->
{ok, map()} | {error, any()}. {ok, map()} | {error, any()}.

View File

@ -379,9 +379,9 @@ sso_parameters() ->
sso_parameters(Params) -> sso_parameters(Params) ->
emqx_dashboard_sso_api:sso_parameters(Params). emqx_dashboard_sso_api:sso_parameters(Params).
username(#{bindings := #{backend := local}}, Username) -> username(#{query_string := #{<<"backend">> := ?BACKEND_LOCAL}}, Username) ->
Username; Username;
username(#{bindings := #{backend := Backend}}, Username) -> username(#{query_string := #{<<"backend">> := Backend}}, Username) ->
?SSO_USERNAME(Backend, Username); ?SSO_USERNAME(Backend, Username);
username(_Req, Username) -> username(_Req, Username) ->
Username. Username.

View File

@ -191,7 +191,7 @@ token_ttl() ->
format(Token, ?SSO_USERNAME(Backend, Name), Role, ExpTime) -> format(Token, ?SSO_USERNAME(Backend, Name), Role, ExpTime) ->
format(Token, Backend, Name, Role, ExpTime); format(Token, Backend, Name, Role, ExpTime);
format(Token, Username, Role, ExpTime) -> format(Token, Username, Role, ExpTime) ->
format(Token, local, Username, Role, ExpTime). format(Token, ?BACKEND_LOCAL, Username, Role, ExpTime).
format(Token, Backend, Username, Role, ExpTime) -> format(Token, Backend, Username, Role, ExpTime) ->
#?ADMIN_JWT{ #?ADMIN_JWT{

View File

@ -159,17 +159,23 @@ login(post, #{bindings := #{backend := Backend}, body := Body} = Request) ->
State -> State ->
case emqx_dashboard_sso:login(provider(Backend), Request, State) of case emqx_dashboard_sso:login(provider(Backend), Request, State) of
{ok, Role, Token} -> {ok, Role, Token} ->
?SLOG(info, #{msg => "dashboard_sso_login_successful", request => Request}), ?SLOG(info, #{
msg => "dashboard_sso_login_successful",
request => emqx_utils:redact(Request)
}),
Username = maps:get(<<"username">>, Body), Username = maps:get(<<"username">>, Body),
{200, login_meta(Username, Role, Token)}; {200, login_meta(Username, Role, Token)};
{redirect, Redirect} -> {redirect, Redirect} ->
?SLOG(info, #{msg => "dashboard_sso_login_redirect", request => Request}), ?SLOG(info, #{
msg => "dashboard_sso_login_redirect",
request => emqx_utils:redact(Request)
}),
Redirect; Redirect;
{error, Reason} -> {error, Reason} ->
?SLOG(info, #{ ?SLOG(info, #{
msg => "dashboard_sso_login_failed", msg => "dashboard_sso_login_failed",
request => Request, request => emqx_utils:redact(Request),
reason => Reason reason => emqx_utils:redact(Reason)
}), }),
{401, #{code => ?BAD_USERNAME_OR_PWD, message => <<"Auth failed">>}} {401, #{code => ?BAD_USERNAME_OR_PWD, message => <<"Auth failed">>}}
end end
@ -193,10 +199,14 @@ backend(get, #{bindings := #{backend := Type}}) ->
{200, to_json(Backend)} {200, to_json(Backend)}
end; end;
backend(put, #{bindings := #{backend := Backend}, body := Config}) -> backend(put, #{bindings := #{backend := Backend}, body := Config}) ->
?SLOG(info, #{msg => "Update SSO backend", backend => Backend, config => Config}), ?SLOG(info, #{
msg => "update_sso_backend",
backend => Backend,
config => emqx_utils:redact(Config)
}),
on_backend_update(Backend, Config, fun emqx_dashboard_sso_manager:update/2); on_backend_update(Backend, Config, fun emqx_dashboard_sso_manager:update/2);
backend(delete, #{bindings := #{backend := Backend}}) -> backend(delete, #{bindings := #{backend := Backend}}) ->
?SLOG(info, #{msg => "Delete SSO backend", backend => Backend}), ?SLOG(info, #{msg => "delete_sso_backend", backend => Backend}),
handle_backend_update_result(emqx_dashboard_sso_manager:delete(Backend), undefined). handle_backend_update_result(emqx_dashboard_sso_manager:delete(Backend), undefined).
sso_parameters(Params) -> sso_parameters(Params) ->

View File

@ -34,16 +34,13 @@ admins(["passwd", Username, Password]) ->
print_error(Reason) print_error(Reason)
end; end;
admins(["del", Username]) -> admins(["del", Username]) ->
case emqx_dashboard_admin:remove_user(bin(Username)) of delete_user(bin(Username));
{ok, _} -> admins(["del", Username, BackendName]) ->
emqx_ctl:print("ok~n"); case atom(BackendName) of
{error, Reason} -> {ok, ?BACKEND_LOCAL} ->
print_error(Reason) delete_user(bin(Username));
end; {ok, Backend} ->
admins(["del", Username, Backend]) -> delete_user(?SSO_USERNAME(Backend, bin(Username)));
case emqx_dashboard_admin:remove_user(?SSO_USERNAME(atom(Backend), bin(Username))) of
{ok, _} ->
emqx_ctl:print("ok~n");
{error, Reason} -> {error, Reason} ->
print_error(Reason) print_error(Reason)
end; end;
@ -52,9 +49,18 @@ admins(_) ->
[ [
{"admins add <Username> <Password> <Description> <Role>", "Add dashboard user"}, {"admins add <Username> <Password> <Description> <Role>", "Add dashboard user"},
{"admins passwd <Username> <Password>", "Reset dashboard user password"}, {"admins passwd <Username> <Password>", "Reset dashboard user password"},
{"admins del <Username> <Backend>", "Delete dashboard user"} {"admins del <Username> <Backend>",
"Delete dashboard user, <Backend> can be omitted, the default value is 'local'"}
] ]
). ).
atom(S) -> atom(S) ->
erlang:list_to_atom(S). emqx_utils:safe_to_existing_atom(S).
delete_user(Username) ->
case emqx_dashboard_admin:remove_user(Username) of
{ok, _} ->
emqx_ctl:print("ok~n");
{error, Reason} ->
print_error(Reason)
end.

View File

@ -6,6 +6,9 @@
-behaviour(hocon_schema). -behaviour(hocon_schema).
-include_lib("typerefl/include/types.hrl").
-include_lib("hocon/include/hoconsc.hrl").
-export([namespace/0, roots/0, fields/1, translations/0, translation/1, desc/1, validations/0]). -export([namespace/0, roots/0, fields/1, translations/0, translation/1, desc/1, validations/0]).
-define(EE_SCHEMA_MODULES, [ -define(EE_SCHEMA_MODULES, [
@ -22,6 +25,64 @@ roots() ->
fields("node") -> fields("node") ->
redefine_node(emqx_conf_schema:fields("node")); redefine_node(emqx_conf_schema:fields("node"));
fields("log") ->
redefine_log(emqx_conf_schema:fields("log"));
fields("log_audit_handler") ->
[
{"level",
sc(
emqx_conf_schema:log_level(),
#{
default => info,
desc => ?DESC("audit_handler_level"),
importance => ?IMPORTANCE_HIDDEN
}
)},
{"path",
hoconsc:mk(
emqx_conf_schema:file(),
#{
desc => ?DESC(emqx_conf_schema, "audit_file_handler_path"),
default => <<"${EMQX_LOG_DIR}/audit.log">>,
importance => ?IMPORTANCE_HIGH,
converter => fun(Path, Opts) ->
emqx_schema:naive_env_interpolation(
emqx_conf_schema:ensure_unicode_path(Path, Opts)
)
end
}
)},
{"rotation_count",
hoconsc:mk(
range(1, 128),
#{
default => 10,
converter => fun emqx_conf_schema:convert_rotation/2,
desc => ?DESC(emqx_conf_schema, "log_rotation_count"),
importance => ?IMPORTANCE_MEDIUM
}
)},
{"rotation_size",
hoconsc:mk(
hoconsc:union([infinity, emqx_schema:bytesize()]),
#{
default => <<"50MB">>,
desc => ?DESC(emqx_conf_schema, "log_file_handler_max_size"),
importance => ?IMPORTANCE_MEDIUM
}
)}
] ++
%% Only support json
lists:keydelete(
"level",
1,
lists:keydelete(
"formatter",
1,
log_handler_common_confs(file, #{})
)
);
fields(Name) -> fields(Name) ->
ee_delegate(fields, ?EE_SCHEMA_MODULES, Name). ee_delegate(fields, ?EE_SCHEMA_MODULES, Name).
@ -31,6 +92,8 @@ translations() ->
translation(Name) -> translation(Name) ->
emqx_conf_schema:translation(Name). emqx_conf_schema:translation(Name).
desc("log_audit_handler") ->
?DESC(emqx_conf_schema, "desc_audit_log_handler");
desc(Name) -> desc(Name) ->
ee_delegate(desc, ?EE_SCHEMA_MODULES, Name). ee_delegate(desc, ?EE_SCHEMA_MODULES, Name).
@ -60,13 +123,20 @@ ee_delegate(Method, [], Name) ->
apply(emqx_conf_schema, Method, [Name]). apply(emqx_conf_schema, Method, [Name]).
redefine_roots(Roots) -> redefine_roots(Roots) ->
Overrides = [{"node", #{type => hoconsc:ref(?MODULE, "node")}}], Overrides = [
{"node", #{type => hoconsc:ref(?MODULE, "node")}},
{"log", #{type => hoconsc:ref(?MODULE, "log")}}
],
override(Roots, Overrides). override(Roots, Overrides).
redefine_node(Fields) -> redefine_node(Fields) ->
Overrides = [], Overrides = [],
override(Fields, Overrides). override(Fields, Overrides).
redefine_log(Fields) ->
Overrides = [],
override(Fields, Overrides) ++ audit_log_conf().
override(Fields, []) -> override(Fields, []) ->
Fields; Fields;
override(Fields, [{Name, Override} | More]) -> override(Fields, [{Name, Override} | More]) ->
@ -81,3 +151,19 @@ find_schema(Name, Fields) ->
replace_schema(Name, Schema, Fields) -> replace_schema(Name, Schema, Fields) ->
lists:keyreplace(Name, 1, Fields, {Name, Schema}). lists:keyreplace(Name, 1, Fields, {Name, Schema}).
audit_log_conf() ->
[
{"audit",
hoconsc:mk(
hoconsc:ref(?MODULE, "log_audit_handler"),
#{
%% note: we need to keep the descriptions associated with
%% `emqx_conf_schema' module hocon i18n file because that's what
%% `emqx_conf:gen_config_md' seems to expect.
desc => ?DESC(emqx_conf_schema, "log_audit_handler"),
importance => ?IMPORTANCE_HIGH,
default => #{<<"enable">> => true, <<"level">> => <<"info">>}
}
)}
].

View File

@ -13,6 +13,25 @@
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
init_per_testcase(t_audit_log_conf, Config) ->
Apps = emqx_cth_suite:start(
[
emqx_enterprise,
{emqx_conf, #{schema_mod => emqx_enterprise_schema}}
],
#{work_dir => emqx_cth_suite:work_dir(Config)}
),
[{apps, Apps} | Config];
init_per_testcase(_TestCase, Config) ->
Config.
end_per_testcase(t_audit_log_conf, Config) ->
Apps = ?config(apps, Config),
ok = emqx_cth_suite:stop(Apps),
ok;
end_per_testcase(_TestCase, _Config) ->
ok.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Tests %% Tests
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -50,3 +69,36 @@ t_translations(_Config) ->
emqx_conf_schema:translation(Root), emqx_conf_schema:translation(Root),
emqx_enterprise_schema:translation(Root) emqx_enterprise_schema:translation(Root)
). ).
t_audit_log_conf(_Config) ->
FileExpect = #{
<<"enable">> => true,
<<"formatter">> => <<"text">>,
<<"level">> => <<"warning">>,
<<"rotation_count">> => 10,
<<"rotation_size">> => <<"50MB">>,
<<"time_offset">> => <<"system">>,
<<"path">> => <<"log/emqx.log">>
},
ExpectLog1 = #{
<<"console">> =>
#{
<<"enable">> => false,
<<"formatter">> => <<"text">>,
<<"level">> => <<"warning">>,
<<"time_offset">> => <<"system">>
},
<<"file">> =>
#{<<"default">> => FileExpect},
<<"audit">> =>
#{
<<"enable">> => true,
<<"level">> => <<"info">>,
<<"path">> => <<"log/audit.log">>,
<<"rotation_count">> => 10,
<<"rotation_size">> => <<"50MB">>,
<<"time_offset">> => <<"system">>
}
},
?assertEqual(ExpectLog1, emqx_conf:get_raw([<<"log">>])),
ok.

View File

@ -16,3 +16,38 @@ doc_gen_test() ->
ok = emqx_conf:dump_schema(Dir, emqx_enterprise_schema) ok = emqx_conf:dump_schema(Dir, emqx_enterprise_schema)
end end
}. }.
audit_log_test() ->
ensure_acl_conf(),
Conf0 = <<"node {cookie = aaa, data_dir = \"/tmp\"}">>,
{ok, ConfMap0} = hocon:binary(Conf0, #{format => richmap}),
ConfList = hocon_tconf:generate(emqx_enterprise_schema, ConfMap0),
Kernel = proplists:get_value(kernel, ConfList),
Loggers = proplists:get_value(logger, Kernel),
FileHandlers = lists:filter(fun(L) -> element(3, L) =:= logger_disk_log_h end, Loggers),
AuditHandler = lists:keyfind(emqx_audit, 2, FileHandlers),
%% default is enable and log level is info.
?assertMatch(
{handler, emqx_audit, logger_disk_log_h, #{
config := #{
type := wrap,
file := "log/audit.log",
max_no_bytes := _,
max_no_files := _
},
filesync_repeat_interval := no_repeat,
filters := [{filter_audit, {_, stop}}],
formatter := _,
level := info
}},
AuditHandler
),
ok.
ensure_acl_conf() ->
File = emqx_schema:naive_env_interpolation(<<"${EMQX_ETC_DIR}/acl.conf">>),
ok = filelib:ensure_dir(filename:dirname(File)),
case filelib:is_regular(File) of
true -> ok;
false -> file:write_file(File, <<"">>)
end.

View File

@ -158,7 +158,7 @@ on_start(
{error, Reason} -> {error, Reason} ->
?tp( ?tp(
ldap_connector_start_failed, ldap_connector_start_failed,
#{error => Reason} #{error => emqx_utils:redact(Reason)}
), ),
{error, Reason} {error, Reason}
end. end.
@ -248,7 +248,7 @@ do_ldap_query(
SearchOptions, SearchOptions,
#{pool_name := PoolName} = State #{pool_name := PoolName} = State
) -> ) ->
LogMeta = #{connector => InstId, search => SearchOptions, state => State}, LogMeta = #{connector => InstId, search => SearchOptions, state => emqx_utils:redact(State)},
?TRACE("QUERY", "ldap_connector_received", LogMeta), ?TRACE("QUERY", "ldap_connector_received", LogMeta),
case case
ecpool:pick_and_do( ecpool:pick_and_do(
@ -268,7 +268,10 @@ do_ldap_query(
{error, Reason} -> {error, Reason} ->
?SLOG( ?SLOG(
error, error,
LogMeta#{msg => "ldap_connector_do_query_failed", reason => Reason} LogMeta#{
msg => "ldap_connector_do_query_failed",
reason => emqx_utils:redact(Reason)
}
), ),
{error, {unrecoverable_error, Reason}} {error, {unrecoverable_error, Reason}}
end. end.

View File

@ -116,7 +116,7 @@ authorize(
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{ ?SLOG(error, #{
msg => "query_ldap_error", msg => "query_ldap_error",
reason => Reason, reason => emqx_utils:redact(Reason),
resource_id => ResourceID resource_id => ResourceID
}), }),
nomatch nomatch

View File

@ -61,7 +61,7 @@ on_query(
{bind, Data}, {bind, Data},
#{ #{
base_tokens := DNTks, base_tokens := DNTks,
bind_password_tokens := PWTks, bind_password := PWTks,
bind_pool_name := PoolName bind_pool_name := PoolName
} = State } = State
) -> ) ->
@ -86,7 +86,7 @@ on_query(
{error, Reason} -> {error, Reason} ->
?SLOG( ?SLOG(
error, error,
LogMeta#{msg => "ldap_bind_failed", reason => Reason} LogMeta#{msg => "ldap_bind_failed", reason => emqx_utils:redact(Reason)}
), ),
{error, {unrecoverable_error, Reason}} {error, {unrecoverable_error, Reason}}
end. end.
@ -100,7 +100,9 @@ prepare_template(Config, State) ->
do_prepare_template(maps:to_list(maps:with([bind_password], Config)), State). do_prepare_template(maps:to_list(maps:with([bind_password], Config)), State).
do_prepare_template([{bind_password, V} | T], State) -> do_prepare_template([{bind_password, V} | T], State) ->
do_prepare_template(T, State#{bind_password_tokens => emqx_placeholder:preproc_tmpl(V)}); %% This is sensitive data
%% to reduce match cases, here we reuse the existing sensitive filter key: bind_password
do_prepare_template(T, State#{bind_password => emqx_placeholder:preproc_tmpl(V)});
do_prepare_template([], State) -> do_prepare_template([], State) ->
State. State.

View File

@ -266,7 +266,7 @@ fields(node_status) ->
})}, })},
{status, ?HOCON(?R_REF(status))} {status, ?HOCON(?R_REF(status))}
]; ];
fields({Type, with_name}) -> fields("with_name_" ++ Type) ->
listener_struct_with_name(Type); listener_struct_with_name(Type);
fields(Type) -> fields(Type) ->
listener_struct(Type). listener_struct(Type).
@ -308,7 +308,7 @@ listener_union_member_selector(Opts) ->
create_listener_schema(Opts) -> create_listener_schema(Opts) ->
Schemas = [ Schemas = [
?R_REF(Mod, {Type, with_name}) ?R_REF(Mod, "with_name_" ++ Type)
|| #{ref := ?R_REF(Mod, Type)} <- listeners_info(Opts) || #{ref := ?R_REF(Mod, Type)} <- listeners_info(Opts)
], ],
Example = maps:remove(id, tcp_schema_example()), Example = maps:remove(id, tcp_schema_example()),
@ -399,7 +399,7 @@ list_listeners(get, #{query_string := Query}) ->
end, end,
{200, listener_status_by_id(NodeL)}; {200, listener_status_by_id(NodeL)};
list_listeners(post, #{body := Body}) -> list_listeners(post, #{body := Body}) ->
create_listener(Body). create_listener(name, Body).
crud_listeners_by_id(get, #{bindings := #{id := Id}}) -> crud_listeners_by_id(get, #{bindings := #{id := Id}}) ->
case find_listeners_by_id(Id) of case find_listeners_by_id(Id) of
@ -407,7 +407,7 @@ crud_listeners_by_id(get, #{bindings := #{id := Id}}) ->
[L] -> {200, L} [L] -> {200, L}
end; end;
crud_listeners_by_id(put, #{bindings := #{id := Id}, body := Body0}) -> crud_listeners_by_id(put, #{bindings := #{id := Id}, body := Body0}) ->
case parse_listener_conf(Body0) of case parse_listener_conf(id, Body0) of
{Id, Type, Name, Conf} -> {Id, Type, Name, Conf} ->
case get_raw(Type, Name) of case get_raw(Type, Name) of
undefined -> undefined ->
@ -430,7 +430,7 @@ crud_listeners_by_id(put, #{bindings := #{id := Id}, body := Body0}) ->
{400, #{code => 'BAD_LISTENER_ID', message => ?LISTENER_ID_INCONSISTENT}} {400, #{code => 'BAD_LISTENER_ID', message => ?LISTENER_ID_INCONSISTENT}}
end; end;
crud_listeners_by_id(post, #{body := Body}) -> crud_listeners_by_id(post, #{body := Body}) ->
create_listener(Body); create_listener(id, Body);
crud_listeners_by_id(delete, #{bindings := #{id := Id}}) -> crud_listeners_by_id(delete, #{bindings := #{id := Id}}) ->
{ok, #{type := Type, name := Name}} = emqx_listeners:parse_listener_id(Id), {ok, #{type := Type, name := Name}} = emqx_listeners:parse_listener_id(Id),
case find_listeners_by_id(Id) of case find_listeners_by_id(Id) of
@ -441,11 +441,10 @@ crud_listeners_by_id(delete, #{bindings := #{id := Id}}) ->
{404, #{code => 'BAD_LISTENER_ID', message => ?LISTENER_NOT_FOUND}} {404, #{code => 'BAD_LISTENER_ID', message => ?LISTENER_NOT_FOUND}}
end. end.
parse_listener_conf(Conf0) -> parse_listener_conf(id, Conf0) ->
Conf1 = maps:without([<<"running">>, <<"current_connections">>], Conf0), Conf1 = maps:without([<<"running">>, <<"current_connections">>], Conf0),
{TypeBin, Conf2} = maps:take(<<"type">>, Conf1), {TypeBin, Conf2} = maps:take(<<"type">>, Conf1),
TypeAtom = binary_to_existing_atom(TypeBin), TypeAtom = binary_to_existing_atom(TypeBin),
case maps:take(<<"id">>, Conf2) of case maps:take(<<"id">>, Conf2) of
{IdBin, Conf3} -> {IdBin, Conf3} ->
{ok, #{type := Type, name := Name}} = emqx_listeners:parse_listener_id(IdBin), {ok, #{type := Type, name := Name}} = emqx_listeners:parse_listener_id(IdBin),
@ -454,13 +453,18 @@ parse_listener_conf(Conf0) ->
false -> {error, listener_type_inconsistent} false -> {error, listener_type_inconsistent}
end; end;
_ -> _ ->
case maps:take(<<"name">>, Conf2) of {error, listener_config_invalid}
{Name, Conf3} -> end;
IdBin = <<TypeBin/binary, $:, Name/binary>>, parse_listener_conf(name, Conf0) ->
{binary_to_atom(IdBin), TypeAtom, Name, Conf3}; Conf1 = maps:without([<<"running">>, <<"current_connections">>], Conf0),
_ -> {TypeBin, Conf2} = maps:take(<<"type">>, Conf1),
{error, listener_config_invalid} TypeAtom = binary_to_existing_atom(TypeBin),
end case maps:take(<<"name">>, Conf2) of
{Name, Conf3} ->
IdBin = <<TypeBin/binary, $:, Name/binary>>,
{binary_to_atom(IdBin), TypeAtom, Name, Conf3};
_ ->
{error, listener_config_invalid}
end. end.
stop_listeners_by_id(Method, Body = #{bindings := Bindings}) -> stop_listeners_by_id(Method, Body = #{bindings := Bindings}) ->
@ -832,8 +836,8 @@ tcp_schema_example() ->
type => tcp type => tcp
}. }.
create_listener(Body) -> create_listener(From, Body) ->
case parse_listener_conf(Body) of case parse_listener_conf(From, Body) of
{Id, Type, Name, Conf} -> {Id, Type, Name, Conf} ->
case create(Type, Name, Conf) of case create(Type, Name, Conf) of
{ok, #{raw_config := _RawConf}} -> {ok, #{raw_config := _RawConf}} ->

View File

@ -238,7 +238,6 @@ t_clear_certs(Config) when is_list(Config) ->
NewConf2 = emqx_utils_maps:deep_put( NewConf2 = emqx_utils_maps:deep_put(
[<<"ssl_options">>, <<"keyfile">>], NewConf, cert_file("keyfile") [<<"ssl_options">>, <<"keyfile">>], NewConf, cert_file("keyfile")
), ),
_ = request(post, NewPath, [], NewConf2), _ = request(post, NewPath, [], NewConf2),
ListResult1 = list_pem_dir("ssl", "clear"), ListResult1 = list_pem_dir("ssl", "clear"),
?assertMatch({ok, [_, _]}, ListResult1), ?assertMatch({ok, [_, _]}, ListResult1),
@ -251,7 +250,7 @@ t_clear_certs(Config) when is_list(Config) ->
_ = emqx_tls_certfile_gc:force(), _ = emqx_tls_certfile_gc:force(),
ListResult2 = list_pem_dir("ssl", "clear"), ListResult2 = list_pem_dir("ssl", "clear"),
%% make sure the old cret file is deleted %% make sure the old cert file is deleted
?assertMatch({ok, [_, _]}, ListResult2), ?assertMatch({ok, [_, _]}, ListResult2),
{ok, ResultList1} = ListResult1, {ok, ResultList1} = ListResult1,
@ -273,6 +272,17 @@ t_clear_certs(Config) when is_list(Config) ->
_ = delete(NewPath), _ = delete(NewPath),
_ = emqx_tls_certfile_gc:force(), _ = emqx_tls_certfile_gc:force(),
?assertMatch({error, enoent}, list_pem_dir("ssl", "clear")), ?assertMatch({error, enoent}, list_pem_dir("ssl", "clear")),
%% test create listeners without id in path
NewPath1 = emqx_mgmt_api_test_util:api_path(["listeners"]),
NewConf3 = maps:remove(<<"id">>, NewConf2#{<<"name">> => <<"clear">>}),
?assertNotMatch({error, {"HTTP/1.1", 400, _}}, request(post, NewPath1, [], NewConf3)),
ListResult3 = list_pem_dir("ssl", "clear"),
?assertMatch({ok, [_, _]}, ListResult3),
_ = delete(NewPath),
_ = emqx_tls_certfile_gc:force(),
?assertMatch({error, enoent}, list_pem_dir("ssl", "clear")),
ok. ok.
get_tcp_listeners(Node) -> get_tcp_listeners(Node) ->

View File

@ -841,7 +841,4 @@ Defaults to 100000."""
node_channel_cleanup_batch_size.label: node_channel_cleanup_batch_size.label:
"""Node Channel Cleanup Batch Size""" """Node Channel Cleanup Batch Size"""
prevent_overlapping_partitions.desc:
"""https://www.erlang.org/doc/man/global.html#description"""
} }