fix(authz): resources not created when authz started

This commit is contained in:
Shawn 2021-07-16 18:04:12 +08:00
parent bb6d7c4e0b
commit 6d871cc52f
11 changed files with 89 additions and 84 deletions

View File

@ -113,12 +113,9 @@ do_update_config([], Handlers, OldConf, UpdateReq) ->
call_handle_update_config(Handlers, OldConf, UpdateReq);
do_update_config([ConfKey | ConfKeyPath], Handlers, OldConf, UpdateReq) ->
SubOldConf = get_sub_config(ConfKey, OldConf),
case maps:find(ConfKey, Handlers) of
error -> throw({handler_not_found, ConfKey});
{ok, SubHandlers} ->
NewUpdateReq = do_update_config(ConfKeyPath, SubHandlers, SubOldConf, UpdateReq),
call_handle_update_config(Handlers, OldConf, #{bin(ConfKey) => NewUpdateReq})
end.
SubHandlers = maps:get(ConfKey, Handlers, #{}),
NewUpdateReq = do_update_config(ConfKeyPath, SubHandlers, SubOldConf, UpdateReq),
call_handle_update_config(Handlers, OldConf, #{bin(ConfKey) => NewUpdateReq}).
get_sub_config(_, undefined) ->
undefined;
@ -131,7 +128,7 @@ call_handle_update_config(Handlers, OldConf, UpdateReq) ->
HandlerName = maps:get(?MOD, Handlers, undefined),
case erlang:function_exported(HandlerName, handle_update_config, 2) of
true -> HandlerName:handle_update_config(UpdateReq, OldConf);
false -> UpdateReq %% the default behaviour is overwriting the old config
false -> merge_to_old_config(UpdateReq, OldConf)
end.
%% callbacks for the top-level handler
@ -139,11 +136,15 @@ handle_update_config(UpdateReq, OldConf) ->
FullRawConf = merge_to_old_config(UpdateReq, OldConf),
{maps:keys(UpdateReq), FullRawConf}.
%% default callback of config handlers
merge_to_old_config(UpdateReq, undefined) ->
merge_to_old_config(UpdateReq, #{});
merge_to_old_config(UpdateReq, RawConf) ->
maps:merge(RawConf, UpdateReq).
%% The default callback of config handlers
%% the behaviour is overwriting the old config if:
%% 1. the old config is undefined
%% 2. either the old or the new config is not of map type
%% the behaviour is merging the new the config to the old config if they are maps.
merge_to_old_config(UpdateReq, RawConf) when is_map(UpdateReq), is_map(RawConf) ->
maps:merge(RawConf, UpdateReq);
merge_to_old_config(UpdateReq, _RawConf) ->
UpdateReq.
%%============================================================================
save_configs(RootKeys, RawConf) ->
@ -199,8 +200,9 @@ load_config_file() ->
end, #{}, emqx:get_env(config_files, [])).
emqx_override_conf_name() ->
filename:join([emqx:get_env(data_dir), "emqx_override.conf"]).
File = filename:join([emqx:get_env(data_dir), "emqx_override.conf"]),
ok = filelib:ensure_dir(File),
File.
to_richmap(Map) ->
{ok, RichMap} = hocon:binary(jsx:encode(Map), #{format => richmap}),

View File

@ -15,6 +15,7 @@
%%--------------------------------------------------------------------
-module(emqx_authz).
-behaviour(emqx_config_handler).
-include("emqx_authz.hrl").
-include_lib("emqx/include/logger.hrl").
@ -23,30 +24,41 @@
-export([ register_metrics/0
, init/0
, compile/1
, init_rule/1
, lookup/0
, update/1
, authorize/4
, authorize/5
, match/4
]).
-export([handle_update_config/2]).
-define(CONF_KEY_PATH, [emqx_authz, rules]).
-spec(register_metrics() -> ok).
register_metrics() ->
lists:foreach(fun emqx_metrics:ensure/1, ?AUTHZ_METRICS).
init() ->
ok = register_metrics(),
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, []}, -1).
emqx_config_handler:add_handler(?CONF_KEY_PATH, ?MODULE),
NRules = [init_rule(Rule) || Rule <- lookup()],
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1).
lookup() ->
emqx_config:get([emqx_authz, rules], []).
emqx_config:get(?CONF_KEY_PATH, []).
update(Rules) ->
emqx_config:put([emqx_authz], #{rules => Rules}),
emqx_config:update_config(?CONF_KEY_PATH, Rules).
%% For now we only support re-creating the entire rule list
handle_update_config(Rules, _OldConf) ->
InitedRules = [init_rule(Rule) || Rule <- Rules],
Action = find_action_in_hooks(),
ok = emqx_hooks:del('client.authorize', Action),
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, []}, -1),
ok = emqx_acl_cache:empty_acl_cache().
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
ok = emqx_acl_cache:drain_cache(),
Rules.
%%--------------------------------------------------------------------
%% Internal functions
@ -74,27 +86,27 @@ create_resource(#{type := DB,
error({load_config_error, Reason})
end.
-spec(compile(rule()) -> rule()).
compile(#{topics := Topics,
action := Action,
permission := Permission,
principal := Principal
-spec(init_rule(rule()) -> rule()).
init_rule(#{topics := Topics,
action := Action,
permission := Permission,
principal := Principal
} = Rule) when ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(Topics) ->
NTopics = [compile_topic(Topic) || Topic <- Topics],
Rule#{principal => compile_principal(Principal),
topics => NTopics
};
compile(#{principal := Principal,
type := DB
init_rule(#{principal := Principal,
type := DB
} = Rule) when DB =:= redis;
DB =:= mongo ->
NRule = create_resource(Rule),
NRule#{principal => compile_principal(Principal)};
compile(#{principal := Principal,
type := DB,
sql := SQL
init_rule(#{principal := Principal,
type := DB,
sql := SQL
} = Rule) when DB =:= mysql;
DB =:= pgsql ->
Mod = list_to_existing_atom(io_lib:format("~s_~s",[?APP, DB])),
@ -144,11 +156,12 @@ b2l(B) when is_binary(B) -> binary_to_list(B).
%%--------------------------------------------------------------------
%% @doc Check AuthZ
-spec(authorize(emqx_types:clientinfo(), emqx_types:all(), emqx_topic:topic(),
emqx_permission_rule:acl_result()) -> {stop, allow} | {ok, deny}).
authorize(#{username := Username, peerhost := IpAddress} = Client,
PubSub, Topic, _DefaultResult) ->
case do_authorize(Client, PubSub, Topic, [compile(Rule) || Rule <- lookup()]) of
-spec(authorize(emqx_types:clientinfo(), emqx_types:all(), emqx_topic:topic(), emqx_permission_rule:acl_result(), rules())
-> {stop, allow} | {ok, deny}).
authorize(#{username := Username,
peerhost := IpAddress
} = Client, PubSub, Topic, _DefaultResult, Rules) ->
case do_authorize(Client, PubSub, Topic, Rules) of
{matched, allow} ->
?LOG(info, "Client succeeded authorization: Username: ~p, IP: ~p, Topic: ~p, Permission: allow", [Username, IpAddress, Topic]),
emqx_metrics:inc(?AUTHZ_METRICS(allow)),

