feat(authz): support api
Signed-off-by: zhanghongtong <rory-z@outlook.com>
This commit is contained in:
parent
ae8c3cf779
commit
c26ec5c0dd
|
@ -41,42 +41,56 @@ register_metrics() ->
|
||||||
init() ->
|
init() ->
|
||||||
ok = register_metrics(),
|
ok = register_metrics(),
|
||||||
emqx_config_handler:add_handler(?CONF_KEY_PATH, ?MODULE),
|
emqx_config_handler:add_handler(?CONF_KEY_PATH, ?MODULE),
|
||||||
NRules = [init_rule(Rule) || Rule <- lookup()],
|
NRules = [init_rule(Rule) || Rule <- emqx_config:get(?CONF_KEY_PATH, [])],
|
||||||
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1).
|
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1).
|
||||||
|
|
||||||
lookup() ->
|
lookup() ->
|
||||||
emqx_config:get(?CONF_KEY_PATH, []).
|
{_M, _F, A}= find_action_in_hooks(),
|
||||||
|
A.
|
||||||
|
|
||||||
update(Cmd, Rules) ->
|
update(Cmd, Rules) ->
|
||||||
emqx_config:update(emqx_authz_schema, ?CONF_KEY_PATH, {Cmd, Rules}).
|
emqx_config:update(emqx_authz_schema, ?CONF_KEY_PATH, {Cmd, Rules}).
|
||||||
|
|
||||||
%% For now we only support re-creating the entire rule list
|
%% For now we only support re-creating the entire rule list
|
||||||
pre_config_update({head, Rule}, OldConf) when is_map(Rule), is_list(OldConf) ->
|
pre_config_update({head, Rules}, OldConf) when is_list(Rules), is_list(OldConf) ->
|
||||||
[Rule | OldConf];
|
Rules ++ OldConf;
|
||||||
pre_config_update({tail, Rule}, OldConf) when is_map(Rule), is_list(OldConf) ->
|
pre_config_update({tail, Rules}, OldConf) when is_list(Rules), is_list(OldConf) ->
|
||||||
OldConf ++ [Rule];
|
OldConf ++ Rules;
|
||||||
pre_config_update({_, NewConf}, _OldConf) ->
|
pre_config_update({_, Rules}, _OldConf) when is_list(Rules)->
|
||||||
%% overwrite the entire config!
|
%% overwrite the entire config!
|
||||||
case is_list(NewConf) of
|
Rules.
|
||||||
true -> NewConf;
|
|
||||||
false -> [NewConf]
|
|
||||||
end.
|
|
||||||
|
|
||||||
post_config_update(_, undefined, _OldConf) ->
|
post_config_update(_, undefined, _OldConf) ->
|
||||||
%_ = [release_rules(Rule) || Rule <- OldConf],
|
|
||||||
ok;
|
ok;
|
||||||
|
post_config_update({head, Rules}, _NewRules, _OldConf) ->
|
||||||
|
InitedRules = [init_rule(Rule) || Rule <- check_rules(Rules)],
|
||||||
|
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [lists:append(InitedRules, lookup())]}, -1),
|
||||||
|
ok = emqx_authz_cache:drain_cache();
|
||||||
|
post_config_update({tail, Rules}, _NewRules, _OldConf) ->
|
||||||
|
InitedRules = [init_rule(Rule) || Rule <- check_rules(Rules)],
|
||||||
|
emqx_hooks:put('client.authorize', {?MODULE, authorize, [lists:append(InitedRules, lookup())]}, -1),
|
||||||
|
ok = emqx_authz_cache:drain_cache();
|
||||||
post_config_update(_, NewRules, _OldConf) ->
|
post_config_update(_, NewRules, _OldConf) ->
|
||||||
%_ = [release_rules(Rule) || Rule <- OldConf],
|
%% overwrite the entire config!
|
||||||
|
OldInitedRules = lookup(),
|
||||||
InitedRules = [init_rule(Rule) || Rule <- NewRules],
|
InitedRules = [init_rule(Rule) || Rule <- NewRules],
|
||||||
Action = find_action_in_hooks(),
|
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
|
||||||
ok = emqx_hooks:del('client.authorize', Action),
|
lists:foreach(fun (#{type := _Type, enable := true, metadata := #{id := Id}}) ->
|
||||||
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
|
ok = emqx_resource:remove(Id);
|
||||||
|
(_) -> ok
|
||||||
|
end, OldInitedRules),
|
||||||
ok = emqx_authz_cache:drain_cache().
|
ok = emqx_authz_cache:drain_cache().
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
check_rules(RawRules) ->
|
||||||
|
{ok, Conf} = hocon:binary(jsx:encode(#{<<"authorization">> => #{<<"rules">> => RawRules}}), #{format => richmap}),
|
||||||
|
CheckConf = hocon_schema:check(emqx_authz_schema, Conf, #{atom_key => true}),
|
||||||
|
#{authorization := #{rules := Rules}} = hocon_schema:richmap_to_map(CheckConf),
|
||||||
|
Rules.
|
||||||
|
|
||||||
find_action_in_hooks() ->
|
find_action_in_hooks() ->
|
||||||
Callbacks = emqx_hooks:lookup('client.authorize'),
|
Callbacks = emqx_hooks:lookup('client.authorize'),
|
||||||
[Action] = [Action || {callback,{?MODULE, authorize, _} = Action, _, _} <- Callbacks ],
|
[Action] = [Action || {callback,{?MODULE, authorize, _} = Action, _, _} <- Callbacks ],
|
||||||
|
|
|
@ -16,74 +16,108 @@
|
||||||
|
|
||||||
-module(emqx_authz_api).
|
-module(emqx_authz_api).
|
||||||
|
|
||||||
|
-behavior(minirest_api).
|
||||||
|
|
||||||
-include("emqx_authz.hrl").
|
-include("emqx_authz.hrl").
|
||||||
|
|
||||||
-rest_api(#{name => lookup_authz,
|
-define(EXAMPLE_RETURNED_RULES,
|
||||||
method => 'GET',
|
#{rules => [ #{principal => <<"all">>,
|
||||||
path => "/authz",
|
permission => <<"allow">>,
|
||||||
func => lookup_authz,
|
action => <<"all">>,
|
||||||
descr => "Lookup Authorization"
|
topics => [<<"#">>],
|
||||||
}).
|
metadata => #{id => 1}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).
|
||||||
|
|
||||||
-rest_api(#{name => update_authz,
|
-define(EXAMPLE_RULE1, #{principal => <<"all">>,
|
||||||
method => 'PUT',
|
permission => <<"allow">>,
|
||||||
path => "/authz",
|
action => <<"all">>,
|
||||||
func => update_authz,
|
topics => [<<"#">>]}).
|
||||||
descr => "Rewrite authz list"
|
|
||||||
}).
|
|
||||||
|
|
||||||
-rest_api(#{name => append_authz,
|
-export([ api_spec/0
|
||||||
method => 'POST',
|
, authorization/2
|
||||||
path => "/authz/append",
|
|
||||||
func => append_authz,
|
|
||||||
descr => "Add a new rule at the end of the authz list"
|
|
||||||
}).
|
|
||||||
|
|
||||||
-rest_api(#{name => push_authz,
|
|
||||||
method => 'POST',
|
|
||||||
path => "/authz/push",
|
|
||||||
func => push_authz,
|
|
||||||
descr => "Add a new rule at the start of the authz list"
|
|
||||||
}).
|
|
||||||
|
|
||||||
-export([ lookup_authz/2
|
|
||||||
, update_authz/2
|
|
||||||
, append_authz/2
|
|
||||||
, push_authz/2
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
lookup_authz(_Bindings, _Params) ->
|
api_spec() ->
|
||||||
return({ok, emqx_authz:lookup()}).
|
{[ authorization_api()
|
||||||
|
], definitions()}.
|
||||||
|
|
||||||
update_authz(_Bindings, Params) ->
|
definitions() -> emqx_authz_api_schema:definitions().
|
||||||
Rules = form_rules(Params),
|
|
||||||
return(emqx_authz:update(replace, Rules)).
|
|
||||||
|
|
||||||
append_authz(_Bindings, Params) ->
|
authorization_api() ->
|
||||||
Rules = form_rules(Params),
|
Metadata = #{
|
||||||
return(emqx_authz:update(tail, Rules)).
|
get => #{
|
||||||
|
description => "List authorization rules",
|
||||||
|
parameters => [],
|
||||||
|
responses => #{
|
||||||
|
<<"200">> => #{
|
||||||
|
description => <<"OK">>,
|
||||||
|
content => #{
|
||||||
|
'application/json' => #{
|
||||||
|
schema => #{
|
||||||
|
type => object,
|
||||||
|
required => [rules],
|
||||||
|
properties => #{rules => #{
|
||||||
|
type => array,
|
||||||
|
items => minirest:ref(<<"returned_rules">>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
examples => #{
|
||||||
|
rules => #{
|
||||||
|
summary => <<"Rules">>,
|
||||||
|
value => jsx:encode(?EXAMPLE_RETURNED_RULES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
<<"404">> => #{description => <<"Not Found">>}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
post => #{
|
||||||
|
description => "Add new rule",
|
||||||
|
requestBody => #{
|
||||||
|
content => #{
|
||||||
|
'application/json' => #{
|
||||||
|
schema => minirest:ref(<<"rules">>),
|
||||||
|
examples => #{
|
||||||
|
simple_rule => #{
|
||||||
|
summary => <<"Rules">>,
|
||||||
|
value => jsx:encode(?EXAMPLE_RULE1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses => #{
|
||||||
|
<<"201">> => #{description => <<"Created">>},
|
||||||
|
<<"400">> => #{description => <<"Bad Request">>}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"/authorization", Metadata, authorization}.
|
||||||
|
|
||||||
push_authz(_Bindings, Params) ->
|
authorization(get, _Request) ->
|
||||||
Rules = form_rules(Params),
|
Rules = lists:foldl(fun (#{type := _Type, enable := true, metadata := #{id := Id} = MataData} = Rule, AccIn) ->
|
||||||
return(emqx_authz:update(head, Rules)).
|
NRule = case emqx_resource:health_check(Id) of
|
||||||
|
ok ->
|
||||||
%%------------------------------------------------------------------------------
|
Rule#{metadata => MataData#{status => healthy}};
|
||||||
%% Interval Funcs
|
_ ->
|
||||||
%%------------------------------------------------------------------------------
|
Rule#{metadata => MataData#{status => unhealthy}}
|
||||||
|
end,
|
||||||
form_rules(Params) ->
|
lists:append(AccIn, [NRule]);
|
||||||
Params.
|
(Rule, AccIn) ->
|
||||||
|
lists:append(AccIn, [Rule])
|
||||||
%%--------------------------------------------------------------------
|
end, [], emqx_authz:lookup()),
|
||||||
%% EUnits
|
{200, #{rules => [Rules]}};
|
||||||
%%--------------------------------------------------------------------
|
authorization(post, Request) ->
|
||||||
|
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||||
-ifdef(TEST).
|
RawConfig = jsx:decode(Body, [return_maps]),
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
case emqx_authz:update(head, [RawConfig]) of
|
||||||
|
ok -> {201};
|
||||||
|
{error, Reason} -> {400, #{messgae => atom_to_binary(Reason)}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-endif.
|
|
||||||
|
|
||||||
return(_) ->
|
|
||||||
%% TODO: V5 api
|
|
||||||
ok.
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_authz_api_schema).
|
||||||
|
|
||||||
|
-export([definitions/0]).
|
||||||
|
|
||||||
|
definitions() ->
|
||||||
|
RetruenedRules = #{
|
||||||
|
allOf => [ #{type => object,
|
||||||
|
properties => #{
|
||||||
|
annotations => #{
|
||||||
|
type => object,
|
||||||
|
required => [id],
|
||||||
|
properties => #{
|
||||||
|
id => #{
|
||||||
|
type => string
|
||||||
|
},
|
||||||
|
principal => minirest:ref(<<"principal">>)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, minirest:ref(<<"rules">>)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Rules = #{
|
||||||
|
oneOf => [ minirest:ref(<<"simple_rule">>)
|
||||||
|
% , minirest:ref(<<"connector_redis">>)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
% ConnectorRedis = #{
|
||||||
|
% type => object,
|
||||||
|
% required => [principal, type, enable, config, cmd]
|
||||||
|
% properties => #{
|
||||||
|
% principal => minirest:ref(<<"principal">>),
|
||||||
|
% type => #{
|
||||||
|
% type => string,
|
||||||
|
% enum => [<<"redis">>],
|
||||||
|
% example => <<"redis">>
|
||||||
|
% },
|
||||||
|
% enable => #{
|
||||||
|
% type => boolean,
|
||||||
|
% example => true
|
||||||
|
% }
|
||||||
|
% config => #{
|
||||||
|
% type =>
|
||||||
|
% }
|
||||||
|
% }
|
||||||
|
% }
|
||||||
|
SimpleRule = #{
|
||||||
|
type => object,
|
||||||
|
required => [principal, permission, action, topics],
|
||||||
|
properties => #{
|
||||||
|
action => #{
|
||||||
|
type => string,
|
||||||
|
enum => [<<"publish">>, <<"subscribe">>, <<"all">>],
|
||||||
|
example => <<"publish">>
|
||||||
|
},
|
||||||
|
permission => #{
|
||||||
|
type => string,
|
||||||
|
enum => [<<"allow">>, <<"deny">>],
|
||||||
|
example => <<"allow">>
|
||||||
|
},
|
||||||
|
topics => #{
|
||||||
|
type => array,
|
||||||
|
items => #{
|
||||||
|
oneOf => [ #{type => string, example => <<"#">>}
|
||||||
|
, #{type => object,
|
||||||
|
required => [eq],
|
||||||
|
properties => #{
|
||||||
|
eq => #{type => string}
|
||||||
|
},
|
||||||
|
example => #{eq => <<"#">>}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
principal => minirest:ref(<<"principal">>)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Principal = #{
|
||||||
|
oneOf => [ minirest:ref(<<"principal_username">>)
|
||||||
|
, minirest:ref(<<"principal_clientid">>)
|
||||||
|
, minirest:ref(<<"principal_ipaddress">>)
|
||||||
|
, #{type => string, enum=>[<<"all">>], example => <<"all">>}
|
||||||
|
, #{type => object,
|
||||||
|
required => ['and'],
|
||||||
|
properties => #{'and' => #{type => array,
|
||||||
|
items => #{oneOf => [ minirest:ref(<<"principal_username">>)
|
||||||
|
, minirest:ref(<<"principal_clientid">>)
|
||||||
|
, minirest:ref(<<"principal_ipaddress">>)
|
||||||
|
]}}},
|
||||||
|
example => #{'and' => [#{username => <<"emqx">>}, #{clientid => <<"emqx">>}]}
|
||||||
|
}
|
||||||
|
, #{type => object,
|
||||||
|
required => ['or'],
|
||||||
|
properties => #{'and' => #{type => array,
|
||||||
|
items => #{oneOf => [ minirest:ref(<<"principal_username">>)
|
||||||
|
, minirest:ref(<<"principal_clientid">>)
|
||||||
|
, minirest:ref(<<"principal_ipaddress">>)
|
||||||
|
]}}},
|
||||||
|
example => #{'or' => [#{username => <<"emqx">>}, #{clientid => <<"emqx">>}]}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
PrincipalUsername = #{type => object,
|
||||||
|
required => [username],
|
||||||
|
properties => #{username => #{type => string}},
|
||||||
|
example => #{username => <<"emqx">>}
|
||||||
|
},
|
||||||
|
PrincipalClientid = #{type => object,
|
||||||
|
required => [clientid],
|
||||||
|
properties => #{clientid => #{type => string}},
|
||||||
|
example => #{clientid => <<"emqx">>}
|
||||||
|
},
|
||||||
|
PrincipalIpaddress = #{type => object,
|
||||||
|
required => [ipaddress],
|
||||||
|
properties => #{ipaddress => #{type => string}},
|
||||||
|
example => #{ipaddress => <<"127.0.0.1">>}
|
||||||
|
},
|
||||||
|
[ #{<<"returned_rules">> => RetruenedRules}
|
||||||
|
, #{<<"rules">> => Rules}
|
||||||
|
, #{<<"simple_rule">> => SimpleRule}
|
||||||
|
, #{<<"principal">> => Principal}
|
||||||
|
, #{<<"principal_username">> => PrincipalUsername}
|
||||||
|
, #{<<"principal_clientid">> => PrincipalClientid}
|
||||||
|
, #{<<"principal_ipaddress">> => PrincipalIpaddress}
|
||||||
|
].
|
Loading…
Reference in New Issue