From ce2b159022c164673a700ab8fec4db1334613845 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Tue, 1 Aug 2023 16:48:45 +0300 Subject: [PATCH 01/14] chore(auth): make pre_hook_authenticate be part of emqx_access_control domain --- apps/emqx/include/emqx_access_control.hrl | 3 ++ apps/emqx/include/emqx_authentication.hrl | 2 +- apps/emqx/src/emqx_access_control.erl | 39 ++++++++++++++++++++++- apps/emqx/src/emqx_authentication.erl | 17 ---------- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/apps/emqx/include/emqx_access_control.hrl b/apps/emqx/include/emqx_access_control.hrl index e840d2b4a..08146339b 100644 --- a/apps/emqx/include/emqx_access_control.hrl +++ b/apps/emqx/include/emqx_access_control.hrl @@ -32,3 +32,6 @@ -define(authz_action(PUBSUB, QOS), #{action_type := PUBSUB, qos := QOS}). -define(authz_action(PUBSUB), ?authz_action(PUBSUB, _)). -define(authz_action, ?authz_action(_)). + +-define(AUTHN_TRACE_TAG, "AUTHN"). + diff --git a/apps/emqx/include/emqx_authentication.hrl b/apps/emqx/include/emqx_authentication.hrl index 70b35a474..d668e9a54 100644 --- a/apps/emqx/include/emqx_authentication.hrl +++ b/apps/emqx/include/emqx_authentication.hrl @@ -18,8 +18,8 @@ -define(EMQX_AUTHENTICATION_HRL, true). -include_lib("emqx/include/logger.hrl"). +-include_lib("emqx/include/emqx_access_control.hrl"). --define(AUTHN_TRACE_TAG, "AUTHN"). -define(GLOBAL, 'mqtt:global'). -define(TRACE_AUTHN_PROVIDER(Msg), ?TRACE_AUTHN_PROVIDER(Msg, #{})). diff --git a/apps/emqx/src/emqx_access_control.erl b/apps/emqx/src/emqx_access_control.erl index 43669bf6c..a8ffadb44 100644 --- a/apps/emqx/src/emqx_access_control.erl +++ b/apps/emqx/src/emqx_access_control.erl @@ -17,6 +17,7 @@ -module(emqx_access_control). -include("emqx.hrl"). +-include("emqx_access_control.hrl"). -include("logger.hrl"). -export([ @@ -29,6 +30,14 @@ -compile(nowarn_export_all). -endif. +-define(TRACE_RESULT(Label, Tag, Result, Reason), begin + ?TRACE(Label, Tag, #{ + result => (Result), + reason => (Reason) + }), + Result +end). + %%-------------------------------------------------------------------- %% APIs %%-------------------------------------------------------------------- @@ -44,7 +53,7 @@ authenticate(Credential) -> %% if auth backend returning nothing but just 'ok' %% it means it's not a superuser, or there is no way to tell. NotSuperUser = #{is_superuser => false}, - case emqx_authentication:pre_hook_authenticate(Credential) of + case pre_hook_authenticate(Credential) of ok -> inc_authn_metrics(anonymous), {ok, NotSuperUser}; @@ -99,6 +108,34 @@ authorize(ClientInfo, Action, Topic) -> inc_authz_metrics(Result), Result. +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- + +-spec pre_hook_authenticate(emqx_types:clientinfo()) -> + ok | continue | {error, not_authorized}. +pre_hook_authenticate(#{enable_authn := false}) -> + ?TRACE_RESULT("pre_hook_authenticate", ?AUTHN_TRACE_TAG, ok, enable_authn_false); +pre_hook_authenticate(#{enable_authn := quick_deny_anonymous} = Credential) -> + case is_username_defined(Credential) of + true -> + continue; + false -> + ?TRACE_RESULT( + "pre_hook_authenticate", + ?AUTHN_TRACE_TAG, + {error, not_authorized}, + enable_authn_false + ) + end; +pre_hook_authenticate(_) -> + continue. + +is_username_defined(#{username := undefined}) -> false; +is_username_defined(#{username := <<>>}) -> false; +is_username_defined(#{username := _Username}) -> true; +is_username_defined(_) -> false. + check_authorization_cache(ClientInfo, Action, Topic) -> case emqx_authz_cache:get_authz_cache(Action, Topic) of not_found -> diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx/src/emqx_authentication.erl index cce789f24..92182f93a 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx/src/emqx_authentication.erl @@ -29,11 +29,9 @@ -include_lib("stdlib/include/ms_transform.hrl"). -define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM). --define(IS_UNDEFINED(X), (X =:= undefined orelse X =:= <<>>)). %% The authentication entrypoint. -export([ - pre_hook_authenticate/1, authenticate/2 ]). @@ -220,21 +218,6 @@ when %%------------------------------------------------------------------------------ %% Authenticate %%------------------------------------------------------------------------------ --spec pre_hook_authenticate(emqx_types:clientinfo()) -> - ok | continue | {error, not_authorized}. -pre_hook_authenticate(#{enable_authn := false}) -> - ?TRACE_RESULT("authentication_result", ok, enable_authn_false); -pre_hook_authenticate(#{enable_authn := quick_deny_anonymous} = Credential) -> - case maps:get(username, Credential, undefined) of - U when ?IS_UNDEFINED(U) -> - ?TRACE_RESULT( - "authentication_result", {error, not_authorized}, enable_authn_false - ); - _ -> - continue - end; -pre_hook_authenticate(_) -> - continue. authenticate(#{listener := Listener, protocol := Protocol} = Credential, AuthResult) -> case get_authenticators(Listener, global_chain(Protocol)) of From ca8c1e3ef87c5feff932e0a28dd456422bee9a12 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Tue, 1 Aug 2023 19:38:58 +0300 Subject: [PATCH 02/14] chore(auth): removed direct usages of authn from emqx core app --- apps/emqx/include/emqx_access_control.hrl | 4 + apps/emqx/include/emqx_hooks.hrl | 1 + .../emqx_authentication_listener_hooks.erl | 86 +++++++++++++++++++ apps/emqx/src/emqx_listeners.erl | 35 ++------ apps/emqx/test/emqx_broker_SUITE.erl | 31 +++---- 5 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 apps/emqx/src/emqx_authentication_listener_hooks.erl diff --git a/apps/emqx/include/emqx_access_control.hrl b/apps/emqx/include/emqx_access_control.hrl index 08146339b..f7348899b 100644 --- a/apps/emqx/include/emqx_access_control.hrl +++ b/apps/emqx/include/emqx_access_control.hrl @@ -14,6 +14,9 @@ %% limitations under the License. %%-------------------------------------------------------------------- +-ifndef(EMQX_ACCESS_CONTROL_HRL). +-define(EMQX_ACCESS_CONTROL_HRL, true). + %% config root name all auth providers have to agree on. -define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, "authorization"). -define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_ATOM, authorization). @@ -35,3 +38,4 @@ -define(AUTHN_TRACE_TAG, "AUTHN"). +-endif. diff --git a/apps/emqx/include/emqx_hooks.hrl b/apps/emqx/include/emqx_hooks.hrl index 2373b5928..dffc09fcd 100644 --- a/apps/emqx/include/emqx_hooks.hrl +++ b/apps/emqx/include/emqx_hooks.hrl @@ -29,6 +29,7 @@ -define(HP_RETAINER, 930). -define(HP_AUTO_SUB, 920). -define(HP_RULE_ENGINE, 900). + %% apps that can work with the republish action -define(HP_SLOW_SUB, 880). -define(HP_BRIDGE, 870). diff --git a/apps/emqx/src/emqx_authentication_listener_hooks.erl b/apps/emqx/src/emqx_authentication_listener_hooks.erl new file mode 100644 index 000000000..1109ca96b --- /dev/null +++ b/apps/emqx/src/emqx_authentication_listener_hooks.erl @@ -0,0 +1,86 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2017-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authentication_listener_hooks). + +-include_lib("emqx/include/emqx_hooks.hrl"). + +-export([ + on_listener_started/4, + on_listener_stopped/4, + on_listener_updated/4 +]). + +-export([ + load/0, + unload/0 +]). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +load() -> + ok = emqx_hook:put('listener.started', {?MODULE, on_listener_started, []}, ?HP_AUTHN), + ok = emqx_hook:put('listener.stopped', {?MODULE, on_listener_stopped, []}, ?HP_AUTHN), + ok = emqx_hook:put('listener.updated', {?MODULE, on_listener_updated, []}, ?HP_AUTHN), + ok. + +unload() -> + ok = emqx_hooks:del('listener.started', {?MODULE, authenticate, []}), + ok = emqx_hooks:del('listener.stopped', {?MODULE, authenticate, []}), + ok = emqx_hooks:del('listener.updated', {?MODULE, authenticate, []}), + ok. + +%%-------------------------------------------------------------------- +%% Hooks +%%-------------------------------------------------------------------- + +on_listener_started(Type, Name, Conf, ok) -> + recreate_authenticators(Type, Name, Conf); +on_listener_started(_Type, _Name, _Conf, _Error) -> + ok. + +on_listener_updated(Type, Name, {_OldConf, NewConf}, ok) -> + recreate_authenticators(Type, Name, NewConf); +on_listener_updated(_Type, _Name, _Conf, _Error) -> + ok. + +on_listener_stopped(Type, Name, _OldConf, ok) -> + _ = emqx_authentication:delete_chain(emqx_listeners:listener_id(Type, Name)), + ok; +on_listener_stopped(_Type, _Name, _Conf, _Error) -> + ok. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +recreate_authenticators(Type, Name, Conf) -> + Chain = emqx_listeners:listener_id(Type, Name), + _ = emqx_authentication:delete_chain(Chain), + do_create_authneticators(Chain, maps:get(authentication, Conf, [])). + +do_create_authneticators(Chain, [AuthN | T]) -> + case emqx_authentication:create_authenticator(Chain, AuthN) of + {ok, _} -> + do_create_authneticators(Chain, T); + Error -> + _ = emqx_authentication:delete_chain(Chain), + {ok, Error} + end; +do_create_authneticators(_Chain, []) -> + ok. diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index 964873e53..5d641de39 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/apps/emqx/src/emqx_listeners.erl @@ -531,41 +531,18 @@ post_config_update(_Path, _Request, _NewConf, _OldConf, _AppEnvs) -> ok. create_listener(Type, Name, NewConf) -> - Res = start_listener(Type, Name, NewConf), - recreate_authenticators(Res, Type, Name, NewConf). - -recreate_authenticators(ok, Type, Name, Conf) -> - Chain = listener_id(Type, Name), - _ = emqx_authentication:delete_chain(Chain), - do_create_authneticators(Chain, maps:get(authentication, Conf, [])); -recreate_authenticators(Error, _Type, _Name, _NewConf) -> - Error. - -do_create_authneticators(Chain, [AuthN | T]) -> - case emqx_authentication:create_authenticator(Chain, AuthN) of - {ok, _} -> - do_create_authneticators(Chain, T); - Error -> - _ = emqx_authentication:delete_chain(Chain), - Error - end; -do_create_authneticators(_Chain, []) -> - ok. + StartRes = start_listener(Type, Name, NewConf), + emqx_hooks:run_fold('listener.started', [Type, Name, NewConf], StartRes). remove_listener(Type, Name, OldConf) -> ok = unregister_ocsp_stapling_refresh(Type, Name), - case stop_listener(Type, Name, OldConf) of - ok -> - _ = emqx_authentication:delete_chain(listener_id(Type, Name)), - ok; - Err -> - Err - end. + StopRes = stop_listener(Type, Name, OldConf), + emqx_hooks:run_fold('listener.stopped', [Type, Name, OldConf], StopRes). update_listener(Type, Name, {OldConf, NewConf}) -> ok = maybe_unregister_ocsp_stapling_refresh(Type, Name, NewConf), - Res = restart_listener(Type, Name, {OldConf, NewConf}), - recreate_authenticators(Res, Type, Name, NewConf). + RestartRes = restart_listener(Type, Name, {OldConf, NewConf}), + emqx_hooks:run_fold('listener.restarted', [Type, Name, {OldConf, NewConf}], RestartRes). perform_listener_changes([]) -> ok; diff --git a/apps/emqx/test/emqx_broker_SUITE.erl b/apps/emqx/test/emqx_broker_SUITE.erl index 6e03971a5..ba5438641 100644 --- a/apps/emqx/test/emqx_broker_SUITE.erl +++ b/apps/emqx/test/emqx_broker_SUITE.erl @@ -26,6 +26,7 @@ -include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("emqx/include/emqx.hrl"). +-include_lib("emqx/include/emqx_hooks.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). all() -> @@ -680,28 +681,17 @@ t_connect_client_never_negative({'end', _Config}) -> t_connack_auth_error({init, Config}) -> process_flag(trap_exit, true), - ChainName = 'mqtt:global', - AuthenticatorConfig = #{ - enable => true, - mechanism => password_based, - backend => built_in_database, - user_id_type => username, - password_hash_algorithm => #{ - name => plain, - salt_position => disable - }, - user_group => <<"global:mqtt">> - }, - ok = emqx_authentication:register_providers( - [{{password_based, built_in_database}, emqx_authentication_SUITE}] + emqx_hooks:put( + 'client.authenticate', + {?MODULE, authenticate_deny, []}, + ?HP_AUTHN ), - emqx_authentication:initialize_authentication(ChainName, AuthenticatorConfig), Config; t_connack_auth_error({'end', _Config}) -> - ChainName = 'mqtt:global', - AuthenticatorID = <<"password_based:built_in_database">>, - ok = emqx_authentication:deregister_provider({password_based, built_in_database}), - ok = emqx_authentication:delete_authenticator(ChainName, AuthenticatorID), + emqx_hooks:del( + 'client.authenticate', + {?MODULE, authenticate_deny, []} + ), ok; t_connack_auth_error(Config) when is_list(Config) -> %% MQTT 3.1 @@ -733,6 +723,9 @@ t_handle_in_empty_client_subscribe_hook(Config) when is_list(Config) -> emqtt:disconnect(C) end. +authenticate_deny(_Credentials, _Default) -> + {stop, {error, bad_username_or_password}}. + wait_for_events(Action, Kinds) -> wait_for_events(Action, Kinds, 500). From 8213aa42c9a5290a373b1b896117e4a28191f41f Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Tue, 1 Aug 2023 21:15:04 +0300 Subject: [PATCH 03/14] chore(auth): move authn leftovers to the authn app --- apps/emqx/include/emqx.hrl | 16 ---- apps/emqx/include/emqx_access_control.hrl | 1 - apps/emqx/src/emqx_broker_sup.erl | 12 +-- apps/emqx/src/emqx_schema.erl | 46 +---------- apps/emqx/src/emqx_schema_hooks.erl | 78 +++++++++++++++++++ apps/emqx/test/emqx_common_test_helpers.erl | 2 +- apps/emqx/test/emqx_cth_suite.erl | 2 +- .../include/emqx_authentication.hrl | 16 ++-- apps/emqx_authn/include/emqx_authn.hrl | 2 +- .../src/emqx_authentication.erl | 15 +++- .../src/emqx_authentication_config.erl | 2 +- .../emqx_authentication_listener_hooks.erl | 0 .../src/emqx_authentication_sup.erl | 0 apps/emqx_authn/src/emqx_authn_api.erl | 1 - apps/emqx_authn/src/emqx_authn_app.erl | 2 +- apps/emqx_authn/src/emqx_authn_schema.erl | 43 +++++++++- apps/emqx_authn/src/emqx_authn_sup.erl | 12 ++- .../src/emqx_authn_user_import_api.erl | 1 - .../test/emqx_authentication_SUITE.erl | 0 apps/emqx_conf/src/emqx_conf_cli.erl | 4 +- apps/emqx_conf/src/emqx_conf_schema.erl | 18 +++-- apps/emqx_gateway/src/emqx_gateway.app.src | 2 +- apps/emqx_gateway/src/emqx_gateway_conf.erl | 2 +- apps/emqx_gateway/src/emqx_gateway_http.erl | 2 +- apps/emqx_gateway/src/emqx_gateway_schema.erl | 2 +- rel/i18n/emqx_authn_schema.hocon | 27 +++++++ rel/i18n/emqx_schema.hocon | 24 ------ 27 files changed, 200 insertions(+), 132 deletions(-) create mode 100644 apps/emqx/src/emqx_schema_hooks.erl rename apps/{emqx => emqx_authn}/include/emqx_authentication.hrl (85%) rename apps/{emqx => emqx_authn}/src/emqx_authentication.erl (99%) rename apps/{emqx => emqx_authn}/src/emqx_authentication_config.erl (99%) rename apps/{emqx => emqx_authn}/src/emqx_authentication_listener_hooks.erl (100%) rename apps/{emqx => emqx_authn}/src/emqx_authentication_sup.erl (100%) rename apps/{emqx => emqx_authn}/test/emqx_authentication_SUITE.erl (100%) diff --git a/apps/emqx/include/emqx.hrl b/apps/emqx/include/emqx.hrl index ac9d297de..2cba12365 100644 --- a/apps/emqx/include/emqx.hrl +++ b/apps/emqx/include/emqx.hrl @@ -123,20 +123,4 @@ until :: integer() }). -%%-------------------------------------------------------------------- -%% Authentication -%%-------------------------------------------------------------------- - --record(authenticator, { - id :: binary(), - provider :: module(), - enable :: boolean(), - state :: map() -}). - --record(chain, { - name :: atom(), - authenticators :: [#authenticator{}] -}). - -endif. diff --git a/apps/emqx/include/emqx_access_control.hrl b/apps/emqx/include/emqx_access_control.hrl index f7348899b..65a159dd6 100644 --- a/apps/emqx/include/emqx_access_control.hrl +++ b/apps/emqx/include/emqx_access_control.hrl @@ -17,7 +17,6 @@ -ifndef(EMQX_ACCESS_CONTROL_HRL). -define(EMQX_ACCESS_CONTROL_HRL, true). -%% config root name all auth providers have to agree on. -define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, "authorization"). -define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_ATOM, authorization). -define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_BINARY, <<"authorization">>). diff --git a/apps/emqx/src/emqx_broker_sup.erl b/apps/emqx/src/emqx_broker_sup.erl index ac2fe587c..74baf5674 100644 --- a/apps/emqx/src/emqx_broker_sup.erl +++ b/apps/emqx/src/emqx_broker_sup.erl @@ -49,16 +49,6 @@ init([]) -> modules => [emqx_shared_sub] }, - %% Authentication - AuthNSup = #{ - id => emqx_authentication_sup, - start => {emqx_authentication_sup, start_link, []}, - restart => permanent, - shutdown => infinity, - type => supervisor, - modules => [emqx_authentication_sup] - }, - %% Broker helper Helper = #{ id => helper, @@ -69,4 +59,4 @@ init([]) -> modules => [emqx_broker_helper] }, - {ok, {{one_for_all, 0, 1}, [BrokerPool, SharedSub, AuthNSup, Helper]}}. + {ok, {{one_for_all, 0, 1}, [BrokerPool, SharedSub, Helper]}}. diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index e6bff790e..3f4507a3c 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -24,7 +24,6 @@ -elvis([{elvis_style, invalid_dynamic_call, disable}]). -include("emqx_schema.hrl"). --include("emqx_authentication.hrl"). -include("emqx_access_control.hrl"). -include_lib("typerefl/include/types.hrl"). -include_lib("hocon/include/hoconsc.hrl"). @@ -216,7 +215,6 @@ roots(high) -> importance => ?IMPORTANCE_HIDDEN } )}, - {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, authentication(global)}, %% NOTE: authorization schema here is only to keep emqx app pure %% the full schema for EMQX node is injected in emqx_conf_schema. {?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, @@ -224,7 +222,7 @@ roots(high) -> ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME), #{importance => ?IMPORTANCE_HIDDEN} )} - ]; + ] ++ emqx_schema_hooks:injection_point('roots.high'); roots(medium) -> [ {"broker", @@ -1750,11 +1748,8 @@ mqtt_listener(Bind) -> desc => ?DESC(mqtt_listener_proxy_protocol_timeout), default => <<"3s">> } - )}, - {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, (authentication(listener))#{ - importance => ?IMPORTANCE_HIDDEN - }} - ]. + )} + ] ++ emqx_schema_hooks:injection_point('mqtt.listener'). base_listener(Bind) -> [ @@ -2762,41 +2757,6 @@ str(B) when is_binary(B) -> str(S) when is_list(S) -> S. -authentication(Which) -> - {Importance, Desc} = - case Which of - global -> - %% For root level authentication, it is recommended to configure - %% from the dashboard or API. - %% Hence it's considered a low-importance when it comes to - %% configuration importance. - {?IMPORTANCE_LOW, ?DESC(global_authentication)}; - listener -> - {?IMPORTANCE_HIDDEN, ?DESC(listener_authentication)} - end, - %% poor man's dependency injection - %% this is due to the fact that authn is implemented outside of 'emqx' app. - %% so it can not be a part of emqx_schema since 'emqx' app is supposed to - %% work standalone. - Type = - case persistent_term:get(?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY, undefined) of - undefined -> - hoconsc:array(typerefl:map()); - Module -> - Module:root_type() - end, - hoconsc:mk(Type, #{ - desc => Desc, - converter => fun ensure_array/2, - default => [], - importance => Importance - }). - -%% the older version schema allows individual element (instead of a chain) in config -ensure_array(undefined, _) -> undefined; -ensure_array(L, _) when is_list(L) -> L; -ensure_array(M, _) -> [M]. - -spec qos() -> typerefl:type(). qos() -> typerefl:alias("qos", typerefl:union([0, 1, 2])). diff --git a/apps/emqx/src/emqx_schema_hooks.erl b/apps/emqx/src/emqx_schema_hooks.erl new file mode 100644 index 000000000..5a0d9a813 --- /dev/null +++ b/apps/emqx/src/emqx_schema_hooks.erl @@ -0,0 +1,78 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2017-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_schema_hooks). + +-type hookpoint() :: atom(). + +-callback injected_fields() -> + #{ + hookpoint() => [hocon_schema:field()] + }. +-optional_callbacks([injected_fields/0]). + +-define(HOOKPOINT_PT_KEY(POINT_NAME), {?MODULE, fields, POINT_NAME}). +-define(MODULE_PT_KEY(MOD_NAME), {?MODULE, mod, MOD_NAME}). + +-export([ + inject_fields/3, + injection_point/1, + + inject_fields_from_mod/1 +]). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +injection_point(PointName) -> + InjectedFields = persistent_term:get(?HOOKPOINT_PT_KEY(PointName), #{}), + lists:concat(maps:values(InjectedFields)). + +inject_fields(PointName, Name, Fields) -> + Key = ?HOOKPOINT_PT_KEY(PointName), + InjectedFields = persistent_term:get(Key, #{}), + persistent_term:put(Key, InjectedFields#{Name => Fields}). + +%%-------------------------------------------------------------------- +%% Internal API +%%-------------------------------------------------------------------- + +inject_fields_from_mod(Module) -> + case persistent_term:get(?MODULE_PT_KEY(Module), false) of + false -> + persistent_term:put(?MODULE_PT_KEY(Module), true), + do_inject_fields_from_mod(Module); + true -> + ok + end. + +do_inject_fields_from_mod(Module) -> + _ = Module:module_info(), + case erlang:function_exported(Module, injected_fields, 0) of + true -> + do_inject_fields_from_mod(Module, Module:injected_fields()); + false -> + ok + end. + +do_inject_fields_from_mod(Module, HookFields) -> + maps:foreach( + fun(PointName, Fields) -> + inject_fields(PointName, Module, Fields) + end, + HookFields + ). diff --git a/apps/emqx/test/emqx_common_test_helpers.erl b/apps/emqx/test/emqx_common_test_helpers.erl index 7f1fe4628..edf21c4f6 100644 --- a/apps/emqx/test/emqx_common_test_helpers.erl +++ b/apps/emqx/test/emqx_common_test_helpers.erl @@ -16,7 +16,7 @@ -module(emqx_common_test_helpers). --include_lib("emqx/include/emqx_authentication.hrl"). +-include_lib("emqx_authn/include/emqx_authentication.hrl"). -type special_config_handler() :: fun(). diff --git a/apps/emqx/test/emqx_cth_suite.erl b/apps/emqx/test/emqx_cth_suite.erl index 1ae6ceded..853281648 100644 --- a/apps/emqx/test/emqx_cth_suite.erl +++ b/apps/emqx/test/emqx_cth_suite.erl @@ -17,7 +17,7 @@ -module(emqx_cth_suite). -include_lib("common_test/include/ct.hrl"). --include_lib("emqx/include/emqx_authentication.hrl"). +-include_lib("emqx/include/emqx_access_control.hrl"). -export([start/2]). -export([stop/1]). diff --git a/apps/emqx/include/emqx_authentication.hrl b/apps/emqx_authn/include/emqx_authentication.hrl similarity index 85% rename from apps/emqx/include/emqx_authentication.hrl rename to apps/emqx_authn/include/emqx_authentication.hrl index d668e9a54..a367af291 100644 --- a/apps/emqx/include/emqx_authentication.hrl +++ b/apps/emqx_authn/include/emqx_authentication.hrl @@ -20,6 +20,11 @@ -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_access_control.hrl"). +%% config root name all auth providers have to agree on. +-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, "authentication"). +-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication). +-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, <<"authentication">>). + -define(GLOBAL, 'mqtt:global'). -define(TRACE_AUTHN_PROVIDER(Msg), ?TRACE_AUTHN_PROVIDER(Msg, #{})). @@ -31,17 +36,6 @@ -define(TRACE_AUTHN(Msg, Meta), ?TRACE_AUTHN(debug, Msg, Meta)). -define(TRACE_AUTHN(Level, Msg, Meta), ?TRACE(Level, ?AUTHN_TRACE_TAG, Msg, Meta)). -%% config root name all auth providers have to agree on. --define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, "authentication"). --define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication). --define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, <<"authentication">>). - -%% key to a persistent term which stores a module name in order to inject -%% schema module at run-time to keep emqx app's compile time purity. -%% see emqx_schema.erl for more details -%% and emqx_conf_schema for an examples --define(EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY, emqx_authentication_schema_module). - %% authentication move cmd -define(CMD_MOVE_FRONT, front). -define(CMD_MOVE_REAR, rear). diff --git a/apps/emqx_authn/include/emqx_authn.hrl b/apps/emqx_authn/include/emqx_authn.hrl index 601b161d5..9574d092f 100644 --- a/apps/emqx_authn/include/emqx_authn.hrl +++ b/apps/emqx_authn/include/emqx_authn.hrl @@ -17,7 +17,7 @@ -ifndef(EMQX_AUTHN_HRL). -define(EMQX_AUTHN_HRL, true). --include_lib("emqx/include/emqx_authentication.hrl"). +-include_lib("emqx_authentication.hrl"). -define(APP, emqx_authn). diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx_authn/src/emqx_authentication.erl similarity index 99% rename from apps/emqx/src/emqx_authentication.erl rename to apps/emqx_authn/src/emqx_authentication.erl index 92182f93a..ed02cc67a 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx_authn/src/emqx_authentication.erl @@ -22,14 +22,25 @@ -behaviour(gen_server). --include("emqx.hrl"). --include("logger.hrl"). -include("emqx_authentication.hrl"). +-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_hooks.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). -define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM). +-record(authenticator, { + id :: binary(), + provider :: module(), + enable :: boolean(), + state :: map() +}). + +-record(chain, { + name :: atom(), + authenticators :: [#authenticator{}] +}). + %% The authentication entrypoint. -export([ authenticate/2 diff --git a/apps/emqx/src/emqx_authentication_config.erl b/apps/emqx_authn/src/emqx_authentication_config.erl similarity index 99% rename from apps/emqx/src/emqx_authentication_config.erl rename to apps/emqx_authn/src/emqx_authentication_config.erl index 96718d611..0137e7b68 100644 --- a/apps/emqx/src/emqx_authentication_config.erl +++ b/apps/emqx_authn/src/emqx_authentication_config.erl @@ -37,8 +37,8 @@ -export_type([config/0]). --include("logger.hrl"). -include("emqx_authentication.hrl"). +-include_lib("emqx/include/logger.hrl"). -type parsed_config() :: #{ mechanism := atom(), diff --git a/apps/emqx/src/emqx_authentication_listener_hooks.erl b/apps/emqx_authn/src/emqx_authentication_listener_hooks.erl similarity index 100% rename from apps/emqx/src/emqx_authentication_listener_hooks.erl rename to apps/emqx_authn/src/emqx_authentication_listener_hooks.erl diff --git a/apps/emqx/src/emqx_authentication_sup.erl b/apps/emqx_authn/src/emqx_authentication_sup.erl similarity index 100% rename from apps/emqx/src/emqx_authentication_sup.erl rename to apps/emqx_authn/src/emqx_authentication_sup.erl diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index fa9f6c820..ce4647110 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -21,7 +21,6 @@ -include("emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). --include_lib("emqx/include/emqx_authentication.hrl"). -include_lib("hocon/include/hoconsc.hrl"). -import(hoconsc, [mk/2, ref/1, ref/2]). diff --git a/apps/emqx_authn/src/emqx_authn_app.erl b/apps/emqx_authn/src/emqx_authn_app.erl index 5d4be5f41..9ba6f5fbc 100644 --- a/apps/emqx_authn/src/emqx_authn_app.erl +++ b/apps/emqx_authn/src/emqx_authn_app.erl @@ -26,7 +26,7 @@ stop/1 ]). --include_lib("emqx/include/emqx_authentication.hrl"). +-include_lib("emqx_authentication.hrl"). -dialyzer({nowarn_function, [start/2]}). diff --git a/apps/emqx_authn/src/emqx_authn_schema.erl b/apps/emqx_authn/src/emqx_authn_schema.erl index a7cdaac5f..2a12899aa 100644 --- a/apps/emqx_authn/src/emqx_authn_schema.erl +++ b/apps/emqx_authn/src/emqx_authn_schema.erl @@ -19,6 +19,12 @@ -elvis([{elvis_style, invalid_dynamic_call, disable}]). -include_lib("hocon/include/hoconsc.hrl"). -include("emqx_authn.hrl"). +-include("emqx_authentication.hrl"). + +-behaviour(emqx_schema_hooks). +-export([ + injected_fields/0 +]). -export([ common_fields/0, @@ -28,13 +34,18 @@ fields/1, authenticator_type/0, authenticator_type_without_scram/0, - root_type/0, mechanism/1, backend/1 ]). roots() -> []. +injected_fields() -> + #{ + 'roots.high' => global_auth_fields(), + 'mqtt.listener' => mqtt_listener_auth_fields() + }. + tags() -> [<<"Authentication">>]. @@ -121,12 +132,36 @@ try_select_union_member(Module, Value) -> Module:refs() end. -%% authn is a core functionality however implemented outside of emqx app -%% in emqx_schema, 'authentication' is a map() type which is to allow -%% EMQX more pluggable. root_type() -> hoconsc:array(authenticator_type()). +global_auth_fields() -> + [ + {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, + hoconsc:mk(root_type(), #{ + desc => ?DESC(global_authentication), + converter => fun ensure_array/2, + default => [], + importance => ?IMPORTANCE_LOW + })} + ]. + +mqtt_listener_auth_fields() -> + [ + {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, + hoconsc:mk(root_type(), #{ + desc => ?DESC(listener_authentication), + converter => fun ensure_array/2, + default => [], + importance => ?IMPORTANCE_HIDDEN + })} + ]. + +%% the older version schema allows individual element (instead of a chain) in config +ensure_array(undefined, _) -> undefined; +ensure_array(L, _) when is_list(L) -> L; +ensure_array(M, _) -> [M]. + mechanism(Name) -> ?HOCON( Name, diff --git a/apps/emqx_authn/src/emqx_authn_sup.erl b/apps/emqx_authn/src/emqx_authn_sup.erl index 635bd7323..211ebd518 100644 --- a/apps/emqx_authn/src/emqx_authn_sup.erl +++ b/apps/emqx_authn/src/emqx_authn_sup.erl @@ -27,5 +27,15 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - ChildSpecs = [], + AuthNSup = #{ + id => emqx_authentication_sup, + start => {emqx_authentication_sup, start_link, []}, + restart => permanent, + shutdown => infinity, + type => supervisor, + modules => [emqx_authentication_sup] + }, + + ChildSpecs = [AuthNSup], + {ok, {{one_for_one, 10, 10}, ChildSpecs}}. diff --git a/apps/emqx_authn/src/emqx_authn_user_import_api.erl b/apps/emqx_authn/src/emqx_authn_user_import_api.erl index 30836d3ba..f9d4208e6 100644 --- a/apps/emqx_authn/src/emqx_authn_user_import_api.erl +++ b/apps/emqx_authn/src/emqx_authn_user_import_api.erl @@ -20,7 +20,6 @@ -include("emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("emqx/include/emqx_authentication.hrl"). -include_lib("hocon/include/hoconsc.hrl"). -import(emqx_dashboard_swagger, [error_codes/2]). diff --git a/apps/emqx/test/emqx_authentication_SUITE.erl b/apps/emqx_authn/test/emqx_authentication_SUITE.erl similarity index 100% rename from apps/emqx/test/emqx_authentication_SUITE.erl rename to apps/emqx_authn/test/emqx_authentication_SUITE.erl diff --git a/apps/emqx_conf/src/emqx_conf_cli.erl b/apps/emqx_conf/src/emqx_conf_cli.erl index fde3059d3..8a4bb131f 100644 --- a/apps/emqx_conf/src/emqx_conf_cli.erl +++ b/apps/emqx_conf/src/emqx_conf_cli.erl @@ -16,8 +16,8 @@ -module(emqx_conf_cli). -include("emqx_conf.hrl"). --include_lib("emqx/include/emqx_access_control.hrl"). --include_lib("emqx/include/emqx_authentication.hrl"). +-include_lib("emqx_authn/include/emqx_authentication.hrl"). +-include_lib("emqx/include/logger.hrl"). -export([ load/0, diff --git a/apps/emqx_conf/src/emqx_conf_schema.erl b/apps/emqx_conf/src/emqx_conf_schema.erl index 246f36f41..14cd4f17c 100644 --- a/apps/emqx_conf/src/emqx_conf_schema.erl +++ b/apps/emqx_conf/src/emqx_conf_schema.erl @@ -22,9 +22,9 @@ -dialyzer(no_unused). -dialyzer(no_fail_call). +-include_lib("emqx/include/emqx_access_control.hrl"). -include_lib("typerefl/include/types.hrl"). -include_lib("hocon/include/hoconsc.hrl"). --include_lib("emqx/include/emqx_authentication.hrl"). -type log_level() :: debug | info | notice | warning | error | critical | alert | emergency | all. -type file() :: string(). @@ -66,6 +66,10 @@ emqx_otel_schema, emqx_mgmt_api_key_schema ]). +-define(INJECTING_CONFIGS, [ + emqx_authn_schema +]). + %% 1 million default ports counter -define(DEFAULT_MAX_PORTS, 1024 * 1024). @@ -76,11 +80,7 @@ tags() -> [<<"EMQX">>]. roots() -> - PtKey = ?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY, - case persistent_term:get(PtKey, undefined) of - undefined -> persistent_term:put(PtKey, emqx_authn_schema); - _ -> ok - end, + ok = ensure_fields_injected(), emqx_schema_high_prio_roots() ++ [ {"node", @@ -1434,3 +1434,9 @@ ensure_unicode_path(Path, _) when is_list(Path) -> Path; ensure_unicode_path(Path, _) -> throw({"not_string", Path}). + +ensure_fields_injected() -> + lists:foreach( + fun(Module) -> emqx_schema_hooks:inject_fields_from_mod(Module) end, + ?INJECTING_CONFIGS + ). diff --git a/apps/emqx_gateway/src/emqx_gateway.app.src b/apps/emqx_gateway/src/emqx_gateway.app.src index b5fe5e100..582269ce6 100644 --- a/apps/emqx_gateway/src/emqx_gateway.app.src +++ b/apps/emqx_gateway/src/emqx_gateway.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_gateway, [ {description, "The Gateway management application"}, - {vsn, "0.1.22"}, + {vsn, "0.1.23"}, {registered, []}, {mod, {emqx_gateway_app, []}}, {applications, [kernel, stdlib, emqx, emqx_authn, emqx_ctl]}, diff --git a/apps/emqx_gateway/src/emqx_gateway_conf.erl b/apps/emqx_gateway/src/emqx_gateway_conf.erl index 2a64a6914..480633652 100644 --- a/apps/emqx_gateway/src/emqx_gateway_conf.erl +++ b/apps/emqx_gateway/src/emqx_gateway_conf.erl @@ -71,7 +71,7 @@ ]). -include_lib("emqx/include/logger.hrl"). --include_lib("emqx/include/emqx_authentication.hrl"). +-include_lib("emqx_authn/include/emqx_authentication.hrl"). -define(AUTHN_BIN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY). -type atom_or_bin() :: atom() | binary(). diff --git a/apps/emqx_gateway/src/emqx_gateway_http.erl b/apps/emqx_gateway/src/emqx_gateway_http.erl index 2186ac3d7..997539e7d 100644 --- a/apps/emqx_gateway/src/emqx_gateway_http.erl +++ b/apps/emqx_gateway/src/emqx_gateway_http.erl @@ -19,7 +19,7 @@ -include("include/emqx_gateway.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("emqx/include/emqx_authentication.hrl"). +-include_lib("emqx_authn/include/emqx_authentication.hrl"). -define(AUTHN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM). diff --git a/apps/emqx_gateway/src/emqx_gateway_schema.erl b/apps/emqx_gateway/src/emqx_gateway_schema.erl index b43f4ba98..8d9cc5a11 100644 --- a/apps/emqx_gateway/src/emqx_gateway_schema.erl +++ b/apps/emqx_gateway/src/emqx_gateway_schema.erl @@ -24,9 +24,9 @@ -dialyzer(no_unused). -dialyzer(no_fail_call). --include_lib("emqx/include/emqx_authentication.hrl"). -include_lib("hocon/include/hoconsc.hrl"). -include_lib("typerefl/include/types.hrl"). +-include_lib("emqx_authn/include/emqx_authentication.hrl"). -type ip_port() :: tuple() | integer(). -type duration() :: non_neg_integer(). diff --git a/rel/i18n/emqx_authn_schema.hocon b/rel/i18n/emqx_authn_schema.hocon index 98263ca49..a1910f95b 100644 --- a/rel/i18n/emqx_authn_schema.hocon +++ b/rel/i18n/emqx_authn_schema.hocon @@ -1,5 +1,32 @@ emqx_authn_schema { +global_authentication.desc: +"""Default authentication configs for all MQTT listeners. + +For per-listener overrides see authentication in listener configs + +This option can be configured with: +
    +
  • []: The default value, it allows *ALL* logins
  • +
  • one: For example {enable:true,backend:"built_in_database",mechanism="password_based"}
  • +
  • chain: An array of structs.
  • +
+ +When a chain is configured, the login credentials are checked against the backends per the configured order, until an 'allow' or 'deny' decision can be made. + +If there is no decision after a full chain exhaustion, the login is rejected.""" + +global_authentication.label: +"""Global authentication""" + +listener_authentication.desc: +"""Per-listener authentication override. +Authentication can be one single authenticator instance or a chain of authenticators as an array. +When authenticating a login (username, client ID, etc.) the authenticators are checked in the configured order.""" + +listener_authentication.label: +"""Per-listener authentication override""" + backend.desc: """Backend type.""" diff --git a/rel/i18n/emqx_schema.hocon b/rel/i18n/emqx_schema.hocon index 64de73b24..57b24f57d 100644 --- a/rel/i18n/emqx_schema.hocon +++ b/rel/i18n/emqx_schema.hocon @@ -532,22 +532,6 @@ mqtt_server_keepalive.desc: mqtt_server_keepalive.label: """Server Keep Alive""" -global_authentication.desc: -"""Default authentication configs for all MQTT listeners. - -For per-listener overrides see authentication in listener configs - -This option can be configured with: -
    -
  • []: The default value, it allows *ALL* logins
  • -
  • one: For example {enable:true,backend:"built_in_database",mechanism="password_based"}
  • -
  • chain: An array of structs.
  • -
- -When a chain is configured, the login credentials are checked against the backends per the configured order, until an 'allow' or 'deny' decision can be made. - -If there is no decision after a full chain exhaustion, the login is rejected.""" - fields_mqtt_quic_listener_load_balancing_mode.desc: """0: Disabled, 1: SERVER_ID_IP, 2: SERVER_ID_FIXED. default: 0""" @@ -1103,14 +1087,6 @@ See: https://erlang.org/doc/man/inet.html#setopts-2""" fields_tcp_opts_active_n.label: """active_n""" -listener_authentication.desc: -"""Per-listener authentication override. -Authentication can be one single authenticator instance or a chain of authenticators as an array. -When authenticating a login (username, client ID, etc.) the authenticators are checked in the configured order.""" - -listener_authentication.label: -"""Per-listener authentication override""" - fields_trace_payload_encode.desc: """Determine the format of the payload format in the trace file.
`text`: Text-based protocol or plain text protocol. From 128aa19d474cc993a9c1c42a8d455a8fe1a09f65 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Thu, 3 Aug 2023 10:43:33 +0300 Subject: [PATCH 04/14] chore(auth): improve emqx_config_handler to replace adhoc config updates --- apps/emqx/src/emqx_config_handler.erl | 293 +++++++++++++++--- apps/emqx/src/emqx_listeners.erl | 29 +- apps/emqx/src/emqx_schema_hooks.erl | 36 +++ apps/emqx/test/emqx_common_test_helpers.erl | 4 +- apps/emqx/test/emqx_config_handler_SUITE.erl | 90 +++++- apps/emqx/test/emqx_cth_suite.erl | 4 +- apps/emqx_authn/rebar.config | 2 + apps/emqx_authn/src/emqx_authentication.erl | 7 + .../src/emqx_authentication_config.erl | 32 +- .../emqx_authentication_listener_hooks.erl | 86 ----- apps/emqx_authn/src/emqx_authn_app.erl | 3 +- .../test/emqx_authentication_SUITE.erl | 47 ++- apps/emqx_conf/rebar.config | 5 +- apps/emqx_gateway/rebar.config | 3 +- apps/emqx_machine/rebar.config | 1 + 15 files changed, 451 insertions(+), 191 deletions(-) delete mode 100644 apps/emqx_authn/src/emqx_authentication_listener_hooks.erl diff --git a/apps/emqx/src/emqx_config_handler.erl b/apps/emqx/src/emqx_config_handler.erl index 96690c26e..f68992e4e 100644 --- a/apps/emqx/src/emqx_config_handler.erl +++ b/apps/emqx/src/emqx_config_handler.erl @@ -53,11 +53,17 @@ -optional_callbacks([ pre_config_update/3, - post_config_update/5 + propagated_pre_config_update/3, + post_config_update/5, + propagated_post_config_update/5 ]). -callback pre_config_update([atom()], emqx_config:update_request(), emqx_config:raw_config()) -> - {ok, emqx_config:update_request()} | {error, term()}. + ok | {ok, emqx_config:update_request()} | {error, term()}. +-callback propagated_pre_config_update( + [atom()], emqx_config:update_request(), emqx_config:raw_config() +) -> + ok | {error, term()}. -callback post_config_update( [atom()], @@ -68,6 +74,15 @@ ) -> ok | {ok, Result :: any()} | {error, Reason :: term()}. +-callback propagated_post_config_update( + [atom()], + emqx_config:update_request(), + emqx_config:config(), + emqx_config:config(), + emqx_config:app_envs() +) -> + ok | {ok, Result :: any()} | {error, Reason :: term()}. + -type state() :: #{handlers := any()}. start_link() -> @@ -244,7 +259,13 @@ do_update_config(ConfKeyPath, Handlers, OldRawConf, UpdateReq) -> do_update_config(ConfKeyPath, Handlers, OldRawConf, UpdateReq, []). do_update_config([], Handlers, OldRawConf, UpdateReq, ConfKeyPath) -> - call_pre_config_update(Handlers, OldRawConf, UpdateReq, ConfKeyPath); + call_pre_config_update(#{ + handlers => Handlers, + old_raw_conf => OldRawConf, + update_req => UpdateReq, + conf_key_path => ConfKeyPath, + callback => pre_config_update + }); do_update_config( [ConfKey | SubConfKeyPath], Handlers, @@ -331,15 +352,16 @@ do_post_config_update( Result, ConfKeyPath ) -> - call_post_config_update( - Handlers, - OldConf, - NewConf, - AppEnvs, - up_req(UpdateArgs), - Result, - ConfKeyPath - ); + call_post_config_update(#{ + handlers => Handlers, + old_conf => OldConf, + new_conf => NewConf, + app_envs => AppEnvs, + update_req => up_req(UpdateArgs), + result => Result, + conf_key_path => ConfKeyPath, + callback => post_config_update + }); do_post_config_update( [ConfKey | SubConfKeyPath], Handlers, @@ -365,7 +387,7 @@ do_post_config_update( ConfKeyPath ). -get_sub_handlers(ConfKey, Handlers) -> +get_sub_handlers(ConfKey, Handlers) when is_atom(ConfKey) -> case maps:find(ConfKey, Handlers) of error -> maps:get(?WKEY, Handlers, #{}); {ok, SubHandlers} -> SubHandlers @@ -377,57 +399,231 @@ get_sub_config(ConfKey, Conf) when is_map(Conf) -> get_sub_config(_, _Conf) -> undefined. -call_pre_config_update(#{?MOD := HandlerName}, OldRawConf, UpdateReq, ConfKeyPath) -> - case erlang:function_exported(HandlerName, pre_config_update, 3) of +call_pre_config_update(Ctx) -> + case call_proper_pre_config_update(Ctx) of + {ok, NewUpdateReq} -> + case + propagate_pre_config_updates_to_subconf(Ctx#{ + update_req => NewUpdateReq + }) + of + {ok, _} -> + {ok, NewUpdateReq}; + {error, _} = Error -> + Error + end; + {error, _} = Error -> + Error + end. + +call_proper_pre_config_update( + #{ + handlers := #{?MOD := Module}, + callback := Callback, + update_req := UpdateReq, + old_raw_conf := OldRawConf + } = Ctx +) -> + case erlang:function_exported(Module, Callback, 3) of true -> - case HandlerName:pre_config_update(ConfKeyPath, UpdateReq, OldRawConf) of - {ok, NewUpdateReq} -> {ok, NewUpdateReq}; - {error, Reason} -> {error, {pre_config_update, HandlerName, Reason}} + case apply_pre_config_update(Module, Ctx) of + {ok, NewUpdateReq} -> + {ok, NewUpdateReq}; + ok -> + {ok, UpdateReq}; + {error, Reason} -> + {error, {pre_config_update, Module, Reason}} end; false -> merge_to_old_config(UpdateReq, OldRawConf) end; -call_pre_config_update(_Handlers, OldRawConf, UpdateReq, _ConfKeyPath) -> - merge_to_old_config(UpdateReq, OldRawConf). - -call_post_config_update( - #{?MOD := HandlerName}, - OldConf, - NewConf, - AppEnvs, - UpdateReq, - Result, - ConfKeyPath +call_proper_pre_config_update( + #{update_req := UpdateReq} ) -> - case erlang:function_exported(HandlerName, post_config_update, 5) of - true -> + {ok, UpdateReq}. + +apply_pre_config_update(Module, #{ + conf_key_path := ConfKeyPath, + update_req := UpdateReq, + old_raw_conf := OldRawConf, + callback := Callback +}) -> + Module:Callback( + ConfKeyPath, UpdateReq, OldRawConf + ). + +propagate_pre_config_updates_to_subconf( + #{handlers := #{?WKEY := _}} = Ctx +) -> + propagate_pre_config_updates_to_subconf_wkey(Ctx); +propagate_pre_config_updates_to_subconf( + #{handlers := Handlers} = Ctx +) -> + Keys = maps:keys(maps:without([?MOD], Handlers)), + propagate_pre_config_updates_to_subconf_keys(Keys, Ctx). + +propagate_pre_config_updates_to_subconf_wkey( + #{ + update_req := UpdateReq, + old_raw_conf := OldRawConf + } = Ctx +) -> + Keys = propagate_keys(UpdateReq, OldRawConf), + propagate_pre_config_updates_to_subconf_keys(Keys, Ctx). + +propagate_pre_config_updates_to_subconf_keys([], #{update_req := UpdateReq}) -> + {ok, UpdateReq}; +propagate_pre_config_updates_to_subconf_keys([Key | Keys], Ctx) -> + case propagate_pre_config_updates_to_subconf_key(Key, Ctx) of + ok -> + propagate_pre_config_updates_to_subconf_keys(Keys, Ctx); + {error, _} = Error -> + Error + end. + +propagate_pre_config_updates_to_subconf_key( + Key, + #{ + handlers := Handlers, + old_raw_conf := OldRawConf, + update_req := UpdateReq, + conf_key_path := ConfKeyPath + } = Ctx +) -> + AtomKey = atom(Key), + SubHandlers = get_sub_handlers(AtomKey, Handlers), + BinKey = bin(Key), + SubUpdateReq = get_sub_config(BinKey, UpdateReq), + SubOldConf = get_sub_config(BinKey, OldRawConf), + SubConfKeyPath = ConfKeyPath ++ [AtomKey], + case {SubOldConf, SubUpdateReq} of + {undefined, undefined} -> + ok; + {_, _} -> case - HandlerName:post_config_update( - ConfKeyPath, - UpdateReq, - NewConf, - OldConf, - AppEnvs - ) + call_pre_config_update(Ctx#{ + handlers := SubHandlers, + old_raw_conf := SubOldConf, + update_req := SubUpdateReq, + conf_key_path := SubConfKeyPath, + callback := propagated_pre_config_update + }) of + {ok, _SubNewConf1} -> + ok; + {error, _} = Error -> + Error + end + end. + +call_post_config_update(#{handlers := Handlers} = Ctx) -> + case call_proper_post_config_update(Ctx) of + {ok, Result} -> + SubHandlers = maps:without([?MOD], Handlers), + propagate_post_config_updates_to_subconf(Ctx#{ + handlers := SubHandlers, + callback := propagated_post_config_update, + result := Result + }); + {error, _} = Error -> + Error + end. + +call_proper_post_config_update( + #{ + handlers := #{?MOD := Module}, + callback := Callback, + result := Result + } = Ctx +) -> + case erlang:function_exported(Module, Callback, 5) of + true -> + case apply_post_config_update(Module, Ctx) of ok -> {ok, Result}; - {ok, Result1} -> {ok, Result#{HandlerName => Result1}}; - {error, Reason} -> {error, {post_config_update, HandlerName, Reason}} + {ok, Result1} -> {ok, Result#{Module => Result1}}; + {error, Reason} -> {error, {post_config_update, Module, Reason}} end; false -> {ok, Result} end; -call_post_config_update( - _Handlers, - _OldConf, - _NewConf, - _AppEnvs, - _UpdateReq, - Result, - _ConfKeyPath +call_proper_post_config_update( + #{result := Result} = _Ctx ) -> {ok, Result}. +apply_post_config_update(Module, #{ + conf_key_path := ConfKeyPath, + update_req := UpdateReq, + new_conf := NewConf, + old_conf := OldConf, + app_envs := AppEnvs, + callback := Callback +}) -> + Module:Callback( + ConfKeyPath, + UpdateReq, + NewConf, + OldConf, + AppEnvs + ). + +propagate_post_config_updates_to_subconf( + #{handlers := #{?WKEY := _}} = Ctx +) -> + propagate_post_config_updates_to_subconf_wkey(Ctx); +propagate_post_config_updates_to_subconf( + #{handlers := Handlers} = Ctx +) -> + Keys = maps:keys(Handlers), + propagate_post_config_updates_to_subconf_keys(Keys, Ctx). + +propagate_post_config_updates_to_subconf_wkey( + #{ + old_conf := OldConf, + new_conf := NewConf + } = Ctx +) -> + Keys = propagate_keys(OldConf, NewConf), + propagate_post_config_updates_to_subconf_keys(Keys, Ctx). +propagate_post_config_updates_to_subconf_keys([], #{result := Result}) -> + {ok, Result}; +propagate_post_config_updates_to_subconf_keys([Key | Keys], Ctx) -> + case propagate_post_config_updates_to_subconf_key(Key, Ctx) of + {ok, Result1} -> + propagate_post_config_updates_to_subconf_keys(Keys, Ctx#{result := Result1}); + Error -> + Error + end. + +propagate_keys(OldConf, NewConf) -> + sets:to_list(sets:union(propagate_keys(OldConf), propagate_keys(NewConf))). + +propagate_keys(Conf) when is_map(Conf) -> sets:from_list(maps:keys(Conf), [{version, 2}]); +propagate_keys(_) -> sets:new([{version, 2}]). + +propagate_post_config_updates_to_subconf_key( + Key, + #{ + handlers := Handlers, + new_conf := NewConf, + old_conf := OldConf, + result := Result, + conf_key_path := ConfKeyPath + } = Ctx +) -> + SubHandlers = maps:get(Key, Handlers, maps:get(?WKEY, Handlers, undefined)), + SubNewConf = get_sub_config(Key, NewConf), + SubOldConf = get_sub_config(Key, OldConf), + SubConfKeyPath = ConfKeyPath ++ [Key], + call_post_config_update(Ctx#{ + handlers := SubHandlers, + new_conf := SubNewConf, + old_conf := SubOldConf, + result := Result, + conf_key_path := SubConfKeyPath, + callback := propagated_post_config_update + }). + %% The default callback of config handlers %% the behaviour is overwriting the old config if: %% 1. the old config is undefined @@ -517,6 +713,7 @@ remove_empty_leaf(KeyPath, Handlers) -> end. assert_callback_function(Mod) -> + _ = Mod:module_info(), case erlang:function_exported(Mod, pre_config_update, 3) orelse erlang:function_exported(Mod, post_config_update, 5) diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index 5d641de39..b95169d3c 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/apps/emqx/src/emqx_listeners.erl @@ -531,18 +531,15 @@ post_config_update(_Path, _Request, _NewConf, _OldConf, _AppEnvs) -> ok. create_listener(Type, Name, NewConf) -> - StartRes = start_listener(Type, Name, NewConf), - emqx_hooks:run_fold('listener.started', [Type, Name, NewConf], StartRes). + start_listener(Type, Name, NewConf). remove_listener(Type, Name, OldConf) -> ok = unregister_ocsp_stapling_refresh(Type, Name), - StopRes = stop_listener(Type, Name, OldConf), - emqx_hooks:run_fold('listener.stopped', [Type, Name, OldConf], StopRes). + stop_listener(Type, Name, OldConf). update_listener(Type, Name, {OldConf, NewConf}) -> ok = maybe_unregister_ocsp_stapling_refresh(Type, Name, NewConf), - RestartRes = restart_listener(Type, Name, {OldConf, NewConf}), - emqx_hooks:run_fold('listener.restarted', [Type, Name, {OldConf, NewConf}], RestartRes). + restart_listener(Type, Name, {OldConf, NewConf}). perform_listener_changes([]) -> ok; @@ -824,10 +821,9 @@ convert_certs(ListenerConf) -> fun(Type, Listeners0, Acc) -> Listeners1 = maps:fold( - fun(Name, Conf, Acc1) -> - Conf1 = convert_certs(Type, Name, Conf), - Conf2 = convert_authn_certs(Type, Name, Conf1), - Acc1#{Name => Conf2} + fun(Name, Conf0, Acc1) -> + Conf1 = convert_certs(Type, Name, Conf0), + Acc1#{Name => Conf1} end, #{}, Listeners0 @@ -850,19 +846,6 @@ convert_certs(Type, Name, Conf) -> throw({bad_ssl_config, Reason}) end. -convert_authn_certs(Type, Name, #{<<"authentication">> := AuthNList} = Conf) -> - ChainName = listener_id(Type, Name), - AuthNList1 = lists:map( - fun(AuthN) -> - CertsDir = emqx_authentication_config:certs_dir(ChainName, AuthN), - emqx_authentication_config:convert_certs(CertsDir, AuthN) - end, - AuthNList - ), - Conf#{<<"authentication">> => AuthNList1}; -convert_authn_certs(_Type, _Name, Conf) -> - Conf. - filter_stacktrace({Reason, _Stacktrace}) -> Reason; filter_stacktrace(Reason) -> Reason. diff --git a/apps/emqx/src/emqx_schema_hooks.erl b/apps/emqx/src/emqx_schema_hooks.erl index 5a0d9a813..59fb1588b 100644 --- a/apps/emqx/src/emqx_schema_hooks.erl +++ b/apps/emqx/src/emqx_schema_hooks.erl @@ -34,6 +34,12 @@ inject_fields_from_mod/1 ]). +%% for tests +-export([ + erase_injections/0, + any_injections/0 +]). + %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- @@ -60,6 +66,36 @@ inject_fields_from_mod(Module) -> ok end. +erase_injections() -> + lists:foreach( + fun + ({?HOOKPOINT_PT_KEY(_) = Key, _}) -> + persistent_term:erase(Key); + ({?MODULE_PT_KEY(_) = Key, _}) -> + persistent_term:erase(Key); + (_) -> + ok + end, + persistent_term:get() + ). + +any_injections() -> + lists:any( + fun + ({?HOOKPOINT_PT_KEY(_), _}) -> + true; + ({?MODULE_PT_KEY(_), _}) -> + true; + (_) -> + false + end, + persistent_term:get() + ). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + do_inject_fields_from_mod(Module) -> _ = Module:module_info(), case erlang:function_exported(Module, injected_fields, 0) of diff --git a/apps/emqx/test/emqx_common_test_helpers.erl b/apps/emqx/test/emqx_common_test_helpers.erl index edf21c4f6..3645fa06b 100644 --- a/apps/emqx/test/emqx_common_test_helpers.erl +++ b/apps/emqx/test/emqx_common_test_helpers.erl @@ -16,8 +16,6 @@ -module(emqx_common_test_helpers). --include_lib("emqx_authn/include/emqx_authentication.hrl"). - -type special_config_handler() :: fun(). -type apps() :: list(atom()). @@ -351,7 +349,7 @@ stop_apps(Apps, Opts) -> %% to avoid inter-suite flakiness application:unset_env(emqx, config_loader), application:unset_env(emqx, boot_modules), - persistent_term:erase(?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY), + emqx_schema_hooks:erase_injections(), case Opts of #{erase_all_configs := false} -> %% FIXME: this means inter-suite or inter-test dependencies diff --git a/apps/emqx/test/emqx_config_handler_SUITE.erl b/apps/emqx/test/emqx_config_handler_SUITE.erl index b13da79f6..af2b4996c 100644 --- a/apps/emqx/test/emqx_config_handler_SUITE.erl +++ b/apps/emqx/test/emqx_config_handler_SUITE.erl @@ -26,7 +26,8 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -all() -> emqx_common_test_helpers:all(?MODULE). +all() -> + emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> emqx_common_test_helpers:boot_modules(all), @@ -239,6 +240,62 @@ t_post_update_error(_Config) -> ), ok. +t_post_update_propagate_error_wkey(_Config) -> + Conf0 = emqx_config:get_raw([sysmon]), + Conf1 = emqx_utils_maps:deep_put([<<"os">>, <<"sysmem_high_watermark">>], Conf0, <<"60%">>), + callback_error( + [ + [sysmon, '?', sysmem_high_watermark], + [sysmon] + ], + [sysmon], + Conf1, + {error, {post_config_update, ?MODULE, post_config_update_error}} + ), + ok. + +t_post_update_propagate_error_key(_Config) -> + Conf0 = emqx_config:get_raw([sysmon]), + Conf1 = emqx_utils_maps:deep_put([<<"os">>, <<"sysmem_high_watermark">>], Conf0, <<"60%">>), + callback_error( + [ + [sysmon, os, sysmem_high_watermark], + [sysmon] + ], + [sysmon], + Conf1, + {error, {post_config_update, ?MODULE, post_config_update_error}} + ), + ok. + +t_pre_update_propagate_error_wkey(_Config) -> + Conf0 = emqx_config:get_raw([sysmon]), + Conf1 = emqx_utils_maps:deep_put([<<"os">>, <<"mem_check_interval">>], Conf0, <<"70s">>), + callback_error( + [ + [sysmon, '?', mem_check_interval], + [sysmon] + ], + [sysmon], + Conf1, + {error, {pre_config_update, ?MODULE, pre_config_update_error}} + ), + ok. + +t_pre_update_propagate_error_key(_Config) -> + Conf0 = emqx_config:get_raw([sysmon]), + Conf1 = emqx_utils_maps:deep_put([<<"os">>, <<"mem_check_interval">>], Conf0, <<"70s">>), + callback_error( + [ + [sysmon, os, mem_check_interval], + [sysmon] + ], + [sysmon], + Conf1, + {error, {pre_config_update, ?MODULE, pre_config_update_error}} + ), + ok. + t_handler_root() -> %% Don't rely on default emqx_config_handler's merge behaviour. RootKey = [], @@ -295,6 +352,11 @@ pre_config_update([sysmon, os, sysmem_high_watermark], UpdateReq, _RawConf) -> pre_config_update([sysmon, os, mem_check_interval], _UpdateReq, _RawConf) -> {error, pre_config_update_error}. +propagated_pre_config_update([sysmon, os, mem_check_interval], _UpdateReq, _RawConf) -> + {error, pre_config_update_error}; +propagated_pre_config_update(_ConfKeyPath, _UpdateReq, _RawConf) -> + ok. + post_config_update([sysmon], _UpdateReq, _NewConf, _OldConf, _AppEnvs) -> {ok, ok}; post_config_update([sysmon, os], _UpdateReq, _NewConf, _OldConf, _AppEnvs) -> @@ -308,6 +370,13 @@ post_config_update([sysmon, os, cpu_high_watermark], _UpdateReq, _NewConf, _OldC post_config_update([sysmon, os, sysmem_high_watermark], _UpdateReq, _NewConf, _OldConf, _AppEnvs) -> {error, post_config_update_error}. +propagated_post_config_update( + [sysmon, os, sysmem_high_watermark], _UpdateReq, _NewConf, _OldConf, _AppEnvs +) -> + {error, post_config_update_error}; +propagated_post_config_update(_ConfKeyPath, _UpdateReq, _NewConf, _OldConf, _AppEnvs) -> + ok. + wait_for_new_pid() -> case erlang:whereis(emqx_config_handler) of undefined -> @@ -318,10 +387,16 @@ wait_for_new_pid() -> end. callback_error(FailedPath, Update, ExpectError) -> + callback_error([FailedPath], FailedPath, Update, ExpectError). + +callback_error(Paths, UpdatePath, Update, ExpectError) -> + ok = lists:foreach( + fun(Path) -> emqx_config_handler:add_handler(Path, ?MODULE) end, + Paths + ), Opts = #{rawconf_with_defaults => true}, - ok = emqx_config_handler:add_handler(FailedPath, ?MODULE), - Old = emqx:get_raw_config(FailedPath, undefined), - Error = emqx:update_config(FailedPath, Update, Opts), + Old = emqx:get_raw_config(UpdatePath, undefined), + Error = emqx:update_config(UpdatePath, Update, Opts), case ExpectError of {error, {post_config_update, ?MODULE, post_config_update_error}} -> ?assertMatch( @@ -330,7 +405,10 @@ callback_error(FailedPath, Update, ExpectError) -> _ -> ?assertEqual(ExpectError, Error) end, - New = emqx:get_raw_config(FailedPath, undefined), + New = emqx:get_raw_config(UpdatePath, undefined), ?assertEqual(Old, New), - ok = emqx_config_handler:remove_handler(FailedPath), + ok = lists:foreach( + fun(Path) -> emqx_config_handler:remove_handler(Path) end, + Paths + ), ok. diff --git a/apps/emqx/test/emqx_cth_suite.erl b/apps/emqx/test/emqx_cth_suite.erl index 853281648..61f87e0ea 100644 --- a/apps/emqx/test/emqx_cth_suite.erl +++ b/apps/emqx/test/emqx_cth_suite.erl @@ -347,12 +347,12 @@ stop_apps(Apps) -> verify_clean_suite_state(#{work_dir := WorkDir}) -> {ok, []} = file:list_dir(WorkDir), - none = persistent_term:get(?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY, none), + false = emqx_schema_hooks:any_injections(), [] = emqx_config:get_root_names(), ok. clean_suite_state() -> - _ = persistent_term:erase(?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY), + _ = emqx_schema_hooks:erase_injections(), _ = emqx_config:erase_all(), ok. diff --git a/apps/emqx_authn/rebar.config b/apps/emqx_authn/rebar.config index 932a1ff77..5bc8d3e91 100644 --- a/apps/emqx_authn/rebar.config +++ b/apps/emqx_authn/rebar.config @@ -34,4 +34,6 @@ {cover_opts, [verbose]}. {cover_export_enabled, true}. +{erl_first_files, ["src/emqx_authentication.erl"]}. + {project_plugins, [erlfmt]}. diff --git a/apps/emqx_authn/src/emqx_authentication.erl b/apps/emqx_authn/src/emqx_authentication.erl index ed02cc67a..5b49f5d28 100644 --- a/apps/emqx_authn/src/emqx_authentication.erl +++ b/apps/emqx_authn/src/emqx_authentication.erl @@ -60,6 +60,7 @@ register_providers/1, deregister_provider/1, deregister_providers/1, + providers/0, delete_chain/1, lookup_chain/1, list_chains/0, @@ -331,6 +332,10 @@ deregister_providers(AuthNTypes) when is_list(AuthNTypes) -> deregister_provider(AuthNType) -> deregister_providers([AuthNType]). +-spec providers() -> [{authn_type(), module()}]. +providers() -> + call(providers). + -spec delete_chain(chain_name()) -> ok | {error, term()}. delete_chain(Name) -> call({delete_chain, Name}). @@ -463,6 +468,8 @@ handle_call( end; handle_call({deregister_providers, AuthNTypes}, _From, #{providers := Providers} = State) -> reply(ok, State#{providers := maps:without(AuthNTypes, Providers)}); +handle_call(providers, _From, #{providers := Providers} = State) -> + reply(maps:to_list(Providers), State); handle_call({delete_chain, ChainName}, _From, State) -> UpdateFun = fun(Chain) -> {_MatchedIDs, NewChain} = do_delete_authenticators(fun(_) -> true end, Chain), diff --git a/apps/emqx_authn/src/emqx_authentication_config.erl b/apps/emqx_authn/src/emqx_authentication_config.erl index 0137e7b68..13b9214de 100644 --- a/apps/emqx_authn/src/emqx_authentication_config.erl +++ b/apps/emqx_authn/src/emqx_authentication_config.erl @@ -21,7 +21,9 @@ -export([ pre_config_update/3, - post_config_update/5 + post_config_update/5, + propagated_pre_config_update/3, + propagated_post_config_update/5 ]). -export([ @@ -37,8 +39,8 @@ -export_type([config/0]). +-include("logger.hrl"). -include("emqx_authentication.hrl"). --include_lib("emqx/include/logger.hrl"). -type parsed_config() :: #{ mechanism := atom(), @@ -145,6 +147,12 @@ do_pre_config_update(Paths, NewConfig, _OldConfig) -> || New <- to_list(NewConfig) ]}. +-spec propagated_pre_config_update(list(atom()), update_request(), emqx_config:raw_config()) -> + ok | {error, term()}. +propagated_pre_config_update(Paths, NewConfig, OldConfig) -> + {ok, _} = do_pre_config_update(Paths, NewConfig, OldConfig), + ok. + -spec post_config_update( list(atom()), update_request(), @@ -203,6 +211,26 @@ do_post_config_update(Paths, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> ok = emqx_authentication:reorder_authenticator(ChainName, NewIds), ok. +-spec propagated_post_config_update( + list(atom()), + update_request(), + map() | list() | undefined, + emqx_config:raw_config(), + emqx_config:app_envs() +) -> + ok | {ok, map()} | {error, term()}. + +propagated_post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs) -> + ok = post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs), + ChainName = chain_name(Paths), + ok = maybe_delete_chain(ChainName, NewConfig), + ok. + +maybe_delete_chain(ChainName, undefined) -> + ok = emqx_authentication:delete_chain(ChainName); +maybe_delete_chain(_ChainName, _NewConfig) -> + ok. + %% create new authenticators and update existing ones create_or_update_authenticators(OldIds, ChainName, NewConfig) -> lists:foreach( diff --git a/apps/emqx_authn/src/emqx_authentication_listener_hooks.erl b/apps/emqx_authn/src/emqx_authentication_listener_hooks.erl deleted file mode 100644 index 1109ca96b..000000000 --- a/apps/emqx_authn/src/emqx_authentication_listener_hooks.erl +++ /dev/null @@ -1,86 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2017-2023 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_authentication_listener_hooks). - --include_lib("emqx/include/emqx_hooks.hrl"). - --export([ - on_listener_started/4, - on_listener_stopped/4, - on_listener_updated/4 -]). - --export([ - load/0, - unload/0 -]). - -%%-------------------------------------------------------------------- -%% API -%%-------------------------------------------------------------------- - -load() -> - ok = emqx_hook:put('listener.started', {?MODULE, on_listener_started, []}, ?HP_AUTHN), - ok = emqx_hook:put('listener.stopped', {?MODULE, on_listener_stopped, []}, ?HP_AUTHN), - ok = emqx_hook:put('listener.updated', {?MODULE, on_listener_updated, []}, ?HP_AUTHN), - ok. - -unload() -> - ok = emqx_hooks:del('listener.started', {?MODULE, authenticate, []}), - ok = emqx_hooks:del('listener.stopped', {?MODULE, authenticate, []}), - ok = emqx_hooks:del('listener.updated', {?MODULE, authenticate, []}), - ok. - -%%-------------------------------------------------------------------- -%% Hooks -%%-------------------------------------------------------------------- - -on_listener_started(Type, Name, Conf, ok) -> - recreate_authenticators(Type, Name, Conf); -on_listener_started(_Type, _Name, _Conf, _Error) -> - ok. - -on_listener_updated(Type, Name, {_OldConf, NewConf}, ok) -> - recreate_authenticators(Type, Name, NewConf); -on_listener_updated(_Type, _Name, _Conf, _Error) -> - ok. - -on_listener_stopped(Type, Name, _OldConf, ok) -> - _ = emqx_authentication:delete_chain(emqx_listeners:listener_id(Type, Name)), - ok; -on_listener_stopped(_Type, _Name, _Conf, _Error) -> - ok. - -%%-------------------------------------------------------------------- -%% Internal functions -%%-------------------------------------------------------------------- - -recreate_authenticators(Type, Name, Conf) -> - Chain = emqx_listeners:listener_id(Type, Name), - _ = emqx_authentication:delete_chain(Chain), - do_create_authneticators(Chain, maps:get(authentication, Conf, [])). - -do_create_authneticators(Chain, [AuthN | T]) -> - case emqx_authentication:create_authenticator(Chain, AuthN) of - {ok, _} -> - do_create_authneticators(Chain, T); - Error -> - _ = emqx_authentication:delete_chain(Chain), - {ok, Error} - end; -do_create_authneticators(_Chain, []) -> - ok. diff --git a/apps/emqx_authn/src/emqx_authn_app.erl b/apps/emqx_authn/src/emqx_authn_app.erl index 9ba6f5fbc..689f6619a 100644 --- a/apps/emqx_authn/src/emqx_authn_app.erl +++ b/apps/emqx_authn/src/emqx_authn_app.erl @@ -35,8 +35,7 @@ %%------------------------------------------------------------------------------ start(_StartType, _StartArgs) -> - %% required by test cases, ensure the injection of - %% EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY + %% required by test cases, ensure the injection of schema _ = emqx_conf_schema:roots(), ok = mria_rlog:wait_for_shards([?AUTH_SHARD], infinity), {ok, Sup} = emqx_authn_sup:start_link(), diff --git a/apps/emqx_authn/test/emqx_authentication_SUITE.erl b/apps/emqx_authn/test/emqx_authentication_SUITE.erl index fb73a3fc1..dfeeb458e 100644 --- a/apps/emqx_authn/test/emqx_authentication_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authentication_SUITE.erl @@ -94,19 +94,19 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - LogLevel = emqx_logger:get_primary_log_level(), - ok = emqx_logger:set_log_level(debug), - application:set_env(ekka, strict_mode, true), - emqx_config:erase_all(), - emqx_common_test_helpers:stop_apps([]), - emqx_common_test_helpers:boot_modules(all), - emqx_common_test_helpers:start_apps([]), - [{log_level, LogLevel} | Config]. + Apps = emqx_cth_suite:start( + [ + emqx, + emqx_conf, + emqx_authn + ], + #{work_dir => ?config(priv_dir)} + ), + ok = deregister_providers(), + [{apps, Apps} | Config]. end_per_suite(Config) -> - emqx_common_test_helpers:stop_apps([]), - LogLevel = ?config(log_level), - emqx_logger:set_log_level(LogLevel), + emqx_cth_suite:stop(?config(apps)), ok. init_per_testcase(Case, Config) -> @@ -302,15 +302,20 @@ t_update_config(Config) when is_list(Config) -> ok = register_provider(?config("auth1"), ?MODULE), ok = register_provider(?config("auth2"), ?MODULE), Global = ?config(global), + %% We mocked provider implementation, but did't mock the schema + %% so we should provide full config AuthenticatorConfig1 = #{ - mechanism => password_based, - backend => built_in_database, - enable => true + <<"mechanism">> => <<"password_based">>, + <<"backend">> => <<"built_in_database">>, + <<"enable">> => true }, AuthenticatorConfig2 = #{ - mechanism => password_based, - backend => mysql, - enable => true + <<"mechanism">> => <<"password_based">>, + <<"backend">> => <<"mysql">>, + <<"query">> => <<"SELECT password_hash, salt FROM users WHERE username = ?">>, + <<"server">> => <<"127.0.0.1:5432">>, + <<"database">> => <<"emqx">>, + <<"enable">> => true }, ID1 = <<"password_based:built_in_database">>, ID2 = <<"password_based:mysql">>, @@ -580,3 +585,11 @@ certs(Certs) -> register_provider(Type, Module) -> ok = ?AUTHN:register_providers([{Type, Module}]). + +deregister_providers() -> + lists:foreach( + fun({Type, _Module}) -> + ok = ?AUTHN:deregister_provider(Type) + end, + lists:flatten([?AUTHN:providers()]) + ). diff --git a/apps/emqx_conf/rebar.config b/apps/emqx_conf/rebar.config index c947932a0..1d2f23bd0 100644 --- a/apps/emqx_conf/rebar.config +++ b/apps/emqx_conf/rebar.config @@ -1,7 +1,10 @@ %% -*- mode: erlang -*- {erl_opts, [debug_info]}. -{deps, [{emqx, {path, "../emqx"}}]}. +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_authn, {path, "../emqx_authn"}} +]}. {shell, [ % {config, "config/sys.config"}, diff --git a/apps/emqx_gateway/rebar.config b/apps/emqx_gateway/rebar.config index 2340a2dd8..e78c8a44b 100644 --- a/apps/emqx_gateway/rebar.config +++ b/apps/emqx_gateway/rebar.config @@ -2,5 +2,6 @@ {erl_opts, [debug_info]}. {deps, [ {emqx, {path, "../emqx"}}, - {emqx_utils, {path, "../emqx_utils"}} + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_authn, {path, "../emqx_authn"}} ]}. diff --git a/apps/emqx_machine/rebar.config b/apps/emqx_machine/rebar.config index 53b7bec13..8953b54a7 100644 --- a/apps/emqx_machine/rebar.config +++ b/apps/emqx_machine/rebar.config @@ -3,6 +3,7 @@ {deps, [ {emqx, {path, "../emqx"}}, {emqx_dashboard, {path, "../emqx_dashboard"}}, + {emqx_conf, {path, "../emqx_conf"}}, {emqx_utils, {path, "../emqx_utils"}} ]}. From ae3355880e06e40ea096f63741adc785981e2b16 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Thu, 3 Aug 2023 20:26:04 +0300 Subject: [PATCH 05/14] chore(auth): update emqx_authn tests --- .../src/emqx_authentication_config.erl | 3 ++- apps/emqx_authn/test/emqx_authn_api_SUITE.erl | 26 ++++++++++++------- .../test/emqx_authn_enable_flag_SUITE.erl | 16 +++++++----- .../emqx_authn/test/emqx_authn_http_SUITE.erl | 13 +++++----- .../test/emqx_authn_https_SUITE.erl | 13 +++++----- apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl | 19 +++++--------- .../test/emqx_authn_mnesia_SUITE.erl | 15 +++++------ .../test/emqx_authn_mongo_SUITE.erl | 15 +++++------ .../test/emqx_authn_mongo_tls_SUITE.erl | 15 +++++------ .../test/emqx_authn_mysql_SUITE.erl | 15 +++++------ .../test/emqx_authn_mysql_tls_SUITE.erl | 15 +++++------ .../test/emqx_authn_pgsql_SUITE.erl | 16 +++++------- .../test/emqx_authn_pgsql_tls_SUITE.erl | 13 +++++----- .../test/emqx_authn_redis_SUITE.erl | 15 +++++------ .../test/emqx_authn_redis_tls_SUITE.erl | 16 +++++------- .../test/emqx_authn_schema_SUITE.erl | 13 +++++----- ...emqx_enhanced_authn_scram_mnesia_SUITE.erl | 11 ++++---- 17 files changed, 123 insertions(+), 126 deletions(-) diff --git a/apps/emqx_authn/src/emqx_authentication_config.erl b/apps/emqx_authn/src/emqx_authentication_config.erl index 13b9214de..cf8ba57b1 100644 --- a/apps/emqx_authn/src/emqx_authentication_config.erl +++ b/apps/emqx_authn/src/emqx_authentication_config.erl @@ -359,7 +359,8 @@ dir(ChainName, Config) when is_map(Config) -> chain_name([authentication]) -> ?GLOBAL; chain_name([listeners, Type, Name, authentication]) -> - binary_to_existing_atom(<<(atom_to_binary(Type))/binary, ":", (atom_to_binary(Name))/binary>>). + %% Type, Name atoms exist, so let 'Type:Name' exist too. + binary_to_atom(<<(atom_to_binary(Type))/binary, ":", (atom_to_binary(Name))/binary>>). merge_authenticators(OriginConf0, NewConf0) -> {OriginConf1, NewConf1} = diff --git a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl index c0b3fe22f..5e740de15 100644 --- a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl @@ -23,6 +23,7 @@ -include("emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). -define(TCP_DEFAULT, 'tcp:default'). @@ -43,7 +44,6 @@ init_per_testcase(t_authenticator_fail, Config) -> meck:expect(emqx_authn_proto_v1, lookup_from_all_nodes, 3, [{error, {exception, badarg}}]), init_per_testcase(default, Config); init_per_testcase(_Case, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authn_test_lib:delete_authenticators( [?CONF_NS_ATOM], ?GLOBAL @@ -64,19 +64,27 @@ end_per_testcase(_, Config) -> Config. init_per_suite(Config) -> - emqx_config:erase(?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY), - _ = application:load(emqx_conf), - ok = emqx_mgmt_api_test_util:init_suite( - [emqx_conf, emqx_authn] + Apps = emqx_cth_suite:start( + [ + emqx, + emqx_conf, + emqx_authn, + emqx_management, + {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"} + ], + #{ + work_dir => ?config(priv_dir, Config) + } ), - + _ = emqx_common_test_http:create_default_app(), ?AUTHN:delete_chain(?GLOBAL), {ok, Chains} = ?AUTHN:list_chains(), ?assertEqual(length(Chains), 0), - Config. + [{apps, Apps} | Config]. -end_per_suite(_Config) -> - emqx_mgmt_api_test_util:end_suite([emqx_authn]), +end_per_suite(Config) -> + _ = emqx_common_test_http:delete_default_app(), + ok = emqx_cth_suite:stop(?config(apps, Config)), ok. %%------------------------------------------------------------------------------ diff --git a/apps/emqx_authn/test/emqx_authn_enable_flag_SUITE.erl b/apps/emqx_authn/test/emqx_authn_enable_flag_SUITE.erl index cc2785b1e..ae2cc436e 100644 --- a/apps/emqx_authn/test/emqx_authn_enable_flag_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_enable_flag_SUITE.erl @@ -24,16 +24,19 @@ -define(PATH, [?CONF_NS_ATOM]). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn]), - Config. + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]. -end_per_suite(_) -> - emqx_common_test_helpers:stop_apps([emqx_authn, emqx_conf]), +end_per_suite(Config) -> + ok = emqx_cth_suite:stop(?config(apps, Config)), ok. init_per_testcase(_Case, Config) -> @@ -42,9 +45,10 @@ init_per_testcase(_Case, Config) -> <<"backend">> => <<"built_in_database">>, <<"user_id_type">> => <<"clientid">> }, - {ok, _} = emqx:update_config( + {ok, _} = emqx_conf:update( ?PATH, - {create_authenticator, ?GLOBAL, AuthnConfig} + {create_authenticator, ?GLOBAL, AuthnConfig}, + #{} ), {ok, _} = emqx_conf:update( [listeners, tcp, listener_authn_enabled], diff --git a/apps/emqx_authn/test/emqx_authn_http_SUITE.erl b/apps/emqx_authn/test/emqx_authn_http_SUITE.erl index b08167a5b..dcd41a28d 100644 --- a/apps/emqx_authn/test/emqx_authn_http_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_http_SUITE.erl @@ -65,18 +65,17 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - _ = application:load(emqx_conf), - emqx_common_test_helpers:start_apps([emqx_authn]), - application:ensure_all_started(cowboy), - Config. + Apps = emqx_cth_suite:start([cowboy, emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]. -end_per_suite(_) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), - emqx_common_test_helpers:stop_apps([emqx_authn]), - application:stop(cowboy), + ok = emqx_cth_suite:stop(?config(apps, Config)), ok. init_per_testcase(_Case, Config) -> diff --git a/apps/emqx_authn/test/emqx_authn_https_SUITE.erl b/apps/emqx_authn/test/emqx_authn_https_SUITE.erl index c4315b69f..6fb8de294 100644 --- a/apps/emqx_authn/test/emqx_authn_https_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_https_SUITE.erl @@ -39,18 +39,17 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - _ = application:load(emqx_conf), - emqx_common_test_helpers:start_apps([emqx_authn]), - application:ensure_all_started(cowboy), - Config. + Apps = emqx_cth_suite:start([cowboy, emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]. -end_per_suite(_) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), - emqx_common_test_helpers:stop_apps([emqx_authn]), - application:stop(cowboy), + ok = emqx_cth_suite:stop(?config(apps, Config)), ok. init_per_testcase(_Case, Config) -> diff --git a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl index bd18367b6..75dfcbc6f 100644 --- a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl @@ -31,21 +31,14 @@ all() -> emqx_common_test_helpers:all(?MODULE). -init_per_testcase(_, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), - Config. - init_per_suite(Config) -> - _ = application:load(emqx_conf), - emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn]), - application:ensure_all_started(emqx_resource), - application:ensure_all_started(emqx_connector), - Config. + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]. -end_per_suite(_) -> - application:stop(emqx_connector), - application:stop(emqx_resource), - emqx_common_test_helpers:stop_apps([emqx_authn]), +end_per_suite(Config) -> + ok = emqx_cth_suite:stop(?config(apps, Config)), ok. %%------------------------------------------------------------------------------ diff --git a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl index 599eae92e..9781b8ca7 100644 --- a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl @@ -20,8 +20,7 @@ -compile(nowarn_export_all). -include_lib("eunit/include/eunit.hrl"). - --include("emqx_authn.hrl"). +-include_lib("common_test/include/ct.hrl"). -define(AUTHN_ID, <<"mechanism:backend">>). @@ -29,16 +28,16 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - _ = application:load(emqx_conf), - emqx_common_test_helpers:start_apps([emqx_authn]), - Config. + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]. -end_per_suite(_) -> - emqx_common_test_helpers:stop_apps([emqx_authn]), +end_per_suite(Config) -> + ok = emqx_cth_suite:stop(?config(apps, Config)), ok. init_per_testcase(_Case, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), mria:clear_table(emqx_authn_mnesia), Config. diff --git a/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl index 07933031e..9ea7f9eb2 100644 --- a/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl @@ -33,7 +33,6 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_testcase(_TestCase, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], @@ -46,23 +45,23 @@ end_per_testcase(_TestCase, _Config) -> ok = mc_worker_api:disconnect(?MONGO_CLIENT). init_per_suite(Config) -> - _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?MONGO_HOST, ?MONGO_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps([emqx_authn]), - ok = start_apps([emqx_resource]), - Config; + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]; false -> {skip, no_mongo} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. %%------------------------------------------------------------------------------ %% Tests diff --git a/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl index 34f906dd9..af550379b 100644 --- a/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl @@ -33,7 +33,6 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_testcase(_TestCase, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], @@ -42,23 +41,23 @@ init_per_testcase(_TestCase, Config) -> Config. init_per_suite(Config) -> - _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?MONGO_HOST, ?MONGO_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps([emqx_authn]), - ok = start_apps([emqx_resource]), - Config; + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]; false -> {skip, no_mongo} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. %%------------------------------------------------------------------------------ %% Tests diff --git a/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl index 914ce4dd1..2173b943b 100644 --- a/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl @@ -37,7 +37,6 @@ groups() -> [{require_seeds, [], [t_authenticate, t_update, t_destroy]}]. init_per_testcase(_, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], @@ -54,11 +53,11 @@ end_per_group(require_seeds, Config) -> Config. init_per_suite(Config) -> - _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?MYSQL_HOST, ?MYSQL_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps([emqx_authn]), - ok = start_apps([emqx_resource]), + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), {ok, _} = emqx_resource:create_local( ?MYSQL_RESOURCE, ?RESOURCE_GROUP, @@ -66,19 +65,19 @@ init_per_suite(Config) -> mysql_config(), #{} ), - Config; + [{apps, Apps} | Config]; false -> {skip, no_mysql} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), ok = emqx_resource:remove_local(?MYSQL_RESOURCE), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. %%------------------------------------------------------------------------------ %% Tests diff --git a/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl index 5d5a3f7ac..888ff5e6b 100644 --- a/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl @@ -36,7 +36,6 @@ groups() -> []. init_per_testcase(_, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], @@ -45,23 +44,23 @@ init_per_testcase(_, Config) -> Config. init_per_suite(Config) -> - _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?MYSQL_HOST, ?MYSQL_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps([emqx_authn]), - ok = start_apps([emqx_resource]), - Config; + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]; false -> {skip, no_mysql_tls} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. %%------------------------------------------------------------------------------ %% Tests diff --git a/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl b/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl index 075ae5cb7..1c9f0f86b 100644 --- a/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl @@ -23,7 +23,6 @@ -include_lib("emqx_authn/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). --include_lib("emqx/include/emqx_placeholder.hrl"). -define(PGSQL_HOST, "pgsql"). -define(PGSQL_RESOURCE, <<"emqx_authn_pgsql_SUITE">>). @@ -42,7 +41,6 @@ groups() -> [{require_seeds, [], [t_create, t_authenticate, t_update, t_destroy, t_is_superuser]}]. init_per_testcase(_, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], @@ -59,11 +57,11 @@ end_per_group(require_seeds, Config) -> Config. init_per_suite(Config) -> - _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?PGSQL_HOST, ?PGSQL_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps([emqx_authn]), - ok = start_apps([emqx_resource]), + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), {ok, _} = emqx_resource:create_local( ?PGSQL_RESOURCE, ?RESOURCE_GROUP, @@ -71,19 +69,19 @@ init_per_suite(Config) -> pgsql_config(), #{} ), - Config; + [{apps, Apps} | Config]; false -> {skip, no_pgsql} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), ok = emqx_resource:remove_local(?PGSQL_RESOURCE), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. %%------------------------------------------------------------------------------ %% Tests diff --git a/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl b/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl index ae0a01572..4862572e6 100644 --- a/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl @@ -48,20 +48,21 @@ init_per_suite(Config) -> _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?PGSQL_HOST, ?PGSQL_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps([emqx_authn]), - ok = start_apps([emqx_resource]), - Config; + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]; false -> {skip, no_pgsql_tls} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. %%------------------------------------------------------------------------------ %% Tests diff --git a/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl b/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl index 31602ecec..c8ae3d2a2 100644 --- a/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl @@ -42,7 +42,6 @@ groups() -> [{require_seeds, [], [t_authenticate, t_update, t_destroy]}]. init_per_testcase(_, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], @@ -59,11 +58,11 @@ end_per_group(require_seeds, Config) -> Config. init_per_suite(Config) -> - _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?REDIS_HOST, ?REDIS_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps([emqx_authn]), - ok = start_apps([emqx_resource]), + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), {ok, _} = emqx_resource:create_local( ?REDIS_RESOURCE, ?RESOURCE_GROUP, @@ -71,19 +70,19 @@ init_per_suite(Config) -> redis_config(), #{} ), - Config; + [{apps, Apps} | Config]; false -> {skip, no_redis} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), ok = emqx_resource:remove_local(?REDIS_RESOURCE), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. %%------------------------------------------------------------------------------ %% Tests diff --git a/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl b/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl index 8df54ebce..291caed1b 100644 --- a/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl @@ -19,7 +19,6 @@ -compile(nowarn_export_all). -compile(export_all). --include_lib("emqx_connector/include/emqx_connector.hrl"). -include_lib("emqx_authn/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -36,7 +35,6 @@ groups() -> []. init_per_testcase(_, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], @@ -45,23 +43,23 @@ init_per_testcase(_, Config) -> Config. init_per_suite(Config) -> - _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?REDIS_HOST, ?REDIS_TLS_PORT) of true -> - ok = emqx_common_test_helpers:start_apps([emqx_authn]), - ok = start_apps([emqx_resource]), - Config; + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]; false -> {skip, no_redis} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. %%------------------------------------------------------------------------------ %% Tests diff --git a/apps/emqx_authn/test/emqx_authn_schema_SUITE.erl b/apps/emqx_authn/test/emqx_authn_schema_SUITE.erl index 3afb8e973..8266ade10 100644 --- a/apps/emqx_authn/test/emqx_authn_schema_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_schema_SUITE.erl @@ -4,6 +4,7 @@ -compile(nowarn_export_all). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). -include("emqx_authn.hrl"). @@ -11,16 +12,16 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - _ = application:load(emqx_conf), - emqx_common_test_helpers:start_apps([emqx_authn]), - Config. + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]. -end_per_suite(_) -> - emqx_common_test_helpers:stop_apps([emqx_authn]), +end_per_suite(Config) -> + ok = emqx_cth_suite:stop(?config(apps, Config)), ok. init_per_testcase(_Case, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), mria:clear_table(emqx_authn_mnesia), Config. diff --git a/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl b/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl index f52e895cc..baaf15175 100644 --- a/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl +++ b/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl @@ -36,17 +36,18 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - _ = application:load(emqx_conf), - ok = emqx_common_test_helpers:start_apps([emqx_authn]), + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), IdleTimeout = emqx_config:get([mqtt, idle_timeout]), - [{idle_timeout, IdleTimeout} | Config]. + [{apps, Apps}, {idle_timeout, IdleTimeout} | Config]. end_per_suite(Config) -> ok = emqx_config:put([mqtt, idle_timeout], ?config(idle_timeout, Config)), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. init_per_testcase(_Case, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), mria:clear_table(emqx_enhanced_authn_scram_mnesia), emqx_authn_test_lib:delete_authenticators( [authentication], From cd88dfbbb8c47b5279c620f66695d4b2f575a46f Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Fri, 4 Aug 2023 18:01:24 +0300 Subject: [PATCH 06/14] chore(auth): update authn includes --- apps/emqx_authn/src/emqx_authentication_config.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx_authn/src/emqx_authentication_config.erl b/apps/emqx_authn/src/emqx_authentication_config.erl index cf8ba57b1..a00d0b555 100644 --- a/apps/emqx_authn/src/emqx_authentication_config.erl +++ b/apps/emqx_authn/src/emqx_authentication_config.erl @@ -39,7 +39,7 @@ -export_type([config/0]). --include("logger.hrl"). +-include_lib("emqx/include/logger.hrl"). -include("emqx_authentication.hrl"). -type parsed_config() :: #{ From 8a3e8ee528124229e4bb8e0ad4f73fe7d856bad3 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Fri, 4 Aug 2023 20:19:07 +0300 Subject: [PATCH 07/14] chore(auth): add config propagation tests for authn --- apps/emqx/src/emqx_config_handler.erl | 39 ++- apps/emqx/test/emqx_config_handler_SUITE.erl | 68 +++-- .../src/emqx_authentication_config.erl | 12 +- .../test/emqx_authn_listeners_SUITE.erl | 242 ++++++++++++++++++ 4 files changed, 316 insertions(+), 45 deletions(-) create mode 100644 apps/emqx_authn/test/emqx_authn_listeners_SUITE.erl diff --git a/apps/emqx/src/emqx_config_handler.erl b/apps/emqx/src/emqx_config_handler.erl index f68992e4e..e634112d7 100644 --- a/apps/emqx/src/emqx_config_handler.erl +++ b/apps/emqx/src/emqx_config_handler.erl @@ -63,7 +63,7 @@ -callback propagated_pre_config_update( [atom()], emqx_config:update_request(), emqx_config:raw_config() ) -> - ok | {error, term()}. + ok | {ok, emqx_config:update_request()} | {error, term()}. -callback post_config_update( [atom()], @@ -401,14 +401,14 @@ get_sub_config(_, _Conf) -> call_pre_config_update(Ctx) -> case call_proper_pre_config_update(Ctx) of - {ok, NewUpdateReq} -> + {ok, NewUpdateReq0} -> case propagate_pre_config_updates_to_subconf(Ctx#{ - update_req => NewUpdateReq + update_req => NewUpdateReq0 }) of - {ok, _} -> - {ok, NewUpdateReq}; + {ok, #{update_req := NewUpdateReq1}} -> + {ok, NewUpdateReq1}; {error, _} = Error -> Error end; @@ -471,12 +471,12 @@ propagate_pre_config_updates_to_subconf_wkey( Keys = propagate_keys(UpdateReq, OldRawConf), propagate_pre_config_updates_to_subconf_keys(Keys, Ctx). -propagate_pre_config_updates_to_subconf_keys([], #{update_req := UpdateReq}) -> - {ok, UpdateReq}; -propagate_pre_config_updates_to_subconf_keys([Key | Keys], Ctx) -> - case propagate_pre_config_updates_to_subconf_key(Key, Ctx) of - ok -> - propagate_pre_config_updates_to_subconf_keys(Keys, Ctx); +propagate_pre_config_updates_to_subconf_keys([], Ctx) -> + {ok, Ctx}; +propagate_pre_config_updates_to_subconf_keys([Key | Keys], Ctx0) -> + case propagate_pre_config_updates_to_subconf_key(Key, Ctx0) of + {ok, Ctx1} -> + propagate_pre_config_updates_to_subconf_keys(Keys, Ctx1); {error, _} = Error -> Error end. @@ -497,8 +497,10 @@ propagate_pre_config_updates_to_subconf_key( SubOldConf = get_sub_config(BinKey, OldRawConf), SubConfKeyPath = ConfKeyPath ++ [AtomKey], case {SubOldConf, SubUpdateReq} of + %% we have handler, but no relevant keys in both configs (new and old), + %% so we don't need to go further {undefined, undefined} -> - ok; + {ok, Ctx}; {_, _} -> case call_pre_config_update(Ctx#{ @@ -509,8 +511,17 @@ propagate_pre_config_updates_to_subconf_key( callback := propagated_pre_config_update }) of - {ok, _SubNewConf1} -> - ok; + {ok, SubNewConf1} -> + %% we update only if the new config is not to be removed + %% i.e. SubUpdateReq is not undefined + case SubUpdateReq of + undefined -> + {ok, Ctx}; + _ -> + {ok, Ctx#{ + update_req := maps:put(BinKey, SubNewConf1, UpdateReq) + }} + end; {error, _} = Error -> Error end diff --git a/apps/emqx/test/emqx_config_handler_SUITE.erl b/apps/emqx/test/emqx_config_handler_SUITE.erl index af2b4996c..ec24b54e6 100644 --- a/apps/emqx/test/emqx_config_handler_SUITE.erl +++ b/apps/emqx/test/emqx_config_handler_SUITE.erl @@ -224,8 +224,8 @@ t_callback_crash(_Config) -> ok = emqx_config_handler:remove_handler(CrashPath), ok. -t_pre_callback_error(_Config) -> - callback_error( +t_pre_assert_update_result(_Config) -> + assert_update_result( [sysmon, os, mem_check_interval], <<"100s">>, {error, {pre_config_update, ?MODULE, pre_config_update_error}} @@ -233,7 +233,7 @@ t_pre_callback_error(_Config) -> ok. t_post_update_error(_Config) -> - callback_error( + assert_update_result( [sysmon, os, sysmem_high_watermark], <<"60%">>, {error, {post_config_update, ?MODULE, post_config_update_error}} @@ -243,7 +243,7 @@ t_post_update_error(_Config) -> t_post_update_propagate_error_wkey(_Config) -> Conf0 = emqx_config:get_raw([sysmon]), Conf1 = emqx_utils_maps:deep_put([<<"os">>, <<"sysmem_high_watermark">>], Conf0, <<"60%">>), - callback_error( + assert_update_result( [ [sysmon, '?', sysmem_high_watermark], [sysmon] @@ -257,7 +257,7 @@ t_post_update_propagate_error_wkey(_Config) -> t_post_update_propagate_error_key(_Config) -> Conf0 = emqx_config:get_raw([sysmon]), Conf1 = emqx_utils_maps:deep_put([<<"os">>, <<"sysmem_high_watermark">>], Conf0, <<"60%">>), - callback_error( + assert_update_result( [ [sysmon, os, sysmem_high_watermark], [sysmon] @@ -271,7 +271,7 @@ t_post_update_propagate_error_key(_Config) -> t_pre_update_propagate_error_wkey(_Config) -> Conf0 = emqx_config:get_raw([sysmon]), Conf1 = emqx_utils_maps:deep_put([<<"os">>, <<"mem_check_interval">>], Conf0, <<"70s">>), - callback_error( + assert_update_result( [ [sysmon, '?', mem_check_interval], [sysmon] @@ -285,7 +285,7 @@ t_pre_update_propagate_error_wkey(_Config) -> t_pre_update_propagate_error_key(_Config) -> Conf0 = emqx_config:get_raw([sysmon]), Conf1 = emqx_utils_maps:deep_put([<<"os">>, <<"mem_check_interval">>], Conf0, <<"70s">>), - callback_error( + assert_update_result( [ [sysmon, os, mem_check_interval], [sysmon] @@ -296,6 +296,25 @@ t_pre_update_propagate_error_key(_Config) -> ), ok. +t_pre_update_propagate_key_rewrite(_Config) -> + Conf0 = emqx_config:get_raw([sysmon]), + Conf1 = emqx_utils_maps:deep_put([<<"os">>, <<"cpu_check_interval">>], Conf0, <<"333s">>), + with_update_result( + [ + [sysmon, '?', cpu_check_interval], + [sysmon] + ], + [sysmon], + Conf1, + fun(_, Result) -> + ?assertMatch( + {ok, #{config := #{os := #{cpu_check_interval := 444000}}}}, + Result + ) + end + ), + ok. + t_handler_root() -> %% Don't rely on default emqx_config_handler's merge behaviour. RootKey = [], @@ -352,6 +371,8 @@ pre_config_update([sysmon, os, sysmem_high_watermark], UpdateReq, _RawConf) -> pre_config_update([sysmon, os, mem_check_interval], _UpdateReq, _RawConf) -> {error, pre_config_update_error}. +propagated_pre_config_update([sysmon, os, cpu_check_interval], <<"333s">>, _RawConf) -> + {ok, <<"444s">>}; propagated_pre_config_update([sysmon, os, mem_check_interval], _UpdateReq, _RawConf) -> {error, pre_config_update_error}; propagated_pre_config_update(_ConfKeyPath, _UpdateReq, _RawConf) -> @@ -386,27 +407,32 @@ wait_for_new_pid() -> Pid end. -callback_error(FailedPath, Update, ExpectError) -> - callback_error([FailedPath], FailedPath, Update, ExpectError). +assert_update_result(FailedPath, Update, Expect) -> + assert_update_result([FailedPath], FailedPath, Update, Expect). -callback_error(Paths, UpdatePath, Update, ExpectError) -> +assert_update_result(Paths, UpdatePath, Update, Expect) -> + with_update_result(Paths, UpdatePath, Update, fun(Old, Result) -> + case Expect of + {error, {post_config_update, ?MODULE, post_config_update_error}} -> + ?assertMatch( + {error, {post_config_update, ?MODULE, {post_config_update_error, _}}}, Result + ); + _ -> + ?assertEqual(Expect, Result) + end, + New = emqx:get_raw_config(UpdatePath, undefined), + ?assertEqual(Old, New) + end). + +with_update_result(Paths, UpdatePath, Update, Fun) -> ok = lists:foreach( fun(Path) -> emqx_config_handler:add_handler(Path, ?MODULE) end, Paths ), Opts = #{rawconf_with_defaults => true}, Old = emqx:get_raw_config(UpdatePath, undefined), - Error = emqx:update_config(UpdatePath, Update, Opts), - case ExpectError of - {error, {post_config_update, ?MODULE, post_config_update_error}} -> - ?assertMatch( - {error, {post_config_update, ?MODULE, {post_config_update_error, _}}}, Error - ); - _ -> - ?assertEqual(ExpectError, Error) - end, - New = emqx:get_raw_config(UpdatePath, undefined), - ?assertEqual(Old, New), + Result = emqx:update_config(UpdatePath, Update, Opts), + _ = Fun(Old, Result), ok = lists:foreach( fun(Path) -> emqx_config_handler:remove_handler(Path) end, Paths diff --git a/apps/emqx_authn/src/emqx_authentication_config.erl b/apps/emqx_authn/src/emqx_authentication_config.erl index a00d0b555..431a2cf49 100644 --- a/apps/emqx_authn/src/emqx_authentication_config.erl +++ b/apps/emqx_authn/src/emqx_authentication_config.erl @@ -150,13 +150,12 @@ do_pre_config_update(Paths, NewConfig, _OldConfig) -> -spec propagated_pre_config_update(list(atom()), update_request(), emqx_config:raw_config()) -> ok | {error, term()}. propagated_pre_config_update(Paths, NewConfig, OldConfig) -> - {ok, _} = do_pre_config_update(Paths, NewConfig, OldConfig), - ok. + do_pre_config_update(Paths, NewConfig, OldConfig). -spec post_config_update( list(atom()), update_request(), - map() | list(), + map() | list() | undefined, emqx_config:raw_config(), emqx_config:app_envs() ) -> @@ -222,13 +221,6 @@ do_post_config_update(Paths, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> propagated_post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs) -> ok = post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs), - ChainName = chain_name(Paths), - ok = maybe_delete_chain(ChainName, NewConfig), - ok. - -maybe_delete_chain(ChainName, undefined) -> - ok = emqx_authentication:delete_chain(ChainName); -maybe_delete_chain(_ChainName, _NewConfig) -> ok. %% create new authenticators and update existing ones diff --git a/apps/emqx_authn/test/emqx_authn_listeners_SUITE.erl b/apps/emqx_authn/test/emqx_authn_listeners_SUITE.erl new file mode 100644 index 000000000..9708bf1bb --- /dev/null +++ b/apps/emqx_authn/test/emqx_authn_listeners_SUITE.erl @@ -0,0 +1,242 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_listeners_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include("emqx_authn.hrl"). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +init_per_suite(Config) -> + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), + [{apps, Apps} | Config]. + +end_per_suite(Config) -> + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. + +init_per_testcase(_Case, Config) -> + Port = emqx_common_test_helpers:select_free_port(tcp), + [{port, Port} | Config]. + +end_per_testcase(_Case, _Config) -> + ok. + +t_create_update_delete(Config) -> + ListenerConf = listener_mqtt_tcp_conf(Config), + AuthnConfig0 = #{ + <<"mechanism">> => <<"password_based">>, + <<"backend">> => <<"built_in_database">>, + <<"user_id_type">> => <<"clientid">> + }, + %% Create + {ok, _} = emqx_conf:update( + [listeners], + #{ + <<"tcp">> => #{ + <<"listener0">> => ListenerConf#{ + ?CONF_NS_BINARY => AuthnConfig0 + } + } + }, + #{} + ), + ?assertMatch( + {ok, [ + #{ + authenticators := [ + #{ + id := <<"password_based:built_in_database">>, + state := #{ + user_id_type := clientid + } + } + ], + name := 'tcp:listener0' + } + ]}, + emqx_authentication:list_chains() + ), + + %% Drop old, create new + {ok, _} = emqx_conf:update( + [listeners], + #{ + <<"tcp">> => #{ + <<"listener1">> => ListenerConf#{ + ?CONF_NS_BINARY => AuthnConfig0 + } + } + }, + #{} + ), + ?assertMatch( + {ok, [ + #{ + authenticators := [ + #{ + id := <<"password_based:built_in_database">>, + state := #{ + user_id_type := clientid + } + } + ], + name := 'tcp:listener1' + } + ]}, + emqx_authentication:list_chains() + ), + + %% Update + {ok, _} = emqx_conf:update( + [listeners], + #{ + <<"tcp">> => #{ + <<"listener1">> => ListenerConf#{ + ?CONF_NS_BINARY => AuthnConfig0#{<<"user_id_type">> => <<"username">>} + } + } + }, + #{} + ), + ?assertMatch( + {ok, [ + #{ + authenticators := [ + #{ + id := <<"password_based:built_in_database">>, + state := #{ + user_id_type := username + } + } + ], + name := 'tcp:listener1' + } + ]}, + emqx_authentication:list_chains() + ), + + %% Update by listener path + {ok, _} = emqx_conf:update( + [listeners, tcp, listener1], + {update, ListenerConf#{ + ?CONF_NS_BINARY => AuthnConfig0#{<<"user_id_type">> => <<"clientid">>} + }}, + #{} + ), + ?assertMatch( + {ok, [ + #{ + authenticators := [ + #{ + id := <<"password_based:built_in_database">>, + state := #{ + user_id_type := clientid + } + } + ], + name := 'tcp:listener1' + } + ]}, + emqx_authentication:list_chains() + ), + + %% Delete + {ok, _} = emqx_conf:tombstone( + [listeners, tcp, listener1], + #{} + ), + ?assertMatch( + {ok, []}, + emqx_authentication:list_chains() + ). + +t_convert_certs(Config) -> + ListenerConf = listener_mqtt_tcp_conf(Config), + AuthnConfig0 = #{ + <<"mechanism">> => <<"password_based">>, + <<"password_hash_algorithm">> => #{ + <<"name">> => <<"plain">>, + <<"salt_position">> => <<"suffix">> + }, + <<"enable">> => <<"true">>, + + <<"backend">> => <<"redis">>, + <<"cmd">> => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>, + <<"database">> => <<"1">>, + <<"password">> => <<"public">>, + <<"server">> => <<"127.0.0.1:55555">>, + <<"redis_type">> => <<"single">>, + <<"ssl">> => #{ + <<"enable">> => true, + <<"cacertfile">> => some_pem(), + <<"certfile">> => some_pem(), + <<"keyfile">> => some_pem() + } + }, + {ok, _} = emqx_conf:update( + [listeners], + #{ + <<"tcp">> => #{ + <<"listener0">> => ListenerConf#{ + ?CONF_NS_BINARY => AuthnConfig0 + } + } + }, + #{} + ), + lists:foreach( + fun(Key) -> + [#{ssl := #{Key := FilePath}}] = emqx_config:get([ + listeners, tcp, listener0, authentication + ]), + ?assert(filelib:is_regular(FilePath)) + end, + [cacertfile, certfile, keyfile] + ). + +%%-------------------------------------------------------------------- +%% Helper Functions +%%-------------------------------------------------------------------- + +listener_mqtt_tcp_conf(Config) -> + Port = ?config(port, Config), + PortS = integer_to_binary(Port), + #{ + <<"acceptors">> => 16, + <<"access_rules">> => [<<"allow all">>], + <<"bind">> => <<"0.0.0.0:", PortS/binary>>, + <<"max_connections">> => 1024000, + <<"mountpoint">> => <<>>, + <<"proxy_protocol">> => false, + <<"proxy_protocol_timeout">> => <<"3s">>, + <<"enable_authn">> => true + }. + +some_pem() -> + Dir = code:lib_dir(emqx_authn, test), + Path = filename:join([Dir, "data", "private_key.pem"]), + {ok, Pem} = file:read_file(Path), + Pem. From f00f4568dc2ac5e0a20b9ca3a9942daa78dd69f2 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Mon, 7 Aug 2023 11:38:21 +0300 Subject: [PATCH 08/14] chore(auth): simplify schema injection --- apps/emqx/src/emqx_schema.erl | 4 ++-- apps/emqx/src/emqx_schema_hooks.erl | 22 +++++++--------------- apps/emqx_authn/src/emqx_authn_schema.erl | 4 ++-- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 3f4507a3c..40654a84b 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -222,7 +222,7 @@ roots(high) -> ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME), #{importance => ?IMPORTANCE_HIDDEN} )} - ] ++ emqx_schema_hooks:injection_point('roots.high'); + ] ++ emqx_schema_hooks:injection_point('authentication'); roots(medium) -> [ {"broker", @@ -1749,7 +1749,7 @@ mqtt_listener(Bind) -> default => <<"3s">> } )} - ] ++ emqx_schema_hooks:injection_point('mqtt.listener'). + ] ++ emqx_schema_hooks:injection_point('listeners.authentication'). base_listener(Bind) -> [ diff --git a/apps/emqx/src/emqx_schema_hooks.erl b/apps/emqx/src/emqx_schema_hooks.erl index 59fb1588b..fd80db635 100644 --- a/apps/emqx/src/emqx_schema_hooks.erl +++ b/apps/emqx/src/emqx_schema_hooks.erl @@ -28,9 +28,7 @@ -define(MODULE_PT_KEY(MOD_NAME), {?MODULE, mod, MOD_NAME}). -export([ - inject_fields/3, injection_point/1, - inject_fields_from_mod/1 ]). @@ -45,17 +43,7 @@ %%-------------------------------------------------------------------- injection_point(PointName) -> - InjectedFields = persistent_term:get(?HOOKPOINT_PT_KEY(PointName), #{}), - lists:concat(maps:values(InjectedFields)). - -inject_fields(PointName, Name, Fields) -> - Key = ?HOOKPOINT_PT_KEY(PointName), - InjectedFields = persistent_term:get(Key, #{}), - persistent_term:put(Key, InjectedFields#{Name => Fields}). - -%%-------------------------------------------------------------------- -%% Internal API -%%-------------------------------------------------------------------- + persistent_term:get(?HOOKPOINT_PT_KEY(PointName), []). inject_fields_from_mod(Module) -> case persistent_term:get(?MODULE_PT_KEY(Module), false) of @@ -105,10 +93,14 @@ do_inject_fields_from_mod(Module) -> ok end. -do_inject_fields_from_mod(Module, HookFields) -> +do_inject_fields_from_mod(_Module, HookFields) -> maps:foreach( fun(PointName, Fields) -> - inject_fields(PointName, Module, Fields) + inject_fields(PointName, Fields) end, HookFields ). + +inject_fields(PointName, Fields) -> + Key = ?HOOKPOINT_PT_KEY(PointName), + persistent_term:put(Key, Fields). diff --git a/apps/emqx_authn/src/emqx_authn_schema.erl b/apps/emqx_authn/src/emqx_authn_schema.erl index 2a12899aa..f77158f0c 100644 --- a/apps/emqx_authn/src/emqx_authn_schema.erl +++ b/apps/emqx_authn/src/emqx_authn_schema.erl @@ -42,8 +42,8 @@ roots() -> []. injected_fields() -> #{ - 'roots.high' => global_auth_fields(), - 'mqtt.listener' => mqtt_listener_auth_fields() + 'authentication' => global_auth_fields(), + 'listeners.authentication' => mqtt_listener_auth_fields() }. tags() -> From abcdf18ca4500bb8bfdeca2dded8b67b5c0563b2 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Mon, 7 Aug 2023 13:47:58 +0300 Subject: [PATCH 09/14] chore(auth): update ldap authn test --- apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl b/apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl index d3b7a90f4..5167d2267 100644 --- a/apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl +++ b/apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl @@ -8,6 +8,7 @@ -include_lib("emqx_authn/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). -define(LDAP_HOST, "ldap"). -define(LDAP_DEFAULT_PORT, 389). @@ -20,7 +21,6 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_testcase(_, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], @@ -32,8 +32,9 @@ init_per_suite(Config) -> _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?LDAP_HOST, ?LDAP_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps([emqx_authn]), - ok = start_apps([emqx_resource]), + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + work_dir => ?config(priv_dir, Config) + }), {ok, _} = emqx_resource:create_local( ?LDAP_RESOURCE, ?RESOURCE_GROUP, @@ -41,19 +42,18 @@ init_per_suite(Config) -> ldap_config(), #{} ), - Config; + [{apps, Apps} | Config]; false -> {skip, no_ldap} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL ), ok = emqx_resource:remove_local(?LDAP_RESOURCE), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + ok = emqx_cth_suite:stop(?config(apps, Config)). %%------------------------------------------------------------------------------ %% Tests From 67e06b317183df7c296b18b91501f5ec428a2736 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Mon, 7 Aug 2023 19:29:50 +0300 Subject: [PATCH 10/14] chore(auth): make schema injection be more universal --- apps/emqx/src/emqx_schema.erl | 4 +- apps/emqx/src/emqx_schema_hooks.erl | 74 +++++++++++++---------- apps/emqx_authn/src/emqx_authn_schema.erl | 4 +- apps/emqx_conf/src/emqx_conf_schema.erl | 8 +-- 4 files changed, 48 insertions(+), 42 deletions(-) diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 40654a84b..3f4507a3c 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -222,7 +222,7 @@ roots(high) -> ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME), #{importance => ?IMPORTANCE_HIDDEN} )} - ] ++ emqx_schema_hooks:injection_point('authentication'); + ] ++ emqx_schema_hooks:injection_point('roots.high'); roots(medium) -> [ {"broker", @@ -1749,7 +1749,7 @@ mqtt_listener(Bind) -> default => <<"3s">> } )} - ] ++ emqx_schema_hooks:injection_point('listeners.authentication'). + ] ++ emqx_schema_hooks:injection_point('mqtt.listener'). base_listener(Bind) -> [ diff --git a/apps/emqx/src/emqx_schema_hooks.erl b/apps/emqx/src/emqx_schema_hooks.erl index fd80db635..bd50a069f 100644 --- a/apps/emqx/src/emqx_schema_hooks.erl +++ b/apps/emqx/src/emqx_schema_hooks.erl @@ -25,11 +25,12 @@ -optional_callbacks([injected_fields/0]). -define(HOOKPOINT_PT_KEY(POINT_NAME), {?MODULE, fields, POINT_NAME}). --define(MODULE_PT_KEY(MOD_NAME), {?MODULE, mod, MOD_NAME}). -export([ injection_point/1, - inject_fields_from_mod/1 + any_injections/1, + inject_fields/2, + inject_from_modules/1 ]). %% for tests @@ -45,22 +46,15 @@ injection_point(PointName) -> persistent_term:get(?HOOKPOINT_PT_KEY(PointName), []). -inject_fields_from_mod(Module) -> - case persistent_term:get(?MODULE_PT_KEY(Module), false) of - false -> - persistent_term:put(?MODULE_PT_KEY(Module), true), - do_inject_fields_from_mod(Module); - true -> - ok - end. +inject_fields(PointName, Fields) -> + Key = ?HOOKPOINT_PT_KEY(PointName), + persistent_term:put(Key, Fields). erase_injections() -> lists:foreach( fun ({?HOOKPOINT_PT_KEY(_) = Key, _}) -> persistent_term:erase(Key); - ({?MODULE_PT_KEY(_) = Key, _}) -> - persistent_term:erase(Key); (_) -> ok end, @@ -72,35 +66,53 @@ any_injections() -> fun ({?HOOKPOINT_PT_KEY(_), _}) -> true; - ({?MODULE_PT_KEY(_), _}) -> - true; (_) -> false end, persistent_term:get() ). +any_injections(PointName) -> + persistent_term:get(?HOOKPOINT_PT_KEY(PointName), undefined) =/= undefined. + +inject_from_modules(Modules) -> + Injections = + lists:foldl( + fun append_module_injections/2, + #{}, + Modules + ), + ok = inject_fields(maps:to_list(Injections)). + %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- -do_inject_fields_from_mod(Module) -> - _ = Module:module_info(), - case erlang:function_exported(Module, injected_fields, 0) of - true -> - do_inject_fields_from_mod(Module, Module:injected_fields()); - false -> - ok - end. - -do_inject_fields_from_mod(_Module, HookFields) -> - maps:foreach( - fun(PointName, Fields) -> - inject_fields(PointName, Fields) +append_module_injections(Module, AllInjections) when is_atom(Module) -> + append_module_injections(Module:injected_fields(), AllInjections); +append_module_injections(ModuleInjections, AllInjections) when is_map(ModuleInjections) -> + maps:fold( + fun(PointName, Fields, Acc) -> + maps:update_with( + PointName, + fun(Fields0) -> + Fields0 ++ Fields + end, + Fields, + Acc + ) end, - HookFields + AllInjections, + ModuleInjections ). -inject_fields(PointName, Fields) -> - Key = ?HOOKPOINT_PT_KEY(PointName), - persistent_term:put(Key, Fields). +inject_fields([]) -> + ok; +inject_fields([{PointName, Fields} | Rest]) -> + case emqx_schema_hooks:any_injections(PointName) of + true -> + inject_fields(Rest); + false -> + ok = emqx_schema_hooks:inject_fields(PointName, Fields), + inject_fields(Rest) + end. diff --git a/apps/emqx_authn/src/emqx_authn_schema.erl b/apps/emqx_authn/src/emqx_authn_schema.erl index f77158f0c..b0a68e702 100644 --- a/apps/emqx_authn/src/emqx_authn_schema.erl +++ b/apps/emqx_authn/src/emqx_authn_schema.erl @@ -42,8 +42,8 @@ roots() -> []. injected_fields() -> #{ - 'authentication' => global_auth_fields(), - 'listeners.authentication' => mqtt_listener_auth_fields() + 'mqtt.listener' => global_auth_fields(), + 'roots.high' => mqtt_listener_auth_fields() }. tags() -> diff --git a/apps/emqx_conf/src/emqx_conf_schema.erl b/apps/emqx_conf/src/emqx_conf_schema.erl index 14cd4f17c..5b73b9e03 100644 --- a/apps/emqx_conf/src/emqx_conf_schema.erl +++ b/apps/emqx_conf/src/emqx_conf_schema.erl @@ -80,7 +80,7 @@ tags() -> [<<"EMQX">>]. roots() -> - ok = ensure_fields_injected(), + ok = emqx_schema_hooks:inject_from_modules(?INJECTING_CONFIGS), emqx_schema_high_prio_roots() ++ [ {"node", @@ -1434,9 +1434,3 @@ ensure_unicode_path(Path, _) when is_list(Path) -> Path; ensure_unicode_path(Path, _) -> throw({"not_string", Path}). - -ensure_fields_injected() -> - lists:foreach( - fun(Module) -> emqx_schema_hooks:inject_fields_from_mod(Module) end, - ?INJECTING_CONFIGS - ). From db31e5f0d946071c2cd560c7cdf4de49e7f6200c Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Tue, 8 Aug 2023 11:52:10 +0300 Subject: [PATCH 11/14] chore(auth): tidy up the code --- apps/emqx/include/emqx_hooks.hrl | 1 - apps/emqx/src/emqx_access_control.erl | 13 ++++------- apps/emqx/src/emqx_schema_hooks.erl | 22 +++++++++---------- .../include/emqx_authentication.hrl | 10 ++++----- apps/emqx_authn/src/emqx_authentication.erl | 8 +------ .../src/emqx_authentication_config.erl | 5 ++--- .../test/emqx_authentication_SUITE.erl | 2 +- 7 files changed, 24 insertions(+), 37 deletions(-) diff --git a/apps/emqx/include/emqx_hooks.hrl b/apps/emqx/include/emqx_hooks.hrl index dffc09fcd..2373b5928 100644 --- a/apps/emqx/include/emqx_hooks.hrl +++ b/apps/emqx/include/emqx_hooks.hrl @@ -29,7 +29,6 @@ -define(HP_RETAINER, 930). -define(HP_AUTO_SUB, 920). -define(HP_RULE_ENGINE, 900). - %% apps that can work with the republish action -define(HP_SLOW_SUB, 880). -define(HP_BRIDGE, 870). diff --git a/apps/emqx/src/emqx_access_control.erl b/apps/emqx/src/emqx_access_control.erl index a8ffadb44..82604710a 100644 --- a/apps/emqx/src/emqx_access_control.erl +++ b/apps/emqx/src/emqx_access_control.erl @@ -30,8 +30,8 @@ -compile(nowarn_export_all). -endif. --define(TRACE_RESULT(Label, Tag, Result, Reason), begin - ?TRACE(Label, Tag, #{ +-define(TRACE_RESULT(Label, Result, Reason), begin + ?TRACE(Label, ?AUTHN_TRACE_TAG, #{ result => (Result), reason => (Reason) }), @@ -115,18 +115,13 @@ authorize(ClientInfo, Action, Topic) -> -spec pre_hook_authenticate(emqx_types:clientinfo()) -> ok | continue | {error, not_authorized}. pre_hook_authenticate(#{enable_authn := false}) -> - ?TRACE_RESULT("pre_hook_authenticate", ?AUTHN_TRACE_TAG, ok, enable_authn_false); + ?TRACE_RESULT("pre_hook_authenticate", ok, enable_authn_false); pre_hook_authenticate(#{enable_authn := quick_deny_anonymous} = Credential) -> case is_username_defined(Credential) of true -> continue; false -> - ?TRACE_RESULT( - "pre_hook_authenticate", - ?AUTHN_TRACE_TAG, - {error, not_authorized}, - enable_authn_false - ) + ?TRACE_RESULT("pre_hook_authenticate", {error, not_authorized}, enable_authn_false) end; pre_hook_authenticate(_) -> continue. diff --git a/apps/emqx/src/emqx_schema_hooks.erl b/apps/emqx/src/emqx_schema_hooks.erl index bd50a069f..e704af6cc 100644 --- a/apps/emqx/src/emqx_schema_hooks.erl +++ b/apps/emqx/src/emqx_schema_hooks.erl @@ -24,12 +24,12 @@ }. -optional_callbacks([injected_fields/0]). +-export_type([hookpoint/0]). + -define(HOOKPOINT_PT_KEY(POINT_NAME), {?MODULE, fields, POINT_NAME}). -export([ injection_point/1, - any_injections/1, - inject_fields/2, inject_from_modules/1 ]). @@ -46,10 +46,6 @@ injection_point(PointName) -> persistent_term:get(?HOOKPOINT_PT_KEY(PointName), []). -inject_fields(PointName, Fields) -> - Key = ?HOOKPOINT_PT_KEY(PointName), - persistent_term:put(Key, Fields). - erase_injections() -> lists:foreach( fun @@ -72,9 +68,6 @@ any_injections() -> persistent_term:get() ). -any_injections(PointName) -> - persistent_term:get(?HOOKPOINT_PT_KEY(PointName), undefined) =/= undefined. - inject_from_modules(Modules) -> Injections = lists:foldl( @@ -109,10 +102,17 @@ append_module_injections(ModuleInjections, AllInjections) when is_map(ModuleInje inject_fields([]) -> ok; inject_fields([{PointName, Fields} | Rest]) -> - case emqx_schema_hooks:any_injections(PointName) of + case any_injections(PointName) of true -> inject_fields(Rest); false -> - ok = emqx_schema_hooks:inject_fields(PointName, Fields), + ok = inject_fields(PointName, Fields), inject_fields(Rest) end. + +inject_fields(PointName, Fields) -> + Key = ?HOOKPOINT_PT_KEY(PointName), + persistent_term:put(Key, Fields). + +any_injections(PointName) -> + persistent_term:get(?HOOKPOINT_PT_KEY(PointName), undefined) =/= undefined. diff --git a/apps/emqx_authn/include/emqx_authentication.hrl b/apps/emqx_authn/include/emqx_authentication.hrl index a367af291..c294b8d99 100644 --- a/apps/emqx_authn/include/emqx_authentication.hrl +++ b/apps/emqx_authn/include/emqx_authentication.hrl @@ -20,11 +20,6 @@ -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_access_control.hrl"). -%% config root name all auth providers have to agree on. --define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, "authentication"). --define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication). --define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, <<"authentication">>). - -define(GLOBAL, 'mqtt:global'). -define(TRACE_AUTHN_PROVIDER(Msg), ?TRACE_AUTHN_PROVIDER(Msg, #{})). @@ -36,6 +31,11 @@ -define(TRACE_AUTHN(Msg, Meta), ?TRACE_AUTHN(debug, Msg, Meta)). -define(TRACE_AUTHN(Level, Msg, Meta), ?TRACE(Level, ?AUTHN_TRACE_TAG, Msg, Meta)). +%% config root name all auth providers have to agree on. +-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, "authentication"). +-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication). +-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, <<"authentication">>). + %% authentication move cmd -define(CMD_MOVE_FRONT, front). -define(CMD_MOVE_REAR, rear). diff --git a/apps/emqx_authn/src/emqx_authentication.erl b/apps/emqx_authn/src/emqx_authentication.erl index 5b49f5d28..8f055e049 100644 --- a/apps/emqx_authn/src/emqx_authentication.erl +++ b/apps/emqx_authn/src/emqx_authentication.erl @@ -60,7 +60,6 @@ register_providers/1, deregister_provider/1, deregister_providers/1, - providers/0, delete_chain/1, lookup_chain/1, list_chains/0, @@ -266,6 +265,7 @@ get_enabled(Authenticators) -> %%------------------------------------------------------------------------------ %% @doc Get all registered authentication providers. +-spec get_providers() -> #{authn_type() => module()}. get_providers() -> call(get_providers). @@ -332,10 +332,6 @@ deregister_providers(AuthNTypes) when is_list(AuthNTypes) -> deregister_provider(AuthNType) -> deregister_providers([AuthNType]). --spec providers() -> [{authn_type(), module()}]. -providers() -> - call(providers). - -spec delete_chain(chain_name()) -> ok | {error, term()}. delete_chain(Name) -> call({delete_chain, Name}). @@ -468,8 +464,6 @@ handle_call( end; handle_call({deregister_providers, AuthNTypes}, _From, #{providers := Providers} = State) -> reply(ok, State#{providers := maps:without(AuthNTypes, Providers)}); -handle_call(providers, _From, #{providers := Providers} = State) -> - reply(maps:to_list(Providers), State); handle_call({delete_chain, ChainName}, _From, State) -> UpdateFun = fun(Chain) -> {_MatchedIDs, NewChain} = do_delete_authenticators(fun(_) -> true end, Chain), diff --git a/apps/emqx_authn/src/emqx_authentication_config.erl b/apps/emqx_authn/src/emqx_authentication_config.erl index 431a2cf49..97d082da6 100644 --- a/apps/emqx_authn/src/emqx_authentication_config.erl +++ b/apps/emqx_authn/src/emqx_authentication_config.erl @@ -148,7 +148,7 @@ do_pre_config_update(Paths, NewConfig, _OldConfig) -> ]}. -spec propagated_pre_config_update(list(atom()), update_request(), emqx_config:raw_config()) -> - ok | {error, term()}. + {ok, map() | list()} | {error, term()}. propagated_pre_config_update(Paths, NewConfig, OldConfig) -> do_pre_config_update(Paths, NewConfig, OldConfig). @@ -217,8 +217,7 @@ do_post_config_update(Paths, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> emqx_config:raw_config(), emqx_config:app_envs() ) -> - ok | {ok, map()} | {error, term()}. - + ok. propagated_post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs) -> ok = post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs), ok. diff --git a/apps/emqx_authn/test/emqx_authentication_SUITE.erl b/apps/emqx_authn/test/emqx_authentication_SUITE.erl index dfeeb458e..a15f22c41 100644 --- a/apps/emqx_authn/test/emqx_authentication_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authentication_SUITE.erl @@ -591,5 +591,5 @@ deregister_providers() -> fun({Type, _Module}) -> ok = ?AUTHN:deregister_provider(Type) end, - lists:flatten([?AUTHN:providers()]) + maps:to_list(?AUTHN:get_providers()) ). From 0dae0400478bf2f034613fbe9a90a1d687c0377e Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Wed, 16 Aug 2023 20:26:43 +0300 Subject: [PATCH 12/14] chore(auth): restore injected schema key position --- apps/emqx/src/emqx_schema.erl | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 3f4507a3c..84695981a 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -214,15 +214,18 @@ roots(high) -> desc => ?DESC(zones), importance => ?IMPORTANCE_HIDDEN } - )}, - %% NOTE: authorization schema here is only to keep emqx app pure - %% the full schema for EMQX node is injected in emqx_conf_schema. - {?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, - sc( - ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME), - #{importance => ?IMPORTANCE_HIDDEN} )} - ] ++ emqx_schema_hooks:injection_point('roots.high'); + ] ++ + emqx_schema_hooks:injection_point('roots.high') ++ + [ + %% NOTE: authorization schema here is only to keep emqx app pure + %% the full schema for EMQX node is injected in emqx_conf_schema. + {?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, + sc( + ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME), + #{importance => ?IMPORTANCE_HIDDEN} + )} + ]; roots(medium) -> [ {"broker", From 3943830eae0f40ce7bf954a2dd940d15b4f0cf4b Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Wed, 16 Aug 2023 20:27:36 +0300 Subject: [PATCH 13/14] chore(auth): remove unnecessary atom conversions --- apps/emqx/src/emqx_config_handler.erl | 24 +++++-- apps/emqx/test/emqx_config_handler_SUITE.erl | 8 ++- .../src/emqx_authentication_config.erl | 62 ++++++++++++------- apps/emqx_conf/src/emqx_conf.app.src | 2 +- 4 files changed, 65 insertions(+), 31 deletions(-) diff --git a/apps/emqx/src/emqx_config_handler.erl b/apps/emqx/src/emqx_config_handler.erl index e634112d7..a189fc9e5 100644 --- a/apps/emqx/src/emqx_config_handler.erl +++ b/apps/emqx/src/emqx_config_handler.erl @@ -61,7 +61,7 @@ -callback pre_config_update([atom()], emqx_config:update_request(), emqx_config:raw_config()) -> ok | {ok, emqx_config:update_request()} | {error, term()}. -callback propagated_pre_config_update( - [atom()], emqx_config:update_request(), emqx_config:raw_config() + [binary()], emqx_config:update_request(), emqx_config:raw_config() ) -> ok | {ok, emqx_config:update_request()} | {error, term()}. @@ -264,7 +264,8 @@ do_update_config([], Handlers, OldRawConf, UpdateReq, ConfKeyPath) -> old_raw_conf => OldRawConf, update_req => UpdateReq, conf_key_path => ConfKeyPath, - callback => pre_config_update + callback => pre_config_update, + is_propagated => false }); do_update_config( [ConfKey | SubConfKeyPath], @@ -391,6 +392,12 @@ get_sub_handlers(ConfKey, Handlers) when is_atom(ConfKey) -> case maps:find(ConfKey, Handlers) of error -> maps:get(?WKEY, Handlers, #{}); {ok, SubHandlers} -> SubHandlers + end; +get_sub_handlers(ConfKey, Handlers) when is_binary(ConfKey) -> + ConcreteHandlerKeys = maps:keys(Handlers) -- [?MOD, ?WKEY], + case lists:search(fun(K) -> bin(K) =:= ConfKey end, ConcreteHandlerKeys) of + {value, Key} -> maps:get(Key, Handlers); + false -> maps:get(?WKEY, Handlers, #{}) end. get_sub_config(ConfKey, Conf) when is_map(Conf) -> @@ -487,15 +494,19 @@ propagate_pre_config_updates_to_subconf_key( handlers := Handlers, old_raw_conf := OldRawConf, update_req := UpdateReq, - conf_key_path := ConfKeyPath + conf_key_path := ConfKeyPath, + is_propagated := IsPropagated } = Ctx ) -> - AtomKey = atom(Key), - SubHandlers = get_sub_handlers(AtomKey, Handlers), BinKey = bin(Key), + SubHandlers = get_sub_handlers(BinKey, Handlers), SubUpdateReq = get_sub_config(BinKey, UpdateReq), SubOldConf = get_sub_config(BinKey, OldRawConf), - SubConfKeyPath = ConfKeyPath ++ [AtomKey], + SubConfKeyPath = + case IsPropagated of + true -> ConfKeyPath ++ [BinKey]; + false -> bin_path(ConfKeyPath) ++ [BinKey] + end, case {SubOldConf, SubUpdateReq} of %% we have handler, but no relevant keys in both configs (new and old), %% so we don't need to go further @@ -508,6 +519,7 @@ propagate_pre_config_updates_to_subconf_key( old_raw_conf := SubOldConf, update_req := SubUpdateReq, conf_key_path := SubConfKeyPath, + is_propagated := true, callback := propagated_pre_config_update }) of diff --git a/apps/emqx/test/emqx_config_handler_SUITE.erl b/apps/emqx/test/emqx_config_handler_SUITE.erl index ec24b54e6..bb91bcbe4 100644 --- a/apps/emqx/test/emqx_config_handler_SUITE.erl +++ b/apps/emqx/test/emqx_config_handler_SUITE.erl @@ -371,9 +371,13 @@ pre_config_update([sysmon, os, sysmem_high_watermark], UpdateReq, _RawConf) -> pre_config_update([sysmon, os, mem_check_interval], _UpdateReq, _RawConf) -> {error, pre_config_update_error}. -propagated_pre_config_update([sysmon, os, cpu_check_interval], <<"333s">>, _RawConf) -> +propagated_pre_config_update( + [<<"sysmon">>, <<"os">>, <<"cpu_check_interval">>], <<"333s">>, _RawConf +) -> {ok, <<"444s">>}; -propagated_pre_config_update([sysmon, os, mem_check_interval], _UpdateReq, _RawConf) -> +propagated_pre_config_update( + [<<"sysmon">>, <<"os">>, <<"mem_check_interval">>], _UpdateReq, _RawConf +) -> {error, pre_config_update_error}; propagated_pre_config_update(_ConfKeyPath, _UpdateReq, _RawConf) -> ok. diff --git a/apps/emqx_authn/src/emqx_authentication_config.erl b/apps/emqx_authn/src/emqx_authentication_config.erl index 97d082da6..95140a0e8 100644 --- a/apps/emqx_authn/src/emqx_authentication_config.erl +++ b/apps/emqx_authn/src/emqx_authentication_config.erl @@ -67,8 +67,8 @@ -spec pre_config_update(list(atom()), update_request(), emqx_config:raw_config()) -> {ok, map() | list()} | {error, term()}. -pre_config_update(Paths, UpdateReq, OldConfig) -> - try do_pre_config_update(Paths, UpdateReq, to_list(OldConfig)) of +pre_config_update(ConfPath, UpdateReq, OldConfig) -> + try do_pre_config_update(ConfPath, UpdateReq, to_list(OldConfig)) of {error, Reason} -> {error, Reason}; {ok, NewConfig} -> {ok, NewConfig} catch @@ -132,25 +132,22 @@ do_pre_config_update(_, {move_authenticator, _ChainName, AuthenticatorID, Positi end end end; -do_pre_config_update(Paths, {merge_authenticators, NewConfig}, OldConfig) -> +do_pre_config_update(ConfPath, {merge_authenticators, NewConfig}, OldConfig) -> MergeConfig = merge_authenticators(OldConfig, NewConfig), - do_pre_config_update(Paths, MergeConfig, OldConfig); + do_pre_config_update(ConfPath, MergeConfig, OldConfig); do_pre_config_update(_, OldConfig, OldConfig) -> {ok, OldConfig}; -do_pre_config_update(Paths, NewConfig, _OldConfig) -> - ChainName = chain_name(Paths), - {ok, [ - begin - CertsDir = certs_dir(ChainName, New), - convert_certs(CertsDir, New) - end - || New <- to_list(NewConfig) - ]}. +do_pre_config_update(ConfPath, NewConfig, _OldConfig) -> + convert_certs_for_conf_path(ConfPath, NewConfig). --spec propagated_pre_config_update(list(atom()), update_request(), emqx_config:raw_config()) -> +%% @doc Handle listener config changes made at higher level. + +-spec propagated_pre_config_update(list(binary()), update_request(), emqx_config:raw_config()) -> {ok, map() | list()} | {error, term()}. -propagated_pre_config_update(Paths, NewConfig, OldConfig) -> - do_pre_config_update(Paths, NewConfig, OldConfig). +propagated_pre_config_update(_, OldConfig, OldConfig) -> + {ok, OldConfig}; +propagated_pre_config_update(ConfPath, NewConfig, _OldConfig) -> + convert_certs_for_conf_path(ConfPath, NewConfig). -spec post_config_update( list(atom()), @@ -160,8 +157,8 @@ propagated_pre_config_update(Paths, NewConfig, OldConfig) -> emqx_config:app_envs() ) -> ok | {ok, map()} | {error, term()}. -post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs) -> - do_post_config_update(Paths, UpdateReq, to_list(NewConfig), OldConfig, AppEnvs). +post_config_update(ConfPath, UpdateReq, NewConfig, OldConfig, AppEnvs) -> + do_post_config_update(ConfPath, UpdateReq, to_list(NewConfig), OldConfig, AppEnvs). do_post_config_update( _, {create_authenticator, ChainName, Config}, NewConfig, _OldConfig, _AppEnvs @@ -199,8 +196,8 @@ do_post_config_update( emqx_authentication:move_authenticator(ChainName, AuthenticatorID, Position); do_post_config_update(_, _UpdateReq, OldConfig, OldConfig, _AppEnvs) -> ok; -do_post_config_update(Paths, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> - ChainName = chain_name(Paths), +do_post_config_update(ConfPath, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> + ChainName = chain_name(ConfPath), OldConfig = to_list(OldConfig0), NewConfig = to_list(NewConfig0), OldIds = lists:map(fun authenticator_id/1, OldConfig), @@ -210,6 +207,8 @@ do_post_config_update(Paths, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> ok = emqx_authentication:reorder_authenticator(ChainName, NewIds), ok. +%% @doc Handle listener config changes made at higher level. + -spec propagated_post_config_update( list(atom()), update_request(), @@ -218,8 +217,8 @@ do_post_config_update(Paths, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> emqx_config:app_envs() ) -> ok. -propagated_post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs) -> - ok = post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs), +propagated_post_config_update(ConfPath, UpdateReq, NewConfig, OldConfig, AppEnvs) -> + ok = post_config_update(ConfPath, UpdateReq, NewConfig, OldConfig, AppEnvs), ok. %% create new authenticators and update existing ones @@ -257,6 +256,17 @@ to_list(M) when M =:= #{} -> []; to_list(M) when is_map(M) -> [M]; to_list(L) when is_list(L) -> L. +convert_certs_for_conf_path(ConfPath, NewConfig) -> + ChainName = chain_name_for_filepath(ConfPath), + CovertedConfs = lists:map( + fun(Conf) -> + CertsDir = certs_dir(ChainName, Conf), + convert_certs(CertsDir, Conf) + end, + to_list(NewConfig) + ), + {ok, CovertedConfs}. + convert_certs(CertsDir, NewConfig) -> NewSSL = maps:get(<<"ssl">>, NewConfig, undefined), case emqx_tls_lib:ensure_ssl_files(CertsDir, NewSSL) of @@ -353,6 +363,14 @@ chain_name([listeners, Type, Name, authentication]) -> %% Type, Name atoms exist, so let 'Type:Name' exist too. binary_to_atom(<<(atom_to_binary(Type))/binary, ":", (atom_to_binary(Name))/binary>>). +chain_name_for_filepath(Path) -> + do_chain_name_for_filepath([to_bin(Key) || Key <- Path]). + +do_chain_name_for_filepath([<<"authentication">>]) -> + to_bin(?GLOBAL); +do_chain_name_for_filepath([<<"listeners">>, Type, Name, <<"authentication">>]) -> + <<(to_bin(Type))/binary, ":", (to_bin(Name))/binary>>. + merge_authenticators(OriginConf0, NewConf0) -> {OriginConf1, NewConf1} = lists:foldl( diff --git a/apps/emqx_conf/src/emqx_conf.app.src b/apps/emqx_conf/src/emqx_conf.app.src index ab65c03c8..a4781e6fb 100644 --- a/apps/emqx_conf/src/emqx_conf.app.src +++ b/apps/emqx_conf/src/emqx_conf.app.src @@ -1,6 +1,6 @@ {application, emqx_conf, [ {description, "EMQX configuration management"}, - {vsn, "0.1.26"}, + {vsn, "0.1.27"}, {registered, []}, {mod, {emqx_conf_app, []}}, {applications, [kernel, stdlib, emqx_ctl]}, From d17bcc94f6e2fe743a0407dcc3af9e03dab83ea6 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Wed, 16 Aug 2023 22:37:42 +0300 Subject: [PATCH 14/14] chore(auth): update emqx_gcp_device tests --- .../test/emqx_gcp_device_SUITE.erl | 14 +++++--- .../test/emqx_gcp_device_api_SUITE.erl | 34 ++++++++++--------- .../test/emqx_gcp_device_authn_SUITE.erl | 12 ++++--- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/apps/emqx_gcp_device/test/emqx_gcp_device_SUITE.erl b/apps/emqx_gcp_device/test/emqx_gcp_device_SUITE.erl index 5f286d629..4c8e89551 100644 --- a/apps/emqx_gcp_device/test/emqx_gcp_device_SUITE.erl +++ b/apps/emqx_gcp_device/test/emqx_gcp_device_SUITE.erl @@ -17,15 +17,19 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - ok = emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn, emqx_retainer, emqx_gcp_device]), - Config. + Apps = emqx_cth_suite:start( + [emqx, emqx_conf, emqx_authn, emqx_gcp_device, {emqx_retainer, "retainer {enable = true}"}], + #{ + work_dir => ?config(priv_dir, Config) + } + ), + [{apps, Apps} | Config]. end_per_suite(Config) -> - _ = emqx_common_test_helpers:stop_apps([emqx_authn, emqx_retainer, emqx_gcp_device]), - Config. + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. init_per_testcase(_TestCase, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL diff --git a/apps/emqx_gcp_device/test/emqx_gcp_device_api_SUITE.erl b/apps/emqx_gcp_device/test/emqx_gcp_device_api_SUITE.erl index 238f99445..4ed34344e 100644 --- a/apps/emqx_gcp_device/test/emqx_gcp_device_api_SUITE.erl +++ b/apps/emqx_gcp_device/test/emqx_gcp_device_api_SUITE.erl @@ -14,32 +14,34 @@ -include_lib("emqx/include/emqx.hrl"). -define(PATH, [authentication]). --define(BASE_CONF, << - "" - "\n" - "retainer {\n" - " enable = true\n" - "}" - "" ->>). all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - ok = emqx_config:init_load(emqx_retainer_schema, ?BASE_CONF), - ok = emqx_common_test_helpers:start_apps([emqx_gcp_device, emqx_authn, emqx_conf, emqx_retainer]), - emqx_dashboard_api_test_helpers:set_default_config(), - emqx_mgmt_api_test_util:init_suite(), - Config. + Apps = emqx_cth_suite:start( + [ + emqx, + emqx_conf, + emqx_authn, + {emqx_retainer, "retainer {enable = true}"}, + emqx_management, + {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"}, + emqx_gcp_device + ], + #{ + work_dir => ?config(priv_dir, Config) + } + ), + _ = emqx_common_test_http:create_default_app(), + [{apps, Apps} | Config]. end_per_suite(Config) -> - emqx_mgmt_api_test_util:end_suite(), - _ = emqx_common_test_helpers:stop_apps([emqx_authn, emqx_retainer, emqx_gcp_device]), + _ = emqx_common_test_http:delete_default_app(), + ok = emqx_cth_suite:stop(?config(apps, Config)), Config. init_per_testcase(_TestCase, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL diff --git a/apps/emqx_gcp_device/test/emqx_gcp_device_authn_SUITE.erl b/apps/emqx_gcp_device/test/emqx_gcp_device_authn_SUITE.erl index 8c3f8e0fa..23e69f4c5 100644 --- a/apps/emqx_gcp_device/test/emqx_gcp_device_authn_SUITE.erl +++ b/apps/emqx_gcp_device/test/emqx_gcp_device_authn_SUITE.erl @@ -23,7 +23,9 @@ all() -> init_per_suite(Config0) -> ok = snabbkaffe:start_trace(), - emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn, emqx_gcp_device]), + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn, emqx_gcp_device], #{ + work_dir => ?config(priv_dir, Config0) + }), ValidExpirationTime = erlang:system_time(second) + 3600, ValidJWT = generate_jws(ValidExpirationTime), ExpiredJWT = generate_jws(0), @@ -35,16 +37,16 @@ init_per_suite(Config0) -> {valid_jwt, ValidJWT}, {expired_jwt, ExpiredJWT}, {valid_client, ValidClient}, - {expired_client, ExpiredClient} + {expired_client, ExpiredClient}, + {apps, Apps} | Config0 ]. -end_per_suite(_) -> - _ = emqx_common_test_helpers:stop_apps([emqx_authn, emqx_gcp_device]), +end_per_suite(Config) -> + ok = emqx_cth_suite:stop(?config(apps, Config)), ok. init_per_testcase(_, Config) -> - {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), Config. end_per_testcase(_Case, Config) ->