View File

@ -71,7 +71,7 @@ match(Client, PubSub, Topic,
#{<<"simple_rule">> => Rule},
#{atom_key => true},
[simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of
true -> {matched, NPermission};
false -> nomatch
end.

View File

@ -90,7 +90,7 @@ match(Client, PubSub, Topic,
#{<<"simple_rule">> => Rule},
#{atom_key => true},
[simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of
true -> {matched, NPermission};
false -> nomatch
end.

View File

@ -94,7 +94,7 @@ match(Client, PubSub, Topic,
#{<<"simple_rule">> => Rule},
#{atom_key => true},
[simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of
true -> {matched, NPermission};
false -> nomatch
end.

View File

@ -74,7 +74,7 @@ match(Client, PubSub, Topic,
#{<<"simple_rule">> => Rule},
#{atom_key => true},
[simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of
true -> {matched, allow};
false -> nomatch
end.

View File

@ -30,9 +30,9 @@ groups() ->
init_per_suite(Config) ->
ok = emqx_ct_helpers:start_apps([emqx_authz]),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, cache, enable], false),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true),
emqx_config:put([emqx_authz], #{rules => []}),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
emqx_authz:update([]),
Config.
end_per_suite(_Config) ->
@ -74,19 +74,19 @@ end_per_suite(_Config) ->
%%------------------------------------------------------------------------------
%% Testcases
%%------------------------------------------------------------------------------
t_compile(_) ->
t_init_rule(_) ->
?assertEqual(#{permission => deny,
action => all,
principal => all,
topics => [['#']]
}, emqx_authz:compile(?RULE1)),
}, emqx_authz:init_rule(?RULE1)),
?assertEqual(#{permission => allow,
action => all,
principal =>
#{ipaddress => {{127,0,0,1},{127,0,0,1},32}},
topics => [#{eq => ['#']},
#{eq => ['+']}]
}, emqx_authz:compile(?RULE2)),
}, emqx_authz:init_rule(?RULE2)),
?assertMatch(
#{permission := allow,
action := publish,
@ -96,7 +96,7 @@ t_compile(_) ->
]
},
topics := [[<<"test">>]]
}, emqx_authz:compile(?RULE3)),
}, emqx_authz:init_rule(?RULE3)),
?assertMatch(
#{permission := deny,
action := publish,
@ -108,7 +108,7 @@ t_compile(_) ->
topics := [#{pattern := [<<"%u">>]},
#{pattern := [<<"%c">>]}
]
}, emqx_authz:compile(?RULE4)),
}, emqx_authz:init_rule(?RULE4)),
ok.
t_authz(_) ->
@ -137,39 +137,29 @@ t_authz(_) ->
listener => mqtt_tcp
},
Rules1 = [?RULE1, ?RULE2],
Rules2 = [?RULE2, ?RULE1],
Rules3 = [?RULE3, ?RULE4],
Rules4 = [?RULE4, ?RULE1],
Rules1 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE1, ?RULE2]],
Rules2 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE2, ?RULE1]],
Rules3 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE3, ?RULE4]],
Rules4 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE4, ?RULE1]],
emqx_config:put([emqx_authz], #{rules => []}),
?assertEqual({stop, deny},
emqx_authz:authorize(ClientInfo1, subscribe, <<"#">>, deny)),
emqx_config:put([emqx_authz], #{rules => Rules1}),
emqx_authz:authorize(ClientInfo1, subscribe, <<"#">>, deny, [])),
?assertEqual({stop, deny},
emqx_authz:authorize(ClientInfo1, subscribe, <<"+">>, deny)),
emqx_config:put([emqx_authz], #{rules => Rules2}),
emqx_authz:authorize(ClientInfo1, subscribe, <<"+">>, deny, Rules1)),
?assertEqual({stop, allow},
emqx_authz:authorize(ClientInfo1, subscribe, <<"+">>, deny)),
emqx_config:put([emqx_authz], #{rules => Rules3}),
emqx_authz:authorize(ClientInfo1, subscribe, <<"+">>, deny, Rules2)),
?assertEqual({stop, allow},
emqx_authz:authorize(ClientInfo1, publish, <<"test">>, deny)),
emqx_config:put([emqx_authz], #{rules => Rules4}),
emqx_authz:authorize(ClientInfo1, publish, <<"test">>, deny, Rules3)),
?assertEqual({stop, deny},
emqx_authz:authorize(ClientInfo1, publish, <<"test">>, deny)),
emqx_config:put([emqx_authz], #{rules => Rules2}),
emqx_authz:authorize(ClientInfo1, publish, <<"test">>, deny, Rules4)),
?assertEqual({stop, deny},
emqx_authz:authorize(ClientInfo2, subscribe, <<"#">>, deny)),
emqx_config:put([emqx_authz], #{rules => Rules3}),
emqx_authz:authorize(ClientInfo2, subscribe, <<"#">>, deny, Rules2)),
?assertEqual({stop, deny},
emqx_authz:authorize(ClientInfo3, publish, <<"test">>, deny)),
emqx_config:put([emqx_authz], #{rules => Rules4}),
emqx_authz:authorize(ClientInfo3, publish, <<"test">>, deny, Rules3)),
?assertEqual({stop, deny},
emqx_authz:authorize(ClientInfo3, publish, <<"fake">>, deny)),
emqx_config:put([emqx_authz], #{rules => Rules3}),
emqx_authz:authorize(ClientInfo3, publish, <<"fake">>, deny, Rules4)),
?assertEqual({stop, deny},
emqx_authz:authorize(ClientInfo4, publish, <<"test">>, deny)),
emqx_config:put([emqx_authz], #{rules => Rules4}),
emqx_authz:authorize(ClientInfo4, publish, <<"test">>, deny, Rules3)),
?assertEqual({stop, deny},
emqx_authz:authorize(ClientInfo4, publish, <<"fake">>, deny)),
emqx_authz:authorize(ClientInfo4, publish, <<"fake">>, deny, Rules4)),
ok.

