feat(authn): support quick deny anonymous
This commit is contained in:
parent
cded5fc6cf
commit
2bc8b00419
|
@ -2045,12 +2045,18 @@ Type of the rate limit.
|
||||||
base_listener_enable_authn {
|
base_listener_enable_authn {
|
||||||
desc {
|
desc {
|
||||||
en: """
|
en: """
|
||||||
Set <code>true</code> (default) to enable client authentication on this listener.
|
Set <code>true</code> (default) to enable client authentication on this listener, the authentication
|
||||||
When set to <code>false</code> clients will be allowed to connect without authentication.
|
process goes through the configured authentication chain.
|
||||||
|
When set to <code>false</code> to allow any clients with or without authentication information such as username or password to log in.
|
||||||
|
When set to <code>quick_deny_anonymous<code>, it behaves like when set to <code>true</code> but clients will be
|
||||||
|
denied immediately without going through any authenticators if <code>username</code> is not provided. This is useful to fence off
|
||||||
|
anonymous clients early.
|
||||||
"""
|
"""
|
||||||
zh: """
|
zh: """
|
||||||
配置 <code>true</code> (默认值)启用客户端进行身份认证。
|
配置 <code>true</code> (默认值)启用客户端进行身份认证,通过检查认配置的认认证器链来决定是否允许接入。
|
||||||
配置 <code>false</code> 时,将不对客户端做任何认证。
|
配置 <code>false</code> 时,将不对客户端做任何认证,任何客户端,不论是不是携带用户名等认证信息,都可以接入。
|
||||||
|
配置 <code>quick_deny_anonymous</code> 时,行为跟 <code>true</code> 类似,但是会对匿名
|
||||||
|
客户直接拒绝,不做使用任何认证器对客户端进行身份检查。
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
label: {
|
label: {
|
||||||
|
|
|
@ -38,11 +38,22 @@
|
||||||
| {ok, map(), binary()}
|
| {ok, map(), binary()}
|
||||||
| {continue, map()}
|
| {continue, map()}
|
||||||
| {continue, binary(), map()}
|
| {continue, binary(), map()}
|
||||||
| {error, term()}.
|
| {error, not_authorized}.
|
||||||
authenticate(Credential) ->
|
authenticate(Credential) ->
|
||||||
case run_hooks('client.authenticate', [Credential], {ok, #{is_superuser => false}}) of
|
%% pre-hook quick authentication or
|
||||||
|
%% if auth backend returning nothing but just 'ok'
|
||||||
|
%% it means it's not a superuser, or there is no way to tell.
|
||||||
|
NotSuperUser = #{is_superuser => false},
|
||||||
|
case emqx_authentication:pre_hook_authenticate(Credential) of
|
||||||
ok ->
|
ok ->
|
||||||
{ok, #{is_superuser => false}};
|
{ok, NotSuperUser};
|
||||||
|
continue ->
|
||||||
|
case run_hooks('client.authenticate', [Credential], {ok, #{is_superuser => false}}) of
|
||||||
|
ok ->
|
||||||
|
{ok, NotSuperUser};
|
||||||
|
Other ->
|
||||||
|
Other
|
||||||
|
end;
|
||||||
Other ->
|
Other ->
|
||||||
Other
|
Other
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -29,9 +29,13 @@
|
||||||
-include_lib("stdlib/include/ms_transform.hrl").
|
-include_lib("stdlib/include/ms_transform.hrl").
|
||||||
|
|
||||||
-define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
|
-define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
|
||||||
|
-define(IS_UNDEFINED(X), (X =:= undefined orelse X =:= <<>>)).
|
||||||
|
|
||||||
%% The authentication entrypoint.
|
%% The authentication entrypoint.
|
||||||
-export([authenticate/2]).
|
-export([
|
||||||
|
pre_hook_authenticate/1,
|
||||||
|
authenticate/2
|
||||||
|
]).
|
||||||
|
|
||||||
%% Authenticator manager process start/stop
|
%% Authenticator manager process start/stop
|
||||||
-export([
|
-export([
|
||||||
|
@ -221,10 +225,23 @@ when
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Authenticate
|
%% Authenticate
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
-spec pre_hook_authenticate(emqx_types:clientinfo()) ->
|
||||||
authenticate(#{enable_authn := false}, _AuthResult) ->
|
ok | continue | {error, not_authorized}.
|
||||||
|
pre_hook_authenticate(#{enable_authn := false}) ->
|
||||||
inc_authenticate_metric('authentication.success.anonymous'),
|
inc_authenticate_metric('authentication.success.anonymous'),
|
||||||
?TRACE_RESULT("authentication_result", ignore, enable_authn_false);
|
?TRACE_RESULT("authentication_result", ok, enable_authn_false);
|
||||||
|
pre_hook_authenticate(#{enable_authn := quick_deny_anonymous} = Credential) ->
|
||||||
|
case maps:get(username, Credential, undefined) of
|
||||||
|
U when ?IS_UNDEFINED(U) ->
|
||||||
|
?TRACE_RESULT(
|
||||||
|
"authentication_result", {error, not_authorized}, enable_authn_false
|
||||||
|
);
|
||||||
|
_ ->
|
||||||
|
continue
|
||||||
|
end;
|
||||||
|
pre_hook_authenticate(_) ->
|
||||||
|
continue.
|
||||||
|
|
||||||
authenticate(#{listener := Listener, protocol := Protocol} = Credential, _AuthResult) ->
|
authenticate(#{listener := Listener, protocol := Protocol} = Credential, _AuthResult) ->
|
||||||
case get_authenticators(Listener, global_chain(Protocol)) of
|
case get_authenticators(Listener, global_chain(Protocol)) of
|
||||||
{ok, ChainName, Authenticators} ->
|
{ok, ChainName, Authenticators} ->
|
||||||
|
|
|
@ -1668,7 +1668,7 @@ base_listener(Bind) ->
|
||||||
)},
|
)},
|
||||||
{"enable_authn",
|
{"enable_authn",
|
||||||
sc(
|
sc(
|
||||||
boolean(),
|
hoconsc:enum([true, false, quick_deny_anonymous]),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(base_listener_enable_authn),
|
desc => ?DESC(base_listener_enable_authn),
|
||||||
default => true
|
default => true
|
||||||
|
|
|
@ -37,7 +37,8 @@ init_per_testcase(_, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_testcase(_, _Config) ->
|
end_per_testcase(_, _Config) ->
|
||||||
ok = emqx_hooks:del('client.authorize', {?MODULE, authz_stub}).
|
ok = emqx_hooks:del('client.authorize', {?MODULE, authz_stub}),
|
||||||
|
ok = emqx_hooks:del('client.authenticate', {?MODULE, quick_deny_anonymous_authn}).
|
||||||
|
|
||||||
t_authenticate(_) ->
|
t_authenticate(_) ->
|
||||||
?assertMatch({ok, _}, emqx_access_control:authenticate(clientinfo())).
|
?assertMatch({ok, _}, emqx_access_control:authenticate(clientinfo())).
|
||||||
|
@ -60,6 +61,37 @@ t_delayed_authorize(_) ->
|
||||||
?assertEqual(deny, emqx_access_control:authorize(clientinfo(), Publish2, InvalidTopic)),
|
?assertEqual(deny, emqx_access_control:authorize(clientinfo(), Publish2, InvalidTopic)),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_quick_deny_anonymous(_) ->
|
||||||
|
ok = emqx_hooks:put(
|
||||||
|
'client.authenticate',
|
||||||
|
{?MODULE, quick_deny_anonymous_authn, []},
|
||||||
|
?HP_AUTHN
|
||||||
|
),
|
||||||
|
|
||||||
|
RawClient0 = clientinfo(),
|
||||||
|
RawClient = RawClient0#{username => undefined},
|
||||||
|
|
||||||
|
%% No name, No authn
|
||||||
|
Client1 = RawClient#{enable_authn => false},
|
||||||
|
?assertMatch({ok, _}, emqx_access_control:authenticate(Client1)),
|
||||||
|
|
||||||
|
%% No name, With quick_deny_anonymous
|
||||||
|
Client2 = RawClient#{enable_authn => quick_deny_anonymous},
|
||||||
|
?assertMatch({error, _}, emqx_access_control:authenticate(Client2)),
|
||||||
|
|
||||||
|
%% Bad name, With quick_deny_anonymous
|
||||||
|
Client3 = RawClient#{enable_authn => quick_deny_anonymous, username => <<"badname">>},
|
||||||
|
?assertMatch({error, _}, emqx_access_control:authenticate(Client3)),
|
||||||
|
|
||||||
|
%% Good name, With quick_deny_anonymous
|
||||||
|
Client4 = RawClient#{enable_authn => quick_deny_anonymous, username => <<"goodname">>},
|
||||||
|
?assertMatch({ok, _}, emqx_access_control:authenticate(Client4)),
|
||||||
|
|
||||||
|
%% Name, With authn
|
||||||
|
Client5 = RawClient#{enable_authn => true, username => <<"badname">>},
|
||||||
|
?assertMatch({error, _}, emqx_access_control:authenticate(Client5)),
|
||||||
|
ok.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Helper functions
|
%% Helper functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -67,6 +99,11 @@ t_delayed_authorize(_) ->
|
||||||
authz_stub(_Client, _PubSub, ValidTopic, _DefaultResult, ValidTopic) -> {stop, #{result => allow}};
|
authz_stub(_Client, _PubSub, ValidTopic, _DefaultResult, ValidTopic) -> {stop, #{result => allow}};
|
||||||
authz_stub(_Client, _PubSub, _Topic, _DefaultResult, _ValidTopic) -> {stop, #{result => deny}}.
|
authz_stub(_Client, _PubSub, _Topic, _DefaultResult, _ValidTopic) -> {stop, #{result => deny}}.
|
||||||
|
|
||||||
|
quick_deny_anonymous_authn(#{username := <<"badname">>}, _AuthResult) ->
|
||||||
|
{stop, {error, not_authorized}};
|
||||||
|
quick_deny_anonymous_authn(_ClientInfo, _AuthResult) ->
|
||||||
|
{stop, {ok, #{is_superuser => false}}}.
|
||||||
|
|
||||||
clientinfo() -> clientinfo(#{}).
|
clientinfo() -> clientinfo(#{}).
|
||||||
clientinfo(InitProps) ->
|
clientinfo(InitProps) ->
|
||||||
maps:merge(
|
maps:merge(
|
||||||
|
|
|
@ -2319,5 +2319,4 @@ returncode_name(?SN_RC2_EXCEED_LIMITATION) -> rejected_exceed_limitation;
|
||||||
returncode_name(?SN_RC2_REACHED_MAX_RETRY) -> reached_max_retry_times;
|
returncode_name(?SN_RC2_REACHED_MAX_RETRY) -> reached_max_retry_times;
|
||||||
returncode_name(_) -> accepted.
|
returncode_name(_) -> accepted.
|
||||||
|
|
||||||
name_to_returncode(not_authorized) -> ?SN_RC2_NOT_AUTHORIZE;
|
name_to_returncode(not_authorized) -> ?SN_RC2_NOT_AUTHORIZE.
|
||||||
name_to_returncode(_) -> ?SN_RC2_NOT_AUTHORIZE.
|
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
|
|
||||||
- Keep MQTT v5 User-Property pairs from bridge ingested MQTT messsages to bridge target [#9398](https://github.com/emqx/emqx/pull/9398).
|
- Keep MQTT v5 User-Property pairs from bridge ingested MQTT messsages to bridge target [#9398](https://github.com/emqx/emqx/pull/9398).
|
||||||
|
|
||||||
|
- Add a new config `quick_deny_anonymous` to allow quick deny of anonymous clients (without username) so the auth backend checks can be skipped [#8516](https://github.com/emqx/emqx/pull/8516).
|
||||||
|
|
||||||
## Bug fixes
|
## Bug fixes
|
||||||
|
|
||||||
- Fix `ssl.existingName` option of helm chart not working [#9307](https://github.com/emqx/emqx/issues/9307).
|
- Fix `ssl.existingName` option of helm chart not working [#9307](https://github.com/emqx/emqx/issues/9307).
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
- 为桥接收到的 MQTT v5 消息再转发时保留 User-Property 列表 [#9398](https://github.com/emqx/emqx/pull/9398)。
|
- 为桥接收到的 MQTT v5 消息再转发时保留 User-Property 列表 [#9398](https://github.com/emqx/emqx/pull/9398)。
|
||||||
|
|
||||||
|
- 添加了一个名为 `quick_deny_anonymous` 的新配置,用来在不调用认证链的情况下,快速的拒绝掉匿名用户,从而提高认证效率 [#8516](https://github.com/emqx/emqx/pull/8516)。
|
||||||
|
|
||||||
## 修复
|
## 修复
|
||||||
|
|
||||||
- 修复 helm chart 的 `ssl.existingName` 选项不起作用 [#9307](https://github.com/emqx/emqx/issues/9307)。
|
- 修复 helm chart 的 `ssl.existingName` 选项不起作用 [#9307](https://github.com/emqx/emqx/issues/9307)。
|
||||||
|
|
Loading…
Reference in New Issue