chore: delete internal acl code
This commit is contained in:
parent
f227f5c023
commit
c79e478c42
|
@ -1,152 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2017-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.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqx_access_rule).
|
|
||||||
|
|
||||||
%% APIs
|
|
||||||
-export([ match/3
|
|
||||||
, compile/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-export_type([rule/0]).
|
|
||||||
|
|
||||||
-type(acl_result() :: allow | deny).
|
|
||||||
|
|
||||||
-type(who() :: all | binary() |
|
|
||||||
{client, binary()} |
|
|
||||||
{user, binary()} |
|
|
||||||
{ipaddr, esockd_cidr:cidr_string()}).
|
|
||||||
|
|
||||||
-type(access() :: subscribe | publish | pubsub).
|
|
||||||
|
|
||||||
-type(rule() :: {acl_result(), all} |
|
|
||||||
{acl_result(), who(), access(), list(emqx_topic:topic())}).
|
|
||||||
|
|
||||||
-define(ALLOW_DENY(A), ((A =:= allow) orelse (A =:= deny))).
|
|
||||||
-define(PUBSUB(A), ((A =:= subscribe) orelse (A =:= publish) orelse (A =:= pubsub))).
|
|
||||||
|
|
||||||
%% @doc Compile Access Rule.
|
|
||||||
compile({A, all}) when ?ALLOW_DENY(A) ->
|
|
||||||
{A, all};
|
|
||||||
|
|
||||||
compile({A, Who, Access, Topic}) when ?ALLOW_DENY(A), ?PUBSUB(Access), is_binary(Topic) ->
|
|
||||||
{A, compile(who, Who), Access, [compile(topic, Topic)]};
|
|
||||||
|
|
||||||
compile({A, Who, Access, TopicFilters}) when ?ALLOW_DENY(A), ?PUBSUB(Access) ->
|
|
||||||
{A, compile(who, Who), Access, [compile(topic, Topic) || Topic <- TopicFilters]}.
|
|
||||||
|
|
||||||
compile(who, all) ->
|
|
||||||
all;
|
|
||||||
compile(who, {ipaddr, CIDR}) ->
|
|
||||||
{ipaddr, esockd_cidr:parse(CIDR, true)};
|
|
||||||
compile(who, {client, all}) ->
|
|
||||||
{client, all};
|
|
||||||
compile(who, {client, ClientId}) ->
|
|
||||||
{client, bin(ClientId)};
|
|
||||||
compile(who, {user, all}) ->
|
|
||||||
{user, all};
|
|
||||||
compile(who, {user, Username}) ->
|
|
||||||
{user, bin(Username)};
|
|
||||||
compile(who, {'and', Conds}) when is_list(Conds) ->
|
|
||||||
{'and', [compile(who, Cond) || Cond <- Conds]};
|
|
||||||
compile(who, {'or', Conds}) when is_list(Conds) ->
|
|
||||||
{'or', [compile(who, Cond) || Cond <- Conds]};
|
|
||||||
|
|
||||||
compile(topic, {eq, Topic}) ->
|
|
||||||
{eq, emqx_topic:words(bin(Topic))};
|
|
||||||
compile(topic, Topic) ->
|
|
||||||
Words = emqx_topic:words(bin(Topic)),
|
|
||||||
case pattern(Words) of
|
|
||||||
true -> {pattern, Words};
|
|
||||||
false -> Words
|
|
||||||
end.
|
|
||||||
|
|
||||||
pattern(Words) ->
|
|
||||||
lists:member(<<"%u">>, Words) orelse lists:member(<<"%c">>, Words).
|
|
||||||
|
|
||||||
bin(L) when is_list(L) ->
|
|
||||||
list_to_binary(L);
|
|
||||||
bin(B) when is_binary(B) ->
|
|
||||||
B.
|
|
||||||
|
|
||||||
%% @doc Match access rule
|
|
||||||
-spec(match(emqx_types:clientinfo(), emqx_types:topic(), rule())
|
|
||||||
-> {matched, allow} | {matched, deny} | nomatch).
|
|
||||||
match(_ClientInfo, _Topic, {AllowDeny, all}) when ?ALLOW_DENY(AllowDeny) ->
|
|
||||||
{matched, AllowDeny};
|
|
||||||
match(ClientInfo, Topic, {AllowDeny, Who, _PubSub, TopicFilters})
|
|
||||||
when ?ALLOW_DENY(AllowDeny) ->
|
|
||||||
case match_who(ClientInfo, Who)
|
|
||||||
andalso match_topics(ClientInfo, Topic, TopicFilters) of
|
|
||||||
true -> {matched, AllowDeny};
|
|
||||||
false -> nomatch
|
|
||||||
end.
|
|
||||||
|
|
||||||
match_who(_ClientInfo, all) ->
|
|
||||||
true;
|
|
||||||
match_who(_ClientInfo, {user, all}) ->
|
|
||||||
true;
|
|
||||||
match_who(_ClientInfo, {client, all}) ->
|
|
||||||
true;
|
|
||||||
match_who(#{clientid := ClientId}, {client, ClientId}) ->
|
|
||||||
true;
|
|
||||||
match_who(#{username := Username}, {user, Username}) ->
|
|
||||||
true;
|
|
||||||
match_who(#{peerhost := undefined}, {ipaddr, _Tup}) ->
|
|
||||||
false;
|
|
||||||
match_who(#{peerhost := IP}, {ipaddr, CIDR}) ->
|
|
||||||
esockd_cidr:match(IP, CIDR);
|
|
||||||
match_who(ClientInfo, {'and', Conds}) when is_list(Conds) ->
|
|
||||||
lists:foldl(fun(Who, Allow) ->
|
|
||||||
match_who(ClientInfo, Who) andalso Allow
|
|
||||||
end, true, Conds);
|
|
||||||
match_who(ClientInfo, {'or', Conds}) when is_list(Conds) ->
|
|
||||||
lists:foldl(fun(Who, Allow) ->
|
|
||||||
match_who(ClientInfo, Who) orelse Allow
|
|
||||||
end, false, Conds);
|
|
||||||
match_who(_ClientInfo, _Who) ->
|
|
||||||
false.
|
|
||||||
|
|
||||||
match_topics(_ClientInfo, _Topic, []) ->
|
|
||||||
false;
|
|
||||||
match_topics(ClientInfo, Topic, [{pattern, PatternFilter}|Filters]) ->
|
|
||||||
TopicFilter = feed_var(ClientInfo, PatternFilter),
|
|
||||||
match_topic(emqx_topic:words(Topic), TopicFilter)
|
|
||||||
orelse match_topics(ClientInfo, Topic, Filters);
|
|
||||||
match_topics(ClientInfo, Topic, [TopicFilter|Filters]) ->
|
|
||||||
match_topic(emqx_topic:words(Topic), TopicFilter)
|
|
||||||
orelse match_topics(ClientInfo, Topic, Filters).
|
|
||||||
|
|
||||||
match_topic(Topic, {eq, TopicFilter}) ->
|
|
||||||
Topic == TopicFilter;
|
|
||||||
match_topic(Topic, TopicFilter) ->
|
|
||||||
emqx_topic:match(Topic, TopicFilter).
|
|
||||||
|
|
||||||
feed_var(ClientInfo, Pattern) ->
|
|
||||||
feed_var(ClientInfo, Pattern, []).
|
|
||||||
feed_var(_ClientInfo, [], Acc) ->
|
|
||||||
lists:reverse(Acc);
|
|
||||||
feed_var(ClientInfo = #{clientid := undefined}, [<<"%c">>|Words], Acc) ->
|
|
||||||
feed_var(ClientInfo, Words, [<<"%c">>|Acc]);
|
|
||||||
feed_var(ClientInfo = #{clientid := ClientId}, [<<"%c">>|Words], Acc) ->
|
|
||||||
feed_var(ClientInfo, Words, [ClientId |Acc]);
|
|
||||||
feed_var(ClientInfo = #{username := undefined}, [<<"%u">>|Words], Acc) ->
|
|
||||||
feed_var(ClientInfo, Words, [<<"%u">>|Acc]);
|
|
||||||
feed_var(ClientInfo = #{username := Username}, [<<"%u">>|Words], Acc) ->
|
|
||||||
feed_var(ClientInfo, Words, [Username|Acc]);
|
|
||||||
feed_var(ClientInfo, [W|Words], Acc) ->
|
|
||||||
feed_var(ClientInfo, Words, [W|Acc]).
|
|
||||||
|
|
|
@ -823,8 +823,7 @@ tr_modules(Conf) ->
|
||||||
[{emqx_mod_subscription, Subscriptions()}],
|
[{emqx_mod_subscription, Subscriptions()}],
|
||||||
[{emqx_mod_rewrite, Rewrites()}],
|
[{emqx_mod_rewrite, Rewrites()}],
|
||||||
[{emqx_mod_topic_metrics, []}],
|
[{emqx_mod_topic_metrics, []}],
|
||||||
[{emqx_mod_delayed, []}],
|
[{emqx_mod_delayed, []}]
|
||||||
[{emqx_mod_acl_internal, [{acl_file, conf_get("acl.acl_file", Conf)}]}]
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
tr_sysmon(Conf) ->
|
tr_sysmon(Conf) ->
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
{emqx_mod_acl_internal, true}.
|
{emqx_mod_presence, true}.
|
||||||
{emqx_mod_presence, true}.
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
{allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}.
|
|
||||||
|
|
||||||
{allow, {user, "testuser"}, subscribe, ["a/b/c", "d/e/f/#"]}.
|
|
||||||
|
|
||||||
{allow, {user, "admin"}, pubsub, ["a/b/c", "d/e/f/#"]}.
|
|
||||||
|
|
||||||
{allow, {client, "testClient"}, subscribe, ["testTopics/testClient"]}.
|
|
||||||
|
|
||||||
{allow, all, subscribe, ["clients/%c"]}.
|
|
||||||
|
|
||||||
{allow, all, pubsub, ["users/%u/#"]}.
|
|
||||||
|
|
||||||
{deny, all, subscribe, ["$SYS/#", "#"]}.
|
|
||||||
|
|
||||||
{deny, all}.
|
|
|
@ -1,3 +0,0 @@
|
||||||
{deny, {user, "emqx"}, pubsub, ["acl_deny_action"]}.
|
|
||||||
{deny, {user, "pub_deny"}, publish, ["pub_deny"]}.
|
|
||||||
{allow, all}.
|
|
|
@ -1,97 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2019-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.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqx_access_rule_SUITE).
|
|
||||||
|
|
||||||
-compile(export_all).
|
|
||||||
-compile(nowarn_export_all).
|
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
|
||||||
emqx_ct_helpers:boot_modules([router, broker]),
|
|
||||||
emqx_ct_helpers:start_apps([]),
|
|
||||||
Config.
|
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
|
||||||
emqx_ct_helpers:stop_apps([]).
|
|
||||||
|
|
||||||
t_compile(_) ->
|
|
||||||
Rule1 = {allow, all, pubsub, <<"%u">>},
|
|
||||||
Compile1 = {allow, all, pubsub, [{pattern,[<<"%u">>]}]},
|
|
||||||
|
|
||||||
Rule2 = {allow, {ipaddr, "127.0.0.1"}, pubsub, <<"%c">>},
|
|
||||||
Compile2 = {allow, {ipaddr, {{127,0,0,1}, {127,0,0,1}, 32}}, pubsub, [{pattern,[<<"%c">>]}]},
|
|
||||||
|
|
||||||
Rule3 = {allow, {'and', [{client, <<"testClient">>}, {user, <<"testUser">>}]}, pubsub, [<<"testTopics1">>, <<"testTopics2">>]},
|
|
||||||
Compile3 = {allow, {'and', [{client, <<"testClient">>}, {user, <<"testUser">>}]}, pubsub, [[<<"testTopics1">>], [<<"testTopics2">>]]},
|
|
||||||
|
|
||||||
Rule4 = {allow, {'or', [{client, all}, {user, all}]}, pubsub, [ <<"testTopics1">>, <<"testTopics2">>]},
|
|
||||||
Compile4 = {allow, {'or', [{client, all}, {user, all}]}, pubsub, [[<<"testTopics1">>], [<<"testTopics2">>]]},
|
|
||||||
|
|
||||||
?assertEqual(Compile1, emqx_access_rule:compile(Rule1)),
|
|
||||||
?assertEqual(Compile2, emqx_access_rule:compile(Rule2)),
|
|
||||||
?assertEqual(Compile3, emqx_access_rule:compile(Rule3)),
|
|
||||||
?assertEqual(Compile4, emqx_access_rule:compile(Rule4)).
|
|
||||||
|
|
||||||
t_match(_) ->
|
|
||||||
ClientInfo1 = #{zone => external,
|
|
||||||
clientid => <<"testClient">>,
|
|
||||||
username => <<"TestUser">>,
|
|
||||||
peerhost => {127,0,0,1}
|
|
||||||
},
|
|
||||||
ClientInfo2 = #{zone => external,
|
|
||||||
clientid => <<"testClient">>,
|
|
||||||
username => <<"TestUser">>,
|
|
||||||
peerhost => {192,168,0,10}
|
|
||||||
},
|
|
||||||
ClientInfo3 = #{zone => external,
|
|
||||||
clientid => <<"testClient">>,
|
|
||||||
username => <<"TestUser">>,
|
|
||||||
peerhost => undefined
|
|
||||||
},
|
|
||||||
?assertEqual({matched, deny}, emqx_access_rule:match([], [], {deny, all})),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match([], [], {allow, all})),
|
|
||||||
?assertEqual(nomatch, emqx_access_rule:match(ClientInfo1, <<"Test/Topic">>,
|
|
||||||
emqx_access_rule:compile({allow, {user, all}, pubsub, []}))),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"Test/Topic">>,
|
|
||||||
emqx_access_rule:compile({allow, {client, all}, pubsub, ["$SYS/#", "#"]}))),
|
|
||||||
?assertEqual(nomatch, emqx_access_rule:match(ClientInfo3, <<"Test/Topic">>,
|
|
||||||
emqx_access_rule:compile({allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}))),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"Test/Topic">>,
|
|
||||||
emqx_access_rule:compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}))),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo2, <<"Test/Topic">>,
|
|
||||||
emqx_access_rule:compile({allow, {ipaddr, "192.168.0.1/24"}, subscribe, ["$SYS/#", "#"]}))),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"d/e/f/x">>,
|
|
||||||
emqx_access_rule:compile({allow, {user, "TestUser"}, subscribe, ["a/b/c", "d/e/f/#"]}))),
|
|
||||||
?assertEqual(nomatch, emqx_access_rule:match(ClientInfo1, <<"d/e/f/x">>,
|
|
||||||
emqx_access_rule:compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]}))),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"testTopics/testClient">>,
|
|
||||||
emqx_access_rule:compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]}))),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"clients/testClient">>,
|
|
||||||
emqx_access_rule:compile({allow, all, pubsub, ["clients/%c"]}))),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match(#{username => <<"user2">>}, <<"users/user2/abc/def">>,
|
|
||||||
emqx_access_rule:compile({allow, all, subscribe, ["users/%u/#"]}))),
|
|
||||||
?assertEqual({matched, deny}, emqx_access_rule:match(ClientInfo1, <<"d/e/f">>,
|
|
||||||
emqx_access_rule:compile({deny, all, subscribe, ["$SYS/#", "#"]}))),
|
|
||||||
?assertEqual(nomatch, emqx_access_rule:match(ClientInfo1, <<"Topic">>,
|
|
||||||
emqx_access_rule:compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, <<"Topic">>}))),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"Topic">>,
|
|
||||||
emqx_access_rule:compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"TestUser">>}]}, publish, <<"Topic">>}))),
|
|
||||||
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"Topic">>,
|
|
||||||
emqx_access_rule:compile({allow, {'or', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, ["Topic"]}))).
|
|
|
@ -266,11 +266,18 @@ t_kick_1(_Config) ->
|
||||||
|
|
||||||
% mqtt connection kicked by coap with same client id
|
% mqtt connection kicked by coap with same client id
|
||||||
t_acl(Config) ->
|
t_acl(Config) ->
|
||||||
%% Update acl file and reload mod_acl_internal
|
OldPath = emqx:get_env(plugins_etc_dir),
|
||||||
Path = filename:join([testdir(proplists:get_value(data_dir, Config)), "deny.conf"]),
|
application:set_env(emqx, plugins_etc_dir,
|
||||||
ok = file:write_file(Path, <<"{deny, {user, \"coap\"}, publish, [\"abc\"]}.">>),
|
emqx_ct_helpers:deps_path(emqx_authz, "test")),
|
||||||
OldPath = emqx:get_env(acl_file),
|
Conf = #{<<"authz">> =>
|
||||||
emqx_mod_acl_internal:reload([{acl_file, Path}]),
|
#{<<"rules">> =>
|
||||||
|
[#{<<"principal">> =>#{<<"username">> => <<"coap">>},
|
||||||
|
<<"permission">> => deny,
|
||||||
|
<<"topics">> => [<<"abc">>],
|
||||||
|
<<"action">> => <<"publish">>}
|
||||||
|
]}},
|
||||||
|
ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf'), jsx:encode(Conf)),
|
||||||
|
application:ensure_all_started(emqx_authz),
|
||||||
|
|
||||||
emqx:subscribe(<<"abc">>),
|
emqx:subscribe(<<"abc">>),
|
||||||
URI = "coap://127.0.0.1/mqtt/adbc?c=client1&u=coap&p=secret",
|
URI = "coap://127.0.0.1/mqtt/adbc?c=client1&u=coap&p=secret",
|
||||||
|
@ -282,9 +289,9 @@ t_acl(Config) ->
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
|
|
||||||
application:set_env(emqx, acl_file, OldPath),
|
file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')),
|
||||||
file:delete(Path),
|
application:set_env(emqx, plugins_etc_dir, OldPath),
|
||||||
emqx_mod_acl_internal:reload([{acl_file, OldPath}]).
|
application:stop(emqx_authz).
|
||||||
|
|
||||||
t_stats(_) ->
|
t_stats(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2020-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.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqx_mod_acl_internal).
|
|
||||||
|
|
||||||
-behaviour(emqx_gen_mod).
|
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
|
||||||
-include_lib("emqx/include/logger.hrl").
|
|
||||||
|
|
||||||
-logger_header("[ACL_INTERNAL]").
|
|
||||||
|
|
||||||
%% APIs
|
|
||||||
-export([ check_acl/5
|
|
||||||
, rules_from_file/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
%% emqx_gen_mod callbacks
|
|
||||||
-export([ load/1
|
|
||||||
, unload/1
|
|
||||||
, reload/1
|
|
||||||
, description/0
|
|
||||||
]).
|
|
||||||
|
|
||||||
-type(acl_rules() :: #{publish => [emqx_access_rule:rule()],
|
|
||||||
subscribe => [emqx_access_rule:rule()]}).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% API
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
load(Env) ->
|
|
||||||
Rules = rules_from_file(proplists:get_value(acl_file, Env)),
|
|
||||||
emqx_hooks:add('client.check_acl', {?MODULE, check_acl, [Rules]}, -1).
|
|
||||||
|
|
||||||
unload(_Env) ->
|
|
||||||
emqx_hooks:del('client.check_acl', {?MODULE, check_acl}).
|
|
||||||
|
|
||||||
reload(Env) ->
|
|
||||||
emqx_acl_cache:is_enabled() andalso (
|
|
||||||
lists:foreach(
|
|
||||||
fun(Pid) -> erlang:send(Pid, clean_acl_cache) end,
|
|
||||||
emqx_cm:all_channels())),
|
|
||||||
unload(Env), load(Env).
|
|
||||||
|
|
||||||
description() ->
|
|
||||||
"EMQ X Internal ACL Module".
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% ACL callbacks
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% @doc Check ACL
|
|
||||||
-spec(check_acl(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_topic:topic(),
|
|
||||||
emqx_access_rule:acl_result(), acl_rules())
|
|
||||||
-> {ok, allow} | {ok, deny} | ok).
|
|
||||||
check_acl(Client, PubSub, Topic, _AclResult, Rules) ->
|
|
||||||
case match(Client, Topic, lookup(PubSub, Rules)) of
|
|
||||||
{matched, allow} -> {ok, allow};
|
|
||||||
{matched, deny} -> {ok, deny};
|
|
||||||
nomatch -> ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Internal Functions
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
lookup(PubSub, Rules) ->
|
|
||||||
maps:get(PubSub, Rules, []).
|
|
||||||
|
|
||||||
match(_Client, _Topic, []) ->
|
|
||||||
nomatch;
|
|
||||||
match(Client, Topic, [Rule|Rules]) ->
|
|
||||||
case emqx_access_rule:match(Client, Topic, Rule) of
|
|
||||||
nomatch ->
|
|
||||||
match(Client, Topic, Rules);
|
|
||||||
{matched, AllowDeny} ->
|
|
||||||
{matched, AllowDeny}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec(rules_from_file(file:filename()) -> map()).
|
|
||||||
rules_from_file(AclFile) ->
|
|
||||||
case file:consult(AclFile) of
|
|
||||||
{ok, Terms} ->
|
|
||||||
Rules = [emqx_access_rule:compile(Term) || Term <- Terms],
|
|
||||||
#{publish => [Rule || Rule <- Rules, filter(publish, Rule)],
|
|
||||||
subscribe => [Rule || Rule <- Rules, filter(subscribe, Rule)]};
|
|
||||||
{error, eacces} ->
|
|
||||||
?LOG(alert, "Insufficient permissions to read the ~s file", [AclFile]),
|
|
||||||
#{};
|
|
||||||
{error, enoent} ->
|
|
||||||
?LOG(alert, "The ~s file does not exist", [AclFile]),
|
|
||||||
#{};
|
|
||||||
{error, Reason} ->
|
|
||||||
?LOG(alert, "Failed to read ~s: ~p", [AclFile, Reason]),
|
|
||||||
#{}
|
|
||||||
end.
|
|
||||||
|
|
||||||
filter(_PubSub, {allow, all}) ->
|
|
||||||
true;
|
|
||||||
filter(_PubSub, {deny, all}) ->
|
|
||||||
true;
|
|
||||||
filter(publish, {_AllowDeny, _Who, publish, _Topics}) ->
|
|
||||||
true;
|
|
||||||
filter(_PubSub, {_AllowDeny, _Who, pubsub, _Topics}) ->
|
|
||||||
true;
|
|
||||||
filter(subscribe, {_AllowDeny, _Who, subscribe, _Topics}) ->
|
|
||||||
true;
|
|
||||||
filter(_PubSub, {_AllowDeny, _Who, _, _Topics}) ->
|
|
||||||
false.
|
|
||||||
|
|
|
@ -80,17 +80,6 @@ unload(ModuleName) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec(reload(module()) -> ok | ignore | {error, any()}).
|
-spec(reload(module()) -> ok | ignore | {error, any()}).
|
||||||
reload(emqx_mod_acl_internal) ->
|
|
||||||
Modules = emqx:get_env(modules, []),
|
|
||||||
Env = proplists:get_value(emqx_mod_acl_internal, Modules, undefined),
|
|
||||||
case emqx_mod_acl_internal:reload(Env) of
|
|
||||||
ok ->
|
|
||||||
?LOG(info, "Reload ~s module successfully.", [emqx_mod_acl_internal]),
|
|
||||||
ok;
|
|
||||||
{error, Error} ->
|
|
||||||
?LOG(error, "Reload module ~s failed, cannot start for ~0p", [emqx_mod_acl_internal, Error]),
|
|
||||||
{error, Error}
|
|
||||||
end;
|
|
||||||
reload(_) ->
|
reload(_) ->
|
||||||
ignore.
|
ignore.
|
||||||
|
|
||||||
|
@ -197,13 +186,6 @@ cli(["unload", Name]) ->
|
||||||
emqx_ctl:print("Unload module ~s error: ~p.~n", [Name, Reason])
|
emqx_ctl:print("Unload module ~s error: ~p.~n", [Name, Reason])
|
||||||
end;
|
end;
|
||||||
|
|
||||||
cli(["reload", "emqx_mod_acl_internal" = Name]) ->
|
|
||||||
case emqx_modules:reload(list_to_atom(Name)) of
|
|
||||||
ok ->
|
|
||||||
emqx_ctl:print("Module ~s reloaded successfully.~n", [Name]);
|
|
||||||
{error, Reason} ->
|
|
||||||
emqx_ctl:print("Reload module ~s error: ~p.~n", [Name, Reason])
|
|
||||||
end;
|
|
||||||
cli(["reload", Name]) ->
|
cli(["reload", Name]) ->
|
||||||
emqx_ctl:print("Module: ~p does not need to be reloaded.~n", [Name]);
|
emqx_ctl:print("Module: ~p does not need to be reloaded.~n", [Name]);
|
||||||
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2020-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.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqx_mod_acl_internal_SUITE).
|
|
||||||
|
|
||||||
-compile(export_all).
|
|
||||||
-compile(nowarn_export_all).
|
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
|
||||||
emqx_ct_helpers:boot_modules(all),
|
|
||||||
emqx_ct_helpers:start_apps([]),
|
|
||||||
Config.
|
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
|
||||||
emqx_ct_helpers:stop_apps([]).
|
|
||||||
|
|
||||||
t_load_unload(_) ->
|
|
||||||
?assertEqual(ok, emqx_mod_acl_internal:unload([])),
|
|
||||||
?assertEqual(ok, emqx_mod_acl_internal:load([])),
|
|
||||||
?assertEqual({error,already_exists}, emqx_mod_acl_internal:load([])).
|
|
||||||
|
|
||||||
t_check_acl(_) ->
|
|
||||||
Rules=#{publish => [{allow,all}], subscribe => [{deny, all}]},
|
|
||||||
?assertEqual({ok, allow}, emqx_mod_acl_internal:check_acl(clientinfo(), publish, <<"t">>, [], Rules)),
|
|
||||||
?assertEqual({ok, deny}, emqx_mod_acl_internal:check_acl(clientinfo(), subscribe, <<"t">>, [], Rules)),
|
|
||||||
?assertEqual(ok, emqx_mod_acl_internal:check_acl(clientinfo(), connect, <<"t">>, [], Rules)).
|
|
||||||
|
|
||||||
t_reload_acl(_) ->
|
|
||||||
?assertEqual(ok, emqx_mod_acl_internal:reload([])).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Helper functions
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
clientinfo() -> clientinfo(#{}).
|
|
||||||
clientinfo(InitProps) ->
|
|
||||||
maps:merge(#{zone => zone,
|
|
||||||
protocol => mqtt,
|
|
||||||
peerhost => {127,0,0,1},
|
|
||||||
clientid => <<"clientid">>,
|
|
||||||
username => <<"username">>,
|
|
||||||
password => <<"passwd">>,
|
|
||||||
is_superuser => false,
|
|
||||||
peercert => undefined,
|
|
||||||
mountpoint => undefined
|
|
||||||
}, InitProps).
|
|
|
@ -49,8 +49,7 @@ t_load(_) ->
|
||||||
?assertEqual(ok, emqx_modules:load()),
|
?assertEqual(ok, emqx_modules:load()),
|
||||||
?assertEqual({error, not_found}, emqx_modules:load(not_existed_module)),
|
?assertEqual({error, not_found}, emqx_modules:load(not_existed_module)),
|
||||||
?assertEqual({error, not_started}, emqx_modules:unload(emqx_mod_rewrite)),
|
?assertEqual({error, not_started}, emqx_modules:unload(emqx_mod_rewrite)),
|
||||||
?assertEqual(ignore, emqx_modules:reload(emqx_mod_rewrite)),
|
?assertEqual(ignore, emqx_modules:reload(emqx_mod_rewrite)).
|
||||||
?assertEqual(ok, emqx_modules:reload(emqx_mod_acl_internal)).
|
|
||||||
|
|
||||||
t_list(_) ->
|
t_list(_) ->
|
||||||
?assertMatch([{_, _} | _ ], emqx_modules:list()).
|
?assertMatch([{_, _} | _ ], emqx_modules:list()).
|
||||||
|
|
|
@ -87,7 +87,6 @@ emqxLoadedPlugins: >
|
||||||
{emqx_bridge_mqtt, false}.
|
{emqx_bridge_mqtt, false}.
|
||||||
|
|
||||||
emqxLoadedModules: >
|
emqxLoadedModules: >
|
||||||
{emqx_mod_acl_internal, true}.
|
|
||||||
{emqx_mod_presence, true}.
|
{emqx_mod_presence, true}.
|
||||||
{emqx_mod_delayed, false}.
|
{emqx_mod_delayed, false}.
|
||||||
{emqx_mod_rewrite, false}.
|
{emqx_mod_rewrite, false}.
|
||||||
|
|
|
@ -97,12 +97,11 @@ For example, set mqtt tcp port to 1883
|
||||||
|
|
||||||
Default environment variable ``EMQX_LOADED_MODULES``, including
|
Default environment variable ``EMQX_LOADED_MODULES``, including
|
||||||
|
|
||||||
+ ``emqx_mod_acl_internal``
|
|
||||||
+ ``emqx_mod_presence``
|
+ ``emqx_mod_presence``
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# The default EMQX_LOADED_MODULES env
|
# The default EMQX_LOADED_MODULES env
|
||||||
EMQX_LOADED_MODULES="emqx_mod_acl_internal,emqx_mod_acl_internal"
|
EMQX_LOADED_MODULES="emqx_mod_presence"
|
||||||
```
|
```
|
||||||
|
|
||||||
For example, set ``EMQX_LOADED_MODULES=emqx_mod_delayed,emqx_mod_rewrite`` to load these two modules.
|
For example, set ``EMQX_LOADED_MODULES=emqx_mod_delayed,emqx_mod_rewrite`` to load these two modules.
|
||||||
|
|
Loading…
Reference in New Issue