fix(authz): resources not created when authz started
This commit is contained in:
parent
bb6d7c4e0b
commit
6d871cc52f
|
@ -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}),
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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) ->
|
||||
|
|
Loading…
Reference in New Issue