From 0eed01abee30a7c934b940cd6f89e0642328e1f9 Mon Sep 17 00:00:00 2001 From: zhongwencool Date: Fri, 22 Sep 2023 16:03:28 +0800 Subject: [PATCH 01/65] fix: create ssl listener return 500 crash --- .../src/emqx_mgmt_api_listeners.erl | 36 ++++++++++--------- .../test/emqx_mgmt_api_listeners_SUITE.erl | 14 ++++++-- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/apps/emqx_management/src/emqx_mgmt_api_listeners.erl b/apps/emqx_management/src/emqx_mgmt_api_listeners.erl index 90fb1f98e..8295047b9 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_listeners.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_listeners.erl @@ -266,7 +266,7 @@ fields(node_status) -> })}, {status, ?HOCON(?R_REF(status))} ]; -fields({Type, with_name}) -> +fields("with_name_" ++ Type) -> listener_struct_with_name(Type); fields(Type) -> listener_struct(Type). @@ -308,7 +308,7 @@ listener_union_member_selector(Opts) -> create_listener_schema(Opts) -> Schemas = [ - ?R_REF(Mod, {Type, with_name}) + ?R_REF(Mod, "with_name_" ++ Type) || #{ref := ?R_REF(Mod, Type)} <- listeners_info(Opts) ], Example = maps:remove(id, tcp_schema_example()), @@ -399,7 +399,7 @@ list_listeners(get, #{query_string := Query}) -> end, {200, listener_status_by_id(NodeL)}; list_listeners(post, #{body := Body}) -> - create_listener(Body). + create_listener(name, Body). crud_listeners_by_id(get, #{bindings := #{id := Id}}) -> case find_listeners_by_id(Id) of @@ -407,7 +407,7 @@ crud_listeners_by_id(get, #{bindings := #{id := Id}}) -> [L] -> {200, L} end; 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} -> case get_raw(Type, Name) of undefined -> @@ -430,7 +430,7 @@ crud_listeners_by_id(put, #{bindings := #{id := Id}, body := Body0}) -> {400, #{code => 'BAD_LISTENER_ID', message => ?LISTENER_ID_INCONSISTENT}} end; crud_listeners_by_id(post, #{body := Body}) -> - create_listener(Body); + create_listener(id, Body); crud_listeners_by_id(delete, #{bindings := #{id := Id}}) -> {ok, #{type := Type, name := Name}} = emqx_listeners:parse_listener_id(Id), 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}} end. -parse_listener_conf(Conf0) -> +parse_listener_conf(id, Conf0) -> Conf1 = maps:without([<<"running">>, <<"current_connections">>], Conf0), {TypeBin, Conf2} = maps:take(<<"type">>, Conf1), TypeAtom = binary_to_existing_atom(TypeBin), - case maps:take(<<"id">>, Conf2) of {IdBin, Conf3} -> {ok, #{type := Type, name := Name}} = emqx_listeners:parse_listener_id(IdBin), @@ -454,13 +453,18 @@ parse_listener_conf(Conf0) -> false -> {error, listener_type_inconsistent} end; _ -> - case maps:take(<<"name">>, Conf2) of - {Name, Conf3} -> - IdBin = <>, - {binary_to_atom(IdBin), TypeAtom, Name, Conf3}; - _ -> - {error, listener_config_invalid} - end + {error, listener_config_invalid} + end; +parse_listener_conf(name, Conf0) -> + Conf1 = maps:without([<<"running">>, <<"current_connections">>], Conf0), + {TypeBin, Conf2} = maps:take(<<"type">>, Conf1), + TypeAtom = binary_to_existing_atom(TypeBin), + case maps:take(<<"name">>, Conf2) of + {Name, Conf3} -> + IdBin = <>, + {binary_to_atom(IdBin), TypeAtom, Name, Conf3}; + _ -> + {error, listener_config_invalid} end. stop_listeners_by_id(Method, Body = #{bindings := Bindings}) -> @@ -832,8 +836,8 @@ tcp_schema_example() -> type => tcp }. -create_listener(Body) -> - case parse_listener_conf(Body) of +create_listener(From, Body) -> + case parse_listener_conf(From, Body) of {Id, Type, Name, Conf} -> case create(Type, Name, Conf) of {ok, #{raw_config := _RawConf}} -> diff --git a/apps/emqx_management/test/emqx_mgmt_api_listeners_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_listeners_SUITE.erl index 96ec8f2de..53579406f 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_listeners_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_listeners_SUITE.erl @@ -238,7 +238,6 @@ t_clear_certs(Config) when is_list(Config) -> NewConf2 = emqx_utils_maps:deep_put( [<<"ssl_options">>, <<"keyfile">>], NewConf, cert_file("keyfile") ), - _ = request(post, NewPath, [], NewConf2), ListResult1 = list_pem_dir("ssl", "clear"), ?assertMatch({ok, [_, _]}, ListResult1), @@ -251,7 +250,7 @@ t_clear_certs(Config) when is_list(Config) -> _ = emqx_tls_certfile_gc:force(), 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), {ok, ResultList1} = ListResult1, @@ -273,6 +272,17 @@ t_clear_certs(Config) when is_list(Config) -> _ = delete(NewPath), _ = emqx_tls_certfile_gc:force(), ?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. get_tcp_listeners(Node) -> From ad4fadc2fa755de28b9da92f4f6ea52e1a9a06bc Mon Sep 17 00:00:00 2001 From: JimMoen Date: Sat, 23 Sep 2023 17:29:02 +0800 Subject: [PATCH 02/65] fix: saml login acs redirect to dashboard overview --- .../src/emqx_dashboard_sso_api.erl | 10 +- .../src/emqx_dashboard_sso_saml.erl | 123 ++++++++++-------- .../src/emqx_dashboard_sso_saml_api.erl | 4 +- 3 files changed, 80 insertions(+), 57 deletions(-) diff --git a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl index c19d2b66e..0887d3c24 100644 --- a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl +++ b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl @@ -33,7 +33,7 @@ backend/2 ]). --export([sso_parameters/1, login_reply/2]). +-export([sso_parameters/1, login_meta/3]). -define(REDIRECT, 'REDIRECT'). -define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD'). @@ -151,7 +151,7 @@ running(get, _Request) -> maps:values(SSO) )}. -login(post, #{bindings := #{backend := Backend}} = Request) -> +login(post, #{bindings := #{backend := Backend}, body := Body} = Request) -> case emqx_dashboard_sso_manager:lookup_state(Backend) of undefined -> {404, #{code => ?BACKEND_NOT_FOUND, message => <<"Backend not found">>}}; @@ -159,7 +159,8 @@ login(post, #{bindings := #{backend := Backend}} = Request) -> case emqx_dashboard_sso:login(provider(Backend), Request, State) of {ok, Role, Token} -> ?SLOG(info, #{msg => "dashboard_sso_login_successful", request => Request}), - {200, login_reply(Role, Token)}; + Username = maps:get(<<"username">>, Body), + {200, login_meta(Username, Role, Token)}; {redirect, Redirect} -> ?SLOG(info, #{msg => "dashboard_sso_login_redirect", request => Request}), Redirect; @@ -266,8 +267,9 @@ to_json(Data) -> end ). -login_reply(Role, Token) -> +login_meta(Username, Role, Token) -> #{ + username => Username, role => Role, token => Token, version => iolist_to_binary(proplists:get_value(version, emqx_sys:info())), diff --git a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_saml.erl b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_saml.erl index edfb51712..e36f8777a 100644 --- a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_saml.erl +++ b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_saml.erl @@ -29,6 +29,9 @@ -dialyzer({nowarn_function, do_create/1}). +-define(RESPHEADERS, #{<<"Cache-Control">> => <<"no-cache">>, <<"Pragma">> => <<"no-cache">>}). +-define(REDIRECT_BODY, <<"Redirecting...">>). + -define(DIR, <<"saml_sp_certs">>). %%------------------------------------------------------------------------------ @@ -103,7 +106,49 @@ create(#{sp_sign_request := true} = Config) -> {error, Msg} end; create(#{sp_sign_request := false} = Config) -> - do_create(Config#{key => undefined, certificate => undefined}). + do_create(Config#{sp_private_key => undefined, sp_public_key => undefined}). + +update(Config0, State) -> + destroy(State), + create(Config0). + +destroy(_State) -> + _ = file:del_dir_r(emqx_tls_lib:pem_dir(?DIR)), + _ = application:stop(esaml), + ok. + +login( + #{headers := Headers} = _Req, + #{sp := SP, idp_meta := #esaml_idp_metadata{login_location = IDP}} = _State +) -> + SignedXml = esaml_sp:generate_authn_request(IDP, SP), + Target = esaml_binding:encode_http_redirect(IDP, SignedXml, <<>>), + Redirect = + case is_msie(Headers) of + true -> + Html = esaml_binding:encode_http_post(IDP, SignedXml, <<>>), + {200, ?RESPHEADERS, Html}; + false -> + {302, ?RESPHEADERS#{<<"Location">> => Target}, ?REDIRECT_BODY} + end, + {redirect, Redirect}. + +callback(_Req = #{body := Body}, #{sp := SP, dashboard_addr := DashboardAddr} = _State) -> + case do_validate_assertion(SP, fun esaml_util:check_dupe_ets/2, Body) of + {ok, Assertion, _RelayState} -> + Subject = Assertion#esaml_assertion.subject, + Username = iolist_to_binary(Subject#esaml_subject.name), + gen_redirect_response(DashboardAddr, Username); + {error, Reason0} -> + Reason = [ + "Access denied, assertion failed validation:\n", io_lib:format("~p\n", [Reason0]) + ], + {error, iolist_to_binary(Reason)} + end. + +%%------------------------------------------------------------------------------ +%% Internal functions +%%------------------------------------------------------------------------------ do_create( #{ @@ -145,46 +190,6 @@ do_create( {error, Reason} end. -update(Config0, State) -> - destroy(State), - create(Config0). - -destroy(_State) -> - _ = file:del_dir_r(emqx_tls_lib:pem_dir(?DIR)), - _ = application:stop(esaml), - ok. - -login( - #{headers := Headers} = _Req, - #{sp := SP, idp_meta := #esaml_idp_metadata{login_location = IDP}} = _State -) -> - SignedXml = esaml_sp:generate_authn_request(IDP, SP), - Target = esaml_binding:encode_http_redirect(IDP, SignedXml, <<>>), - RespHeaders = #{<<"Cache-Control">> => <<"no-cache">>, <<"Pragma">> => <<"no-cache">>}, - Redirect = - case is_msie(Headers) of - true -> - Html = esaml_binding:encode_http_post(IDP, SignedXml, <<>>), - {200, RespHeaders, Html}; - false -> - RespHeaders1 = RespHeaders#{<<"Location">> => Target}, - {302, RespHeaders1, <<"Redirecting...">>} - end, - {redirect, Redirect}. - -callback(_Req = #{body := Body}, #{sp := SP} = _State) -> - case do_validate_assertion(SP, fun esaml_util:check_dupe_ets/2, Body) of - {ok, Assertion, _RelayState} -> - Subject = Assertion#esaml_assertion.subject, - Username = iolist_to_binary(Subject#esaml_subject.name), - ensure_user_exists(Username); - {error, Reason0} -> - Reason = [ - "Access denied, assertion failed validation:\n", io_lib:format("~p\n", [Reason0]) - ], - {error, iolist_to_binary(Reason)} - end. - do_validate_assertion(SP, DuplicateFun, Body) -> PostVals = cow_qs:parse_qs(Body), SAMLEncoding = proplists:get_value(<<"SAMLEncoding">>, PostVals), @@ -200,8 +205,17 @@ do_validate_assertion(SP, DuplicateFun, Body) -> end end. +gen_redirect_response(DashboardAddr, Username) -> + case ensure_user_exists(Username) of + {ok, Role, Token} -> + Target = login_redirect_target(DashboardAddr, Username, Role, Token), + {redirect, {302, ?RESPHEADERS#{<<"Location">> => Target}, ?REDIRECT_BODY}}; + {error, Reason} -> + {error, Reason} + end. + %%------------------------------------------------------------------------------ -%% Internal functions +%% Helpers functions %%------------------------------------------------------------------------------ ensure_cert_and_key(#{sp_public_key := Cert, sp_private_key := Key} = Config) -> @@ -216,15 +230,6 @@ ensure_cert_and_key(#{sp_public_key := Cert, sp_private_key := Key} = Config) -> error({missing_key, lists:flatten(KeyPath)}) end. -maybe_load_cert_or_key(undefined, _) -> - undefined; -maybe_load_cert_or_key(Path, Func) -> - Func(Path). - -is_msie(Headers) -> - UA = maps:get(<<"user-agent">>, Headers, <<"">>), - not (binary:match(UA, <<"MSIE">>) =:= nomatch). - %% TODO: unify with emqx_dashboard_sso_manager:ensure_user_exists/1 ensure_user_exists(Username) -> case emqx_dashboard_admin:lookup_user(saml, Username) of @@ -238,3 +243,19 @@ ensure_user_exists(Username) -> Error end end. + +maybe_load_cert_or_key(undefined, _) -> + undefined; +maybe_load_cert_or_key(Path, Func) -> + Func(Path). + +is_msie(Headers) -> + UA = maps:get(<<"user-agent">>, Headers, <<"">>), + not (binary:match(UA, <<"MSIE">>) =:= nomatch). + +login_redirect_target(DashboardAddr, Username, Role, Token) -> + LoginMeta = emqx_dashboard_sso_api:login_meta(Username, Role, Token), + <>}}; State -> case (provider(saml)):callback(Req, State) of - {ok, Role, Token} -> - {200, emqx_dashboard_sso_api:login_reply(Role, Token)}; + {redirect, Redirect} -> + Redirect; {error, Reason} -> ?SLOG(info, #{ msg => "dashboard_saml_sso_login_failed", From 30862a94c698beef3c6717dfd70d1c6ec4bb79aa Mon Sep 17 00:00:00 2001 From: JianBo He Date: Sun, 24 Sep 2023 20:19:05 +0800 Subject: [PATCH 03/65] chore: typo fixes --- apps/emqx_management/src/emqx_mgmt_cli.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx_management/src/emqx_mgmt_cli.erl b/apps/emqx_management/src/emqx_mgmt_cli.erl index 85388b444..15f05da04 100644 --- a/apps/emqx_management/src/emqx_mgmt_cli.erl +++ b/apps/emqx_management/src/emqx_mgmt_cli.erl @@ -482,7 +482,7 @@ trace(_) -> {"trace stop topic ", "Stop tracing for a topic on local node"}, {"trace start ip_address [] ", "Traces for a client ip on local node"}, - {"trace stop ip_addresss ", "Stop tracing for a client ip on local node"} + {"trace stop ip_address ", "Stop tracing for a client ip on local node"} ]). trace_on(Name, Type, Filter, Level, LogFile) -> From 4f4868a46c716fb6441f29d479548423aa347077 Mon Sep 17 00:00:00 2001 From: firest Date: Mon, 25 Sep 2023 10:31:38 +0800 Subject: [PATCH 04/65] fix(sso): Disable access to `logout` endpoint by the `API key` --- apps/emqx_management/src/emqx_mgmt_auth.erl | 2 ++ rel/i18n/emqx_dashboard_api.hocon | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/emqx_management/src/emqx_mgmt_auth.erl b/apps/emqx_management/src/emqx_mgmt_auth.erl index ace4c155a..3d32afc19 100644 --- a/apps/emqx_management/src/emqx_mgmt_auth.erl +++ b/apps/emqx_management/src/emqx_mgmt_auth.erl @@ -156,6 +156,8 @@ authorize(<<"/api/v5/users", _/binary>>, _ApiKey, _ApiSecret) -> {error, <<"not_allowed">>}; authorize(<<"/api/v5/api_key", _/binary>>, _ApiKey, _ApiSecret) -> {error, <<"not_allowed">>}; +authorize(<<"/api/v5/logout", _/binary>>, _ApiKey, _ApiSecret) -> + {error, <<"not_allowed">>}; authorize(_Path, ApiKey, ApiSecret) -> Now = erlang:system_time(second), case find_by_api_key(ApiKey) of diff --git a/rel/i18n/emqx_dashboard_api.hocon b/rel/i18n/emqx_dashboard_api.hocon index 5f6bd3cde..f3d47801a 100644 --- a/rel/i18n/emqx_dashboard_api.hocon +++ b/rel/i18n/emqx_dashboard_api.hocon @@ -43,7 +43,9 @@ login_success.desc: """Dashboard Auth Success""" logout_api.desc: -"""Dashboard user logout""" +"""Dashboard user logout. +This endpoint is only for the Dashboard, not the `API Key`. +The token from the `/login` endpoint must be a bearer authorization in the headers.""" logout_api.label: """Dashboard user logout""" From e94192d1fa38f5741e51aa620fe6fc907b993fdb Mon Sep 17 00:00:00 2001 From: firest Date: Mon, 25 Sep 2023 11:36:17 +0800 Subject: [PATCH 05/65] fix(sso): refactor the `admins` CLI 1. revert the opensource version 2. allow delete the SSO user via CLI --- .../src/emqx_dashboard_admin.erl | 4 +- .../emqx_dashboard/src/emqx_dashboard_cli.erl | 75 +++++++------------ .../src/emqx_dashboard_sso_cli.erl | 60 +++++++++++++++ 3 files changed, 89 insertions(+), 50 deletions(-) create mode 100644 apps/emqx_dashboard_sso/src/emqx_dashboard_sso_cli.erl diff --git a/apps/emqx_dashboard/src/emqx_dashboard_admin.erl b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl index cf05b1f9f..e77204541 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_admin.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl @@ -212,8 +212,8 @@ add_user_(Username, Password, Role, Desc) -> mnesia:abort(<<"username_already_exist">>) end. --spec remove_user(binary()) -> {ok, any()} | {error, any()}. -remove_user(Username) when is_binary(Username) -> +-spec remove_user(dashboard_username()) -> {ok, any()} | {error, any()}. +remove_user(Username) -> Trans = fun() -> case lookup_user(Username) of [] -> mnesia:abort(<<"username_not_found">>); diff --git a/apps/emqx_dashboard/src/emqx_dashboard_cli.erl b/apps/emqx_dashboard/src/emqx_dashboard_cli.erl index 3da3e822d..ec197fdb0 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_cli.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_cli.erl @@ -24,9 +24,26 @@ unload/0 ]). -load() -> - emqx_ctl:register_command(admins, {?MODULE, admins}, []). +-export([bin/1, print_error/1]). +-if(?EMQX_RELEASE_EDITION == ee). +-define(CLI_MOD, emqx_dashboard_sso_cli). +-else. +-define(CLI_MOD, ?MODULE). +-endif. + +load() -> + emqx_ctl:register_command(admins, {?CLI_MOD, admins}, []). + +admins(["add", Username, Password]) -> + admins(["add", Username, Password, ""]); +admins(["add", Username, Password, Desc]) -> + case emqx_dashboard_admin:add_user(bin(Username), bin(Password), ?ROLE_DEFAULT, bin(Desc)) of + {ok, _} -> + emqx_ctl:print("ok~n"); + {error, Reason} -> + print_error(Reason) + end; admins(["passwd", Username, Password]) -> case emqx_dashboard_admin:change_password(bin(Username), bin(Password)) of {ok, _} -> @@ -41,8 +58,14 @@ admins(["del", Username]) -> {error, Reason} -> print_error(Reason) end; -admins(Args) -> - inner_admins(Args). +admins(_) -> + emqx_ctl:usage( + [ + {"admins add ", "Add dashboard user"}, + {"admins passwd ", "Reset dashboard user password"}, + {"admins del ", "Delete dashboard user"} + ] + ). unload() -> emqx_ctl:unregister_command(admins). @@ -54,47 +77,3 @@ print_error(Reason) when is_binary(Reason) -> %% Maybe has more types of error, but there is only binary now. So close it for dialyzer. % print_error(Reason) -> % emqx_ctl:print("Error: ~p~n", [Reason]). - --if(?EMQX_RELEASE_EDITION == ee). -usage() -> - [ - {"admins add ", "Add dashboard user"}, - {"admins passwd ", "Reset dashboard user password"}, - {"admins del ", "Delete dashboard user"} - ]. - -inner_admins(["add", Username, Password]) -> - inner_admins(["add", Username, Password, ?ROLE_SUPERUSER]); -inner_admins(["add", Username, Password, Role]) -> - inner_admins(["add", Username, Password, Role, ""]); -inner_admins(["add", Username, Password, Role, Desc]) -> - case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Role), bin(Desc)) of - {ok, _} -> - emqx_ctl:print("ok~n"); - {error, Reason} -> - print_error(Reason) - end; -inner_admins(_) -> - emqx_ctl:usage(usage()). --else. - -usage() -> - [ - {"admins add ", "Add dashboard user"}, - {"admins passwd ", "Reset dashboard user password"}, - {"admins del ", "Delete dashboard user"} - ]. - -inner_admins(["add", Username, Password]) -> - inner_admins(["add", Username, Password, ""]); -inner_admins(["add", Username, Password, Desc]) -> - case emqx_dashboard_admin:add_user(bin(Username), bin(Password), ?ROLE_SUPERUSER, bin(Desc)) of - {ok, _} -> - emqx_ctl:print("ok~n"); - {error, Reason} -> - print_error(Reason) - end; -inner_admins(_) -> - emqx_ctl:usage(usage()). - --endif. diff --git a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_cli.erl b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_cli.erl new file mode 100644 index 000000000..308d37f5c --- /dev/null +++ b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_cli.erl @@ -0,0 +1,60 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_dashboard_sso_cli). + +-include_lib("emqx_dashboard/include/emqx_dashboard.hrl"). + +-export([admins/1]). + +-import(emqx_dashboard_cli, [bin/1, print_error/1]). + +admins(["add", Username, Password]) -> + admins(["add", Username, Password, ""]); +admins(["add", Username, Password, Desc]) -> + case emqx_dashboard_admin:add_user(bin(Username), bin(Password), ?ROLE_DEFAULT, bin(Desc)) of + {ok, _} -> + emqx_ctl:print("ok~n"); + {error, Reason} -> + print_error(Reason) + end; +admins(["add", Username, Password, Desc, Role]) -> + case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Role), bin(Desc)) of + {ok, _} -> + emqx_ctl:print("ok~n"); + {error, Reason} -> + print_error(Reason) + end; +admins(["passwd", Username, Password]) -> + case emqx_dashboard_admin:change_password(bin(Username), bin(Password)) of + {ok, _} -> + emqx_ctl:print("ok~n"); + {error, Reason} -> + print_error(Reason) + end; +admins(["del", Username]) -> + case emqx_dashboard_admin:remove_user(bin(Username)) of + {ok, _} -> + emqx_ctl:print("ok~n"); + {error, Reason} -> + print_error(Reason) + end; +admins(["del", Username, Backend]) -> + case emqx_dashboard_admin:remove_user(?SSO_USERNAME(atom(Backend), bin(Username))) of + {ok, _} -> + emqx_ctl:print("ok~n"); + {error, Reason} -> + print_error(Reason) + end; +admins(_) -> + emqx_ctl:usage( + [ + {"admins add ", "Add dashboard user"}, + {"admins passwd ", "Reset dashboard user password"}, + {"admins del ", "Delete dashboard user"} + ] + ). + +atom(S) -> + erlang:list_to_atom(S). From e63d484632e8bbb14c3e1c25acd35a600ffcad1f Mon Sep 17 00:00:00 2001 From: firest Date: Mon, 25 Sep 2023 15:23:52 +0800 Subject: [PATCH 06/65] fix(sso): move the config path of the SSO feature to `dashboard.sso` --- .../src/emqx_dashboard_schema.erl | 17 +++++++++++++- .../src/emqx_dashboard_sso_api.erl | 7 +++--- .../src/emqx_dashboard_sso_manager.erl | 22 ++++++++++++++----- .../src/emqx_dashboard_sso_schema.erl | 21 +++++++++--------- .../test/emqx_dashboard_sso_ldap_SUITE.erl | 2 +- .../src/emqx_enterprise_schema.erl | 3 +-- 6 files changed, 48 insertions(+), 24 deletions(-) diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl index 7427e81da..59be9706e 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl @@ -68,7 +68,7 @@ fields("dashboard") -> importance => ?IMPORTANCE_HIDDEN } )} - ]; + ] ++ sso_fields(); fields("listeners") -> [ {"http", @@ -299,3 +299,18 @@ https_converter(Conf = #{}, _Opts) -> Conf1#{<<"ssl_options">> => SslOpts}; https_converter(Conf, _Opts) -> Conf. + +-if(?EMQX_RELEASE_EDITION == ee). +sso_fields() -> + [ + {sso, + ?HOCON( + ?R_REF(emqx_dashboard_sso_schema, sso), + #{required => {false, recursively}} + )} + ]. + +-else. +sso_fields() -> + []. +-endif. diff --git a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl index c19d2b66e..986c28d67 100644 --- a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl +++ b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl @@ -40,6 +40,7 @@ -define(BAD_REQUEST, 'BAD_REQUEST'). -define(BACKEND_NOT_FOUND, 'BACKEND_NOT_FOUND'). -define(TAGS, <<"Dashboard Single Sign-On">>). +-define(MOD_KEY_PATH, [dashboard, sso]). namespace() -> "dashboard_sso". @@ -139,7 +140,7 @@ fields(backend_status) -> %%-------------------------------------------------------------------- running(get, _Request) -> - SSO = emqx:get_config([dashboard_sso], #{}), + SSO = emqx:get_config(?MOD_KEY_PATH, #{}), {200, lists:filtermap( fun @@ -174,7 +175,7 @@ login(post, #{bindings := #{backend := Backend}} = Request) -> end. sso(get, _Request) -> - SSO = emqx:get_config([dashboard_sso], #{}), + SSO = emqx:get_config(?MOD_KEY_PATH, #{}), {200, lists:map( fun(Backend) -> @@ -184,7 +185,7 @@ sso(get, _Request) -> )}. backend(get, #{bindings := #{backend := Type}}) -> - case emqx:get_config([dashboard_sso, Type], undefined) of + case emqx:get_config(?MOD_KEY_PATH ++ [Type], undefined) of undefined -> {404, #{code => ?BACKEND_NOT_FOUND, message => <<"Backend not found">>}}; Backend -> diff --git a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_manager.erl b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_manager.erl index afa27cb47..71f2d92d8 100644 --- a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_manager.erl +++ b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_manager.erl @@ -40,7 +40,7 @@ -import(emqx_dashboard_sso, [provider/1]). --define(MOD_KEY_PATH, [dashboard_sso]). +-define(MOD_KEY_PATH, [dashboard, sso]). -define(RESOURCE_GROUP, <<"emqx_dashboard_sso">>). -define(DEFAULT_RESOURCE_OPTS, #{ start_after_created => false @@ -66,7 +66,7 @@ running() -> Acc end, [], - emqx:get_config([emqx_dashboard_sso]) + emqx:get_config(?MOD_KEY_PATH) ). update(Backend, Config) -> @@ -151,7 +151,7 @@ format_status(_Opt, Status) -> %% Internal functions %%------------------------------------------------------------------------------ start_backend_services() -> - Backends = emqx_conf:get([dashboard_sso], #{}), + Backends = emqx_conf:get(?MOD_KEY_PATH, #{}), lists:foreach( fun({Backend, Config}) -> Provider = provider(Backend), @@ -174,7 +174,7 @@ start_backend_services() -> ). update_config(Backend, UpdateReq) -> - case emqx_conf:update([dashboard_sso], UpdateReq, #{override_to => cluster}) of + case emqx_conf:update(?MOD_KEY_PATH, UpdateReq, #{override_to => cluster}) of {ok, UpdateResult} -> #{post_config_update := #{?MODULE := Result}} = UpdateResult, ?SLOG(info, #{ @@ -194,10 +194,10 @@ update_config(Backend, UpdateReq) -> pre_config_update(_Path, {update, Backend, Config}, OldConf) -> BackendBin = bin(Backend), - {ok, OldConf#{BackendBin => Config}}; + {ok, update_raw_sso_cfg(OldConf, BackendBin, Config)}; pre_config_update(_Path, {delete, Backend}, OldConf) -> BackendBin = bin(Backend), - case maps:find(BackendBin, OldConf) of + case find_raw_sso_cfg(BackendBin, OldConf) of error -> throw(not_exists); {ok, _} -> @@ -267,3 +267,13 @@ on_backend_updated(Error, _) -> bin(A) when is_atom(A) -> atom_to_binary(A, utf8); bin(L) when is_list(L) -> list_to_binary(L); bin(X) -> X. + +update_raw_sso_cfg(undefined, Backend, BackendCfg) -> + #{Backend => BackendCfg}; +update_raw_sso_cfg(RAW_SSOCfg, Backend, BackendCfg) -> + RAW_SSOCfg#{Backend => BackendCfg}. + +find_raw_sso_cfg(_Backend, undefined) -> + error; +find_raw_sso_cfg(Backend, RAW_SSOCfg) -> + maps:find(Backend, RAW_SSOCfg). diff --git a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_schema.erl b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_schema.erl index b45bca8e5..92f9ba519 100644 --- a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_schema.erl +++ b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_schema.erl @@ -8,33 +8,32 @@ -include_lib("typerefl/include/types.hrl"). %% Hocon --export([namespace/0, roots/0, fields/1, tags/0, desc/1]). +-export([fields/1, desc/1]). + -export([ common_backend_schema/1, backend_schema/1, username_password_schema/0 ]). + -import(hoconsc, [ref/2, mk/2, enum/1]). %%------------------------------------------------------------------------------ %% Hocon Schema %%------------------------------------------------------------------------------ -namespace() -> dashboard_sso. - -tags() -> - [<<"Dashboard Single Sign-On">>]. - -roots() -> [dashboard_sso]. - -fields(dashboard_sso) -> +fields(sso) -> lists:map( fun({Type, Module}) -> - {Type, mk(emqx_dashboard_sso:hocon_ref(Module), #{required => {false, recursively}})} + {Type, + mk( + emqx_dashboard_sso:hocon_ref(Module), + #{required => {false, recursively}} + )} end, maps:to_list(emqx_dashboard_sso:backends()) ). -desc(dashboard_sso) -> +desc(sso) -> "Dashboard Single Sign-On"; desc(_) -> undefined. diff --git a/apps/emqx_dashboard_sso/test/emqx_dashboard_sso_ldap_SUITE.erl b/apps/emqx_dashboard_sso/test/emqx_dashboard_sso_ldap_SUITE.erl index 85cb23693..671276e59 100644 --- a/apps/emqx_dashboard_sso/test/emqx_dashboard_sso_ldap_SUITE.erl +++ b/apps/emqx_dashboard_sso/test/emqx_dashboard_sso_ldap_SUITE.erl @@ -29,7 +29,7 @@ all() -> init_per_suite(Config) -> _ = application:load(emqx_conf), - emqx_config:save_schema_mod_and_names(emqx_dashboard_sso_schema), + emqx_config:save_schema_mod_and_names(emqx_dashboard_schema), emqx_mgmt_api_test_util:init_suite([emqx_dashboard, emqx_dashboard_sso]), Config. diff --git a/apps/emqx_enterprise/src/emqx_enterprise_schema.erl b/apps/emqx_enterprise/src/emqx_enterprise_schema.erl index c238dcea4..a801825e0 100644 --- a/apps/emqx_enterprise/src/emqx_enterprise_schema.erl +++ b/apps/emqx_enterprise/src/emqx_enterprise_schema.erl @@ -11,8 +11,7 @@ -define(EE_SCHEMA_MODULES, [ emqx_license_schema, emqx_schema_registry_schema, - emqx_ft_schema, - emqx_dashboard_sso_schema + emqx_ft_schema ]). namespace() -> From b970a34ee0efb6f2274dc55a456fc7d8e911e590 Mon Sep 17 00:00:00 2001 From: JianBo He Date: Mon, 25 Sep 2023 18:05:05 +0800 Subject: [PATCH 07/65] chore: redact some audit logs from CLI --- apps/emqx_conf/src/emqx_conf_cli.erl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/emqx_conf/src/emqx_conf_cli.erl b/apps/emqx_conf/src/emqx_conf_cli.erl index 109e88b4d..6cb53f8f3 100644 --- a/apps/emqx_conf/src/emqx_conf_cli.erl +++ b/apps/emqx_conf/src/emqx_conf_cli.erl @@ -108,7 +108,17 @@ admins(_) -> emqx_ctl:usage(usage_sync()). audit(Level, From, Log) -> - ?AUDIT(Level, From, Log#{time => logger:timestamp()}). + Log1 = redact(Log#{time => logger:timestamp()}), + ?AUDIT(Level, From, Log1). + +redact(Logs = #{cmd := admins, args := ["add", Username, _Password | Rest]}) -> + Logs#{args => ["add", Username, "******" | Rest]}; +redact(Logs = #{cmd := admins, args := ["passwd", Username, _Password]}) -> + Logs#{args => ["passwd", Username, "******"]}; +redact(Logs = #{cmd := license, args := ["update", _License]}) -> + Logs#{args => ["update", "******"]}; +redact(Logs) -> + Logs. usage_conf() -> [ From 8c93c79b447140731c4661319ae6546c293db506 Mon Sep 17 00:00:00 2001 From: Ivan Dyachkov Date: Mon, 25 Sep 2023 12:52:45 +0200 Subject: [PATCH 08/65] chore: e5.3.0-alpha.2 --- apps/emqx/include/emqx_release.hrl | 2 +- deploy/charts/emqx-enterprise/Chart.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/emqx/include/emqx_release.hrl b/apps/emqx/include/emqx_release.hrl index 9b6252efb..801773663 100644 --- a/apps/emqx/include/emqx_release.hrl +++ b/apps/emqx/include/emqx_release.hrl @@ -35,7 +35,7 @@ -define(EMQX_RELEASE_CE, "5.2.1"). %% Enterprise edition --define(EMQX_RELEASE_EE, "5.3.0-alpha.1"). +-define(EMQX_RELEASE_EE, "5.3.0-alpha.2"). %% The HTTP API version -define(EMQX_API_VERSION, "5.0"). diff --git a/deploy/charts/emqx-enterprise/Chart.yaml b/deploy/charts/emqx-enterprise/Chart.yaml index e5ab02dfc..c89a7f90d 100644 --- a/deploy/charts/emqx-enterprise/Chart.yaml +++ b/deploy/charts/emqx-enterprise/Chart.yaml @@ -14,8 +14,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 5.3.0-alpha.1 +version: 5.3.0-alpha.2 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. -appVersion: 5.3.0-alpha.1 +appVersion: 5.3.0-alpha.2 From 22193d273a5c174b0f0d949788b21ccbc521921a Mon Sep 17 00:00:00 2001 From: William Yang Date: Mon, 25 Sep 2023 12:57:55 +0200 Subject: [PATCH 09/65] fix(quic): ignore undefined cacertfile --- apps/emqx/src/emqx_listeners.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index 09a1bfc10..9abff250b 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/apps/emqx/src/emqx_listeners.erl @@ -437,6 +437,10 @@ do_start_listener(quic, ListenerName, #{bind := Bind} = Opts) -> case maps:get(cacertfile, SSLOpts, undefined) of undefined -> []; + <<>> -> + []; + "" -> + []; CaCertFile -> [{cacertfile, emqx_schema:naive_env_interpolation(CaCertFile)}] end ++ From 36d3a3a524c120be31aa74c2425e6dc89a429801 Mon Sep 17 00:00:00 2001 From: William Yang Date: Mon, 25 Sep 2023 13:02:01 +0200 Subject: [PATCH 10/65] fix: bump to quicer 0.0.201 do not load cacertfile if verify_none --- apps/emqx/rebar.config.script | 2 +- mix.exs | 2 +- rebar.config.erl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/emqx/rebar.config.script b/apps/emqx/rebar.config.script index 96d19b6d0..db54b6177 100644 --- a/apps/emqx/rebar.config.script +++ b/apps/emqx/rebar.config.script @@ -24,7 +24,7 @@ IsQuicSupp = fun() -> end, Bcrypt = {bcrypt, {git, "https://github.com/emqx/erlang-bcrypt.git", {tag, "0.6.0"}}}, -Quicer = {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.200"}}}. +Quicer = {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.201"}}}. Dialyzer = fun(Config) -> {dialyzer, OldDialyzerConfig} = lists:keyfind(dialyzer, 1, Config), diff --git a/mix.exs b/mix.exs index fdd740b01..0df408ad5 100644 --- a/mix.exs +++ b/mix.exs @@ -835,7 +835,7 @@ defmodule EMQXUmbrella.MixProject do defp quicer_dep() do if enable_quicer?(), # in conflict with emqx and emqtt - do: [{:quicer, github: "emqx/quic", tag: "0.0.200", override: true}], + do: [{:quicer, github: "emqx/quic", tag: "0.0.201", override: true}], else: [] end diff --git a/rebar.config.erl b/rebar.config.erl index 875eec222..da30ec933 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -39,7 +39,7 @@ bcrypt() -> {bcrypt, {git, "https://github.com/emqx/erlang-bcrypt.git", {tag, "0.6.1"}}}. quicer() -> - {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.200"}}}. + {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.201"}}}. jq() -> {jq, {git, "https://github.com/emqx/jq", {tag, "v0.3.10"}}}. From cfdb25b2137ae069e182c0b6f590006fff1a7f53 Mon Sep 17 00:00:00 2001 From: firest Date: Mon, 25 Sep 2023 20:30:47 +0800 Subject: [PATCH 11/65] fix(sso): updates the SSO backend when the `[dashboard]` has updated --- apps/emqx/src/emqx_config_handler.erl | 2 +- .../src/emqx_dashboard_sso_manager.erl | 66 +++++++++++-------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/apps/emqx/src/emqx_config_handler.erl b/apps/emqx/src/emqx_config_handler.erl index a189fc9e5..f38c5563a 100644 --- a/apps/emqx/src/emqx_config_handler.erl +++ b/apps/emqx/src/emqx_config_handler.erl @@ -678,7 +678,7 @@ return_change_result(ConfKeyPath, {{update, Req}, Opts}) -> case Req =/= ?TOMBSTONE_CONFIG_CHANGE_REQ of true -> #{ - config => emqx_config:get(ConfKeyPath), + config => emqx_config:get(ConfKeyPath, undefined), raw_config => return_rawconf(ConfKeyPath, Opts) }; false -> diff --git a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_manager.erl b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_manager.erl index 71f2d92d8..10ae07b3f 100644 --- a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_manager.erl +++ b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_manager.erl @@ -35,12 +35,14 @@ update/2, delete/1, pre_config_update/3, - post_config_update/5 + post_config_update/5, + propagated_post_config_update/5 ]). -import(emqx_dashboard_sso, [provider/1]). -define(MOD_KEY_PATH, [dashboard, sso]). +-define(MOD_KEY_PATH(Sub), [dashboard, sso, Sub]). -define(RESOURCE_GROUP, <<"emqx_dashboard_sso">>). -define(DEFAULT_RESOURCE_OPTS, #{ start_after_created => false @@ -110,7 +112,7 @@ call(Req) -> %%------------------------------------------------------------------------------ init([]) -> process_flag(trap_exit, true), - emqx_conf:add_handler(?MOD_KEY_PATH, ?MODULE), + add_handler(), emqx_utils_ets:new( dashboard_sso, [ @@ -138,7 +140,7 @@ handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> - emqx_conf:remove_handler(?MOD_KEY_PATH), + remove_handler(), ok. code_change(_OldVsn, State, _Extra) -> @@ -174,7 +176,7 @@ start_backend_services() -> ). update_config(Backend, UpdateReq) -> - case emqx_conf:update(?MOD_KEY_PATH, UpdateReq, #{override_to => cluster}) of + case emqx_conf:update(?MOD_KEY_PATH(Backend), UpdateReq, #{override_to => cluster}) of {ok, UpdateResult} -> #{post_config_update := #{?MODULE := Result}} = UpdateResult, ?SLOG(info, #{ @@ -192,25 +194,38 @@ update_config(Backend, UpdateReq) -> Error end. -pre_config_update(_Path, {update, Backend, Config}, OldConf) -> - BackendBin = bin(Backend), - {ok, update_raw_sso_cfg(OldConf, BackendBin, Config)}; -pre_config_update(_Path, {delete, Backend}, OldConf) -> - BackendBin = bin(Backend), - case find_raw_sso_cfg(BackendBin, OldConf) of - error -> - throw(not_exists); - {ok, _} -> - {ok, maps:remove(BackendBin, OldConf)} - end. +pre_config_update(_, {update, _Backend, Config}, _OldConf) -> + {ok, Config}; +pre_config_update(_, {delete, _Backend}, undefined) -> + throw(not_exists); +pre_config_update(_, {delete, _Backend}, _OldConf) -> + {ok, null}. -post_config_update(_Path, UpdateReq, NewConf, _OldConf, _AppEnvs) -> +post_config_update(_, UpdateReq, NewConf, _OldConf, _AppEnvs) -> Result = call({update_config, UpdateReq, NewConf}), {ok, Result}. -on_config_update({update, Backend, _Config}, NewConf) -> +propagated_post_config_update( + ?MOD_KEY_PATH(BackendBin) = Path, _UpdateReq, undefined, OldConf, AppEnvs +) -> + case atom(BackendBin) of + {ok, Backend} -> + post_config_update(Path, {delete, Backend}, undefined, OldConf, AppEnvs); + Error -> + Error + end; +propagated_post_config_update( + ?MOD_KEY_PATH(BackendBin) = Path, _UpdateReq, NewConf, OldConf, AppEnvs +) -> + case atom(BackendBin) of + {ok, Backend} -> + post_config_update(Path, {update, Backend, undefined}, NewConf, OldConf, AppEnvs); + Error -> + Error + end. + +on_config_update({update, Backend, _RawConfig}, Config) -> Provider = provider(Backend), - Config = maps:get(Backend, NewConf), case lookup(Backend) of undefined -> on_backend_updated( @@ -268,12 +283,11 @@ bin(A) when is_atom(A) -> atom_to_binary(A, utf8); bin(L) when is_list(L) -> list_to_binary(L); bin(X) -> X. -update_raw_sso_cfg(undefined, Backend, BackendCfg) -> - #{Backend => BackendCfg}; -update_raw_sso_cfg(RAW_SSOCfg, Backend, BackendCfg) -> - RAW_SSOCfg#{Backend => BackendCfg}. +atom(B) -> + emqx_utils:safe_to_existing_atom(B). -find_raw_sso_cfg(_Backend, undefined) -> - error; -find_raw_sso_cfg(Backend, RAW_SSOCfg) -> - maps:find(Backend, RAW_SSOCfg). +add_handler() -> + ok = emqx_conf:add_handler(?MOD_KEY_PATH('?'), ?MODULE). + +remove_handler() -> + ok = emqx_conf:remove_handler(?MOD_KEY_PATH('?')). From aea1e80290e7e580191aafd5cb2e74806ca51a8f Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Fri, 22 Sep 2023 14:11:56 +0200 Subject: [PATCH 12/65] feat: add 'format' as alias for log 'formatter' --- apps/emqx_conf/src/emqx_conf_schema.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/emqx_conf/src/emqx_conf_schema.erl b/apps/emqx_conf/src/emqx_conf_schema.erl index f1bfc3d31..ea134a3be 100644 --- a/apps/emqx_conf/src/emqx_conf_schema.erl +++ b/apps/emqx_conf/src/emqx_conf_schema.erl @@ -1303,6 +1303,7 @@ log_handler_common_confs(Handler, Default) -> sc( hoconsc:enum([text, json]), #{ + aliases => [format], default => maps:get(formatter, Default, text), desc => ?DESC("common_handler_formatter"), importance => ?IMPORTANCE_MEDIUM From 1fed38c248a1697601aaa19f3d5b3029f04e5b83 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Fri, 22 Sep 2023 14:13:03 +0200 Subject: [PATCH 13/65] fix(logger): write 'json' format logs as JSON --- apps/emqx/src/emqx_logger_jsonfmt.erl | 81 ++++++++++++++++++++++----- rel/i18n/emqx_conf_schema.hocon | 5 +- 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/apps/emqx/src/emqx_logger_jsonfmt.erl b/apps/emqx/src/emqx_logger_jsonfmt.erl index 8710032e6..5063feee6 100644 --- a/apps/emqx/src/emqx_logger_jsonfmt.erl +++ b/apps/emqx/src/emqx_logger_jsonfmt.erl @@ -51,7 +51,8 @@ -type config() :: #{ depth => pos_integer() | unlimited, report_cb => logger:report_cb(), - single_line => boolean() + single_line => boolean(), + chars_limit => unlimited | pos_integer() }. -define(IS_STRING(String), (is_list(String) orelse is_binary(String))). @@ -64,18 +65,16 @@ best_effort_json(Input) -> best_effort_json(Input, [pretty, force_utf8]). best_effort_json(Input, Opts) -> - Config = #{depth => unlimited, single_line => true}, + Config = #{depth => unlimited, single_line => true, chars_limit => unlimited}, JsonReady = best_effort_json_obj(Input, Config), emqx_utils_json:encode(JsonReady, Opts). -spec format(logger:log_event(), config()) -> iodata(). -format(#{level := Level, msg := Msg, meta := Meta} = Event, Config0) when is_map(Config0) -> +format(#{level := Level, msg := Msg, meta := Meta}, Config0) when is_map(Config0) -> Config = add_default_config(Config0), - MsgBin = format(Msg, Meta#{level => Level}, Config), - logger_formatter:format(Event#{msg => {string, MsgBin}}, Config). + [format(Msg, Meta#{level => Level}, Config), "\n"]. -format(Msg, Meta0, Config) -> - Meta = maps:without([time, level], Meta0), +format(Msg, Meta, Config) -> Data0 = try maybe_format_msg(Msg, Meta, Config) of Map when is_map(Map) -> @@ -128,7 +127,7 @@ format_msg({report, Report}, #{report_cb := Fun} = Meta, Config) when is_functio end; format_msg({report, Report}, #{report_cb := Fun}, Config) when is_function(Fun, 2) -> %% a format callback function of arity 2 - case Fun(Report, maps:with([depth, single_line], Config)) of + case Fun(Report, maps:with([depth, single_line, chars_limit], Config)) of Chardata when ?IS_STRING(Chardata) -> try unicode:characters_to_binary(Chardata, utf8) @@ -152,11 +151,13 @@ format_msg({Fmt, Args}, _Meta, Config) -> do_format_msg(Format0, Args, #{ depth := Depth, - single_line := SingleLine + single_line := SingleLine, + chars_limit := Limit }) -> + Opts = chars_limit_to_opts(Limit), Format1 = io_lib:scan_format(Format0, Args), Format = reformat(Format1, Depth, SingleLine), - Text0 = io_lib:build_text(Format, []), + Text0 = io_lib:build_text(Format, Opts), Text = case SingleLine of true -> re:replace(Text0, ",?\r?\n\s*", ", ", [{return, list}, global, unicode]); @@ -164,6 +165,9 @@ do_format_msg(Format0, Args, #{ end, trim(unicode:characters_to_binary(Text, utf8)). +chars_limit_to_opts(unlimited) -> []; +chars_limit_to_opts(Limit) -> [{chars_limit, Limit}]. + %% Get rid of the leading spaces. %% leave alone the trailing spaces. trim(<<$\s, Rest/binary>>) -> trim(Rest); @@ -373,23 +377,72 @@ p_config() -> proper_types:shrink_list( [ {depth, p_limit()}, - {single_line, proper_types:boolean()} + {single_line, proper_types:boolean()}, + {chars_limit, p_limit()} ] ). +%% NOTE: pretty-printing format is asserted in the test +%% This affects the CLI output format, consult the team before changing +%% the format. best_effort_json_test() -> ?assertEqual( <<"{\n \n}">>, - emqx_logger_jsonfmt:best_effort_json([]) + best_effort_json([]) ), ?assertEqual( <<"{\n \"key\" : [\n \n ]\n}">>, - emqx_logger_jsonfmt:best_effort_json(#{key => []}) + best_effort_json(#{key => []}) ), ?assertEqual( <<"[\n {\n \"key\" : [\n \n ]\n }\n]">>, - emqx_logger_jsonfmt:best_effort_json([#{key => []}]) + best_effort_json([#{key => []}]) ), ok. +config() -> + #{ + chars_limit => unlimited, + depth => unlimited, + single_line => true + }. + +make_log(Report) -> + #{ + level => info, + msg => Report, + meta => #{time => 1111, report_cb => ?DEFAULT_FORMATTER} + }. + +ensure_json_output_test() -> + JSON = format(make_log({report, #{foo => bar}}), config()), + ?assert(is_map(emqx_utils_json:decode(JSON))), + ok. + +chars_limit_not_applied_on_raw_map_fields_test() -> + Limit = 32, + Len = 100, + LongStr = lists:duplicate(Len, $a), + Config0 = config(), + Config = Config0#{ + chars_limit => Limit + }, + JSON = format(make_log({report, #{foo => LongStr}}), Config), + #{<<"foo">> := LongStr1} = emqx_utils_json:decode(JSON), + ?assertEqual(Len, size(LongStr1)), + ok. + +chars_limit_applied_on_format_result_test() -> + Limit = 32, + Len = 100, + LongStr = lists:duplicate(Len, $a), + Config0 = config(), + Config = Config0#{ + chars_limit => Limit + }, + JSON = format(make_log({string, LongStr}), Config), + #{<<"msg">> := LongStr1} = emqx_utils_json:decode(JSON), + ?assertEqual(Limit, size(LongStr1)), + ok. + -endif. diff --git a/rel/i18n/emqx_conf_schema.hocon b/rel/i18n/emqx_conf_schema.hocon index a6c158768..674eeddd5 100644 --- a/rel/i18n/emqx_conf_schema.hocon +++ b/rel/i18n/emqx_conf_schema.hocon @@ -101,7 +101,7 @@ common_handler_flush_qlen.label: common_handler_chars_limit.desc: """Set the maximum length of a single log message. If this length is exceeded, the log message will be truncated. -NOTE: Restrict char limiter if formatter is JSON , it will get a truncated incomplete JSON data, which is not recommended.""" +When formatter is 'json' the truncation is done on the JSON values, but not on the log message itself.""" common_handler_chars_limit.label: """Single Log Max Length""" @@ -660,7 +660,8 @@ Can be one of: - system: the time offset used by the local system - utc: the UTC time offset - +-[hh]:[mm]: user specified time offset, such as "-02:00" or "+00:00" -Defaults to: system.""" +Defaults to: system. +This config has no effect for when formatter is 'json' as the timestamp in JSON is miliseconds since epoch.""" common_handler_time_offset.label: """Time Offset""" From 5f45ba50ffdc31d603ec3fe5913b1a758e3ed20a Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Fri, 22 Sep 2023 16:48:48 +0200 Subject: [PATCH 14/65] refactor: delete log formatter config for audit log handler It is using 'json' formatter, the template is useless --- apps/emqx/src/config/emqx_config_logger.erl | 32 +++++++-------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/apps/emqx/src/config/emqx_config_logger.erl b/apps/emqx/src/config/emqx_config_logger.erl index 070fa6f32..3d6249a34 100644 --- a/apps/emqx/src/config/emqx_config_logger.erl +++ b/apps/emqx/src/config/emqx_config_logger.erl @@ -216,38 +216,26 @@ log_formatter(HandlerName, Conf) -> end, SingleLine = conf_get("single_line", Conf), Depth = conf_get("max_depth", Conf), + Format = + case HandlerName of + ?AUDIT_HANDLER -> + json; + _ -> + conf_get("formatter", Conf) + end, do_formatter( - HandlerName, conf_get("formatter", Conf), CharsLimit, SingleLine, TimeOffSet, Depth + Format, CharsLimit, SingleLine, TimeOffSet, Depth ). %% helpers -do_formatter(?AUDIT_HANDLER, _, CharsLimit, SingleLine, TimeOffSet, Depth) -> - {emqx_logger_jsonfmt, #{ - template => [ - time, - " [", - level, - "] ", - %% http api - {method, [code, " ", method, " ", operate_id, " ", username, " "], []}, - %% cli - {cmd, [cmd, " "], []}, - msg, - "\n" - ], - chars_limit => CharsLimit, - single_line => SingleLine, - time_offset => TimeOffSet, - depth => Depth - }}; -do_formatter(_, json, CharsLimit, SingleLine, TimeOffSet, Depth) -> +do_formatter(json, CharsLimit, SingleLine, TimeOffSet, Depth) -> {emqx_logger_jsonfmt, #{ chars_limit => CharsLimit, single_line => SingleLine, time_offset => TimeOffSet, depth => Depth }}; -do_formatter(_, text, CharsLimit, SingleLine, TimeOffSet, Depth) -> +do_formatter(text, CharsLimit, SingleLine, TimeOffSet, Depth) -> {emqx_logger_textfmt, #{ template => [time, " [", level, "] ", msg, "\n"], chars_limit => CharsLimit, From a9df99746a81ea230120bb596ad700c35003bdbe Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Fri, 22 Sep 2023 16:06:40 +0200 Subject: [PATCH 15/65] docs: add changelog for PR 11661 --- changes/ce/fix-11661.en.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changes/ce/fix-11661.en.md diff --git a/changes/ce/fix-11661.en.md b/changes/ce/fix-11661.en.md new file mode 100644 index 000000000..55c22ec60 --- /dev/null +++ b/changes/ce/fix-11661.en.md @@ -0,0 +1,3 @@ +Fix log formatter when log.HANDLER.formatter is set to 'json'. + +The bug was introduced in v5.0.4 where the log line was no longer a valid JSON, but prefixed with timestamp string and level name. From 6a557980e41311f7cc3efce28cd7dc49101ec73a Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Mon, 25 Sep 2023 09:35:13 +0200 Subject: [PATCH 16/65] refactor(logger): ensure JSON log field order Ensure that the log fileds in JSON format are ordered as time, level, msg, mfal, ... --- apps/emqx/src/emqx_logger_jsonfmt.erl | 88 ++++++++++++++++++--------- 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/apps/emqx/src/emqx_logger_jsonfmt.erl b/apps/emqx/src/emqx_logger_jsonfmt.erl index 5063feee6..7405e638b 100644 --- a/apps/emqx/src/emqx_logger_jsonfmt.erl +++ b/apps/emqx/src/emqx_logger_jsonfmt.erl @@ -75,7 +75,7 @@ format(#{level := Level, msg := Msg, meta := Meta}, Config0) when is_map(Config0 [format(Msg, Meta#{level => Level}, Config), "\n"]. format(Msg, Meta, Config) -> - Data0 = + Data = try maybe_format_msg(Msg, Meta, Config) of Map when is_map(Map) -> maps:merge(Map, Meta); @@ -91,8 +91,7 @@ format(Msg, Meta, Config) -> fmt_stacktrace => S } end, - Data = maps:without([report_cb], Data0), - emqx_utils_json:encode(json_obj(Data, Config)). + emqx_utils_json:encode(json_obj_root(Data, Config)). maybe_format_msg({report, Report} = Msg, #{report_cb := Cb} = Meta, Config) -> case is_map(Report) andalso Cb =:= ?DEFAULT_FORMATTER of @@ -248,41 +247,74 @@ json(Map, Config) when is_map(Map) -> json(Term, Config) -> do_format_msg("~p", [Term], Config). +json_obj_root(Data0, Config) -> + Time = maps:get(time, Data0), + Level = maps:get(level, Data0), + Msg1 = + case maps:get(msg, Data0, undefined) of + undefined -> + maps:get('$kind', Data0, undefined); + Msg0 -> + Msg0 + end, + Msg = + case Msg1 of + undefined -> + undefined; + _ -> + json(Msg1, Config) + end, + Line = + case maps:get(line, Data0, undefined) of + undefined -> + <<"">>; + Num -> + iolist_to_binary([":", integer_to_list(Num)]) + end, + + Mfal = + case maps:get(mfa, Data0, undefined) of + {M, F, A} -> + << + (atom_to_binary(M, utf8))/binary, + $:, + (atom_to_binary(F, utf8))/binary, + $/, + (integer_to_binary(A))/binary, + Line/binary + >>; + _ -> + unefined + end, + Data = + maps:fold( + fun(K, V, D) -> + {K1, V1} = json_kv(K, V, Config), + [{K1, V1} | D] + end, + [], + maps:without([time, gl, file, report_cb, msg, '$kind', mfa, level, line], Data0) + ), + lists:filter( + fun({_, V}) -> V =/= undefined end, + [{time, Time}, {level, Level}, {msg, Msg}, {mfa, Mfal}] + ) ++ Data. + json_obj(Data, Config) -> maps:fold( fun(K, V, D) -> - json_kv(K, V, D, Config) + {K1, V1} = json_kv(K, V, Config), + maps:put(K1, V1, D) end, maps:new(), Data ). -json_kv(mfa, {M, F, A}, Data, _Config) -> - maps:put( - mfa, - << - (atom_to_binary(M, utf8))/binary, - $:, - (atom_to_binary(F, utf8))/binary, - $/, - (integer_to_binary(A))/binary - >>, - Data - ); -%% snabbkaffe -json_kv('$kind', Kind, Data, Config) -> - maps:put(msg, json(Kind, Config), Data); -json_kv(gl, _, Data, _Config) -> - %% drop gl because it's not interesting - Data; -json_kv(file, _, Data, _Config) -> - %% drop 'file' because we have mfa - Data; -json_kv(K0, V, Data, Config) -> +json_kv(K0, V, Config) -> K = json_key(K0), case is_map(V) of - true -> maps:put(json(K, Config), best_effort_json_obj(V, Config), Data); - false -> maps:put(json(K, Config), json(V, Config), Data) + true -> {K, best_effort_json_obj(V, Config)}; + false -> {K, json(V, Config)} end. json_key(A) when is_atom(A) -> json_key(atom_to_binary(A, utf8)); From 5e6996dc05347c9ed1e9753fb7c81e6bdc82c4e0 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Mon, 25 Sep 2023 12:20:54 +0200 Subject: [PATCH 17/65] refactor: log formatter format mfa+line as m:f/a(line) also improve json formatter when a field is iolist --- apps/emqx/src/emqx_logger_jsonfmt.erl | 41 +++++++++------------------ apps/emqx/src/emqx_logger_textfmt.erl | 15 ++++------ apps/emqx_utils/src/emqx_utils.erl | 27 +++++++++++++++++- rel/i18n/emqx_conf_schema.hocon | 2 +- 4 files changed, 45 insertions(+), 40 deletions(-) diff --git a/apps/emqx/src/emqx_logger_jsonfmt.erl b/apps/emqx/src/emqx_logger_jsonfmt.erl index 7405e638b..c6c04f70d 100644 --- a/apps/emqx/src/emqx_logger_jsonfmt.erl +++ b/apps/emqx/src/emqx_logger_jsonfmt.erl @@ -236,20 +236,24 @@ json(P, C) when is_port(P) -> json(port_to_list(P), C); json(F, C) when is_function(F) -> json(erlang:fun_to_list(F), C); json(B, Config) when is_binary(B) -> best_effort_unicode(B, Config); -json(L, Config) when is_list(L), is_integer(hd(L)) -> - best_effort_unicode(L, Config); json(M, Config) when is_list(M), is_tuple(hd(M)), tuple_size(hd(M)) =:= 2 -> best_effort_json_obj(M, Config); json(L, Config) when is_list(L) -> - [json(I, Config) || I <- L]; + try unicode:characters_to_binary(L, utf8) of + B when is_binary(B) -> B; + _ -> [json(I, Config) || I <- L] + catch + _:_ -> + [json(I, Config) || I <- L] + end; json(Map, Config) when is_map(Map) -> best_effort_json_obj(Map, Config); json(Term, Config) -> do_format_msg("~p", [Term], Config). json_obj_root(Data0, Config) -> - Time = maps:get(time, Data0), - Level = maps:get(level, Data0), + Time = maps:get(time, Data0, undefined), + Level = maps:get(level, Data0, undefined), Msg1 = case maps:get(msg, Data0, undefined) of undefined -> @@ -264,28 +268,7 @@ json_obj_root(Data0, Config) -> _ -> json(Msg1, Config) end, - Line = - case maps:get(line, Data0, undefined) of - undefined -> - <<"">>; - Num -> - iolist_to_binary([":", integer_to_list(Num)]) - end, - - Mfal = - case maps:get(mfa, Data0, undefined) of - {M, F, A} -> - << - (atom_to_binary(M, utf8))/binary, - $:, - (atom_to_binary(F, utf8))/binary, - $/, - (integer_to_binary(A))/binary, - Line/binary - >>; - _ -> - unefined - end, + Mfal = emqx_utils:format_mfal(Data0), Data = maps:fold( fun(K, V, D) -> @@ -293,7 +276,9 @@ json_obj_root(Data0, Config) -> [{K1, V1} | D] end, [], - maps:without([time, gl, file, report_cb, msg, '$kind', mfa, level, line], Data0) + maps:without( + [time, gl, file, report_cb, msg, '$kind', mfa, level, line, is_trace], Data0 + ) ), lists:filter( fun({_, V}) -> V =/= undefined end, diff --git a/apps/emqx/src/emqx_logger_textfmt.erl b/apps/emqx/src/emqx_logger_textfmt.erl index 3dce8a2ec..2e8718c37 100644 --- a/apps/emqx/src/emqx_logger_textfmt.erl +++ b/apps/emqx/src/emqx_logger_textfmt.erl @@ -56,8 +56,7 @@ enrich_report(ReportRaw, Meta) -> end, ClientId = maps:get(clientid, Meta, undefined), Peer = maps:get(peername, Meta, undefined), - MFA = maps:get(mfa, Meta, undefined), - Line = maps:get(line, Meta, undefined), + MFA = emqx_utils:format_mfal(Meta), Msg = maps:get(msg, ReportRaw, undefined), %% turn it into a list so that the order of the fields is determined lists:foldl( @@ -70,8 +69,7 @@ enrich_report(ReportRaw, Meta) -> {topic, try_format_unicode(Topic)}, {clientid, try_format_unicode(ClientId)}, {peername, Peer}, - {line, Line}, - {mfa, mfa(MFA)}, + {mfa, try_format_unicode(MFA)}, {msg, Msg} ] ). @@ -84,7 +82,7 @@ try_format_unicode(Char) -> case unicode:characters_to_list(Char) of {error, _, _} -> error; {incomplete, _, _} -> error; - Binary -> Binary + List1 -> List1 end catch _:_ -> @@ -95,8 +93,8 @@ try_format_unicode(Char) -> _ -> List end. -enrich_mfa({Fmt, Args}, #{mfa := Mfa, line := Line}) when is_list(Fmt) -> - {Fmt ++ " mfa: ~ts line: ~w", Args ++ [mfa(Mfa), Line]}; +enrich_mfa({Fmt, Args}, Data) when is_list(Fmt) -> + {Fmt ++ " mfa: ~ts", Args ++ [emqx_utils:format_mfal(Data)]}; enrich_mfa(Msg, _) -> Msg. @@ -113,6 +111,3 @@ enrich_topic({Fmt, Args}, #{topic := Topic}) when is_list(Fmt) -> {" topic: ~ts" ++ Fmt, [Topic | Args]}; enrich_topic(Msg, _) -> Msg. - -mfa(undefined) -> undefined; -mfa({M, F, A}) -> [atom_to_list(M), ":", atom_to_list(F), "/" ++ integer_to_list(A)]. diff --git a/apps/emqx_utils/src/emqx_utils.erl b/apps/emqx_utils/src/emqx_utils.erl index e21affce6..0682a9b4d 100644 --- a/apps/emqx_utils/src/emqx_utils.erl +++ b/apps/emqx_utils/src/emqx_utils.erl @@ -61,7 +61,8 @@ diff_lists/3, merge_lists/3, tcp_keepalive_opts/4, - format/1 + format/1, + format_mfal/1 ]). -export([ @@ -529,6 +530,30 @@ tcp_keepalive_opts(OS, _Idle, _Interval, _Probes) -> format(Term) -> iolist_to_binary(io_lib:format("~0p", [Term])). +%% @doc Helper function for log formatters. +-spec format_mfal(map()) -> undefined | binary(). +format_mfal(Data) -> + Line = + case maps:get(line, Data, undefined) of + undefined -> + <<"">>; + Num -> + ["(", integer_to_list(Num), ")"] + end, + case maps:get(mfa, Data, undefined) of + {M, F, A} -> + iolist_to_binary([ + atom_to_binary(M, utf8), + $:, + atom_to_binary(F, utf8), + $/, + integer_to_binary(A), + Line + ]); + _ -> + undefined + end. + %%------------------------------------------------------------------------------ %% Internal Functions %%------------------------------------------------------------------------------ diff --git a/rel/i18n/emqx_conf_schema.hocon b/rel/i18n/emqx_conf_schema.hocon index 674eeddd5..e26a21fa0 100644 --- a/rel/i18n/emqx_conf_schema.hocon +++ b/rel/i18n/emqx_conf_schema.hocon @@ -661,7 +661,7 @@ Can be one of: - utc: the UTC time offset - +-[hh]:[mm]: user specified time offset, such as "-02:00" or "+00:00" Defaults to: system. -This config has no effect for when formatter is 'json' as the timestamp in JSON is miliseconds since epoch.""" +This config has no effect for when formatter is 'json' as the timestamp in JSON is milliseconds since epoch.""" common_handler_time_offset.label: """Time Offset""" From ff7f37ccf5e38fafdd6d77a8652308b3594faeeb Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 25 Sep 2023 13:22:41 -0300 Subject: [PATCH 18/65] test(cth): allow defining schema to load for app --- apps/emqx/test/emqx_cth_suite.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/emqx/test/emqx_cth_suite.erl b/apps/emqx/test/emqx_cth_suite.erl index 24105b2b4..5cbca3243 100644 --- a/apps/emqx/test/emqx_cth_suite.erl +++ b/apps/emqx/test/emqx_cth_suite.erl @@ -52,7 +52,7 @@ %% (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 %% 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 %% result from step 2. -module(emqx_cth_suite). @@ -245,6 +245,9 @@ spec_fmt(ffun, {_, X}) -> X. maybe_configure_app(_App, #{config := false}) -> 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}) -> case app_schema(App) of {ok, SchemaModule} -> From cd8a3be59d24a7e09f0c80e5ae45dcfa182702d5 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Mon, 25 Sep 2023 18:26:15 +0200 Subject: [PATCH 19/65] docs: add 'json' to spellcheck dict --- scripts/spellcheck/dicts/emqx.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/spellcheck/dicts/emqx.txt b/scripts/spellcheck/dicts/emqx.txt index 83edb22d1..d482cd3f3 100644 --- a/scripts/spellcheck/dicts/emqx.txt +++ b/scripts/spellcheck/dicts/emqx.txt @@ -166,6 +166,7 @@ ip ipv jenkins jq +json kb keepalive keyfile From 95060302fd3d6f07dc4f0e48fd2072b37937f87a Mon Sep 17 00:00:00 2001 From: zhongwencool Date: Tue, 26 Sep 2023 00:28:47 +0800 Subject: [PATCH 20/65] fix: don't need to change audit log's level --- apps/emqx_conf/src/emqx_conf_schema.erl | 18 ++++++++++++++---- apps/emqx_ctl/src/emqx_ctl.erl | 2 -- .../src/emqx_dashboard_audit.erl | 3 +-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/emqx_conf/src/emqx_conf_schema.erl b/apps/emqx_conf/src/emqx_conf_schema.erl index f1bfc3d31..d1fc6c546 100644 --- a/apps/emqx_conf/src/emqx_conf_schema.erl +++ b/apps/emqx_conf/src/emqx_conf_schema.erl @@ -1014,6 +1014,15 @@ fields("log_file_handler") -> ] ++ 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(), @@ -1048,11 +1057,12 @@ fields("log_audit_handler") -> ] ++ %% Only support json lists:keydelete( - "formatter", + "level", 1, - log_handler_common_confs( - file, - #{level => info, level_desc => "audit_handler_level"} + lists:keydelete( + "formatter", + 1, + log_handler_common_confs(file, #{}) ) ); fields("log_overload_kill") -> diff --git a/apps/emqx_ctl/src/emqx_ctl.erl b/apps/emqx_ctl/src/emqx_ctl.erl index 4947a9715..4c077c56d 100644 --- a/apps/emqx_ctl/src/emqx_ctl.erl +++ b/apps/emqx_ctl/src/emqx_ctl.erl @@ -350,8 +350,6 @@ audit_log(Level, From, Log) -> -define(TOO_SLOW, 3000). -audit_level(ok, Duration) when Duration >= ?TOO_SLOW -> warning; -audit_level({ok, _}, Duration) when Duration >= ?TOO_SLOW -> warning; audit_level(ok, _Duration) -> info; audit_level({ok, _}, _Duration) -> info; audit_level(_, _) -> error. diff --git a/apps/emqx_dashboard/src/emqx_dashboard_audit.erl b/apps/emqx_dashboard/src/emqx_dashboard_audit.erl index e9c119512..5fdc68f70 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_audit.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_audit.erl @@ -43,8 +43,7 @@ from(jwt_token) -> "dashboard"; from(api_key) -> "aip_key"; from(_) -> "unauthorized". -level(_, _Code, Duration) when Duration > 3000 -> warning; -level(get, Code, _) when Code >= 200 andalso Code < 300 -> debug; +level(get, _Code, _) -> debug; level(_, Code, _) when Code >= 200 andalso Code < 300 -> info; level(_, Code, _) when Code >= 300 andalso Code < 400 -> warning; level(_, Code, _) when Code >= 400 andalso Code < 500 -> error; From 5d212e1086e9dcbc31001bd5c4e6be039866977f Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 25 Sep 2023 13:23:39 -0300 Subject: [PATCH 21/65] fix(audit): only support audit log on enterprise edition Fixes https://emqx.atlassian.net/browse/EMQX-11039 --- apps/emqx_conf/src/emqx_conf_schema.erl | 57 +------------- .../emqx_conf/test/emqx_conf_logger_SUITE.erl | 11 +-- .../emqx_conf/test/emqx_conf_schema_tests.erl | 19 +---- .../src/emqx_enterprise_schema.erl | 77 ++++++++++++++++++- .../test/emqx_enterprise_schema_SUITE.erl | 52 +++++++++++++ .../test/emqx_enterprise_schema_tests.erl | 35 +++++++++ rel/i18n/emqx_conf_schema.hocon | 3 - 7 files changed, 169 insertions(+), 85 deletions(-) diff --git a/apps/emqx_conf/src/emqx_conf_schema.erl b/apps/emqx_conf/src/emqx_conf_schema.erl index f1bfc3d31..27799fa93 100644 --- a/apps/emqx_conf/src/emqx_conf_schema.erl +++ b/apps/emqx_conf/src/emqx_conf_schema.erl @@ -43,6 +43,9 @@ ]). -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 %% The list can not be made a dynamic read at run-time as it is used %% by nodetool to generate app.