Merge remote-tracking branch 'ce/release-53' into audit-log-fix-2
This commit is contained in:
commit
a73c3b8e1e
|
@ -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} ->
|
||||||
|
|
|
@ -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") ->
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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, #{
|
||||||
|
|
|
@ -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().
|
||||||
|
|
|
@ -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()}.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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">>}
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
].
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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}} ->
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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"""
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue