chore(authz): use atom key for hocon config

This commit is contained in:
zhanghongtong 2021-07-01 15:52:25 +08:00 committed by turtleDeng
parent 47ce507c07
commit bf4c31b745
13 changed files with 167 additions and 149 deletions

View File

@ -1,4 +1,4 @@
authz:{
emqx_authz:{
rules: [
# {
# type: mysql

View File

@ -1,4 +1,4 @@
-type(rule() :: #{binary() => any()}).
-type(rule() :: #{atom() => any()}).
-type(rules() :: [rule()]).
-define(APP, emqx_authz).

View File

@ -38,16 +38,17 @@ init() ->
ok = register_metrics(),
Conf = filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf'),
{ok, RawConf} = hocon:load(Conf),
#{<<"authz">> := #{<<"rules">> := Rules}} = hocon_schema:check_plain(emqx_authz_schema, RawConf),
ok = application:set_env(?APP, rules, Rules),
#{emqx_authz := #{rules := Rules}} = hocon_schema:check_plain(emqx_authz_schema, RawConf, #{atom_key => true}),
emqx_config:put([emqx_authz], #{rules => Rules}),
% Rules = emqx_config:get([emqx_authz, rules], []),
NRules = [compile(Rule) || Rule <- Rules],
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1).
lookup() ->
application:get_env(?APP, rules, []).
emqx_config:get([emqx_authz, rules], []).
update(Rules) ->
ok = application:set_env(?APP, rules, Rules),
emqx_config:put([emqx_authz], #{rules => Rules}),
NRules = [compile(Rule) || Rule <- Rules],
Action = find_action_in_hooks(),
ok = emqx_hooks:del('client.authorize', Action),
@ -63,12 +64,12 @@ find_action_in_hooks() ->
[Action] = [Action || {callback,{?MODULE, authorize, _} = Action, _, _} <- Callbacks ],
Action.
create_resource(#{<<"type">> := DB,
<<"config">> := Config
create_resource(#{type := DB,
config := Config
} = Rule) ->
ResourceID = iolist_to_binary([io_lib:format("~s_~s",[?APP, DB]), "_", integer_to_list(erlang:system_time())]),
NConfig = case DB of
redis -> #{<<"config">> => Config };
redis -> #{config => Config };
_ -> Config
end,
case emqx_resource:check_and_create(
@ -77,63 +78,63 @@ create_resource(#{<<"type">> := DB,
NConfig)
of
{ok, _} ->
Rule#{<<"resource_id">> => ResourceID};
Rule#{resource_id => ResourceID};
{error, already_created} ->
Rule#{<<"resource_id">> => ResourceID};
Rule#{resource_id => ResourceID};
{error, Reason} ->
error({load_config_error, Reason})
end.
-spec(compile(rule()) -> rule()).
compile(#{<<"topics">> := Topics,
<<"action">> := Action,
<<"permission">> := Permission,
<<"principal">> := Principal
compile(#{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
Rule#{principal => compile_principal(Principal),
topics => NTopics
};
compile(#{<<"principal">> := Principal,
<<"type">> := redis
compile(#{principal := Principal,
type := redis
} = Rule) ->
NRule = create_resource(Rule),
NRule#{<<"principal">> => compile_principal(Principal)};
NRule#{principal => compile_principal(Principal)};
compile(#{<<"principal">> := Principal,
<<"type">> := DB,
<<"sql">> := SQL
compile(#{principal := Principal,
type := DB,
sql := SQL
} = Rule) when DB =:= mysql;
DB =:= pgsql ->
Mod = list_to_existing_atom(io_lib:format("~s_~s",[?APP, DB])),
NRule = create_resource(Rule),
NRule#{<<"principal">> => compile_principal(Principal),
<<"sql">> => Mod:parse_query(SQL)
NRule#{principal => compile_principal(Principal),
sql => Mod:parse_query(SQL)
}.
compile_principal(all) -> all;
compile_principal(#{<<"username">> := Username}) ->
compile_principal(#{username := Username}) ->
{ok, MP} = re:compile(bin(Username)),
#{<<"username">> => MP};
compile_principal(#{<<"clientid">> := Clientid}) ->
#{username => MP};
compile_principal(#{clientid := Clientid}) ->
{ok, MP} = re:compile(bin(Clientid)),
#{<<"clientid">> => MP};
compile_principal(#{<<"ipaddress">> := IpAddress}) ->
#{<<"ipaddress">> => esockd_cidr:parse(b2l(IpAddress), true)};
compile_principal(#{<<"and">> := Principals}) when is_list(Principals) ->
#{<<"and">> => [compile_principal(Principal) || Principal <- Principals]};
compile_principal(#{<<"or">> := Principals}) when is_list(Principals) ->
#{<<"or">> => [compile_principal(Principal) || Principal <- Principals]}.
#{clientid => MP};
compile_principal(#{ipaddress := IpAddress}) ->
#{ipaddress => esockd_cidr:parse(b2l(IpAddress), true)};
compile_principal(#{'and' := Principals}) when is_list(Principals) ->
#{'and' => [compile_principal(Principal) || Principal <- Principals]};
compile_principal(#{'or' := Principals}) when is_list(Principals) ->
#{'or' => [compile_principal(Principal) || Principal <- Principals]}.
compile_topic(<<"eq ", Topic/binary>>) ->
compile_topic(#{<<"eq">> => Topic});
compile_topic(#{<<"eq">> := Topic}) ->
#{<<"eq">> => emqx_topic:words(bin(Topic))};
compile_topic(#{'eq' => Topic});
compile_topic(#{'eq' := Topic}) ->
#{'eq' => emqx_topic:words(bin(Topic))};
compile_topic(Topic) when is_binary(Topic)->
Words = emqx_topic:words(bin(Topic)),
case pattern(Words) of
true -> #{<<"pattern">> => Words};
true -> #{pattern => Words};
false -> Words
end.
@ -173,8 +174,8 @@ authorize(#{username := Username,
end.
do_authorize(Client, PubSub, Topic,
[Connector = #{<<"principal">> := Principal,
<<"type">> := DB} | Tail] ) ->
[Connector = #{principal := Principal,
type := DB} | Tail] ) ->
case match_principal(Client, Principal) of
true ->
Mod = list_to_existing_atom(io_lib:format("~s_~s",[emqx_authz, DB])),
@ -185,7 +186,7 @@ do_authorize(Client, PubSub, Topic,
false -> do_authorize(Client, PubSub, Topic, Tail)
end;
do_authorize(Client, PubSub, Topic,
[#{<<"permission">> := Permission} = Rule | Tail]) ->
[#{permission := Permission} = Rule | Tail]) ->
case match(Client, PubSub, Topic, Rule) of
true -> {matched, Permission};
false -> do_authorize(Client, PubSub, Topic, Tail)
@ -193,9 +194,9 @@ do_authorize(Client, PubSub, Topic,
do_authorize(_Client, _PubSub, _Topic, []) -> nomatch.
match(Client, PubSub, Topic,
#{<<"principal">> := Principal,
<<"topics">> := TopicFilters,
<<"action">> := Action
#{principal := Principal,
topics := TopicFilters,
action := Action
}) ->
match_action(PubSub, Action) andalso
match_principal(Client, Principal) andalso
@ -207,27 +208,27 @@ match_action(_, all) -> true;
match_action(_, _) -> false.
match_principal(_, all) -> true;
match_principal(#{username := undefined}, #{<<"username">> := _MP}) ->
match_principal(#{username := undefined}, #{username := _MP}) ->
false;
match_principal(#{username := Username}, #{<<"username">> := MP}) ->
match_principal(#{username := Username}, #{username := MP}) ->
case re:run(Username, MP) of
{match, _} -> true;
_ -> false
end;
match_principal(#{clientid := Clientid}, #{<<"clientid">> := MP}) ->
match_principal(#{clientid := Clientid}, #{clientid := MP}) ->
case re:run(Clientid, MP) of
{match, _} -> true;
_ -> false
end;
match_principal(#{peerhost := undefined}, #{<<"ipaddress">> := _CIDR}) ->
match_principal(#{peerhost := undefined}, #{ipaddress := _CIDR}) ->
false;
match_principal(#{peerhost := IpAddress}, #{<<"ipaddress">> := CIDR}) ->
match_principal(#{peerhost := IpAddress}, #{ipaddress := CIDR}) ->
esockd_cidr:match(IpAddress, CIDR);
match_principal(ClientInfo, #{<<"and">> := Principals}) when is_list(Principals) ->
match_principal(ClientInfo, #{'and' := Principals}) when is_list(Principals) ->
lists:foldl(fun(Principal, Permission) ->
match_principal(ClientInfo, Principal) andalso Permission
end, true, Principals);
match_principal(ClientInfo, #{<<"or">> := Principals}) when is_list(Principals) ->
match_principal(ClientInfo, #{'or' := Principals}) when is_list(Principals) ->
lists:foldl(fun(Principal, Permission) ->
match_principal(ClientInfo, Principal) orelse Permission
end, false, Principals);
@ -235,7 +236,7 @@ match_principal(_, _) -> false.
match_topics(_ClientInfo, _Topic, []) ->
false;
match_topics(ClientInfo, Topic, [#{<<"pattern">> := PatternFilter}|Filters]) ->
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);
@ -243,7 +244,7 @@ match_topics(ClientInfo, Topic, [TopicFilter|Filters]) ->
match_topic(emqx_topic:words(Topic), TopicFilter)
orelse match_topics(ClientInfo, Topic, Filters).
match_topic(Topic, #{<<"eq">> := TopicFilter}) ->
match_topic(Topic, #{'eq' := TopicFilter}) ->
Topic == TopicFilter;
match_topic(Topic, TopicFilter) ->
emqx_topic:match(Topic, TopicFilter).

View File

@ -74,10 +74,9 @@ push_authz(_Bindings, Params) ->
%%------------------------------------------------------------------------------
get_rules(Params) ->
% #{<<"authz">> := #{<<"rules">> := Rules}} = hocon_schema:check_plain(emqx_authz_schema, #{<<"authz">> => Params}),
{ok, Conf} = hocon:binary(jsx:encode(#{<<"authz">> => Params}), #{format => richmap}),
CheckConf = hocon_schema:check(emqx_authz_schema, Conf),
#{<<"authz">> := #{<<"rules">> := Rules}} = hocon_schema:richmap_to_map(CheckConf),
{ok, Conf} = hocon:binary(jsx:encode(#{<<"emqx_authz">> => Params}), #{format => richmap}),
CheckConf = hocon_schema:check(emqx_authz_schema, Conf, #{atom_key => true}),
#{emqx_authz := #{rules := Rules}} = hocon_schema:richmap_to_map(CheckConf),
Rules.
%%--------------------------------------------------------------------

View File

@ -46,8 +46,8 @@ parse_query(Sql) ->
end.
authorize(Client, PubSub, Topic,
#{<<"resource_id">> := ResourceID,
<<"sql">> := {SQL, Params}
#{resource_id := ResourceID,
sql := {SQL, Params}
}) ->
case emqx_resource:query(ResourceID, {sql, SQL, replvar(Params, Client)}) of
{ok, _Columns, []} -> nomatch;
@ -87,12 +87,12 @@ match(Client, PubSub, Topic,
<<"action">> => Action,
<<"permission">> => Permission
},
#{<<"simple_rule">> :=
#{<<"permission">> := NPermission} = NRule
#{simple_rule :=
#{permission := NPermission} = NRule
} = hocon_schema:check_plain(
emqx_authz_schema,
#{<<"simple_rule">> => Rule},
#{},
#{atom_key => true},
[simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of
true -> {matched, NPermission};

View File

@ -50,8 +50,8 @@ parse_query(Sql) ->
end.
authorize(Client, PubSub, Topic,
#{<<"resource_id">> := ResourceID,
<<"sql">> := {SQL, Params}
#{resource_id := ResourceID,
sql := {SQL, Params}
}) ->
case emqx_resource:query(ResourceID, {sql, SQL, replvar(Params, Client)}) of
{ok, _Columns, []} -> nomatch;
@ -91,12 +91,12 @@ match(Client, PubSub, Topic,
<<"action">> => Action,
<<"permission">> => Permission
},
#{<<"simple_rule">> :=
#{<<"permission">> := NPermission} = NRule
#{simple_rule :=
#{permission := NPermission} = NRule
} = hocon_schema:check_plain(
emqx_authz_schema,
#{<<"simple_rule">> => Rule},
#{},
#{atom_key => true},
[simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of
true -> {matched, NPermission};

View File

@ -34,8 +34,8 @@ description() ->
"AuthZ with redis".
authorize(Client, PubSub, Topic,
#{<<"resource_id">> := ResourceID,
<<"cmd">> := CMD
#{resource_id := ResourceID,
cmd := CMD
}) ->
NCMD = string:tokens(replvar(CMD, Client), " "),
case emqx_resource:query(ResourceID, {cmd, NCMD}) of
@ -68,11 +68,11 @@ match(Client, PubSub, Topic,
<<"action">> => Action,
<<"permission">> => allow
},
#{<<"simple_rule">> := NRule
#{simple_rule := NRule
} = hocon_schema:check_plain(
emqx_authz_schema,
#{<<"simple_rule">> => Rule},
#{},
#{atom_key => true},
[simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of
true -> {matched, allow};

View File

@ -11,9 +11,9 @@
-export([structs/0, fields/1]).
structs() -> [authz].
structs() -> ["emqx_authz"].
fields(authz) ->
fields("emqx_authz") ->
[ {rules, rules()}
];
fields(redis_connector) ->
@ -39,7 +39,7 @@ fields(simple_rule) ->
, {action, #{type => action()}}
, {topics, #{type => union_array(
[ binary()
, hoconsc:ref(eq_topic)
, hoconsc:ref(?MODULE, eq_topic)
]
)}}
, {principal, principal()}
@ -52,18 +52,18 @@ fields(ipaddress) ->
[{ipaddress, #{type => string()}}];
fields(andlist) ->
[{'and', #{type => union_array(
[ hoconsc:ref(username)
, hoconsc:ref(clientid)
, hoconsc:ref(ipaddress)
[ hoconsc:ref(?MODULE, username)
, hoconsc:ref(?MODULE, clientid)
, hoconsc:ref(?MODULE, ipaddress)
])
}
}
];
fields(orlist) ->
[{'or', #{type => union_array(
[ hoconsc:ref(username)
, hoconsc:ref(clientid)
, hoconsc:ref(ipaddress)
[ hoconsc:ref(?MODULE, username)
, hoconsc:ref(?MODULE, clientid)
, hoconsc:ref(?MODULE, ipaddress)
])
}
}
@ -81,9 +81,9 @@ union_array(Item) when is_list(Item) ->
rules() ->
#{type => union_array(
[ hoconsc:ref(simple_rule)
, hoconsc:ref(sql_connector)
, hoconsc:ref(redis_connector)
[ hoconsc:ref(?MODULE, simple_rule)
, hoconsc:ref(?MODULE, sql_connector)
, hoconsc:ref(?MODULE, redis_connector)
])
}.
@ -91,11 +91,11 @@ principal() ->
#{default => all,
type => hoconsc:union(
[ all
, hoconsc:ref(username)
, hoconsc:ref(clientid)
, hoconsc:ref(ipaddress)
, hoconsc:ref(andlist)
, hoconsc:ref(orlist)
, hoconsc:ref(?MODULE, username)
, hoconsc:ref(?MODULE, clientid)
, hoconsc:ref(?MODULE, ipaddress)
, hoconsc:ref(?MODULE, andlist)
, hoconsc:ref(?MODULE, orlist)
])
}.

View File

@ -43,41 +43,42 @@ set_special_configs(emqx) ->
set_special_configs(emqx_authz) ->
application:set_env(emqx, plugins_etc_dir,
emqx_ct_helpers:deps_path(emqx_authz, "test")),
Conf = #{<<"authz">> => #{<<"rules">> => []}},
Conf = #{<<"emqx_authz">> => #{<<"rules">> => []}},
ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf'), jsx:encode(Conf)),
% emqx_config:put([emqx_authz], #{rules => []}),
ok;
set_special_configs(_App) ->
ok.
-define(RULE1, #{<<"principal">> => all,
<<"topics">> => [<<"#">>],
<<"action">> => all,
<<"permission">> => deny}
-define(RULE1, #{principal => all,
topics => [<<"#">>],
action => all,
permission => deny}
).
-define(RULE2, #{<<"principal">> =>
#{<<"ipaddress">> => <<"127.0.0.1">>},
<<"topics">> =>
[#{<<"eq">> => <<"#">>},
#{<<"eq">> => <<"+">>}
-define(RULE2, #{principal =>
#{ipaddress => <<"127.0.0.1">>},
topics =>
[#{eq => <<"#">>},
#{eq => <<"+">>}
] ,
<<"action">> => all,
<<"permission">> => allow}
action => all,
permission => allow}
).
-define(RULE3,#{<<"principal">> =>
#{<<"and">> => [#{<<"username">> => "^test?"},
#{<<"clientid">> => "^test?"}
-define(RULE3,#{principal =>
#{'and' => [#{username => "^test?"},
#{clientid => "^test?"}
]},
<<"topics">> => [<<"test">>],
<<"action">> => publish,
<<"permission">> => allow}
topics => [<<"test">>],
action => publish,
permission => allow}
).
-define(RULE4,#{<<"principal">> =>
#{<<"or">> => [#{<<"username">> => <<"^test">>},
#{<<"clientid">> => <<"test?">>}
]},
<<"topics">> => [<<"%u">>,<<"%c">>],
<<"action">> => publish,
<<"permission">> => deny}
-define(RULE4,#{principal =>
#{'or' => [#{username => <<"^test">>},
#{clientid => <<"test?">>}
]},
topics => [<<"%u">>,<<"%c">>],
action => publish,
permission => deny}
).
@ -85,39 +86,39 @@ set_special_configs(_App) ->
%% Testcases
%%------------------------------------------------------------------------------
t_compile(_) ->
?assertEqual(#{<<"permission">> => deny,
<<"action">> => all,
<<"principal">> => all,
<<"topics">> => [['#']]
?assertEqual(#{permission => deny,
action => all,
principal => all,
topics => [['#']]
},emqx_authz:compile(?RULE1)),
?assertEqual(#{<<"permission">> => allow,
<<"action">> => all,
<<"principal">> =>
#{<<"ipaddress">> => {{127,0,0,1},{127,0,0,1},32}},
<<"topics">> => [#{<<"eq">> => ['#']},
#{<<"eq">> => ['+']}]
?assertEqual(#{permission => allow,
action => all,
principal =>
#{ipaddress => {{127,0,0,1},{127,0,0,1},32}},
topics => [#{eq => ['#']},
#{eq => ['+']}]
}, emqx_authz:compile(?RULE2)),
?assertMatch(
#{<<"permission">> := allow,
<<"action">> := publish,
<<"principal">> :=
#{<<"and">> := [#{<<"username">> := {re_pattern, _, _, _, _}},
#{<<"clientid">> := {re_pattern, _, _, _, _}}
]
#{permission := allow,
action := publish,
principal :=
#{'and' := [#{username := {re_pattern, _, _, _, _}},
#{clientid := {re_pattern, _, _, _, _}}
]
},
<<"topics">> := [[<<"test">>]]
topics := [[<<"test">>]]
}, emqx_authz:compile(?RULE3)),
?assertMatch(
#{<<"permission">> := deny,
<<"action">> := publish,
<<"principal">> :=
#{<<"or">> := [#{<<"username">> := {re_pattern, _, _, _, _}},
#{<<"clientid">> := {re_pattern, _, _, _, _}}
]
#{permission := deny,
action := publish,
principal :=
#{'or' := [#{username := {re_pattern, _, _, _, _}},
#{clientid := {re_pattern, _, _, _, _}}
]
},
<<"topics">> := [#{<<"pattern">> := [<<"%u">>]},
#{<<"pattern">> := [<<"%c">>]}
]
topics := [#{pattern := [<<"%u">>]},
#{pattern := [<<"%c">>]}
]
}, emqx_authz:compile(?RULE4)),
ok.

View File

@ -57,11 +57,10 @@ set_special_configs(emqx) ->
set_special_configs(emqx_authz) ->
application:set_env(emqx, plugins_etc_dir,
emqx_ct_helpers:deps_path(emqx_authz, "test")),
Conf = #{<<"authz">> => #{<<"rules">> => []}},
Conf = #{<<"emqx_authz">> => #{<<"rules">> => []}},
ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf'), jsx:encode(Conf)),
% emqx_config:put([emqx_authz], #{rules => []}),
ok;
set_special_configs(_App) ->
ok.

View File

@ -49,7 +49,7 @@ set_special_configs(emqx) ->
set_special_configs(emqx_authz) ->
application:set_env(emqx, plugins_etc_dir,
emqx_ct_helpers:deps_path(emqx_authz, "test")),
Conf = #{<<"authz">> =>
Conf = #{<<"emqx_authz">> =>
#{<<"rules">> =>
[#{<<"config">> =>#{<<"meck">> => <<"fake">>},
<<"principal">> => all,
@ -57,6 +57,12 @@ set_special_configs(emqx_authz) ->
<<"type">> => mysql}
]}},
ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf'), jsx:encode(Conf)),
% Rules = [#{config =>#{<<"meck">> => <<"fake">>},
% principal => all,
% sql => <<"fake sql">>,
% type => mysql}
% ],
% emqx_config:put([emqx_authz], #{rules => Rules}),
ok;
set_special_configs(_App) ->
ok.

View File

@ -49,7 +49,7 @@ set_special_configs(emqx) ->
set_special_configs(emqx_authz) ->
application:set_env(emqx, plugins_etc_dir,
emqx_ct_helpers:deps_path(emqx_authz, "test")),
Conf = #{<<"authz">> =>
Conf = #{<<"emqx_authz">> =>
#{<<"rules">> =>
[#{<<"config">> =>#{<<"meck">> => <<"fake">>},
<<"principal">> => all,
@ -57,6 +57,12 @@ set_special_configs(emqx_authz) ->
<<"type">> => pgsql}
]}},
ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf'), jsx:encode(Conf)),
% Rules = [#{config =>#{<<"meck">> => <<"fake">>},
% principal => all,
% sql => <<"fake sql">>,
% type => pgsql}
% ],
% emqx_config:put([emqx_authz], #{rules => Rules}),
ok;
set_special_configs(_App) ->
ok.

View File

@ -49,7 +49,7 @@ set_special_configs(emqx) ->
set_special_configs(emqx_authz) ->
application:set_env(emqx, plugins_etc_dir,
emqx_ct_helpers:deps_path(emqx_authz, "test")),
Conf = #{<<"authz">> =>
Conf = #{<<"emqx_authz">> =>
#{<<"rules">> =>
[#{<<"config">> =>#{
<<"server">> => <<"127.0.0.1:6379">>,
@ -63,6 +63,12 @@ set_special_configs(emqx_authz) ->
<<"type">> => redis}
]}},
ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf'), jsx:encode(Conf)),
% Rules = [#{config =>#{<<"meck">> => <<"fake">>},
% principal => all,
% cmd => <<"fake cmd">>,
% type => redis}
% ],
% emqx_config:put([emqx_authz], #{rules => Rules}),
ok;
set_special_configs(_App) ->
ok.