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 e840d2b4a..65a159dd6 100644
--- a/apps/emqx/include/emqx_access_control.hrl
+++ b/apps/emqx/include/emqx_access_control.hrl
@@ -14,7 +14,9 @@
%% limitations under the License.
%%--------------------------------------------------------------------
-%% config root name all auth providers have to agree on.
+-ifndef(EMQX_ACCESS_CONTROL_HRL).
+-define(EMQX_ACCESS_CONTROL_HRL, true).
+
-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, "authorization").
-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_ATOM, authorization).
-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_BINARY, <<"authorization">>).
@@ -32,3 +34,7 @@
-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").
+
+-endif.
diff --git a/apps/emqx/src/emqx_access_control.erl b/apps/emqx/src/emqx_access_control.erl
index 43669bf6c..82604710a 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, Result, Reason), begin
+ ?TRACE(Label, ?AUTHN_TRACE_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,29 @@ 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", 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", {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_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_config_handler.erl b/apps/emqx/src/emqx_config_handler.erl
index 96690c26e..a189fc9e5 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(
+ [binary()], emqx_config:update_request(), emqx_config:raw_config()
+) ->
+ ok | {ok, emqx_config:update_request()} | {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,14 @@ 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,
+ is_propagated => false
+ });
do_update_config(
[ConfKey | SubConfKeyPath],
Handlers,
@@ -331,15 +353,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,10 +388,16 @@ 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
+ 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) ->
@@ -377,57 +406,247 @@ 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, NewUpdateReq0} ->
+ case
+ propagate_pre_config_updates_to_subconf(Ctx#{
+ update_req => NewUpdateReq0
+ })
+ of
+ {ok, #{update_req := NewUpdateReq1}} ->
+ {ok, NewUpdateReq1};
+ {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([], 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.
+
+propagate_pre_config_updates_to_subconf_key(
+ Key,
+ #{
+ handlers := Handlers,
+ old_raw_conf := OldRawConf,
+ update_req := UpdateReq,
+ conf_key_path := ConfKeyPath,
+ is_propagated := IsPropagated
+ } = Ctx
+) ->
+ BinKey = bin(Key),
+ SubHandlers = get_sub_handlers(BinKey, Handlers),
+ SubUpdateReq = get_sub_config(BinKey, UpdateReq),
+ SubOldConf = get_sub_config(BinKey, OldRawConf),
+ 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
+ {undefined, undefined} ->
+ {ok, Ctx};
+ {_, _} ->
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,
+ is_propagated := true,
+ callback := propagated_pre_config_update
+ })
of
+ {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
+ 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 +736,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 964873e53..b95169d3c 100644
--- a/apps/emqx/src/emqx_listeners.erl
+++ b/apps/emqx/src/emqx_listeners.erl
@@ -531,41 +531,15 @@ 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.
+ start_listener(Type, Name, NewConf).
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.
+ stop_listener(Type, Name, OldConf).
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).
+ restart_listener(Type, Name, {OldConf, NewConf}).
perform_listener_changes([]) ->
ok;
@@ -847,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
@@ -873,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.erl b/apps/emqx/src/emqx_schema.erl
index 4fdec3179..07e909ae0 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").
@@ -213,16 +212,18 @@ roots(high) ->
desc => ?DESC(zones),
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,
- sc(
- ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME),
- #{importance => ?IMPORTANCE_HIDDEN}
)}
- ];
+ ] ++
+ 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",
@@ -1748,11 +1749,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) ->
[
@@ -2770,41 +2768,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..e704af6cc
--- /dev/null
+++ b/apps/emqx/src/emqx_schema_hooks.erl
@@ -0,0 +1,118 @@
+%%--------------------------------------------------------------------
+%% 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]).
+
+-export_type([hookpoint/0]).
+
+-define(HOOKPOINT_PT_KEY(POINT_NAME), {?MODULE, fields, POINT_NAME}).
+
+-export([
+ injection_point/1,
+ inject_from_modules/1
+]).
+
+%% for tests
+-export([
+ erase_injections/0,
+ any_injections/0
+]).
+
+%%--------------------------------------------------------------------
+%% API
+%%--------------------------------------------------------------------
+
+injection_point(PointName) ->
+ persistent_term:get(?HOOKPOINT_PT_KEY(PointName), []).
+
+erase_injections() ->
+ lists:foreach(
+ fun
+ ({?HOOKPOINT_PT_KEY(_) = Key, _}) ->
+ persistent_term:erase(Key);
+ (_) ->
+ ok
+ end,
+ persistent_term:get()
+ ).
+
+any_injections() ->
+ lists:any(
+ fun
+ ({?HOOKPOINT_PT_KEY(_), _}) ->
+ true;
+ (_) ->
+ false
+ end,
+ persistent_term:get()
+ ).
+
+inject_from_modules(Modules) ->
+ Injections =
+ lists:foldl(
+ fun append_module_injections/2,
+ #{},
+ Modules
+ ),
+ ok = inject_fields(maps:to_list(Injections)).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+
+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,
+ AllInjections,
+ ModuleInjections
+ ).
+
+inject_fields([]) ->
+ ok;
+inject_fields([{PointName, Fields} | Rest]) ->
+ case any_injections(PointName) of
+ true ->
+ inject_fields(Rest);
+ false ->
+ 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/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).
diff --git a/apps/emqx/test/emqx_common_test_helpers.erl b/apps/emqx/test/emqx_common_test_helpers.erl
index 7f1fe4628..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/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..bb91bcbe4 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),
@@ -223,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}}
@@ -232,13 +233,88 @@ 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}}
),
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%">>),
+ assert_update_result(
+ [
+ [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%">>),
+ assert_update_result(
+ [
+ [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">>),
+ assert_update_result(
+ [
+ [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">>),
+ assert_update_result(
+ [
+ [sysmon, os, mem_check_interval],
+ [sysmon]
+ ],
+ [sysmon],
+ Conf1,
+ {error, {pre_config_update, ?MODULE, pre_config_update_error}}
+ ),
+ 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 = [],
@@ -295,6 +371,17 @@ 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) ->
+ ok.
+
post_config_update([sysmon], _UpdateReq, _NewConf, _OldConf, _AppEnvs) ->
{ok, ok};
post_config_update([sysmon, os], _UpdateReq, _NewConf, _OldConf, _AppEnvs) ->
@@ -308,6 +395,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 ->
@@ -317,20 +411,34 @@ wait_for_new_pid() ->
Pid
end.
-callback_error(FailedPath, Update, ExpectError) ->
+assert_update_result(FailedPath, Update, Expect) ->
+ assert_update_result([FailedPath], FailedPath, Update, Expect).
+
+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},
- ok = emqx_config_handler:add_handler(FailedPath, ?MODULE),
- Old = emqx:get_raw_config(FailedPath, undefined),
- Error = emqx:update_config(FailedPath, 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(FailedPath, undefined),
- ?assertEqual(Old, New),
- ok = emqx_config_handler:remove_handler(FailedPath),
+ Old = emqx:get_raw_config(UpdatePath, undefined),
+ Result = emqx:update_config(UpdatePath, Update, Opts),
+ _ = Fun(Old, Result),
+ 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 9b3e58da4..307e8d98b 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]).
@@ -360,12 +360,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/include/emqx_authentication.hrl b/apps/emqx_authn/include/emqx_authentication.hrl
similarity index 84%
rename from apps/emqx/include/emqx_authentication.hrl
rename to apps/emqx_authn/include/emqx_authentication.hrl
index 70b35a474..c294b8d99 100644
--- a/apps/emqx/include/emqx_authentication.hrl
+++ b/apps/emqx_authn/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, #{})).
@@ -36,12 +36,6 @@
-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_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/src/emqx_authentication.erl b/apps/emqx_authn/src/emqx_authentication.erl
similarity index 97%
rename from apps/emqx/src/emqx_authentication.erl
rename to apps/emqx_authn/src/emqx_authentication.erl
index cce789f24..8f055e049 100644
--- a/apps/emqx/src/emqx_authentication.erl
+++ b/apps/emqx_authn/src/emqx_authentication.erl
@@ -22,18 +22,27 @@
-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).
--define(IS_UNDEFINED(X), (X =:= undefined orelse X =:= <<>>)).
+
+-record(authenticator, {
+ id :: binary(),
+ provider :: module(),
+ enable :: boolean(),
+ state :: map()
+}).
+
+-record(chain, {
+ name :: atom(),
+ authenticators :: [#authenticator{}]
+}).
%% The authentication entrypoint.
-export([
- pre_hook_authenticate/1,
authenticate/2
]).
@@ -220,21 +229,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
@@ -271,6 +265,7 @@ get_enabled(Authenticators) ->
%%------------------------------------------------------------------------------
%% @doc Get all registered authentication providers.
+-spec get_providers() -> #{authn_type() => module()}.
get_providers() ->
call(get_providers).
diff --git a/apps/emqx/src/emqx_authentication_config.erl b/apps/emqx_authn/src/emqx_authentication_config.erl
similarity index 84%
rename from apps/emqx/src/emqx_authentication_config.erl
rename to apps/emqx_authn/src/emqx_authentication_config.erl
index 96718d611..95140a0e8 100644
--- a/apps/emqx/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,7 +39,7 @@
-export_type([config/0]).
--include("logger.hrl").
+-include_lib("emqx/include/logger.hrl").
-include("emqx_authentication.hrl").
-type parsed_config() :: #{
@@ -65,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
@@ -130,31 +132,33 @@ 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).
+
+%% @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(_, OldConfig, OldConfig) ->
+ {ok, OldConfig};
+propagated_pre_config_update(ConfPath, NewConfig, _OldConfig) ->
+ convert_certs_for_conf_path(ConfPath, NewConfig).
-spec post_config_update(
list(atom()),
update_request(),
- map() | list(),
+ map() | list() | undefined,
emqx_config:raw_config(),
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
@@ -192,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),
@@ -203,6 +207,20 @@ 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(),
+ map() | list() | undefined,
+ emqx_config:raw_config(),
+ emqx_config:app_envs()
+) ->
+ ok.
+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
create_or_update_authenticators(OldIds, ChainName, NewConfig) ->
lists:foreach(
@@ -238,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
@@ -331,7 +360,16 @@ 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>>).
+
+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} =
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..689f6619a 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]}).
@@ -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/src/emqx_authn_schema.erl b/apps/emqx_authn/src/emqx_authn_schema.erl
index a7cdaac5f..b0a68e702 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() ->
+ #{
+ 'mqtt.listener' => global_auth_fields(),
+ 'roots.high' => 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 94%
rename from apps/emqx/test/emqx_authentication_SUITE.erl
rename to apps/emqx_authn/test/emqx_authentication_SUITE.erl
index fb73a3fc1..a15f22c41 100644
--- a/apps/emqx/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,
+ maps:to_list(?AUTHN:get_providers())
+ ).
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_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.
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],
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_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]},
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..5b73b9e03 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 = emqx_schema_hooks:inject_from_modules(?INJECTING_CONFIGS),
emqx_schema_high_prio_roots() ++
[
{"node",
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_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/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) ->
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
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"}}
]}.
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{enable:true,backend:"built_in_database",mechanism="password_based"}
authentication
in listener configs
-
-This option can be configured with:
-[]
: The default value, it allows *ALL* logins{enable:true,backend:"built_in_database",mechanism="password_based"}