Merge pull request #6362 from zmstone/emqx-config-put-raw-with-env-vars

fix: config put raw with env vars
This commit is contained in:
Zaiming (Stone) Shi 2021-12-06 09:22:32 +01:00 committed by GitHub
commit 68a7c096b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 603 additions and 252 deletions

View File

@ -57,7 +57,7 @@ APPS=$(shell $(CURDIR)/scripts/find-apps.sh)
## app/name-ct targets are intended for local tests hence cover is not enabled ## app/name-ct targets are intended for local tests hence cover is not enabled
.PHONY: $(APPS:%=%-ct) .PHONY: $(APPS:%=%-ct)
define gen-app-ct-target define gen-app-ct-target
$1-ct: $1-ct: conf-segs
$(REBAR) ct --name $(CT_NODE_NAME) -v --suite $(shell $(CURDIR)/scripts/find-suites.sh $1) $(REBAR) ct --name $(CT_NODE_NAME) -v --suite $(shell $(CURDIR)/scripts/find-suites.sh $1)
endef endef
$(foreach app,$(APPS),$(eval $(call gen-app-ct-target,$(app)))) $(foreach app,$(APPS),$(eval $(call gen-app-ct-target,$(app))))

View File

@ -0,0 +1,31 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2021 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.
%%--------------------------------------------------------------------
-ifndef(EMQX_AUTHENTICATION_HRL).
-define(EMQX_AUTHENTICATION_HRL, true).
%% 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).
-endif.

View File

@ -17,7 +17,7 @@
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.0"}}} , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.0"}}}
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.11.1"}}} , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.11.1"}}}
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}} , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.20.6"}}} , {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.22.0"}}}
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}} , {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}} , {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.15.0"}}} , {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.15.0"}}}

View File

@ -63,4 +63,5 @@ do_authorize(ClientInfo, PubSub, Topic) ->
-compile({inline, [run_hooks/3]}). -compile({inline, [run_hooks/3]}).
run_hooks(Name, Args, Acc) -> run_hooks(Name, Args, Acc) ->
ok = emqx_metrics:inc(Name), emqx_hooks:run_fold(Name, Args, Acc). ok = emqx_metrics:inc(Name),
emqx_hooks:run_fold(Name, Args, Acc).

View File

@ -24,9 +24,12 @@
-include("emqx.hrl"). -include("emqx.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("emqx_authentication.hrl").
-include_lib("stdlib/include/ms_transform.hrl"). -include_lib("stdlib/include/ms_transform.hrl").
-define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
%% The authentication entrypoint. %% The authentication entrypoint.
-export([ authenticate/2 -export([ authenticate/2
]). ]).
@ -383,8 +386,8 @@ list_users(ChainName, AuthenticatorID, Params) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init(_Opts) -> init(_Opts) ->
ok = emqx_config_handler:add_handler([authentication], ?MODULE), ok = emqx_config_handler:add_handler([?CONF_ROOT], ?MODULE),
ok = emqx_config_handler:add_handler([listeners, '?', '?', authentication], ?MODULE), ok = emqx_config_handler:add_handler([listeners, '?', '?', ?CONF_ROOT], ?MODULE),
{ok, #{hooked => false, providers => #{}}}. {ok, #{hooked => false, providers => #{}}}.
handle_call(get_providers, _From, #{providers := Providers} = State) -> handle_call(get_providers, _From, #{providers := Providers} = State) ->
@ -496,8 +499,8 @@ terminate(Reason, _State) ->
Other -> ?SLOG(error, #{msg => "emqx_authentication_terminating", Other -> ?SLOG(error, #{msg => "emqx_authentication_terminating",
reason => Other}) reason => Other})
end, end,
emqx_config_handler:remove_handler([authentication]), emqx_config_handler:remove_handler([?CONF_ROOT]),
emqx_config_handler:remove_handler([listeners, '?', '?', authentication]), emqx_config_handler:remove_handler([listeners, '?', '?', ?CONF_ROOT]),
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->

View File

@ -34,6 +34,7 @@
-export_type([config/0]). -export_type([config/0]).
-include("logger.hrl"). -include("logger.hrl").
-include("emqx_authentication.hrl").
-type parsed_config() :: #{mechanism := atom(), -type parsed_config() :: #{mechanism := atom(),
backend => atom(), backend => atom(),
@ -132,9 +133,9 @@ do_post_config_update({move_authenticator, ChainName, AuthenticatorID, Position}
check_configs(Configs) -> check_configs(Configs) ->
Providers = emqx_authentication:get_providers(), Providers = emqx_authentication:get_providers(),
lists:map(fun(C) -> do_check_conifg(C, Providers) end, Configs). lists:map(fun(C) -> do_check_config(C, Providers) end, Configs).
do_check_conifg(Config, Providers) -> do_check_config(Config, Providers) ->
Type = authn_type(Config), Type = authn_type(Config),
case maps:get(Type, Providers, false) of case maps:get(Type, Providers, false) of
false -> false ->
@ -143,19 +144,20 @@ do_check_conifg(Config, Providers) ->
providers => Providers}), providers => Providers}),
throw({unknown_authn_type, Type}); throw({unknown_authn_type, Type});
Module -> Module ->
do_check_conifg(Type, Config, Module) do_check_config(Type, Config, Module)
end. end.
do_check_conifg(Type, Config, Module) -> do_check_config(Type, Config, Module) ->
F = case erlang:function_exported(Module, check_config, 1) of F = case erlang:function_exported(Module, check_config, 1) of
true -> true ->
fun Module:check_config/1; fun Module:check_config/1;
false -> false ->
fun(C) -> fun(C) ->
#{config := R} = Key = list_to_binary(?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME),
hocon_schema:check_plain(Module, #{<<"config">> => C}, AtomKey = list_to_atom(?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME),
R = hocon_schema:check_plain(Module, #{Key => C},
#{atom_key => true}), #{atom_key => true}),
R maps:get(AtomKey, R)
end end
end, end,
try try
@ -261,8 +263,8 @@ authn_type(#{mechanism := M}) -> atom(M);
authn_type(#{<<"mechanism">> := M, <<"backend">> := B}) -> {atom(M), atom(B)}; authn_type(#{<<"mechanism">> := M, <<"backend">> := B}) -> {atom(M), atom(B)};
authn_type(#{<<"mechanism">> := M}) -> atom(M). authn_type(#{<<"mechanism">> := M}) -> atom(M).
atom(Bin) -> atom(A) when is_atom(A) -> A;
binary_to_existing_atom(Bin, utf8). atom(Bin) -> binary_to_existing_atom(Bin, utf8).
%% The relative dir for ssl files. %% The relative dir for ssl files.
certs_dir(ChainName, ConfigOrID) -> certs_dir(ChainName, ConfigOrID) ->

View File

@ -268,23 +268,39 @@ init_load(SchemaMod, Conf) when is_list(Conf) orelse is_binary(Conf) ->
}), }),
error(failed_to_load_hocon_conf) error(failed_to_load_hocon_conf)
end; end;
init_load(SchemaMod, RawConf0) when is_map(RawConf0) -> init_load(SchemaMod, RawConf) when is_map(RawConf) ->
ok = save_schema_mod_and_names(SchemaMod), ok = save_schema_mod_and_names(SchemaMod),
%% check and save configs %% check configs agains the schema, with environment variables applied on top
{_AppEnvs, CheckedConf} = check_config(SchemaMod, RawConf0), {_AppEnvs, CheckedConf} =
check_config(SchemaMod, RawConf, #{apply_override_envs => true}),
%% fill default values for raw config
RawConfWithEnvs = merge_envs(SchemaMod, RawConf),
RootNames = get_root_names(),
ok = save_to_config_map(maps:with(get_atom_root_names(), CheckedConf), ok = save_to_config_map(maps:with(get_atom_root_names(), CheckedConf),
maps:with(get_root_names(), RawConf0)). maps:with(RootNames, RawConfWithEnvs)).
include_dirs() -> include_dirs() ->
[filename:join(emqx:data_dir(), "configs")]. [filename:join(emqx:data_dir(), "configs")].
merge_envs(SchemaMod, RawConf) ->
Opts = #{logger => fun(_, _) -> ok end, %% everything should have been logged already when check_config
nullable => true, %% TODO: evil, remove, nullable should be declared in schema
format => map,
apply_override_envs => true
},
hocon_schema:merge_env_overrides(SchemaMod, RawConf, all, Opts).
-spec check_config(module(), raw_config()) -> {AppEnvs, CheckedConf} -spec check_config(module(), raw_config()) -> {AppEnvs, CheckedConf}
when AppEnvs :: app_envs(), CheckedConf :: config(). when AppEnvs :: app_envs(), CheckedConf :: config().
check_config(SchemaMod, RawConf) -> check_config(SchemaMod, RawConf) ->
Opts = #{return_plain => true, check_config(SchemaMod, RawConf, #{}).
nullable => true,
format => map check_config(SchemaMod, RawConf, Opts0) ->
}, Opts1 = #{return_plain => true,
nullable => true, %% TODO: evil, remove, nullable should be declared in schema
format => map
},
Opts = maps:merge(Opts0, Opts1),
{AppEnvs, CheckedConf} = {AppEnvs, CheckedConf} =
hocon_schema:map_translate(SchemaMod, RawConf, Opts), hocon_schema:map_translate(SchemaMod, RawConf, Opts),
{AppEnvs, emqx_map_lib:unsafe_atom_key_map(CheckedConf)}. {AppEnvs, emqx_map_lib:unsafe_atom_key_map(CheckedConf)}.
@ -312,13 +328,15 @@ read_override_conf(#{} = Opts) ->
File = override_conf_file(Opts), File = override_conf_file(Opts),
load_hocon_file(File, map). load_hocon_file(File, map).
override_conf_file(Opts) -> override_conf_file(Opts) when is_map(Opts) ->
Key = Key =
case maps:get(override_to, Opts, local) of case maps:get(override_to, Opts, local) of
local -> local_override_conf_file; local -> local_override_conf_file;
cluster -> cluster_override_conf_file cluster -> cluster_override_conf_file
end, end,
application:get_env(emqx, Key, undefined). application:get_env(emqx, Key, undefined);
override_conf_file(Which) when is_atom(Which) ->
application:get_env(emqx, Which, undefined).
-spec save_schema_mod_and_names(module()) -> ok. -spec save_schema_mod_and_names(module()) -> ok.
save_schema_mod_and_names(SchemaMod) -> save_schema_mod_and_names(SchemaMod) ->

View File

@ -22,6 +22,7 @@
-dialyzer(no_unused). -dialyzer(no_unused).
-dialyzer(no_fail_call). -dialyzer(no_fail_call).
-include("emqx_authentication.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-type duration() :: integer(). -type duration() :: integer().
@ -105,11 +106,29 @@ and can not be deleted."""
The configs here work as default values which can be overriden The configs here work as default values which can be overriden
in <code>zone</code> configs""" in <code>zone</code> configs"""
})} })}
, {"authentication", , {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME,
authentication( authentication(
"""Default authentication configs for all MQTT listeners.<br> """Default authentication configs for all MQTT listeners.
<br>
For per-listener overrides see <code>authentication</code> For per-listener overrides see <code>authentication</code>
in listener configs""")} in listener configs
<br>
<br>
EMQ X can be configured with:
<br>
<ul>
<li><code>[]</code>: The default value, it allows *ALL* logins</li>
<li>one: For example <code>{enable:true,backend:\"built-in-database\",mechanism=\"password-based\"}</code></li>
<li>chain: An array of structs.</li>
</ul>
<br>
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.
<br>
If there is no decision after a full chain exhaustion, the login is rejected.
""")}
%% NOTE: authorization schema here is only to keep emqx app prue
%% the full schema for EMQ X node is injected in emqx_conf_schema.
, {"authorization", , {"authorization",
sc(ref("authorization"), sc(ref("authorization"),
#{})} #{})}
@ -972,7 +991,7 @@ mqtt_listener() ->
sc(duration(), sc(duration(),
#{}) #{})
} }
, {"authentication", , {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME,
authentication("Per-listener authentication override") authentication("Per-listener authentication override")
} }
]. ].
@ -1231,16 +1250,18 @@ ciphers_schema(Default) ->
false -> fun validate_ciphers/1 false -> fun validate_ciphers/1
end end
, desc => , desc =>
"""TLS cipher suite names separated by comma, or as an array of strings """This config holds TLS cipher suite names separated by comma,
or as an array of strings. e.g.
<code>\"TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256\"</code> or <code>\"TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256\"</code> or
<code>[\"TLS_AES_256_GCM_SHA384\",\"TLS_AES_128_GCM_SHA256\"]</code]. <code>[\"TLS_AES_256_GCM_SHA384\",\"TLS_AES_128_GCM_SHA256\"]</code>.
<br> <br>
Ciphers (and their ordering) define the way in which the Ciphers (and their ordering) define the way in which the
client and server encrypts information over the wire. client and server encrypts information over the network connection.
Selecting a good cipher suite is critical for the Selecting a good cipher suite is critical for the
application's data security, confidentiality and performance. application's data security, confidentiality and performance.
The names should be in OpenSSL sting format (not RFC format).
Default values and examples proveded by EMQ X config The names should be in OpenSSL string format (not RFC format).
All default values and examples proveded by EMQ X config
documentation are all in OpenSSL format.<br> documentation are all in OpenSSL format.<br>
NOTE: Certain cipher suites are only compatible with NOTE: Certain cipher suites are only compatible with
@ -1436,12 +1457,23 @@ str(S) when is_list(S) ->
S. S.
authentication(Desc) -> authentication(Desc) ->
#{ type => hoconsc:lazy(hoconsc:union([typerefl:map(), hoconsc:array(typerefl:map())])) %% authentication schemais lazy to make it more 'plugable'
, desc => iolist_to_binary([Desc, "<br>", """ %% the type checks are done in emqx_auth application when it boots.
%% and in emqx_authentication_config module for rutime changes.
Default = hoconsc:lazy(hoconsc:union([typerefl:map(), hoconsc:array(typerefl:map())])),
%% as the type is lazy, the runtime module injection from EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY
%% is for now only affecting document generation.
%% maybe in the future, we can find a more straightforward way to support
%% * document generation (at compile time)
%% * type checks before boot (in bin/emqx config generation)
%% * type checks at runtime (when changing configs via management API)
#{ type => case persistent_term:get(?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY, undefined) of
undefined -> Default;
Module -> hoconsc:lazy(Module:root_type())
end
, desc => iolist_to_binary([Desc, """
Authentication can be one single authenticator instance or a chain of authenticators as an array. 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 When authenticating a login (username, client ID, etc.) the authenticators are checked
in the configured order.<br> in the configured order.<br>
EMQ X comes with a set of pre-built autenticators, for more details, see
<a href=\"#root-authenticator_config\">autenticator_config<a>
"""]) """])
}. }.

View File

@ -25,18 +25,11 @@
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include("emqx_authentication.hrl").
-export([ roots/0, fields/1 ]).
-export([ create/2
, update/2
, authenticate/2
, destroy/1
, check_config/1
]).
-define(AUTHN, emqx_authentication). -define(AUTHN, emqx_authentication).
-define(config(KEY), (fun() -> {KEY, _V_} = lists:keyfind(KEY, 1, Config), _V_ end)()). -define(config(KEY), (fun() -> {KEY, _V_} = lists:keyfind(KEY, 1, Config), _V_ end)()).
-define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Hocon Schema %% Hocon Schema
@ -250,7 +243,7 @@ t_update_config({init, Config}) ->
{"auth2", AuthNType2} | Config]; {"auth2", AuthNType2} | Config];
t_update_config(Config) when is_list(Config) -> t_update_config(Config) when is_list(Config) ->
emqx_config_handler:add_handler([authentication], emqx_authentication), emqx_config_handler:add_handler([?CONF_ROOT], emqx_authentication),
ok = register_provider(?config("auth1"), ?MODULE), ok = register_provider(?config("auth1"), ?MODULE),
ok = register_provider(?config("auth2"), ?MODULE), ok = register_provider(?config("auth2"), ?MODULE),
Global = ?config(global), Global = ?config(global),
@ -267,7 +260,7 @@ t_update_config(Config) when is_list(Config) ->
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
update_config([authentication], {create_authenticator, Global, AuthenticatorConfig1})), update_config([?CONF_ROOT], {create_authenticator, Global, AuthenticatorConfig1})),
?assertMatch( ?assertMatch(
{ok, #{id := ID1, state := #{mark := 1}}}, {ok, #{id := ID1, state := #{mark := 1}}},
@ -275,7 +268,7 @@ t_update_config(Config) when is_list(Config) ->
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
update_config([authentication], {create_authenticator, Global, AuthenticatorConfig2})), update_config([?CONF_ROOT], {create_authenticator, Global, AuthenticatorConfig2})),
?assertMatch( ?assertMatch(
{ok, #{id := ID2, state := #{mark := 1}}}, {ok, #{id := ID2, state := #{mark := 1}}},
@ -283,7 +276,7 @@ t_update_config(Config) when is_list(Config) ->
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
update_config([authentication], update_config([?CONF_ROOT],
{update_authenticator, {update_authenticator,
Global, Global,
ID1, ID1,
@ -296,25 +289,25 @@ t_update_config(Config) when is_list(Config) ->
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
update_config([authentication], {move_authenticator, Global, ID2, top})), update_config([?CONF_ROOT], {move_authenticator, Global, ID2, top})),
?assertMatch({ok, [#{id := ID2}, #{id := ID1}]}, ?AUTHN:list_authenticators(Global)), ?assertMatch({ok, [#{id := ID2}, #{id := ID1}]}, ?AUTHN:list_authenticators(Global)),
?assertMatch({ok, _}, update_config([authentication], {delete_authenticator, Global, ID1})), ?assertMatch({ok, _}, update_config([?CONF_ROOT], {delete_authenticator, Global, ID1})),
?assertEqual( ?assertEqual(
{error, {not_found, {authenticator, ID1}}}, {error, {not_found, {authenticator, ID1}}},
?AUTHN:lookup_authenticator(Global, ID1)), ?AUTHN:lookup_authenticator(Global, ID1)),
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
update_config([authentication], {delete_authenticator, Global, ID2})), update_config([?CONF_ROOT], {delete_authenticator, Global, ID2})),
?assertEqual( ?assertEqual(
{error, {not_found, {authenticator, ID2}}}, {error, {not_found, {authenticator, ID2}}},
?AUTHN:lookup_authenticator(Global, ID2)), ?AUTHN:lookup_authenticator(Global, ID2)),
ListenerID = 'tcp:default', ListenerID = 'tcp:default',
ConfKeyPath = [listeners, tcp, default, authentication], ConfKeyPath = [listeners, tcp, default, ?CONF_ROOT],
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},

View File

@ -1,6 +1 @@
# authentication: { authentication: []
# mechanism: password-based
# backend: built-in-database
# user_id_type: clientid
# }

View File

@ -17,6 +17,8 @@
-ifndef(EMQX_AUTHN_HRL). -ifndef(EMQX_AUTHN_HRL).
-define(EMQX_AUTHN_HRL, true). -define(EMQX_AUTHN_HRL, true).
-include_lib("emqx/include/emqx_authentication.hrl").
-define(APP, emqx_authn). -define(APP, emqx_authn).
-define(AUTHN, emqx_authentication). -define(AUTHN, emqx_authentication).
@ -27,4 +29,9 @@
-define(AUTH_SHARD, emqx_authn_shard). -define(AUTH_SHARD, emqx_authn_shard).
%% has to be the same as the root field name defined in emqx_schema
-define(CONF_NS, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME).
-define(CONF_NS_ATOM, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
-define(CONF_NS_BINARY, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY).
-endif. -endif.

View File

@ -22,6 +22,8 @@
, check_configs/1 , check_configs/1
]). ]).
-include("emqx_authn.hrl").
providers() -> providers() ->
[ {{'password-based', 'built-in-database'}, emqx_authn_mnesia} [ {{'password-based', 'built-in-database'}, emqx_authn_mnesia}
, {{'password-based', mysql}, emqx_authn_mysql} , {{'password-based', mysql}, emqx_authn_mysql}
@ -44,8 +46,8 @@ check_config(Config) ->
check_config(Config, Opts) -> check_config(Config, Opts) ->
case do_check_config(Config, Opts) of case do_check_config(Config, Opts) of
#{config := Checked} -> Checked; #{?CONF_NS_ATOM := Checked} -> Checked;
#{<<"config">> := WithDefaults} -> WithDefaults #{?CONF_NS_BINARY := WithDefaults} -> WithDefaults
end. end.
do_check_config(#{<<"mechanism">> := Mec} = Config, Opts) -> do_check_config(#{<<"mechanism">> := Mec} = Config, Opts) ->
@ -56,10 +58,15 @@ do_check_config(#{<<"mechanism">> := Mec} = Config, Opts) ->
case lists:keyfind(Key, 1, providers()) of case lists:keyfind(Key, 1, providers()) of
false -> false ->
throw({unknown_handler, Key}); throw({unknown_handler, Key});
{_, Provider} -> {_, ProviderModule} ->
hocon_schema:check_plain(Provider, #{<<"config">> => Config}, hocon_schema:check_plain(ProviderModule, #{?CONF_NS_BINARY => Config},
Opts#{atom_key => true}) Opts#{atom_key => true})
end. end.
atom(Bin) -> atom(Bin) ->
binary_to_existing_atom(Bin, utf8). try
binary_to_existing_atom(Bin, utf8)
catch
_ : _ ->
throw({unknown_auth_provider, Bin})
end.

View File

@ -22,6 +22,7 @@
-include("emqx_authn.hrl"). -include("emqx_authn.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_authentication.hrl").
-import(hoconsc, [mk/2, ref/1]). -import(hoconsc, [mk/2, ref/1]).
-import(emqx_dashboard_swagger, [error_codes/2]). -import(emqx_dashboard_swagger, [error_codes/2]).
@ -32,8 +33,10 @@
% Swagger % Swagger
-define(API_TAGS_GLOBAL, [<<"authentication">>, <<"authentication config(global)">>]). -define(API_TAGS_GLOBAL, [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY,
-define(API_TAGS_SINGLE, [<<"authentication">>, <<"authentication config(single listener)">>]). <<"authentication config(global)">>]).
-define(API_TAGS_SINGLE, [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY,
<<"authentication config(single listener)">>]).
-export([ api_spec/0 -export([ api_spec/0
, paths/0 , paths/0

View File

@ -25,6 +25,8 @@
, stop/1 , stop/1
]). ]).
-include_lib("emqx/include/emqx_authentication.hrl").
-dialyzer({nowarn_function, [start/2]}). -dialyzer({nowarn_function, [start/2]}).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -65,7 +67,7 @@ chain_configs() ->
[global_chain_config() | listener_chain_configs()]. [global_chain_config() | listener_chain_configs()].
global_chain_config() -> global_chain_config() ->
{?GLOBAL, emqx:get_raw_config([<<"authentication">>], [])}. {?GLOBAL, emqx:get_raw_config([?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY], [])}.
listener_chain_configs() -> listener_chain_configs() ->
lists:map( lists:map(
@ -77,7 +79,7 @@ listener_chain_configs() ->
auth_config_path(ListenerID) -> auth_config_path(ListenerID) ->
[<<"listeners">>] [<<"listeners">>]
++ binary:split(atom_to_binary(ListenerID), <<":">>) ++ binary:split(atom_to_binary(ListenerID), <<":">>)
++ [<<"authentication">>]. ++ [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY].
provider_types() -> provider_types() ->
lists:map(fun({Type, _Module}) -> Type end, emqx_authn:providers()). lists:map(fun({Type, _Module}) -> Type end, emqx_authn:providers()).

View File

@ -22,10 +22,12 @@
, roots/0 , roots/0
, fields/1 , fields/1
, authenticator_type/0 , authenticator_type/0
, root_type/0
, mechanism/1
, backend/1
]). ]).
%% only for doc generation roots() -> [].
roots() -> [{authenticator_config, hoconsc:mk(authenticator_type())}].
fields(_) -> []. fields(_) -> [].
@ -35,6 +37,7 @@ common_fields() ->
enable(type) -> boolean(); enable(type) -> boolean();
enable(default) -> true; enable(default) -> true;
enable(desc) -> "Set to <code>false</code> to disable this auth provider";
enable(_) -> undefined. enable(_) -> undefined.
authenticator_type() -> authenticator_type() ->
@ -42,3 +45,18 @@ authenticator_type() ->
config_refs(Modules) -> config_refs(Modules) ->
lists:append([Module:refs() || Module <- Modules]). lists:append([Module:refs() || Module <- Modules]).
%% authn is a core functionality however implemented outside fo emqx app
%% in emqx_schema, 'authentication' is a map() type which is to allow
%% EMQ X more plugable.
root_type() ->
T = authenticator_type(),
hoconsc:union([T, hoconsc:array(T)]).
mechanism(Name) ->
hoconsc:mk(hoconsc:enum([Name]),
#{nullable => false}).
backend(Name) ->
hoconsc:mk(hoconsc:enum([Name]),
#{nullable => false}).

View File

@ -83,11 +83,11 @@ mnesia(boot) ->
namespace() -> "authn-scram-builtin_db". namespace() -> "authn-scram-builtin_db".
roots() -> [config]. roots() -> [?CONF_NS].
fields(config) -> fields(?CONF_NS) ->
[ {mechanism, {enum, [scram]}} [ {mechanism, emqx_authn_schema:mechanism('scram')}
, {backend, {enum, ['built-in-database']}} , {backend, emqx_authn_schema:backend('built-in-database')}
, {algorithm, fun algorithm/1} , {algorithm, fun algorithm/1}
, {iteration_count, fun iteration_count/1} , {iteration_count, fun iteration_count/1}
] ++ emqx_authn_schema:common_fields(). ] ++ emqx_authn_schema:common_fields().
@ -105,7 +105,7 @@ iteration_count(_) -> undefined.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
refs() -> refs() ->
[hoconsc:ref(?MODULE, config)]. [hoconsc:ref(?MODULE, ?CONF_NS)].
create(AuthenticatorID, create(AuthenticatorID,
#{algorithm := Algorithm, #{algorithm := Algorithm,

View File

@ -43,8 +43,9 @@
namespace() -> "authn-http". namespace() -> "authn-http".
roots() -> roots() ->
[ {config, hoconsc:mk(hoconsc:union(refs()), [ {?CONF_NS,
#{})} hoconsc:mk(hoconsc:union(refs()),
#{})}
]. ].
fields(get) -> fields(get) ->
@ -60,8 +61,8 @@ fields(post) ->
] ++ common_fields(). ] ++ common_fields().
common_fields() -> common_fields() ->
[ {mechanism, hoconsc:enum(['password-based'])} [ {mechanism, emqx_authn_schema:mechanism('password-based')}
, {backend, hoconsc:enum(['http'])} , {backend, emqx_authn_schema:backend(http)}
, {url, fun url/1} , {url, fun url/1}
, {body, fun body/1} , {body, fun body/1}
, {request_timeout, fun request_timeout/1} , {request_timeout, fun request_timeout/1}
@ -233,9 +234,9 @@ transform_header_name(Headers) ->
end, #{}, Headers). end, #{}, Headers).
check_ssl_opts(Conf) -> check_ssl_opts(Conf) ->
case parse_url(hocon_schema:get_value("config.url", Conf)) of case parse_url(get_conf_val("url", Conf)) of
#{scheme := https} -> #{scheme := https} ->
case hocon_schema:get_value("config.ssl.enable", Conf) of case get_conf_val("ssl.enable", Conf) of
true -> ok; true -> ok;
false -> false false -> false
end; end;
@ -244,8 +245,8 @@ check_ssl_opts(Conf) ->
end. end.
check_headers(Conf) -> check_headers(Conf) ->
Method = to_bin(hocon_schema:get_value("config.method", Conf)), Method = to_bin(get_conf_val("method", Conf)),
Headers = hocon_schema:get_value("config.headers", Conf), Headers = get_conf_val("headers", Conf),
Method =:= <<"post">> orelse (not maps:is_key(<<"content-type">>, Headers)). Method =:= <<"post">> orelse (not maps:is_key(<<"content-type">>, Headers)).
parse_url(URL) -> parse_url(URL) ->
@ -340,3 +341,6 @@ to_bin(B) when is_binary(B) ->
B; B;
to_bin(L) when is_list(L) -> to_bin(L) when is_list(L) ->
list_to_binary(L). list_to_binary(L).
get_conf_val(Name, Conf) ->
hocon_schema:get_value(?CONF_NS ++ "." ++ Name, Conf).

View File

@ -16,6 +16,7 @@
-module(emqx_authn_jwt). -module(emqx_authn_jwt).
-include("emqx_authn.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-behaviour(hocon_schema). -behaviour(hocon_schema).
@ -40,9 +41,9 @@
namespace() -> "authn-jwt". namespace() -> "authn-jwt".
roots() -> roots() ->
[ {config, hoconsc:mk(hoconsc:union(refs()), [ {?CONF_NS,
#{} hoconsc:mk(hoconsc:union(refs()),
)} #{})}
]. ].
fields('hmac-based') -> fields('hmac-based') ->
@ -82,7 +83,7 @@ fields(ssl_disable) ->
[ {enable, #{type => false}} ]. [ {enable, #{type => false}} ].
common_fields() -> common_fields() ->
[ {mechanism, {enum, [jwt]}} [ {mechanism, emqx_authn_schema:mechanism('jwt')}
, {verify_claims, fun verify_claims/1} , {verify_claims, fun verify_claims/1}
] ++ emqx_authn_schema:common_fields(). ] ++ emqx_authn_schema:common_fields().

View File

@ -85,11 +85,11 @@ mnesia(boot) ->
namespace() -> "authn-builtin_db". namespace() -> "authn-builtin_db".
roots() -> [config]. roots() -> [?CONF_NS].
fields(config) -> fields(?CONF_NS) ->
[ {mechanism, {enum, ['password-based']}} [ {mechanism, emqx_authn_schema:mechanism('password-based')}
, {backend, {enum, ['built-in-database']}} , {backend, emqx_authn_schema:backend('built-in-database')}
, {user_id_type, fun user_id_type/1} , {user_id_type, fun user_id_type/1}
, {password_hash_algorithm, fun password_hash_algorithm/1} , {password_hash_algorithm, fun password_hash_algorithm/1}
] ++ emqx_authn_schema:common_fields(); ] ++ emqx_authn_schema:common_fields();
@ -104,7 +104,7 @@ fields(other_algorithms) ->
]. ].
user_id_type(type) -> user_id_type(); user_id_type(type) -> user_id_type();
user_id_type(default) -> username; user_id_type(default) -> <<"username">>;
user_id_type(_) -> undefined. user_id_type(_) -> undefined.
password_hash_algorithm(type) -> hoconsc:union([hoconsc:ref(?MODULE, bcrypt), password_hash_algorithm(type) -> hoconsc:union([hoconsc:ref(?MODULE, bcrypt),
@ -121,7 +121,7 @@ salt_rounds(_) -> undefined.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
refs() -> refs() ->
[hoconsc:ref(?MODULE, config)]. [hoconsc:ref(?MODULE, ?CONF_NS)].
create(AuthenticatorID, create(AuthenticatorID,
#{user_id_type := Type, #{user_id_type := Type,

View File

@ -42,8 +42,8 @@
namespace() -> "authn-mongodb". namespace() -> "authn-mongodb".
roots() -> roots() ->
[ {config, hoconsc:mk(hoconsc:union(refs()), [ {?CONF_NS, hoconsc:mk(hoconsc:union(refs()),
#{})} #{})}
]. ].
fields(standalone) -> fields(standalone) ->
@ -56,8 +56,8 @@ fields('sharded-cluster') ->
common_fields() ++ emqx_connector_mongo:fields(sharded). common_fields() ++ emqx_connector_mongo:fields(sharded).
common_fields() -> common_fields() ->
[ {mechanism, {enum, ['password-based']}} [ {mechanism, emqx_authn_schema:mechanism('password-based')}
, {backend, {enum, [mongodb]}} , {backend, emqx_authn_schema:backend(mongodb)}
, {collection, fun collection/1} , {collection, fun collection/1}
, {selector, fun selector/1} , {selector, fun selector/1}
, {password_hash_field, fun password_hash_field/1} , {password_hash_field, fun password_hash_field/1}

View File

@ -41,11 +41,11 @@
namespace() -> "authn-mysql". namespace() -> "authn-mysql".
roots() -> [config]. roots() -> [?CONF_NS].
fields(config) -> fields(?CONF_NS) ->
[ {mechanism, {enum, ['password-based']}} [ {mechanism, emqx_authn_schema:mechanism('password-based')}
, {backend, {enum, [mysql]}} , {backend, emqx_authn_schema:backend(mysql)}
, {password_hash_algorithm, fun password_hash_algorithm/1} , {password_hash_algorithm, fun password_hash_algorithm/1}
, {salt_position, fun salt_position/1} , {salt_position, fun salt_position/1}
, {query, fun query/1} , {query, fun query/1}
@ -74,7 +74,7 @@ query_timeout(_) -> undefined.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
refs() -> refs() ->
[hoconsc:ref(?MODULE, config)]. [hoconsc:ref(?MODULE, ?CONF_NS)].
create(_AuthenticatorID, Config) -> create(_AuthenticatorID, Config) ->
create(Config). create(Config).

View File

@ -47,11 +47,11 @@
namespace() -> "authn-postgresql". namespace() -> "authn-postgresql".
roots() -> [config]. roots() -> [?CONF_NS].
fields(config) -> fields(?CONF_NS) ->
[ {mechanism, {enum, ['password-based']}} [ {mechanism, emqx_authn_schema:mechanism('password-based')}
, {backend, {enum, [postgresql]}} , {backend, emqx_authn_schema:backend(postgresql)}
, {password_hash_algorithm, fun password_hash_algorithm/1} , {password_hash_algorithm, fun password_hash_algorithm/1}
, {salt_position, fun salt_position/1} , {salt_position, fun salt_position/1}
, {query, fun query/1} , {query, fun query/1}
@ -75,7 +75,7 @@ query(_) -> undefined.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
refs() -> refs() ->
[hoconsc:ref(?MODULE, config)]. [hoconsc:ref(?MODULE, ?CONF_NS)].
create(_AuthenticatorID, Config) -> create(_AuthenticatorID, Config) ->
create(Config). create(Config).

View File

@ -42,8 +42,8 @@
namespace() -> "authn-redis". namespace() -> "authn-redis".
roots() -> roots() ->
[ {config, hoconsc:mk(hoconsc:union(refs()), [ {?CONF_NS, hoconsc:mk(hoconsc:union(refs()),
#{})} #{})}
]. ].
fields(standalone) -> fields(standalone) ->
@ -56,11 +56,11 @@ fields(sentinel) ->
common_fields() ++ emqx_connector_redis:fields(sentinel). common_fields() ++ emqx_connector_redis:fields(sentinel).
common_fields() -> common_fields() ->
[{mechanism, {enum, ['password-based']}}, [ {mechanism, emqx_authn_schema:mechanism('password-based')}
{backend, {enum, [redis]}}, , {backend, emqx_authn_schema:backend(redis)}
{cmd, fun cmd/1}, , {cmd, fun cmd/1}
{password_hash_algorithm, fun password_hash_algorithm/1}, , {password_hash_algorithm, fun password_hash_algorithm/1}
{salt_position, fun salt_position/1} , {salt_position, fun salt_position/1}
] ++ emqx_authn_schema:common_fields(). ] ++ emqx_authn_schema:common_fields().
cmd(type) -> string(); cmd(type) -> string();

View File

@ -45,11 +45,11 @@ groups() ->
init_per_testcase(_, Config) -> init_per_testcase(_, Config) ->
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [?CONF_NS_ATOM],
?GLOBAL), ?GLOBAL),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[listeners, tcp, default, authentication], [listeners, tcp, default, ?CONF_NS_ATOM],
?TCP_DEFAULT), ?TCP_DEFAULT),
{atomic, ok} = mria:clear_table(emqx_authn_mnesia), {atomic, ok} = mria:clear_table(emqx_authn_mnesia),
@ -89,8 +89,8 @@ set_special_configs(_App) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
t_invalid_listener(_) -> t_invalid_listener(_) ->
{ok, 404, _} = request(get, uri(["listeners", "invalid", "authentication"])), {ok, 404, _} = request(get, uri(["listeners", "invalid", ?CONF_NS])),
{ok, 404, _} = request(get, uri(["listeners", "in:valid", "authentication"])). {ok, 404, _} = request(get, uri(["listeners", "in:valid", ?CONF_NS])).
t_authenticators(_) -> t_authenticators(_) ->
test_authenticators([]). test_authenticators([]).
@ -133,86 +133,86 @@ test_authenticators(PathPrefix) ->
ValidConfig = emqx_authn_test_lib:http_example(), ValidConfig = emqx_authn_test_lib:http_example(),
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ [?CONF_NS]),
ValidConfig), ValidConfig),
{ok, 409, _} = request( {ok, 409, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ [?CONF_NS]),
ValidConfig), ValidConfig),
InvalidConfig0 = ValidConfig#{method => <<"delete">>}, InvalidConfig0 = ValidConfig#{method => <<"delete">>},
{ok, 400, _} = request( {ok, 400, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ [?CONF_NS]),
InvalidConfig0), InvalidConfig0),
InvalidConfig1 = ValidConfig#{method => <<"get">>, InvalidConfig1 = ValidConfig#{method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>}}, headers => #{<<"content-type">> => <<"application/json">>}},
{ok, 400, _} = request( {ok, 400, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ [?CONF_NS]),
InvalidConfig1), InvalidConfig1),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
[#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}], [#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}],
PathPrefix ++ ["authentication"]). PathPrefix ++ [?CONF_NS]).
test_authenticator(PathPrefix) -> test_authenticator(PathPrefix) ->
ValidConfig0 = emqx_authn_test_lib:http_example(), ValidConfig0 = emqx_authn_test_lib:http_example(),
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ [?CONF_NS]),
ValidConfig0), ValidConfig0),
{ok, 200, _} = request( {ok, 200, _} = request(
get, get,
uri(PathPrefix ++ ["authentication", "password-based:http"])), uri(PathPrefix ++ [?CONF_NS, "password-based:http"])),
{ok, 404, _} = request( {ok, 404, _} = request(
get, get,
uri(PathPrefix ++ ["authentication", "password-based:redis"])), uri(PathPrefix ++ [?CONF_NS, "password-based:redis"])),
{ok, 404, _} = request( {ok, 404, _} = request(
put, put,
uri(PathPrefix ++ ["authentication", "password-based:built-in-database"]), uri(PathPrefix ++ [?CONF_NS, "password-based:built-in-database"]),
emqx_authn_test_lib:built_in_database_example()), emqx_authn_test_lib:built_in_database_example()),
InvalidConfig0 = ValidConfig0#{method => <<"delete">>}, InvalidConfig0 = ValidConfig0#{method => <<"delete">>},
{ok, 400, _} = request( {ok, 400, _} = request(
put, put,
uri(PathPrefix ++ ["authentication", "password-based:http"]), uri(PathPrefix ++ [?CONF_NS, "password-based:http"]),
InvalidConfig0), InvalidConfig0),
InvalidConfig1 = ValidConfig0#{method => <<"get">>, InvalidConfig1 = ValidConfig0#{method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>}}, headers => #{<<"content-type">> => <<"application/json">>}},
{ok, 400, _} = request( {ok, 400, _} = request(
put, put,
uri(PathPrefix ++ ["authentication", "password-based:http"]), uri(PathPrefix ++ [?CONF_NS, "password-based:http"]),
InvalidConfig1), InvalidConfig1),
ValidConfig1 = ValidConfig0#{pool_size => 9}, ValidConfig1 = ValidConfig0#{pool_size => 9},
{ok, 200, _} = request( {ok, 200, _} = request(
put, put,
uri(PathPrefix ++ ["authentication", "password-based:http"]), uri(PathPrefix ++ [?CONF_NS, "password-based:http"]),
ValidConfig1), ValidConfig1),
{ok, 404, _} = request( {ok, 404, _} = request(
delete, delete,
uri(PathPrefix ++ ["authentication", "password-based:redis"])), uri(PathPrefix ++ [?CONF_NS, "password-based:redis"])),
{ok, 204, _} = request( {ok, 204, _} = request(
delete, delete,
uri(PathPrefix ++ ["authentication", "password-based:http"])), uri(PathPrefix ++ [?CONF_NS, "password-based:http"])),
?assertAuthenticatorsMatch([], PathPrefix ++ ["authentication"]). ?assertAuthenticatorsMatch([], PathPrefix ++ [?CONF_NS]).
test_authenticator_users(PathPrefix) -> test_authenticator_users(PathPrefix) ->
UsersUri = uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]), UsersUri = uri(PathPrefix ++ [?CONF_NS, "password-based:built-in-database", "users"]),
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ [?CONF_NS]),
emqx_authn_test_lib:built_in_database_example()), emqx_authn_test_lib:built_in_database_example()),
InvalidUsers = [ InvalidUsers = [
@ -263,11 +263,11 @@ test_authenticator_users(PathPrefix) ->
lists:usort([ UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users])). lists:usort([ UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users])).
test_authenticator_user(PathPrefix) -> test_authenticator_user(PathPrefix) ->
UsersUri = uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]), UsersUri = uri(PathPrefix ++ [?CONF_NS, "password-based:built-in-database", "users"]),
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ [?CONF_NS]),
emqx_authn_test_lib:built_in_database_example()), emqx_authn_test_lib:built_in_database_example()),
User = #{user_id => <<"u1">>, password => <<"p1">>}, User = #{user_id => <<"u1">>, password => <<"p1">>},
@ -311,7 +311,7 @@ test_authenticator_move(PathPrefix) ->
fun(Conf) -> fun(Conf) ->
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ [?CONF_NS]),
Conf) Conf)
end, end,
AuthenticatorConfs), AuthenticatorConfs),
@ -322,40 +322,40 @@ test_authenticator_move(PathPrefix) ->
#{<<"mechanism">> := <<"jwt">>}, #{<<"mechanism">> := <<"jwt">>},
#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>} #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>}
], ],
PathPrefix ++ ["authentication"]), PathPrefix ++ [?CONF_NS]),
% Invalid moves % Invalid moves
{ok, 400, _} = request( {ok, 400, _} = request(
post, post,
uri(PathPrefix ++ ["authentication", "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"up">>}), #{position => <<"up">>}),
{ok, 400, _} = request( {ok, 400, _} = request(
post, post,
uri(PathPrefix ++ ["authentication", "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{}), #{}),
{ok, 404, _} = request( {ok, 404, _} = request(
post, post,
uri(PathPrefix ++ ["authentication", "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"before:invalid">>}), #{position => <<"before:invalid">>}),
{ok, 404, _} = request( {ok, 404, _} = request(
post, post,
uri(PathPrefix ++ ["authentication", "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"before:password-based:redis">>}), #{position => <<"before:password-based:redis">>}),
{ok, 404, _} = request( {ok, 404, _} = request(
post, post,
uri(PathPrefix ++ ["authentication", "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"before:password-based:redis">>}), #{position => <<"before:password-based:redis">>}),
% Valid moves % Valid moves
{ok, 204, _} = request( {ok, 204, _} = request(
post, post,
uri(PathPrefix ++ ["authentication", "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"top">>}), #{position => <<"top">>}),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
@ -364,11 +364,11 @@ test_authenticator_move(PathPrefix) ->
#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}, #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>},
#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>} #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>}
], ],
PathPrefix ++ ["authentication"]), PathPrefix ++ [?CONF_NS]),
{ok, 204, _} = request( {ok, 204, _} = request(
post, post,
uri(PathPrefix ++ ["authentication", "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"bottom">>}), #{position => <<"bottom">>}),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
@ -377,11 +377,11 @@ test_authenticator_move(PathPrefix) ->
#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>}, #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>},
#{<<"mechanism">> := <<"jwt">>} #{<<"mechanism">> := <<"jwt">>}
], ],
PathPrefix ++ ["authentication"]), PathPrefix ++ [?CONF_NS]),
{ok, 204, _} = request( {ok, 204, _} = request(
post, post,
uri(PathPrefix ++ ["authentication", "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"before:password-based:built-in-database">>}), #{position => <<"before:password-based:built-in-database">>}),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
@ -390,17 +390,17 @@ test_authenticator_move(PathPrefix) ->
#{<<"mechanism">> := <<"jwt">>}, #{<<"mechanism">> := <<"jwt">>},
#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>} #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>}
], ],
PathPrefix ++ ["authentication"]). PathPrefix ++ [?CONF_NS]).
test_authenticator_import_users(PathPrefix) -> test_authenticator_import_users(PathPrefix) ->
ImportUri = uri( ImportUri = uri(
PathPrefix ++ PathPrefix ++
["authentication", "password-based:built-in-database", "import_users"]), [?CONF_NS, "password-based:built-in-database", "import_users"]),
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ [?CONF_NS]),
emqx_authn_test_lib:built_in_database_example()), emqx_authn_test_lib:built_in_database_example()),
{ok, 400, _} = request(post, ImportUri, #{}), {ok, 400, _} = request(post, ImportUri, #{}),

View File

@ -24,7 +24,7 @@
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl").
-define(PATH, [authentication]). -define(PATH, [?CONF_NS_ATOM]).
-define(HTTP_PORT, 33333). -define(HTTP_PORT, 33333).
-define(HTTP_PATH, "/auth"). -define(HTTP_PATH, "/auth").

View File

@ -49,6 +49,8 @@ end_per_testcase(_Case, Config) ->
%% Tests %% Tests
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-define(CONF(Conf), #{?CONF_NS_BINARY => Conf}).
t_check_schema(_Config) -> t_check_schema(_Config) ->
ConfigOk = #{ ConfigOk = #{
<<"mechanism">> => <<"password-based">>, <<"mechanism">> => <<"password-based">>,
@ -60,7 +62,7 @@ t_check_schema(_Config) ->
} }
}, },
hocon_schema:check_plain(emqx_authn_mnesia, #{<<"config">> => ConfigOk}), hocon_schema:check_plain(emqx_authn_mnesia, ?CONF(ConfigOk)),
ConfigNotOk = #{ ConfigNotOk = #{
<<"mechanism">> => <<"password-based">>, <<"mechanism">> => <<"password-based">>,
@ -74,7 +76,7 @@ t_check_schema(_Config) ->
?assertException( ?assertException(
throw, throw,
{emqx_authn_mnesia, _}, {emqx_authn_mnesia, _},
hocon_schema:check_plain(emqx_authn_mnesia, #{<<"config">> => ConfigNotOk})). hocon_schema:check_plain(emqx_authn_mnesia, ?CONF(ConfigNotOk))).
t_create(_) -> t_create(_) ->
Config0 = config(), Config0 = config(),

View File

@ -31,7 +31,7 @@ groups() ->
init_per_suite(Config) -> init_per_suite(Config) ->
ok = emqx_common_test_helpers:start_apps( ok = emqx_common_test_helpers:start_apps(
[emqx_conf, emqx_authz], [emqx_connector, emqx_conf, emqx_authz],
fun set_special_configs/1 fun set_special_configs/1
), ),
Config. Config.

View File

@ -33,7 +33,7 @@
catch catch
error:{invalid_bridge_id, Id0} -> error:{invalid_bridge_id, Id0} ->
{400, #{code => 'INVALID_ID', message => <<"invalid_bridge_id: ", Id0/binary, {400, #{code => 'INVALID_ID', message => <<"invalid_bridge_id: ", Id0/binary,
". Bridge Ids must be of format <bridge_type>:<name>">>}} ". Bridge ID must be of format 'bridge_type:name'">>}}
end). end).
-define(METRICS(MATCH, SUCC, FAILED, RATE, RATE_5, RATE_MAX), -define(METRICS(MATCH, SUCC, FAILED, RATE, RATE_5, RATE_MAX),

View File

@ -108,7 +108,8 @@ connector_name() ->
#{ nullable => false #{ nullable => false
, desc =>""" , desc =>"""
The connector name to be used for this bridge. The connector name to be used for this bridge.
Connectors are configured by 'connectors.<type>.<name> Connectors are configured as 'connectors.type.name',
for example 'connectors.http.mybridge'.
""" """
})}. })}.

View File

@ -0,0 +1,192 @@
EMQ X configuration file is in [HOCON](https://github.com/emqx/hocon) format.
HOCON, or Human-Optimized Config Object Notation is a format for human-readable data,
and a superset of JSON.
## Syntax
In config file the values can be notated as JSON like ojbects, such as
```
node {
name = "emqx@127.0.0.1"
cookie = "mysecret"
}
```
Another equivalent representation is flat, suh as
```
node.name="127.0.0.1"
node.cookie="mysecret"
```
This flat format is almost backward compatible with EMQ X's config file format
in 4.x series (the so called 'cuttlefish' format).
It is 'almost' compabile because the often HOCON requires strings to be quoted,
while cuttlefish treats all characters to the right of the `=` mark as the value.
e.g. cuttlefish: `node.name = emqx@127.0.0.1`, HOCON: `node.name = "emqx@127.0.0.1"`
Strings without special characters in them can be unquoted in HOCON too,
e.g. `foo`, `foo_bar`, `foo_bar_1`:
For more HOCON syntax, pelase refer to the [specification](https://github.com/lightbend/config/blob/main/HOCON.md)
## Schema
To make the HOCON objects type-safe, EMQ X introduded a schema for it.
The schema defines data types, and data fields' names and metadata for config value validation
and more. In fact, this config document itself is generated from schema metadata.
### Complex Data Types
There are 4 complex data types in EMQ X's HOCON config:
1. Struct: Named using an unquoted string, followed by a pre-defined list of fields,
fields can not start with a number, and are only allowed to use
lowercase letters and underscores as word separater.
1. Map: Map is like Struct, however the fields are not pre-defined.
1-based index number can also be used as map keys for an alternative
representation of an Array.
1. Union: `MemberType1 | MemberType2 | ...`
1. Array: `[ElementType]`
### Primitive Data Types
Complex types define data 'boxes' wich may contain other complex data
or primitive values.
There are quite some different primitive types, to name a fiew:
* `atom()`
* `boolean()`
* `string()`
* `integer()`
* `float()`
* `number()`
* `binary()` # another format of string()
* `emqx_schema:duration()` # time duration, another format of integer()
* ...
The primitive types are mostly self-describing, some are built-in, such
as `atom()`, some are defiend in EMQ X modules, such as `emqx_schema:duration()`.
### Config Paths
If we consider the whole EMQ X config as a tree,
to reference a primitive value, we can use a dot-separated names form string for
the path from the tree-root (always a Struct) down to the primitive values at tree-leaves.
Each segment of the dotted string is a Struct filed name or Map key.
For Array elements, 1-based index is used.
below are some examples
```
node.name="emqx.127.0.0.1"
zone.zone1.max_packet_size="10M"
authentication.1.enable=true
```
### Environment varialbes
Environment variables can be used to define or override config values.
Due to the fact that dots (`.`) are not allowed in environment variables, dots are
replaced with double-underscores (`__`).
And a the `EMQX_` prefix is used as the namespace.
For example `node.name` can be represented as `EMQX_NODE__NAME`
Environment varialbe values are parsed as hocon values, this allows users
to even set complex values from environment variables.
For example, this environment variable sets an array value.
```
export EMQX_LISTENERS__SSL__L1__AUTHENTICATION__SSL__CIPHERS="[\"TLS_AES_256_GCM_SHA384\"]"
```
Unknown environment variables are logged as a `warning` level log, for example:
```
[warning] unknown_env_vars: ["EMQX_AUTHENTICATION__ENABLED"]
```
because the field name is `enable`, not `enabled`.
<strong>NOTE:</strong> Unknown root keys are however silently discarded.
### Config overlay
HOCON values are overlayed, earlier defined values are at layers closer to the bottom.
The overall order of the overlay rules from bottom up are:
1. `emqx.conf` the base config file
1. `EMQX_` prfixed environment variables
1. Cluster override file, the path of which is configured as `cluster_override_conf_file` in the lower layers
1. Local override file, the path of which is configured as `local_override_conf_file` in the lower layers
Below are the rules of config value overlay.
#### Struct Fileds
Later config values overwrites earlier values.
For example, in below config, the last line `debug` overwrites `errro` for
console log handler's `level` config, but leaving `enable` unchanged.
```
log {
console_handler{
enable=true,
level=error
}
}
## ... more configs ...
log.console_handler.level=debug
```
#### Map Values
Maps are like structs, only the files are user-defined rather than
the config schema. For instance, `zone1` in the exampele below.
```
zone {
zone1 {
mqtt.max_packet_size = 1M
}
}
## The maximum packet size can be defined as above,
## then overriden as below
zone.zone1.mqtt.max_packet_size = 10M
```
#### Array Elements
Arrays in EMQ X config have two different representations
* list, such as: `[1, 2, 3]`
* indexed-map, such as: `{"1"=1, "2"=2, "3"=3}`
Dot-separated paths with number in it are parsed to indexed-maps
e.g. `authentication.1={...}` is parsed as `authentication={"1": {...}}`
Indexed-map arrays can be used to override list arrays:
```
authentication=[{enable=true, backend="built-in-database", mechanism="password-based"}]
# we can disable this authentication provider with:
authentication.1.enable=false
```
However, list arrays do not get recursively merged into indexed-map arrays.
e.g.
```
authentication=[{enable=true, backend="built-in-database", mechanism="password-based"}]
## below value will replace the whole array, but not to override just one field.
authentication=[{enable=true}]
```

View File

@ -24,6 +24,7 @@
-export([update/3, update/4]). -export([update/3, update/4]).
-export([remove/2, remove/3]). -export([remove/2, remove/3]).
-export([reset/2, reset/3]). -export([reset/2, reset/3]).
-export([gen_doc/1]).
%% for rpc %% for rpc
-export([get_node_and_config/1]). -export([get_node_and_config/1]).
@ -123,6 +124,16 @@ reset(Node, KeyPath, Opts) when Node =:= node() ->
reset(Node, KeyPath, Opts) -> reset(Node, KeyPath, Opts) ->
rpc:call(Node, ?MODULE, reset, [KeyPath, Opts]). rpc:call(Node, ?MODULE, reset, [KeyPath, Opts]).
-spec gen_doc(file:name_all()) -> ok.
gen_doc(File) ->
Version = emqx_release:version(),
Title = "# EMQ X " ++ Version ++ " Configuration",
BodyFile = filename:join([code:lib_dir(emqx_conf), "etc", "emqx_conf.md"]),
{ok, Body} = file:read_file(BodyFile),
Doc = hocon_schema_doc:gen(emqx_conf_schema, #{title => Title,
body => Body}),
file:write_file(File, Doc).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -24,6 +24,7 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include_lib("hocon/include/hoconsc.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 log_level() :: debug | info | notice | warning | error | critical | alert | emergency | all.
-type file() :: string(). -type file() :: string().
@ -62,8 +63,12 @@
namespace() -> undefined. namespace() -> undefined.
roots() -> roots() ->
%% authorization configs are merged in THIS schema's "authorization" fields PtKey = ?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY,
lists:keydelete("authorization", 1, emqx_schema:roots(high)) ++ case persistent_term:get(PtKey, undefined) of
undefined -> persistent_term:put(PtKey, emqx_authn_schema);
_ -> ok
end,
emqx_schema_high_prio_roots() ++
[ {"node", [ {"node",
sc(hoconsc:ref("node"), sc(hoconsc:ref("node"),
#{ desc => "Node name, cookie, config & data directories " #{ desc => "Node name, cookie, config & data directories "
@ -87,20 +92,6 @@ roots() ->
"should work, but in case you need to do performance " "should work, but in case you need to do performance "
"fine-turning or experiment a bit, this is where to look." "fine-turning or experiment a bit, this is where to look."
})} })}
, {"authorization",
sc(hoconsc:ref("authorization"),
#{ desc => """
Authorization a.k.a ACL.<br>
In EMQ X, MQTT client access control is extremly flexible.<br>
An out of the box set of authorization data sources are supported.
For example,<br>
'file' source is to support concise and yet generic ACL rules in a file;<br>
'built-in-database' source can be used to store per-client customisable rule sets,
natively in the EMQ X node;<br>
'http' source to make EMQ X call an external HTTP API to make the decision;<br>
'postgresql' etc. to look up clients or rules from external databases;<br>
"""
})}
, {"db", , {"db",
sc(ref("db"), sc(ref("db"),
#{ desc => "Settings of the embedded database." #{ desc => "Settings of the embedded database."
@ -251,14 +242,12 @@ fields("node") ->
[ {"name", [ {"name",
sc(string(), sc(string(),
#{ default => "emqx@127.0.0.1" #{ default => "emqx@127.0.0.1"
, override_env => "EMQX_NODE_NAME"
})} })}
, {"cookie", , {"cookie",
sc(string(), sc(string(),
#{ mapping => "vm_args.-setcookie", #{ mapping => "vm_args.-setcookie",
default => "emqxsecretcookie", default => "emqxsecretcookie",
sensitive => true, sensitive => true
override_env => "EMQX_NODE_COOKIE"
})} })}
, {"data_dir", , {"data_dir",
sc(string(), sc(string(),
@ -845,3 +834,22 @@ ensure_list(V) ->
roots(Module) -> roots(Module) ->
lists:map(fun({_BinName, Root}) -> Root end, hocon_schema:roots(Module)). lists:map(fun({_BinName, Root}) -> Root end, hocon_schema:roots(Module)).
%% Like authentication schema, authorization schema is incomplete in emqx_schema
%% module, this function replaces the root filed "authorization" with a new schema
emqx_schema_high_prio_roots() ->
Roots = emqx_schema:roots(high),
Authz = {"authorization",
sc(hoconsc:ref("authorization"),
#{ desc => """
Authorization a.k.a ACL.<br>
In EMQ X, MQTT client access control is extremly flexible.<br>
An out of the box set of authorization data sources are supported.
For example,<br>
'file' source is to support concise and yet generic ACL rules in a file;<br>
'built-in-database' source can be used to store per-client customisable rule sets,
natively in the EMQ X node;<br>
'http' source to make EMQ X call an external HTTP API to make the decision;<br>
'postgresql' etc. to look up clients or rules from external databases;<br>
""" })},
lists:keyreplace("authorization", 1, Roots, Authz).

View File

@ -38,7 +38,7 @@
catch catch
error:{invalid_bridge_id, Id0} -> error:{invalid_bridge_id, Id0} ->
{400, #{code => 'INVALID_ID', message => <<"invalid_bridge_id: ", Id0/binary, {400, #{code => 'INVALID_ID', message => <<"invalid_bridge_id: ", Id0/binary,
". Bridge Ids must be of format <bridge_type>:<name>">>}} ". Bridge ID must be of format 'bridge_type:name'">>}}
end). end).
namespace() -> "connector". namespace() -> "connector".
@ -74,7 +74,7 @@ schema("/connectors_test") ->
post => #{ post => #{
tags => [<<"connectors">>], tags => [<<"connectors">>],
description => <<"Test creating a new connector by given Id <br>" description => <<"Test creating a new connector by given Id <br>"
"The Id must be of format <type>:<name>">>, "The ID must be of format 'type:name'">>,
summary => <<"Test creating connector">>, summary => <<"Test creating connector">>,
requestBody => connector_test_info(), requestBody => connector_test_info(),
responses => #{ responses => #{
@ -98,7 +98,7 @@ schema("/connectors") ->
post => #{ post => #{
tags => [<<"connectors">>], tags => [<<"connectors">>],
description => <<"Create a new connector by given Id <br>" description => <<"Create a new connector by given Id <br>"
"The Id must be of format <type>:<name>">>, "The ID must be of format 'type:name'">>,
summary => <<"Create connector">>, summary => <<"Create connector">>,
requestBody => connector_info(), requestBody => connector_info(),
responses => #{ responses => #{

View File

@ -11,6 +11,7 @@
roots() -> ["connectors"]. roots() -> ["connectors"].
fields(connectors) -> fields("connectors");
fields("connectors") -> fields("connectors") ->
[ {mqtt, [ {mqtt,
sc(hoconsc:map(name, sc(hoconsc:map(name,

View File

@ -182,12 +182,12 @@ check_parameter([{Name, Type} | Spec], Bindings, QueryStr, Module, BindingsAcc,
Schema = ?INIT_SCHEMA#{roots => [{Name, Type}]}, Schema = ?INIT_SCHEMA#{roots => [{Name, Type}]},
case hocon_schema:field_schema(Type, in) of case hocon_schema:field_schema(Type, in) of
path -> path ->
Option = #{atom_key => true, override_env => false}, Option = #{atom_key => true},
NewBindings = hocon_schema:check_plain(Schema, Bindings, Option), NewBindings = hocon_schema:check_plain(Schema, Bindings, Option),
NewBindingsAcc = maps:merge(BindingsAcc, NewBindings), NewBindingsAcc = maps:merge(BindingsAcc, NewBindings),
check_parameter(Spec, Bindings, QueryStr, Module, NewBindingsAcc, QueryStrAcc); check_parameter(Spec, Bindings, QueryStr, Module, NewBindingsAcc, QueryStrAcc);
query -> query ->
Option = #{override_env => false}, Option = #{},
NewQueryStr = hocon_schema:check_plain(Schema, QueryStr, Option), NewQueryStr = hocon_schema:check_plain(Schema, QueryStr, Option),
NewQueryStrAcc = maps:merge(QueryStrAcc, NewQueryStr), NewQueryStrAcc = maps:merge(QueryStrAcc, NewQueryStr),
check_parameter(Spec, Bindings, QueryStr, Module,BindingsAcc, NewQueryStrAcc) check_parameter(Spec, Bindings, QueryStr, Module,BindingsAcc, NewQueryStrAcc)
@ -201,7 +201,7 @@ check_request_body(#{body := Body}, Schema, Module, CheckFun, true) ->
_ -> Type0 _ -> Type0
end, end,
NewSchema = ?INIT_SCHEMA#{roots => [{root, Type}]}, NewSchema = ?INIT_SCHEMA#{roots => [{root, Type}]},
Option = #{override_env => false, nullable => true}, Option = #{nullable => true},
#{<<"root">> := NewBody} = CheckFun(NewSchema, #{<<"root">> => Body}, Option), #{<<"root">> := NewBody} = CheckFun(NewSchema, #{<<"root">> => Body}, Option),
NewBody; NewBody;
%% TODO not support nest object check yet, please use ref! %% TODO not support nest object check yet, please use ref!
@ -214,7 +214,7 @@ check_request_body(#{body := Body}, Schema, Module, CheckFun, true) ->
check_request_body(#{body := Body}, Spec, _Module, CheckFun, false) -> check_request_body(#{body := Body}, Spec, _Module, CheckFun, false) ->
lists:foldl(fun({Name, Type}, Acc) -> lists:foldl(fun({Name, Type}, Acc) ->
Schema = ?INIT_SCHEMA#{roots => [{Name, Type}]}, Schema = ?INIT_SCHEMA#{roots => [{Name, Type}]},
maps:merge(Acc, CheckFun(Schema, Body, #{override_env => false})) maps:merge(Acc, CheckFun(Schema, Body, #{}))
end, #{}, Spec). end, #{}, Spec).
%% tags, description, summary, security, deprecated %% tags, description, summary, security, deprecated

View File

@ -18,9 +18,6 @@
-behaviour(emqx_gateway_channel). -behaviour(emqx_gateway_channel).
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx_gateway/src/coap/include/emqx_coap.hrl").
%% API %% API
-export([ info/1 -export([ info/1
, info/2 , info/2
@ -44,6 +41,12 @@
-export_type([channel/0]). -export_type([channel/0]).
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx_gateway/src/coap/include/emqx_coap.hrl").
-include_lib("emqx/include/emqx_authentication.hrl").
-define(AUTHN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
-record(channel, { -record(channel, {
%% Context %% Context
ctx :: emqx_gateway_ctx:context(), ctx :: emqx_gateway_ctx:context(),
@ -283,7 +286,7 @@ try_takeover(idle, DesireId, Msg, Channel) ->
%% udp connection baseon the clientid %% udp connection baseon the clientid
call_session(handle_request, Msg, Channel); call_session(handle_request, Msg, Channel);
_ -> _ ->
case emqx_conf:get([gateway, coap, authentication], undefined) of case emqx_conf:get([gateway, coap, ?AUTHN], undefined) of
undefined -> undefined ->
call_session(handle_request, Msg, Channel); call_session(handle_request, Msg, Channel);
_ -> _ ->

View File

@ -17,6 +17,7 @@
-module(emqx_gateway_api). -module(emqx_gateway_api).
-include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl").
-include_lib("emqx/include/emqx_authentication.hrl").
-behaviour(minirest_api). -behaviour(minirest_api).
@ -243,7 +244,7 @@ schema_gateway_overview_list() ->
%% %%
%% NOTE: It is a temporary measure to generate swagger-schema %% NOTE: It is a temporary measure to generate swagger-schema
-define(COAP_GATEWAY_CONFS, -define(COAP_GATEWAY_CONFS,
#{<<"authentication">> => #{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY =>
#{<<"mechanism">> => <<"password-based">>, #{<<"mechanism">> => <<"password-based">>,
<<"name">> => <<"authenticator1">>, <<"name">> => <<"authenticator1">>,
<<"server_type">> => <<"built-in-database">>, <<"server_type">> => <<"built-in-database">>,
@ -331,7 +332,7 @@ schema_gateway_overview_list() ->
). ).
-define(STOMP_GATEWAY_CONFS, -define(STOMP_GATEWAY_CONFS,
#{<<"authentication">> => #{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY =>
#{<<"mechanism">> => <<"password-based">>, #{<<"mechanism">> => <<"password-based">>,
<<"name">> => <<"authenticator1">>, <<"name">> => <<"authenticator1">>,
<<"server_type">> => <<"built-in-database">>, <<"server_type">> => <<"built-in-database">>,

View File

@ -17,8 +17,6 @@
%% @doc The gateway configuration management module %% @doc The gateway configuration management module
-module(emqx_gateway_conf). -module(emqx_gateway_conf).
-include_lib("emqx/include/logger.hrl").
%% Load/Unload %% Load/Unload
-export([ load/0 -export([ load/0
, unload/0 , unload/0
@ -56,6 +54,10 @@
, post_config_update/5 , post_config_update/5
]). ]).
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_authentication.hrl").
-define(AUTHN_BIN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY).
-type atom_or_bin() :: atom() | binary(). -type atom_or_bin() :: atom() | binary().
-type ok_or_err() :: ok_or_err(). -type ok_or_err() :: ok_or_err().
-type listener_ref() :: {ListenerType :: atom_or_bin(), -type listener_ref() :: {ListenerType :: atom_or_bin(),
@ -106,8 +108,9 @@ maps_key_take([K | Ks], M, Acc) ->
-spec update_gateway(atom_or_bin(), map()) -> ok_or_err(). -spec update_gateway(atom_or_bin(), map()) -> ok_or_err().
update_gateway(GwName, Conf0) -> update_gateway(GwName, Conf0) ->
Conf = maps:without([listeners, authentication, Exclude0 = [listeners, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM],
<<"listeners">>, <<"authentication">>], Conf0), Exclude1 = [atom_to_binary(K, utf8) || K <- Exclude0],
Conf = maps:without(Exclude0 ++ Exclude1, Conf0),
update({?FUNCTION_NAME, bin(GwName), Conf}). update({?FUNCTION_NAME, bin(GwName), Conf}).
%% FIXME: delete cert files ?? %% FIXME: delete cert files ??
@ -263,8 +266,7 @@ pre_config_update(_, {update_gateway, GwName, Conf}, RawConf) ->
undefined -> undefined ->
{error, not_found}; {error, not_found};
_ -> _ ->
NConf = maps:without([<<"listeners">>, NConf = maps:without([<<"listeners">>, ?AUTHN_BIN], Conf),
<<"authentication">>], Conf),
{ok, emqx_map_lib:deep_merge(RawConf, #{GwName => NConf})} {ok, emqx_map_lib:deep_merge(RawConf, #{GwName => NConf})}
end; end;
pre_config_update(_, {unload_gateway, GwName}, RawConf) -> pre_config_update(_, {unload_gateway, GwName}, RawConf) ->
@ -311,11 +313,11 @@ pre_config_update(_, {remove_listener, GwName, {LType, LName}}, RawConf) ->
pre_config_update(_, {add_authn, GwName, Conf}, RawConf) -> pre_config_update(_, {add_authn, GwName, Conf}, RawConf) ->
case emqx_map_lib:deep_get( case emqx_map_lib:deep_get(
[GwName, <<"authentication">>], RawConf, undefined) of [GwName, ?AUTHN_BIN], RawConf, undefined) of
undefined -> undefined ->
{ok, emqx_map_lib:deep_merge( {ok, emqx_map_lib:deep_merge(
RawConf, RawConf,
#{GwName => #{<<"authentication">> => Conf}})}; #{GwName => #{?AUTHN_BIN => Conf}})};
_ -> _ ->
{error, already_exist} {error, already_exist}
end; end;
@ -326,9 +328,9 @@ pre_config_update(_, {add_authn, GwName, {LType, LName}, Conf}, RawConf) ->
undefined -> undefined ->
{error, not_found}; {error, not_found};
Listener -> Listener ->
case maps:get(<<"authentication">>, Listener, undefined) of case maps:get(?AUTHN_BIN, Listener, undefined) of
undefined -> undefined ->
NListener = maps:put(<<"authentication">>, Conf, Listener), NListener = maps:put(?AUTHN_BIN, Conf, Listener),
NGateway = #{GwName => NGateway = #{GwName =>
#{<<"listeners">> => #{<<"listeners">> =>
#{LType => #{LName => NListener}}}}, #{LType => #{LName => NListener}}}},
@ -339,13 +341,13 @@ pre_config_update(_, {add_authn, GwName, {LType, LName}, Conf}, RawConf) ->
end; end;
pre_config_update(_, {update_authn, GwName, Conf}, RawConf) -> pre_config_update(_, {update_authn, GwName, Conf}, RawConf) ->
case emqx_map_lib:deep_get( case emqx_map_lib:deep_get(
[GwName, <<"authentication">>], RawConf, undefined) of [GwName, ?AUTHN_BIN], RawConf, undefined) of
undefined -> undefined ->
{error, not_found}; {error, not_found};
_ -> _ ->
{ok, emqx_map_lib:deep_merge( {ok, emqx_map_lib:deep_merge(
RawConf, RawConf,
#{GwName => #{<<"authentication">> => Conf}})} #{GwName => #{?AUTHN_BIN => Conf}})}
end; end;
pre_config_update(_, {update_authn, GwName, {LType, LName}, Conf}, RawConf) -> pre_config_update(_, {update_authn, GwName, {LType, LName}, Conf}, RawConf) ->
case emqx_map_lib:deep_get( case emqx_map_lib:deep_get(
@ -354,12 +356,12 @@ pre_config_update(_, {update_authn, GwName, {LType, LName}, Conf}, RawConf) ->
undefined -> undefined ->
{error, not_found}; {error, not_found};
Listener -> Listener ->
case maps:get(<<"authentication">>, Listener, undefined) of case maps:get(?AUTHN_BIN, Listener, undefined) of
undefined -> undefined ->
{error, not_found}; {error, not_found};
Auth -> Auth ->
NListener = maps:put( NListener = maps:put(
<<"authentication">>, ?AUTHN_BIN,
emqx_map_lib:deep_merge(Auth, Conf), emqx_map_lib:deep_merge(Auth, Conf),
Listener Listener
), ),
@ -371,9 +373,9 @@ pre_config_update(_, {update_authn, GwName, {LType, LName}, Conf}, RawConf) ->
end; end;
pre_config_update(_, {remove_authn, GwName}, RawConf) -> pre_config_update(_, {remove_authn, GwName}, RawConf) ->
{ok, emqx_map_lib:deep_remove( {ok, emqx_map_lib:deep_remove(
[GwName, <<"authentication">>], RawConf)}; [GwName, ?AUTHN_BIN], RawConf)};
pre_config_update(_, {remove_authn, GwName, {LType, LName}}, RawConf) -> pre_config_update(_, {remove_authn, GwName, {LType, LName}}, RawConf) ->
Path = [GwName, <<"listeners">>, LType, LName, <<"authentication">>], Path = [GwName, <<"listeners">>, LType, LName, ?AUTHN_BIN],
{ok, emqx_map_lib:deep_remove(Path, RawConf)}; {ok, emqx_map_lib:deep_remove(Path, RawConf)};
pre_config_update(_, UnknownReq, _RawConf) -> pre_config_update(_, UnknownReq, _RawConf) ->

View File

@ -19,6 +19,9 @@
-include("include/emqx_gateway.hrl"). -include("include/emqx_gateway.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_authentication.hrl").
-define(AUTHN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
%% Mgmt APIs - gateway %% Mgmt APIs - gateway
-export([ gateways/1 -export([ gateways/1
@ -166,7 +169,7 @@ remove_listener(ListenerId) ->
-spec authn(gateway_name()) -> map(). -spec authn(gateway_name()) -> map().
authn(GwName) -> authn(GwName) ->
%% XXX: Need append chain-nanme, authenticator-id? %% XXX: Need append chain-nanme, authenticator-id?
Path = [gateway, GwName, authentication], Path = [gateway, GwName, ?AUTHN],
ChainName = emqx_gateway_utils:global_chain(GwName), ChainName = emqx_gateway_utils:global_chain(GwName),
wrap_chain_name( wrap_chain_name(
ChainName, ChainName,
@ -176,7 +179,7 @@ authn(GwName) ->
-spec authn(gateway_name(), binary()) -> map(). -spec authn(gateway_name(), binary()) -> map().
authn(GwName, ListenerId) -> authn(GwName, ListenerId) ->
{_, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId), {_, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId),
Path = [gateway, GwName, listeners, Type, Name, authentication], Path = [gateway, GwName, listeners, Type, Name, ?AUTHN],
ChainName = emqx_gateway_utils:listener_chain(GwName, Type, Name), ChainName = emqx_gateway_utils:listener_chain(GwName, Type, Name),
wrap_chain_name( wrap_chain_name(
ChainName, ChainName,

View File

@ -24,6 +24,7 @@
-dialyzer(no_unused). -dialyzer(no_unused).
-dialyzer(no_fail_call). -dialyzer(no_fail_call).
-include_lib("emqx/include/emqx_authentication.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-type ip_port() :: tuple(). -type ip_port() :: tuple().
@ -144,7 +145,7 @@ The client just sends its PUBLISH messages to a GW"
, desc => , desc =>
"The Pre-defined topic ids and topic names.<br> "The Pre-defined topic ids and topic names.<br>
A 'pre-defined' topic id is a topic id whose mapping to a topic name A 'pre-defined' topic id is a topic id whose mapping to a topic name
is known in advance by both the clients application and the gateway" is known in advance by both the client's application and the gateway"
})} })}
, {listeners, sc(ref(udp_listeners))} , {listeners, sc(ref(udp_listeners))}
] ++ gateway_common_options(); ] ++ gateway_common_options();
@ -407,30 +408,14 @@ fields(dtls_opts) ->
, ciphers => dtls_all_available , ciphers => dtls_all_available
}, false). }, false).
authentication() -> authentication_schema() ->
sc(hoconsc:union( sc(emqx_authn_schema:authenticator_type(),
[ hoconsc:ref(emqx_authn_mnesia, config) #{ nullable => {true, recursively}
, hoconsc:ref(emqx_authn_mysql, config) , desc =>
, hoconsc:ref(emqx_authn_pgsql, config)
, hoconsc:ref(emqx_authn_mongodb, standalone)
, hoconsc:ref(emqx_authn_mongodb, 'replica-set')
, hoconsc:ref(emqx_authn_mongodb, 'sharded-cluster')
, hoconsc:ref(emqx_authn_redis, standalone)
, hoconsc:ref(emqx_authn_redis, cluster)
, hoconsc:ref(emqx_authn_redis, sentinel)
, hoconsc:ref(emqx_authn_http, get)
, hoconsc:ref(emqx_authn_http, post)
, hoconsc:ref(emqx_authn_jwt, 'hmac-based')
, hoconsc:ref(emqx_authn_jwt, 'public-key')
, hoconsc:ref(emqx_authn_jwt, 'jwks')
, hoconsc:ref(emqx_enhanced_authn_scram_mnesia, config)
]),
#{ nullable => {true, recursively}
, desc =>
"""Default authentication configs for all of the gateway listeners.<br> """Default authentication configs for all of the gateway listeners.<br>
For per-listener overrides see <code>authentication</code> For per-listener overrides see <code>authentication</code>
in listener configs""" in listener configs"""
}). }).
gateway_common_options() -> gateway_common_options() ->
[ {enable, [ {enable,
@ -464,7 +449,7 @@ it has two purposes:
sc(ref(clientinfo_override), sc(ref(clientinfo_override),
#{ desc => "" #{ desc => ""
})} })}
, {authentication, authentication()} , {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, authentication_schema()}
]. ].
common_listener_opts() -> common_listener_opts() ->
@ -483,7 +468,7 @@ common_listener_opts() ->
sc(integer(), sc(integer(),
#{ default => 1000 #{ default => 1000
})} })}
, {authentication, authentication()} , {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, authentication_schema()}
, {mountpoint, , {mountpoint,
sc(binary(), sc(binary(),
#{ default => undefined #{ default => undefined

View File

@ -28,6 +28,8 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
%% this parses to #{}, will not cause config cleanup
%% so we will need call emqx_config:erase
-define(CONF_DEFAULT, <<" -define(CONF_DEFAULT, <<"
gateway {} gateway {}
">>). ">>).
@ -39,6 +41,7 @@ gateway {}
all() -> emqx_common_test_helpers:all(?MODULE). all() -> emqx_common_test_helpers:all(?MODULE).
init_per_suite(Conf) -> init_per_suite(Conf) ->
emqx_config:erase(gateway),
emqx_config:init_load(emqx_gateway_schema, ?CONF_DEFAULT), emqx_config:init_load(emqx_gateway_schema, ?CONF_DEFAULT),
emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]),
Conf. Conf.

View File

@ -170,9 +170,9 @@ t_update_re_failed(_Config) ->
{error, {error,
{emqx_modules_schema, {emqx_modules_schema,
[{validation_error, [{validation_error,
#{array_index => 1,path => "rewrite.re", #{path => "rewrite.1.re",
reason => {<<"*^test/*">>,{"nothing to repeat",0}}, reason => {<<"*^test/*">>,{"nothing to repeat",0}},
value => <<"*^test/*">>}}]}}}, value => <<"*^test/*">>}}]}}},
?assertError(Error, emqx_rewrite:update(Rules)), ?assertError(Error, emqx_rewrite:update(Rules)),
ok. ok.

View File

@ -56,7 +56,7 @@ A list of outputs of the rule.<br>
An output can be a string that refers to the channel Id of a emqx bridge, or a object An output can be a string that refers to the channel Id of a emqx bridge, or a object
that refers to a function.<br> that refers to a function.<br>
There a some built-in functions like \"republish\" and \"console\", and we also support user There a some built-in functions like \"republish\" and \"console\", and we also support user
provided functions like \"<SomeModule>:<SomeFunction>\".<br> provided functions like \"ModuleName:FunctionName\".<br>
The outputs in the list is executed one by one in order. The outputs in the list is executed one by one in order.
This means that if one of the output is executing slowly, all of the outputs comes after it will not This means that if one of the output is executing slowly, all of the outputs comes after it will not
be executed until it returns.<br> be executed until it returns.<br>

View File

@ -342,6 +342,9 @@ generate_config() {
NOW_TIME="$(call_hocon now_time)" NOW_TIME="$(call_hocon now_time)"
## ths command populates two files: app.<time>.config and vm.<time>.args ## ths command populates two files: app.<time>.config and vm.<time>.args
## NOTE: the generate command merges environment variables to the base config (emqx.conf),
## but does not include the cluster-override.conf and local-override.conf
## meaning, certain overrides will not be mapped to app.<time>.config file
## disable SC2086 to allow EMQX_LICENSE_CONF_OPTION to split ## disable SC2086 to allow EMQX_LICENSE_CONF_OPTION to split
# shellcheck disable=SC2086 # shellcheck disable=SC2086
call_hocon -v -t "$NOW_TIME" -I "$CONFIGS_DIR/" -s $SCHEMA_MOD -c "$RUNNER_ETC_DIR"/emqx.conf $EMQX_LICENSE_CONF_OPTION -d "$RUNNER_DATA_DIR"/configs generate call_hocon -v -t "$NOW_TIME" -I "$CONFIGS_DIR/" -s $SCHEMA_MOD -c "$RUNNER_ETC_DIR"/emqx.conf $EMQX_LICENSE_CONF_OPTION -d "$RUNNER_DATA_DIR"/configs generate
@ -451,18 +454,23 @@ case "${COMMAND}" in
;; ;;
esac esac
## make EMQX_NODE_COOKIE right
if [ -n "${EMQX_NODE_NAME:-}" ]; then
export EMQX_NODE__NAME="${EMQX_NODE_NAME}"
unset EMQX_NODE_NAME
fi
## Possible ways to configure emqx node name: ## Possible ways to configure emqx node name:
## 1. configure node.name in emqx.conf ## 1. configure node.name in emqx.conf
## 2. override with environment variable EMQX_NODE_NAME ## 2. override with environment variable EMQX_NODE__NAME
## Node name is either short-name (without '@'), e.g. 'emqx' ## Node name is either short-name (without '@'), e.g. 'emqx'
## or long name (with '@') e.g. 'emqx@example.net' or 'emqx@127.0.0.1' ## or long name (with '@') e.g. 'emqx@example.net' or 'emqx@127.0.0.1'
NAME="${EMQX_NODE_NAME:-}" NAME="${EMQX_NODE__NAME:-}"
if [ -z "$NAME" ]; then if [ -z "$NAME" ]; then
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
# for boot commands, inspect emqx.conf for node name # for boot commands, inspect emqx.conf for node name
NAME="$(call_hocon -s $SCHEMA_MOD -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.name | tr -d \")" NAME="$(call_hocon -s $SCHEMA_MOD -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.name | tr -d \")"
else else
vm_args_file="$(latest_vm_args 'EMQX_NODE_NAME')" vm_args_file="$(latest_vm_args 'EMQX_NODE__NAME')"
NAME="$(grep -E '^-s?name' "${vm_args_file}" | awk '{print $2}')" NAME="$(grep -E '^-s?name' "${vm_args_file}" | awk '{print $2}')"
fi fi
fi fi
@ -483,18 +491,23 @@ export ESCRIPT_NAME="$SHORT_NAME"
PIPE_DIR="${PIPE_DIR:-/$RUNNER_DATA_DIR/${WHOAMI}_erl_pipes/$NAME/}" PIPE_DIR="${PIPE_DIR:-/$RUNNER_DATA_DIR/${WHOAMI}_erl_pipes/$NAME/}"
COOKIE="${EMQX_NODE_COOKIE:-}" ## make EMQX_NODE_COOKIE right
if [ -n "${EMQX_NODE_COOKIE:-}" ]; then
export EMQX_NODE__COOKIE="${EMQX_NODE_COOKIE}"
unset EMQX_NODE_COOKIE
fi
COOKIE="${EMQX_NODE__COOKIE:-}"
if [ -z "$COOKIE" ]; then if [ -z "$COOKIE" ]; then
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
COOKIE="$(call_hocon -s $SCHEMA_MOD -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.cookie | tr -d \")" COOKIE="$(call_hocon -s $SCHEMA_MOD -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.cookie | tr -d \")"
else else
vm_args_file="$(latest_vm_args 'EMQX_NODE_COOKIE')" vm_args_file="$(latest_vm_args 'EMQX_NODE__COOKIE')"
COOKIE="$(grep -E '^-setcookie' "${vm_args_file}" | awk '{print $2}')" COOKIE="$(grep -E '^-setcookie' "${vm_args_file}" | awk '{print $2}')"
fi fi
fi fi
if [ -z "$COOKIE" ]; then if [ -z "$COOKIE" ]; then
die "Please set node.cookie in $RUNNER_ETC_DIR/emqx.conf or override from environment variable EMQX_NODE_COOKIE" die "Please set node.cookie in $RUNNER_ETC_DIR/emqx.conf or override from environment variable EMQX_NODE__COOKIE"
fi fi
cd "$ROOTDIR" cd "$ROOTDIR"

15
build
View File

@ -51,16 +51,17 @@ log() {
echo "===< $msg" echo "===< $msg"
} }
docgen() { make_doc() {
local libs_dir1 libs_dir2 local libs_dir1 libs_dir2
libs_dir1="$(find "_build/default/lib/" -maxdepth 2 -name ebin -type d)" libs_dir1="$(find "_build/default/lib/" -maxdepth 2 -name ebin -type d)"
libs_dir2="$(find "_build/$PROFILE/lib/" -maxdepth 2 -name ebin -type d)" libs_dir2="$(find "_build/$PROFILE/lib/" -maxdepth 2 -name ebin -type d)"
local conf_doc_markdown local conf_doc_md
conf_doc_markdown="$(pwd)/_build/${PROFILE}/rel/emqx/etc/emqx-config-doc.md" # TODO render md as html
echo "===< Generating config document $conf_doc_markdown" conf_doc_md="$(pwd)/_build/${PROFILE}/lib/emqx_dashboard/priv/config.md"
echo "===< Generating config document $conf_doc_md"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
erl -noshell -pa $libs_dir1 $libs_dir2 -eval "file:write_file('$conf_doc_markdown', hocon_schema_doc:gen(emqx_conf_schema)), halt(0)." erl -noshell -pa $libs_dir1 $libs_dir2 -eval "ok = emqx_conf:gen_doc(\"${conf_doc_md}\"), halt(0)."
} }
make_rel() { make_rel() {
@ -70,7 +71,6 @@ make_rel() {
echo "gpb should not be included in the release" echo "gpb should not be included in the release"
exit 1 exit 1
fi fi
docgen
} }
## unzip previous version .zip files to _build/$PROFILE/rel/emqx/releases before making relup ## unzip previous version .zip files to _build/$PROFILE/rel/emqx/releases before making relup
@ -205,6 +205,9 @@ make_docker_testing() {
log "building artifact=$ARTIFACT for profile=$PROFILE" log "building artifact=$ARTIFACT for profile=$PROFILE"
case "$ARTIFACT" in case "$ARTIFACT" in
doc)
make_doc
;;
rel) rel)
make_rel make_rel
;; ;;

View File

@ -64,7 +64,7 @@
, {observer_cli, "1.7.1"} % NOTE: depends on recon 2.5.x , {observer_cli, "1.7.1"} % NOTE: depends on recon 2.5.x
, {getopt, "1.0.2"} , {getopt, "1.0.2"}
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.15.0"}}} , {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.15.0"}}}
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.20.6"}}} , {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.22.0"}}}
, {emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.4.1"}}} , {emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.4.1"}}}
, {esasl, {git, "https://github.com/emqx/esasl", {tag, "0.2.0"}}} , {esasl, {git, "https://github.com/emqx/esasl", {tag, "0.2.0"}}}
, {jose, {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}} , {jose, {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}}

View File

@ -153,36 +153,42 @@ profiles() ->
, {relx, relx(Vsn, cloud, bin, ce)} , {relx, relx(Vsn, cloud, bin, ce)}
, {overrides, prod_overrides()} , {overrides, prod_overrides()}
, {project_app_dirs, project_app_dirs(ce)} , {project_app_dirs, project_app_dirs(ce)}
, {post_hooks, [{compile, "./build emqx doc"}]}
]} ]}
, {'emqx-pkg', , {'emqx-pkg',
[ {erl_opts, prod_compile_opts()} [ {erl_opts, prod_compile_opts()}
, {relx, relx(Vsn, cloud, pkg, ce)} , {relx, relx(Vsn, cloud, pkg, ce)}
, {overrides, prod_overrides()} , {overrides, prod_overrides()}
, {project_app_dirs, project_app_dirs(ce)} , {project_app_dirs, project_app_dirs(ce)}
, {post_hooks, [{compile, "./build emqx-pkg doc"}]}
]} ]}
, {'emqx-enterprise', , {'emqx-enterprise',
[ {erl_opts, prod_compile_opts()} [ {erl_opts, prod_compile_opts()}
, {relx, relx(Vsn, cloud, bin, ee)} , {relx, relx(Vsn, cloud, bin, ee)}
, {overrides, prod_overrides()} , {overrides, prod_overrides()}
, {project_app_dirs, project_app_dirs(ee)} , {project_app_dirs, project_app_dirs(ee)}
, {post_hooks, [{compile, "./build emqx-enterprise doc"}]}
]} ]}
, {'emqx-enterprise-pkg', , {'emqx-enterprise-pkg',
[ {erl_opts, prod_compile_opts()} [ {erl_opts, prod_compile_opts()}
, {relx, relx(Vsn, cloud, pkg, ee)} , {relx, relx(Vsn, cloud, pkg, ee)}
, {overrides, prod_overrides()} , {overrides, prod_overrides()}
, {project_app_dirs, project_app_dirs(ee)} , {project_app_dirs, project_app_dirs(ee)}
, {post_hooks, [{compile, "./build emqx-enterprise-pkg doc"}]}
]} ]}
, {'emqx-edge', , {'emqx-edge',
[ {erl_opts, prod_compile_opts()} [ {erl_opts, prod_compile_opts()}
, {relx, relx(Vsn, edge, bin, ce)} , {relx, relx(Vsn, edge, bin, ce)}
, {overrides, prod_overrides()} , {overrides, prod_overrides()}
, {project_app_dirs, project_app_dirs(ce)} , {project_app_dirs, project_app_dirs(ce)}
, {post_hooks, [{compile, "./build emqx-edge doc"}]}
]} ]}
, {'emqx-edge-pkg', , {'emqx-edge-pkg',
[ {erl_opts, prod_compile_opts()} [ {erl_opts, prod_compile_opts()}
, {relx, relx(Vsn, edge, pkg, ce)} , {relx, relx(Vsn, edge, pkg, ce)}
, {overrides, prod_overrides()} , {overrides, prod_overrides()}
, {project_app_dirs, project_app_dirs(ce)} , {project_app_dirs, project_app_dirs(ce)}
, {post_hooks, [{compile, "./build emqx-edge-pkg doc"}]}
]} ]}
, {check, , {check,
[ {erl_opts, common_compile_opts()} [ {erl_opts, common_compile_opts()}