View File

@ -33,15 +33,15 @@ init_per_suite(Config) ->
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
ok = emqx_ct_helpers:start_apps([emqx_authz]),
ct:pal("---- emqx_hooks: ~p", [ets:tab2list(emqx_hooks)]),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, cache, enable], false),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
Rules = [#{config =>#{},
principal => all,
collection => <<"fake">>,
find => #{<<"a">> => <<"b">>},
type => mongo}
],
emqx_config:put([emqx_authz], #{rules => Rules}),
emqx_authz:update(Rules),
Config.
end_per_suite(_Config) ->

View File

@ -32,14 +32,14 @@ init_per_suite(Config) ->
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
ok = emqx_ct_helpers:start_apps([emqx_authz]),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, cache, enable], false),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
Rules = [#{config =>#{},
principal => all,
sql => <<"fake">>,
type => mysql}
],
emqx_config:put([emqx_authz], #{rules => Rules}),
emqx_authz:update(Rules),
Config.
end_per_suite(_Config) ->

View File

@ -32,14 +32,14 @@ init_per_suite(Config) ->
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
ok = emqx_ct_helpers:start_apps([emqx_authz]),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, cache, enable], false),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
Rules = [#{config =>#{},
principal => all,
sql => <<"fake">>,
type => pgsql}
],
emqx_config:put([emqx_authz], #{rules => Rules}),
emqx_authz:update(Rules),
Config.
end_per_suite(_Config) ->

View File

@ -32,14 +32,14 @@ init_per_suite(Config) ->
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
ok = emqx_ct_helpers:start_apps([emqx_authz]),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, cache, enable], false),
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
Rules = [#{config =>#{},
principal => all,
cmd => <<"fake">>,
type => redis}
],
emqx_config:put([emqx_authz], #{rules => Rules}),
emqx_authz:update(Rules),
Config.
end_per_suite(_Config) ->