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() ->
|
||||
ok = register_metrics(),
|
||||
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).
|
||||
|
||||
lookup() ->
|
||||
emqx_config:get(?CONF_KEY_PATH, []).
|
||||
{_M, _F, A}= find_action_in_hooks(),
|
||||
A.
|
||||
|
||||
update(Cmd, Rules) ->
|
||||
emqx_config:update(emqx_authz_schema, ?CONF_KEY_PATH, {Cmd, Rules}).
|
||||
|
||||
%% For now we only support re-creating the entire rule list
|
||||
pre_config_update({head, Rule}, OldConf) when is_map(Rule), is_list(OldConf) ->
|
||||
[Rule | OldConf];
|
||||
pre_config_update({tail, Rule}, OldConf) when is_map(Rule), is_list(OldConf) ->
|
||||
OldConf ++ [Rule];
|
||||
pre_config_update({_, NewConf}, _OldConf) ->
|
||||
pre_config_update({head, Rules}, OldConf) when is_list(Rules), is_list(OldConf) ->
|
||||
Rules ++ OldConf;
|
||||
pre_config_update({tail, Rules}, OldConf) when is_list(Rules), is_list(OldConf) ->
|
||||
OldConf ++ Rules;
|
||||
pre_config_update({_, Rules}, _OldConf) when is_list(Rules)->
|
||||
%% overwrite the entire config!
|
||||
case is_list(NewConf) of
|
||||
true -> NewConf;
|
||||
false -> [NewConf]
|
||||
end.
|
||||
Rules.
|
||||
|
||||
post_config_update(_, undefined, _OldConf) ->
|
||||
%_ = [release_rules(Rule) || Rule <- OldConf],
|
||||
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) ->
|
||||
%_ = [release_rules(Rule) || Rule <- OldConf],
|
||||
%% overwrite the entire config!
|
||||
OldInitedRules = lookup(),
|
||||
InitedRules = [init_rule(Rule) || Rule <- NewRules],
|
||||
Action = find_action_in_hooks(),
|
||||
ok = emqx_hooks:del('client.authorize', Action),
|
||||
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
|
||||
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
|
||||
lists:foreach(fun (#{type := _Type, enable := true, metadata := #{id := Id}}) ->
|
||||
ok = emqx_resource:remove(Id);
|
||||
(_) -> ok
|
||||
end, OldInitedRules),
|
||||
ok = emqx_authz_cache:drain_cache().
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% 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() ->
|
||||
Callbacks = emqx_hooks:lookup('client.authorize'),
|
||||
[Action] = [Action || {callback,{?MODULE, authorize, _} = Action, _, _} <- Callbacks ],
|
||||
|
|
|
@ -16,74 +16,108 @@
|
|||
|
||||
-module(emqx_authz_api).
|
||||
|
||||
-behavior(minirest_api).
|
||||
|
||||
-include("emqx_authz.hrl").
|
||||
|
||||
-rest_api(#{name => lookup_authz,
|
||||
method => 'GET',
|
||||
path => "/authz",
|
||||
func => lookup_authz,
|
||||
descr => "Lookup Authorization"
|
||||
-define(EXAMPLE_RETURNED_RULES,
|
||||
#{rules => [ #{principal => <<"all">>,
|
||||
permission => <<"allow">>,
|
||||
action => <<"all">>,
|
||||
topics => [<<"#">>],
|
||||
metadata => #{id => 1}
|
||||
}
|
||||
]
|
||||
}).
|
||||
|
||||
-rest_api(#{name => update_authz,
|
||||
method => 'PUT',
|
||||
path => "/authz",
|
||||
func => update_authz,
|
||||
descr => "Rewrite authz list"
|
||||
}).
|
||||
-define(EXAMPLE_RULE1, #{principal => <<"all">>,
|
||||
permission => <<"allow">>,
|
||||
action => <<"all">>,
|
||||
topics => [<<"#">>]}).
|
||||
|
||||
-rest_api(#{name => append_authz,
|
||||
method => 'POST',
|
||||
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
|
||||
-export([ api_spec/0
|
||||
, authorization/2
|
||||
]).
|
||||
|
||||
lookup_authz(_Bindings, _Params) ->
|
||||
return({ok, emqx_authz:lookup()}).
|
||||
api_spec() ->
|
||||
{[ authorization_api()
|
||||
], definitions()}.
|
||||
|
||||
update_authz(_Bindings, Params) ->
|
||||
Rules = form_rules(Params),
|
||||
return(emqx_authz:update(replace, Rules)).
|
||||
definitions() -> emqx_authz_api_schema:definitions().
|
||||
|
||||
append_authz(_Bindings, Params) ->
|
||||
Rules = form_rules(Params),
|
||||
return(emqx_authz:update(tail, Rules)).
|
||||
authorization_api() ->
|
||||
Metadata = #{
|
||||
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) ->
|
||||
Rules = form_rules(Params),
|
||||
return(emqx_authz:update(head, Rules)).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Interval Funcs
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
form_rules(Params) ->
|
||||
Params.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% EUnits
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
authorization(get, _Request) ->
|
||||
Rules = lists:foldl(fun (#{type := _Type, enable := true, metadata := #{id := Id} = MataData} = Rule, AccIn) ->
|
||||
NRule = case emqx_resource:health_check(Id) of
|
||||
ok ->
|
||||
Rule#{metadata => MataData#{status => healthy}};
|
||||
_ ->
|
||||
Rule#{metadata => MataData#{status => unhealthy}}
|
||||
end,
|
||||
lists:append(AccIn, [NRule]);
|
||||
(Rule, AccIn) ->
|
||||
lists:append(AccIn, [Rule])
|
||||
end, [], emqx_authz:lookup()),
|
||||
{200, #{rules => [Rules]}};
|
||||
authorization(post, Request) ->
|
||||
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||
RawConfig = jsx:decode(Body, [return_maps]),
|
||||
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