chore(auth): move authn leftovers to the authn app

This commit is contained in:
Ilya Averyanov 2023-08-01 21:15:04 +03:00
parent ca8c1e3ef8
commit 8213aa42c9
27 changed files with 200 additions and 132 deletions

View File

@ -123,20 +123,4 @@
until :: integer()
}).
%%--------------------------------------------------------------------
%% Authentication
%%--------------------------------------------------------------------
-record(authenticator, {
id :: binary(),
provider :: module(),
enable :: boolean(),
state :: map()
}).
-record(chain, {
name :: atom(),
authenticators :: [#authenticator{}]
}).
-endif.

View File

@ -17,7 +17,6 @@
-ifndef(EMQX_ACCESS_CONTROL_HRL).
-define(EMQX_ACCESS_CONTROL_HRL, true).
%% config root name all auth providers have to agree on.
-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, "authorization").
-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_ATOM, authorization).
-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_BINARY, <<"authorization">>).

View File

@ -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]}}.

View File

@ -24,7 +24,6 @@
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
-include("emqx_schema.hrl").
-include("emqx_authentication.hrl").
-include("emqx_access_control.hrl").
-include_lib("typerefl/include/types.hrl").
-include_lib("hocon/include/hoconsc.hrl").
@ -216,7 +215,6 @@ roots(high) ->
importance => ?IMPORTANCE_HIDDEN
}
)},
{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, authentication(global)},
%% NOTE: authorization schema here is only to keep emqx app pure
%% the full schema for EMQX node is injected in emqx_conf_schema.
{?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME,
@ -224,7 +222,7 @@ roots(high) ->
ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME),
#{importance => ?IMPORTANCE_HIDDEN}
)}
];
] ++ emqx_schema_hooks:injection_point('roots.high');
roots(medium) ->
[
{"broker",
@ -1750,11 +1748,8 @@ mqtt_listener(Bind) ->
desc => ?DESC(mqtt_listener_proxy_protocol_timeout),
default => <<"3s">>
}
)},
{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, (authentication(listener))#{
importance => ?IMPORTANCE_HIDDEN
}}
].
)}
] ++ emqx_schema_hooks:injection_point('mqtt.listener').
base_listener(Bind) ->
[
@ -2762,41 +2757,6 @@ str(B) when is_binary(B) ->
str(S) when is_list(S) ->
S.
authentication(Which) ->
{Importance, Desc} =
case Which of
global ->
%% For root level authentication, it is recommended to configure
%% from the dashboard or API.
%% Hence it's considered a low-importance when it comes to
%% configuration importance.
{?IMPORTANCE_LOW, ?DESC(global_authentication)};
listener ->
{?IMPORTANCE_HIDDEN, ?DESC(listener_authentication)}
end,
%% poor man's dependency injection
%% this is due to the fact that authn is implemented outside of 'emqx' app.
%% so it can not be a part of emqx_schema since 'emqx' app is supposed to
%% work standalone.
Type =
case persistent_term:get(?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY, undefined) of
undefined ->
hoconsc:array(typerefl:map());
Module ->
Module:root_type()
end,
hoconsc:mk(Type, #{
desc => Desc,
converter => fun ensure_array/2,
default => [],
importance => Importance
}).
%% the older version schema allows individual element (instead of a chain) in config
ensure_array(undefined, _) -> undefined;
ensure_array(L, _) when is_list(L) -> L;
ensure_array(M, _) -> [M].
-spec qos() -> typerefl:type().
qos() ->
typerefl:alias("qos", typerefl:union([0, 1, 2])).

View File

@ -0,0 +1,78 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2017-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_schema_hooks).
-type hookpoint() :: atom().
-callback injected_fields() ->
#{
hookpoint() => [hocon_schema:field()]
}.
-optional_callbacks([injected_fields/0]).
-define(HOOKPOINT_PT_KEY(POINT_NAME), {?MODULE, fields, POINT_NAME}).
-define(MODULE_PT_KEY(MOD_NAME), {?MODULE, mod, MOD_NAME}).
-export([
inject_fields/3,
injection_point/1,
inject_fields_from_mod/1
]).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
injection_point(PointName) ->
InjectedFields = persistent_term:get(?HOOKPOINT_PT_KEY(PointName), #{}),
lists:concat(maps:values(InjectedFields)).
inject_fields(PointName, Name, Fields) ->
Key = ?HOOKPOINT_PT_KEY(PointName),
InjectedFields = persistent_term:get(Key, #{}),
persistent_term:put(Key, InjectedFields#{Name => Fields}).
%%--------------------------------------------------------------------
%% Internal API
%%--------------------------------------------------------------------
inject_fields_from_mod(Module) ->
case persistent_term:get(?MODULE_PT_KEY(Module), false) of
false ->
persistent_term:put(?MODULE_PT_KEY(Module), true),
do_inject_fields_from_mod(Module);
true ->
ok
end.
do_inject_fields_from_mod(Module) ->
_ = Module:module_info(),
case erlang:function_exported(Module, injected_fields, 0) of
true ->
do_inject_fields_from_mod(Module, Module:injected_fields());
false ->
ok
end.
do_inject_fields_from_mod(Module, HookFields) ->
maps:foreach(
fun(PointName, Fields) ->
inject_fields(PointName, Module, Fields)
end,
HookFields
).

View File

@ -16,7 +16,7 @@
-module(emqx_common_test_helpers).
-include_lib("emqx/include/emqx_authentication.hrl").
-include_lib("emqx_authn/include/emqx_authentication.hrl").
-type special_config_handler() :: fun().

View File

@ -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]).

View File

@ -20,6 +20,11 @@
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_access_control.hrl").
%% config root name all auth providers have to agree on.
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, "authentication").
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication).
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, <<"authentication">>).
-define(GLOBAL, 'mqtt:global').
-define(TRACE_AUTHN_PROVIDER(Msg), ?TRACE_AUTHN_PROVIDER(Msg, #{})).
@ -31,17 +36,6 @@
-define(TRACE_AUTHN(Msg, Meta), ?TRACE_AUTHN(debug, Msg, Meta)).
-define(TRACE_AUTHN(Level, Msg, Meta), ?TRACE(Level, ?AUTHN_TRACE_TAG, Msg, Meta)).
%% config root name all auth providers have to agree on.
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME, "authentication").
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication).
-define(EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, <<"authentication">>).
%% key to a persistent term which stores a module name in order to inject
%% schema module at run-time to keep emqx app's compile time purity.
%% see emqx_schema.erl for more details
%% and emqx_conf_schema for an examples
-define(EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY, emqx_authentication_schema_module).
%% authentication move cmd
-define(CMD_MOVE_FRONT, front).
-define(CMD_MOVE_REAR, rear).

View File

@ -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).

View File

@ -22,14 +22,25 @@
-behaviour(gen_server).
-include("emqx.hrl").
-include("logger.hrl").
-include("emqx_authentication.hrl").
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_hooks.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
-record(authenticator, {
id :: binary(),
provider :: module(),
enable :: boolean(),
state :: map()
}).
-record(chain, {
name :: atom(),
authenticators :: [#authenticator{}]
}).
%% The authentication entrypoint.
-export([
authenticate/2

View File

@ -37,8 +37,8 @@
-export_type([config/0]).
-include("logger.hrl").
-include("emqx_authentication.hrl").
-include_lib("emqx/include/logger.hrl").
-type parsed_config() :: #{
mechanism := atom(),

View File

@ -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]).

View File

@ -26,7 +26,7 @@
stop/1
]).
-include_lib("emqx/include/emqx_authentication.hrl").
-include_lib("emqx_authentication.hrl").
-dialyzer({nowarn_function, [start/2]}).

View File

@ -19,6 +19,12 @@
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
-include_lib("hocon/include/hoconsc.hrl").
-include("emqx_authn.hrl").
-include("emqx_authentication.hrl").
-behaviour(emqx_schema_hooks).
-export([
injected_fields/0
]).
-export([
common_fields/0,
@ -28,13 +34,18 @@
fields/1,
authenticator_type/0,
authenticator_type_without_scram/0,
root_type/0,
mechanism/1,
backend/1
]).
roots() -> [].
injected_fields() ->
#{
'roots.high' => global_auth_fields(),
'mqtt.listener' => mqtt_listener_auth_fields()
}.
tags() ->
[<<"Authentication">>].
@ -121,12 +132,36 @@ try_select_union_member(Module, Value) ->
Module:refs()
end.
%% authn is a core functionality however implemented outside of emqx app
%% in emqx_schema, 'authentication' is a map() type which is to allow
%% EMQX more pluggable.
root_type() ->
hoconsc:array(authenticator_type()).
global_auth_fields() ->
[
{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM,
hoconsc:mk(root_type(), #{
desc => ?DESC(global_authentication),
converter => fun ensure_array/2,
default => [],
importance => ?IMPORTANCE_LOW
})}
].
mqtt_listener_auth_fields() ->
[
{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM,
hoconsc:mk(root_type(), #{
desc => ?DESC(listener_authentication),
converter => fun ensure_array/2,
default => [],
importance => ?IMPORTANCE_HIDDEN
})}
].
%% the older version schema allows individual element (instead of a chain) in config
ensure_array(undefined, _) -> undefined;
ensure_array(L, _) when is_list(L) -> L;
ensure_array(M, _) -> [M].
mechanism(Name) ->
?HOCON(
Name,

View File

@ -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}}.

View File

@ -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]).

View File

@ -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,

View File

@ -22,9 +22,9 @@
-dialyzer(no_unused).
-dialyzer(no_fail_call).
-include_lib("emqx/include/emqx_access_control.hrl").
-include_lib("typerefl/include/types.hrl").
-include_lib("hocon/include/hoconsc.hrl").
-include_lib("emqx/include/emqx_authentication.hrl").
-type log_level() :: debug | info | notice | warning | error | critical | alert | emergency | all.
-type file() :: string().
@ -66,6 +66,10 @@
emqx_otel_schema,
emqx_mgmt_api_key_schema
]).
-define(INJECTING_CONFIGS, [
emqx_authn_schema
]).
%% 1 million default ports counter
-define(DEFAULT_MAX_PORTS, 1024 * 1024).
@ -76,11 +80,7 @@ tags() ->
[<<"EMQX">>].
roots() ->
PtKey = ?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY,
case persistent_term:get(PtKey, undefined) of
undefined -> persistent_term:put(PtKey, emqx_authn_schema);
_ -> ok
end,
ok = ensure_fields_injected(),
emqx_schema_high_prio_roots() ++
[
{"node",
@ -1434,3 +1434,9 @@ ensure_unicode_path(Path, _) when is_list(Path) ->
Path;
ensure_unicode_path(Path, _) ->
throw({"not_string", Path}).
ensure_fields_injected() ->
lists:foreach(
fun(Module) -> emqx_schema_hooks:inject_fields_from_mod(Module) end,
?INJECTING_CONFIGS
).

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*-
{application, emqx_gateway, [
{description, "The Gateway management application"},
{vsn, "0.1.22"},
{vsn, "0.1.23"},
{registered, []},
{mod, {emqx_gateway_app, []}},
{applications, [kernel, stdlib, emqx, emqx_authn, emqx_ctl]},

View File

@ -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().

View File

@ -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).

View File

@ -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().

View File

@ -1,5 +1,32 @@
emqx_authn_schema {
global_authentication.desc:
"""Default authentication configs for all MQTT listeners.
For per-listener overrides see <code>authentication</code> in listener configs
This option can be configured with:
<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>
When a chain is configured, the login credentials are checked against the backends per the configured order, until an 'allow' or 'deny' decision can be made.
If there is no decision after a full chain exhaustion, the login is rejected."""
global_authentication.label:
"""Global authentication"""
listener_authentication.desc:
"""Per-listener authentication override.
Authentication can be one single authenticator instance or a chain of authenticators as an array.
When authenticating a login (username, client ID, etc.) the authenticators are checked in the configured order."""
listener_authentication.label:
"""Per-listener authentication override"""
backend.desc:
"""Backend type."""

View File

@ -532,22 +532,6 @@ mqtt_server_keepalive.desc:
mqtt_server_keepalive.label:
"""Server Keep Alive"""
global_authentication.desc:
"""Default authentication configs for all MQTT listeners.
For per-listener overrides see <code>authentication</code> in listener configs
This option can be configured with:
<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>
When a chain is configured, the login credentials are checked against the backends per the configured order, until an 'allow' or 'deny' decision can be made.
If there is no decision after a full chain exhaustion, the login is rejected."""
fields_mqtt_quic_listener_load_balancing_mode.desc:
"""0: Disabled, 1: SERVER_ID_IP, 2: SERVER_ID_FIXED. default: 0"""
@ -1103,14 +1087,6 @@ See: https://erlang.org/doc/man/inet.html#setopts-2"""
fields_tcp_opts_active_n.label:
"""active_n"""
listener_authentication.desc:
"""Per-listener authentication override.
Authentication can be one single authenticator instance or a chain of authenticators as an array.
When authenticating a login (username, client ID, etc.) the authenticators are checked in the configured order."""
listener_authentication.label:
"""Per-listener authentication override"""
fields_trace_payload_encode.desc:
"""Determine the format of the payload format in the trace file.<br/>
`text`: Text-based protocol or plain text protocol.