Merge pull request #8780 from HJianBo/authz-ignore-nomatch-rules
fix: exhook client.authorize never be execauted
This commit is contained in:
commit
757cee0d8b
|
@ -1,5 +1,9 @@
|
||||||
# 5.0.8
|
# 5.0.8
|
||||||
|
|
||||||
|
## Bug fixes
|
||||||
|
|
||||||
|
* Fix exhook `client.authorize` never being execauted. [#8780](https://github.com/emqx/emqx/pull/8780)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
* change the `/gateway` API path to plural form. [#8823](https://github.com/emqx/emqx/pull/8823)
|
* change the `/gateway` API path to plural form. [#8823](https://github.com/emqx/emqx/pull/8823)
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
-module(emqx_access_control).
|
-module(emqx_access_control).
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
authenticate/1,
|
authenticate/1,
|
||||||
|
@ -70,9 +71,26 @@ check_authorization_cache(ClientInfo, PubSub, Topic) ->
|
||||||
|
|
||||||
do_authorize(ClientInfo, PubSub, Topic) ->
|
do_authorize(ClientInfo, PubSub, Topic) ->
|
||||||
NoMatch = emqx:get_config([authorization, no_match], allow),
|
NoMatch = emqx:get_config([authorization, no_match], allow),
|
||||||
case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], NoMatch) of
|
Default = #{result => NoMatch, from => default},
|
||||||
allow -> allow;
|
case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], Default) of
|
||||||
_Other -> deny
|
AuthzResult = #{result := Result} when Result == allow; Result == deny ->
|
||||||
|
From = maps:get(from, AuthzResult, unknown),
|
||||||
|
emqx:run_hook(
|
||||||
|
'client.check_authz_complete',
|
||||||
|
[ClientInfo, PubSub, Topic, Result, From]
|
||||||
|
),
|
||||||
|
Result;
|
||||||
|
Other ->
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "unknown_authorization_return_format",
|
||||||
|
expected_example => "#{result => allow, from => default}",
|
||||||
|
got => Other
|
||||||
|
}),
|
||||||
|
emqx:run_hook(
|
||||||
|
'client.check_authz_complete',
|
||||||
|
[ClientInfo, PubSub, Topic, deny, unknown_return_format]
|
||||||
|
),
|
||||||
|
deny
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-compile({inline, [run_hooks/3]}).
|
-compile({inline, [run_hooks/3]}).
|
||||||
|
|
|
@ -69,6 +69,7 @@ conninfo() ->
|
||||||
{conn_props, properties()},
|
{conn_props, properties()},
|
||||||
{connected, boolean()},
|
{connected, boolean()},
|
||||||
{connected_at, timestamp()},
|
{connected_at, timestamp()},
|
||||||
|
{disconnected_at, timestamp()},
|
||||||
{keepalive, range(0, 16#ffff)},
|
{keepalive, range(0, 16#ffff)},
|
||||||
{receive_maximum, non_neg_integer()},
|
{receive_maximum, non_neg_integer()},
|
||||||
{expiry_interval, non_neg_integer()}
|
{expiry_interval, non_neg_integer()}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_authz, [
|
{application, emqx_authz, [
|
||||||
{description, "An OTP application"},
|
{description, "An OTP application"},
|
||||||
{vsn, "0.1.4"},
|
{vsn, "0.1.5"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_authz_app, []}},
|
{mod, {emqx_authz_app, []}},
|
||||||
{applications, [
|
{applications, [
|
||||||
|
|
|
@ -49,7 +49,8 @@
|
||||||
|
|
||||||
-type default_result() :: allow | deny.
|
-type default_result() :: allow | deny.
|
||||||
|
|
||||||
-type authz_result() :: {stop, allow} | {ok, deny}.
|
-type authz_result_value() :: #{result := allow | deny, from => _}.
|
||||||
|
-type authz_result() :: {stop, authz_result_value()} | {ok, authz_result_value()} | ignore.
|
||||||
|
|
||||||
-type sources() :: [source()].
|
-type sources() :: [source()].
|
||||||
|
|
||||||
|
@ -319,7 +320,7 @@ authorize(
|
||||||
is_superuser => true
|
is_superuser => true
|
||||||
}),
|
}),
|
||||||
emqx_metrics:inc(?METRIC_SUPERUSER),
|
emqx_metrics:inc(?METRIC_SUPERUSER),
|
||||||
{stop, allow};
|
{stop, #{result => allow, from => superuser}};
|
||||||
false ->
|
false ->
|
||||||
authorize_non_superuser(Client, PubSub, Topic, DefaultResult, Sources)
|
authorize_non_superuser(Client, PubSub, Topic, DefaultResult, Sources)
|
||||||
end.
|
end.
|
||||||
|
@ -331,15 +332,11 @@ authorize_non_superuser(
|
||||||
} = Client,
|
} = Client,
|
||||||
PubSub,
|
PubSub,
|
||||||
Topic,
|
Topic,
|
||||||
DefaultResult,
|
_DefaultResult,
|
||||||
Sources
|
Sources
|
||||||
) ->
|
) ->
|
||||||
case do_authorize(Client, PubSub, Topic, sources_with_defaults(Sources)) of
|
case do_authorize(Client, PubSub, Topic, sources_with_defaults(Sources)) of
|
||||||
{{matched, allow}, AuthzSource} ->
|
{{matched, allow}, AuthzSource} ->
|
||||||
emqx:run_hook(
|
|
||||||
'client.check_authz_complete',
|
|
||||||
[Client, PubSub, Topic, allow, AuthzSource]
|
|
||||||
),
|
|
||||||
log_allowed(#{
|
log_allowed(#{
|
||||||
username => Username,
|
username => Username,
|
||||||
ipaddr => IpAddress,
|
ipaddr => IpAddress,
|
||||||
|
@ -348,12 +345,8 @@ authorize_non_superuser(
|
||||||
}),
|
}),
|
||||||
emqx_metrics_worker:inc(authz_metrics, AuthzSource, allow),
|
emqx_metrics_worker:inc(authz_metrics, AuthzSource, allow),
|
||||||
emqx_metrics:inc(?METRIC_ALLOW),
|
emqx_metrics:inc(?METRIC_ALLOW),
|
||||||
{stop, allow};
|
{stop, #{result => allow, from => AuthzSource}};
|
||||||
{{matched, deny}, AuthzSource} ->
|
{{matched, deny}, AuthzSource} ->
|
||||||
emqx:run_hook(
|
|
||||||
'client.check_authz_complete',
|
|
||||||
[Client, PubSub, Topic, deny, AuthzSource]
|
|
||||||
),
|
|
||||||
?SLOG(warning, #{
|
?SLOG(warning, #{
|
||||||
msg => "authorization_permission_denied",
|
msg => "authorization_permission_denied",
|
||||||
username => Username,
|
username => Username,
|
||||||
|
@ -363,12 +356,8 @@ authorize_non_superuser(
|
||||||
}),
|
}),
|
||||||
emqx_metrics_worker:inc(authz_metrics, AuthzSource, deny),
|
emqx_metrics_worker:inc(authz_metrics, AuthzSource, deny),
|
||||||
emqx_metrics:inc(?METRIC_DENY),
|
emqx_metrics:inc(?METRIC_DENY),
|
||||||
{stop, deny};
|
{stop, #{result => deny, from => AuthzSource}};
|
||||||
nomatch ->
|
nomatch ->
|
||||||
emqx:run_hook(
|
|
||||||
'client.check_authz_complete',
|
|
||||||
[Client, PubSub, Topic, DefaultResult, default]
|
|
||||||
),
|
|
||||||
?SLOG(info, #{
|
?SLOG(info, #{
|
||||||
msg => "authorization_failed_nomatch",
|
msg => "authorization_failed_nomatch",
|
||||||
username => Username,
|
username => Username,
|
||||||
|
@ -377,7 +366,7 @@ authorize_non_superuser(
|
||||||
reason => "no-match rule"
|
reason => "no-match rule"
|
||||||
}),
|
}),
|
||||||
emqx_metrics:inc(?METRIC_NOMATCH),
|
emqx_metrics:inc(?METRIC_NOMATCH),
|
||||||
{stop, DefaultResult}
|
ignore
|
||||||
end.
|
end.
|
||||||
|
|
||||||
log_allowed(Meta) ->
|
log_allowed(Meta) ->
|
||||||
|
|
|
@ -133,7 +133,7 @@ on_client_authenticate(ClientInfo, AuthResult) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
on_client_authorize(ClientInfo, PubSub, Topic, Result) ->
|
on_client_authorize(ClientInfo, PubSub, Topic, Result) ->
|
||||||
Bool = Result == allow,
|
Bool = maps:get(result, Result, deny) == allow,
|
||||||
Type =
|
Type =
|
||||||
case PubSub of
|
case PubSub of
|
||||||
publish -> 'PUBLISH';
|
publish -> 'PUBLISH';
|
||||||
|
@ -158,7 +158,7 @@ on_client_authorize(ClientInfo, PubSub, Topic, Result) ->
|
||||||
true -> allow;
|
true -> allow;
|
||||||
_ -> deny
|
_ -> deny
|
||||||
end,
|
end,
|
||||||
{StopOrOk, NResult};
|
{StopOrOk, #{result => NResult, from => exhook}};
|
||||||
_ ->
|
_ ->
|
||||||
{ok, Result}
|
{ok, Result}
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_hooks.hrl").
|
||||||
|
|
||||||
-define(DEFAULT_CLUSTER_NAME_ATOM, emqxcl).
|
-define(DEFAULT_CLUSTER_NAME_ATOM, emqxcl).
|
||||||
|
|
||||||
|
@ -105,7 +106,10 @@ load_cfg(Cfg) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_access_failed_if_no_server_running(Config) ->
|
t_access_failed_if_no_server_running(Config) ->
|
||||||
emqx_exhook_mgr:disable(<<"default">>),
|
meck:expect(emqx_metrics_worker, inc, fun(_, _, _) -> ok end),
|
||||||
|
meck:expect(emqx_metrics, inc, fun(_) -> ok end),
|
||||||
|
emqx_hooks:add('client.authorize', {emqx_authz, authorize, [[]]}, ?HP_AUTHZ),
|
||||||
|
|
||||||
ClientInfo = #{
|
ClientInfo = #{
|
||||||
clientid => <<"user-id-1">>,
|
clientid => <<"user-id-1">>,
|
||||||
username => <<"usera">>,
|
username => <<"usera">>,
|
||||||
|
@ -114,14 +118,35 @@ t_access_failed_if_no_server_running(Config) ->
|
||||||
protocol => mqtt,
|
protocol => mqtt,
|
||||||
mountpoint => undefined
|
mountpoint => undefined
|
||||||
},
|
},
|
||||||
|
?assertMatch(
|
||||||
|
allow,
|
||||||
|
emqx_access_control:authorize(
|
||||||
|
ClientInfo#{username => <<"gooduser">>},
|
||||||
|
publish,
|
||||||
|
<<"acl/1">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
?assertMatch(
|
||||||
|
deny,
|
||||||
|
emqx_access_control:authorize(
|
||||||
|
ClientInfo#{username => <<"baduser">>},
|
||||||
|
publish,
|
||||||
|
<<"acl/2">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
emqx_exhook_mgr:disable(<<"default">>),
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{stop, {error, not_authorized}},
|
{stop, {error, not_authorized}},
|
||||||
emqx_exhook_handler:on_client_authenticate(ClientInfo, #{auth_result => success})
|
emqx_exhook_handler:on_client_authenticate(ClientInfo, #{auth_result => success})
|
||||||
),
|
),
|
||||||
|
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{stop, deny},
|
{stop, #{result := deny, from := exhook}},
|
||||||
emqx_exhook_handler:on_client_authorize(ClientInfo, publish, <<"t/1">>, allow)
|
emqx_exhook_handler:on_client_authorize(ClientInfo, publish, <<"t/1">>, #{
|
||||||
|
result => allow, from => exhook
|
||||||
|
})
|
||||||
),
|
),
|
||||||
|
|
||||||
Message = emqx_message:make(<<"t/1">>, <<"abc">>),
|
Message = emqx_message:make(<<"t/1">>, <<"abc">>),
|
||||||
|
@ -130,6 +155,7 @@ t_access_failed_if_no_server_running(Config) ->
|
||||||
emqx_exhook_handler:on_message_publish(Message)
|
emqx_exhook_handler:on_message_publish(Message)
|
||||||
),
|
),
|
||||||
emqx_exhook_mgr:enable(<<"default">>),
|
emqx_exhook_mgr:enable(<<"default">>),
|
||||||
|
emqx_hooks:del('client.authorize', {emqx_authz, authorize}),
|
||||||
assert_get_basic_usage_info(Config).
|
assert_get_basic_usage_info(Config).
|
||||||
|
|
||||||
t_lookup(_) ->
|
t_lookup(_) ->
|
||||||
|
|
|
@ -133,9 +133,16 @@ prop_client_authenticate() ->
|
||||||
).
|
).
|
||||||
|
|
||||||
prop_client_authorize() ->
|
prop_client_authorize() ->
|
||||||
|
MkResult = fun(Result) -> #{result => Result, from => exhook} end,
|
||||||
?ALL(
|
?ALL(
|
||||||
{ClientInfo0, PubSub, Topic, Result, Meta},
|
{ClientInfo0, PubSub, Topic, Result, Meta},
|
||||||
{clientinfo(), oneof([publish, subscribe]), topic(), oneof([allow, deny]), request_meta()},
|
{
|
||||||
|
clientinfo(),
|
||||||
|
oneof([publish, subscribe]),
|
||||||
|
topic(),
|
||||||
|
oneof([MkResult(allow), MkResult(deny)]),
|
||||||
|
request_meta()
|
||||||
|
},
|
||||||
begin
|
begin
|
||||||
ClientInfo = inject_magic_into(username, ClientInfo0),
|
ClientInfo = inject_magic_into(username, ClientInfo0),
|
||||||
OutResult = emqx_hooks:run_fold(
|
OutResult = emqx_hooks:run_fold(
|
||||||
|
@ -145,9 +152,9 @@ prop_client_authorize() ->
|
||||||
),
|
),
|
||||||
ExpectedOutResult =
|
ExpectedOutResult =
|
||||||
case maps:get(username, ClientInfo) of
|
case maps:get(username, ClientInfo) of
|
||||||
<<"baduser">> -> deny;
|
<<"baduser">> -> MkResult(deny);
|
||||||
<<"gooduser">> -> allow;
|
<<"gooduser">> -> MkResult(allow);
|
||||||
<<"normaluser">> -> allow;
|
<<"normaluser">> -> MkResult(allow);
|
||||||
_ -> Result
|
_ -> Result
|
||||||
end,
|
end,
|
||||||
?assertEqual(ExpectedOutResult, OutResult),
|
?assertEqual(ExpectedOutResult, OutResult),
|
||||||
|
@ -544,7 +551,7 @@ subopts(SubOpts) ->
|
||||||
authresult_to_bool(AuthResult) ->
|
authresult_to_bool(AuthResult) ->
|
||||||
AuthResult == ok.
|
AuthResult == ok.
|
||||||
|
|
||||||
aclresult_to_bool(Result) ->
|
aclresult_to_bool(#{result := Result}) ->
|
||||||
Result == allow.
|
Result == allow.
|
||||||
|
|
||||||
pubsub_to_enum(publish) -> 'PUBLISH';
|
pubsub_to_enum(publish) -> 'PUBLISH';
|
||||||
|
|
Loading…
Reference in New Issue