diff --git a/apps/emqx/src/emqx_config_handler.erl b/apps/emqx/src/emqx_config_handler.erl index 6a4ac8703..3f3c43524 100644 --- a/apps/emqx/src/emqx_config_handler.erl +++ b/apps/emqx/src/emqx_config_handler.erl @@ -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}), diff --git a/apps/emqx_authz/src/emqx_authz.erl b/apps/emqx_authz/src/emqx_authz.erl index bb5991748..e759cdfcf 100644 --- a/apps/emqx_authz/src/emqx_authz.erl +++ b/apps/emqx_authz/src/emqx_authz.erl @@ -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)), diff --git a/apps/emqx_authz/src/emqx_authz_mongo.erl b/apps/emqx_authz/src/emqx_authz_mongo.erl index a32054997..c615582d4 100644 --- a/apps/emqx_authz/src/emqx_authz_mongo.erl +++ b/apps/emqx_authz/src/emqx_authz_mongo.erl @@ -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. diff --git a/apps/emqx_authz/src/emqx_authz_mysql.erl b/apps/emqx_authz/src/emqx_authz_mysql.erl index 0ab1418f2..980e9d5c6 100644 --- a/apps/emqx_authz/src/emqx_authz_mysql.erl +++ b/apps/emqx_authz/src/emqx_authz_mysql.erl @@ -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. diff --git a/apps/emqx_authz/src/emqx_authz_pgsql.erl b/apps/emqx_authz/src/emqx_authz_pgsql.erl index c990a29d3..607ba3afa 100644 --- a/apps/emqx_authz/src/emqx_authz_pgsql.erl +++ b/apps/emqx_authz/src/emqx_authz_pgsql.erl @@ -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. diff --git a/apps/emqx_authz/src/emqx_authz_redis.erl b/apps/emqx_authz/src/emqx_authz_redis.erl index 8d24b4534..43e06dd13 100644 --- a/apps/emqx_authz/src/emqx_authz_redis.erl +++ b/apps/emqx_authz/src/emqx_authz_redis.erl @@ -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. diff --git a/apps/emqx_authz/test/emqx_authz_SUITE.erl b/apps/emqx_authz/test/emqx_authz_SUITE.erl index 81574330e..b9b15954c 100644 --- a/apps/emqx_authz/test/emqx_authz_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_SUITE.erl @@ -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. diff --git a/apps/emqx_authz/test/emqx_authz_mongo_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mongo_SUITE.erl index ab7ff548a..e0a8bcfdf 100644 --- a/apps/emqx_authz/test/emqx_authz_mongo_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mongo_SUITE.erl @@ -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) -> diff --git a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl index 0389ffd6e..8ba1ae5be 100644 --- a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl @@ -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) -> diff --git a/apps/emqx_authz/test/emqx_authz_pgsql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_pgsql_SUITE.erl index 5c9c32551..932c9972d 100644 --- a/apps/emqx_authz/test/emqx_authz_pgsql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_pgsql_SUITE.erl @@ -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) -> diff --git a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl b/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl index 233680884..13fd65e95 100644 --- a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl @@ -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) ->