chore(authz): test Mongo backend with real Mongo

This commit is contained in:
Ilya Averyanov 2021-12-15 01:13:02 +03:00
parent 6ad71a483e
commit 28c319b6c4
4 changed files with 245 additions and 113 deletions

View File

@ -217,10 +217,10 @@ do_post_update({{?CMD_DELETE, Type}, _Source}, _NewSources) ->
ok = ensure_resource_deleted(OldSource),
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [Front ++ Rear]}, -1),
ok = emqx_authz_cache:drain_cache();
do_post_update(_, NewSources) ->
do_post_update({?CMD_REPLACE, Sources}, _NewSources) ->
%% overwrite the entire config!
OldInitedSources = lookup(),
InitedSources = init_sources(NewSources),
InitedSources = init_sources(check_sources(Sources)),
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [InitedSources]}, -1),
lists:foreach(fun ensure_resource_deleted/1, OldInitedSources),
ok = emqx_authz_cache:drain_cache().

View File

@ -23,112 +23,238 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
-define(MONGO_HOST, "mongo").
-define(MONGO_PORT, 27017).
-define(MONGO_CLIENT, 'emqx_authz_mongo_SUITE_client').
all() ->
emqx_common_test_helpers:all(?MODULE).
groups() ->
[].
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),
meck:expect(emqx_resource, remove, fun(_) -> ok end ),
init_per_testcase(_TestCase, Config) ->
{ok, _} = mc_worker_api:connect(mongo_config()),
Config.
end_per_testcase(_TestCase, _Config) ->
ok = reset_samples(),
ok = mc_worker_api:disconnect(?MONGO_CLIENT).
init_per_suite(Config) ->
case emqx_authz_test_lib:is_tcp_server_available(?MONGO_HOST, ?MONGO_PORT) of
true ->
ok = emqx_common_test_helpers:start_apps(
[emqx_conf, emqx_authz],
fun set_special_configs/1
),
Rules = [#{<<"type">> => <<"mongodb">>,
<<"mongo_type">> => <<"single">>,
<<"server">> => <<"127.0.0.1:27017">>,
<<"pool_size">> => 1,
<<"database">> => <<"mqtt">>,
<<"ssl">> => #{<<"enable">> => false},
<<"collection">> => <<"fake">>,
<<"selector">> => #{<<"a">> => <<"b">>}
}],
{ok, _} = emqx_authz:update(replace, Rules),
Config.
ok = start_apps([emqx_resource, emqx_connector]),
Config;
false ->
{skip, no_mongo}
end.
end_per_suite(_Config) ->
{ok, _} = emqx:update_config(
[authorization],
#{<<"no_match">> => <<"allow">>,
<<"cache">> => #{<<"enable">> => <<"true">>},
<<"sources">> => []}),
emqx_common_test_helpers:stop_apps([emqx_authz, emqx_conf]),
meck:unload(emqx_resource),
ok.
ok = emqx_authz_test_lib:reset_authorizers(),
ok = stop_apps([emqx_resource, emqx_connector]),
ok = emqx_common_test_helpers:stop_apps([emqx_authz]).
set_special_configs(emqx_authz) ->
{ok, _} = emqx:update_config([authorization, cache, enable], false),
{ok, _} = emqx:update_config([authorization, no_match], deny),
{ok, _} = emqx:update_config([authorization, sources], []),
ok;
set_special_configs(_App) ->
ok.
ok = emqx_authz_test_lib:reset_authorizers();
-define(SOURCE1,[#{<<"topics">> => [<<"#">>],
<<"permission">> => <<"deny">>,
<<"action">> => <<"all">>}]).
-define(SOURCE2,[#{<<"topics">> => [<<"eq #">>],
<<"permission">> => <<"allow">>,
<<"action">> => <<"all">>}]).
-define(SOURCE3,[#{<<"topics">> => [<<"test/", ?PH_CLIENTID/binary>>],
<<"permission">> => <<"allow">>,
<<"action">> => <<"subscribe">>}]).
-define(SOURCE4,[#{<<"topics">> => [<<"test/", ?PH_USERNAME/binary>>],
<<"permission">> => <<"allow">>,
<<"action">> => <<"publish">>}]).
set_special_configs(_) ->
ok.
%%------------------------------------------------------------------------------
%% Testcases
%%------------------------------------------------------------------------------
t_authz(_) ->
ClientInfo1 = #{clientid => <<"test">>,
username => <<"test">>,
peerhost => {127,0,0,1},
zone => default,
listener => {tcp, default}
},
ClientInfo2 = #{clientid => <<"test_clientid">>,
username => <<"test_username">>,
peerhost => {192,168,0,10},
zone => default,
listener => {tcp, default}
},
ClientInfo3 = #{clientid => <<"test_clientid">>,
username => <<"fake_username">>,
t_topic_rules(_Config) ->
ClientInfo = #{clientid => <<"clientid">>,
username => <<"username">>,
peerhost => {127,0,0,1},
zone => default,
listener => {tcp, default}
},
meck:expect(emqx_resource, query, fun(_, _) -> [] end),
?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, subscribe, <<"#">>)), % nomatch
?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, publish, <<"#">>)), % nomatch
%% No rules
meck:expect(emqx_resource, query, fun(_, _) -> ?SOURCE1 ++ ?SOURCE2 end),
?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, subscribe, <<"+">>)),
?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, publish, <<"+">>)),
ok = setup_samples([]),
ok = setup_config(#{}),
meck:expect(emqx_resource, query, fun(_, _) -> ?SOURCE2 ++ ?SOURCE1 end),
?assertEqual(allow, emqx_access_control:authorize(ClientInfo1, subscribe, <<"#">>)),
?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, subscribe, <<"+">>)),
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{deny, subscribe, <<"#">>},
{deny, subscribe, <<"subs">>},
{deny, publish, <<"pub">>}]),
meck:expect(emqx_resource, query, fun(_, _) -> ?SOURCE3 ++ ?SOURCE4 end),
?assertEqual(allow, emqx_access_control:authorize(
ClientInfo2, subscribe, <<"test/test_clientid">>)),
?assertEqual(deny, emqx_access_control:authorize(
ClientInfo2, publish, <<"test/test_clientid">>)),
?assertEqual(deny, emqx_access_control:authorize(
ClientInfo2, subscribe, <<"test/test_username">>)),
?assertEqual(allow, emqx_access_control:authorize(
ClientInfo2, publish, <<"test/test_username">>)),
?assertEqual(deny, emqx_access_control:authorize(
ClientInfo3, subscribe, <<"test">>)), % nomatch
?assertEqual(deny, emqx_access_control:authorize(
ClientInfo3, publish, <<"test">>)), % nomatch
%% Publish rules
Samples0 = populate_records(
[#{<<"topics">> => [<<"eq testpub1/${username}">>]},
#{<<"topics">> => [<<"testpub2/${clientid}">>, <<"testpub3/#">>]}],
#{<<"permission">> => <<"allow">>,
<<"action">> => <<"publish">>,
<<"username">> => <<"username">>}),
ok = setup_samples(Samples0),
ok = setup_config(#{}),
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{deny, publish, <<"testpub1/username">>},
{allow, publish, <<"testpub1/${username}">>},
{allow, publish, <<"testpub2/clientid">>},
{allow, publish, <<"testpub3/foobar">>},
{deny, publish, <<"testpub2/username">>},
{deny, publish, <<"testpub1/clientid">>},
{deny, subscribe, <<"testpub1/username">>},
{deny, subscribe, <<"testpub2/clientid">>},
{deny, subscribe, <<"testpub3/foobar">>}]),
%% Subscribe rules
Samples1 = populate_records(
[#{<<"topics">> => [<<"eq testsub1/${username}">>]},
#{<<"topics">> => [<<"testsub2/${clientid}">>, <<"testsub3/#">>]}],
#{<<"permission">> => <<"allow">>,
<<"action">> => <<"subscribe">>,
<<"username">> => <<"username">>}),
ok = setup_samples(Samples1),
ok = setup_config(#{}),
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{deny, subscribe, <<"testsub1/username">>},
{allow, subscribe, <<"testsub1/${username}">>},
{allow, subscribe, <<"testsub2/clientid">>},
{allow, subscribe, <<"testsub3/foobar">>},
{allow, subscribe, <<"testsub3/+/foobar">>},
{allow, subscribe, <<"testsub3/#">>},
{deny, subscribe, <<"testsub2/username">>},
{deny, subscribe, <<"testsub1/clientid">>},
{deny, subscribe, <<"testsub4/foobar">>},
{deny, publish, <<"testsub1/username">>},
{deny, publish, <<"testsub2/clientid">>},
{deny, publish, <<"testsub3/foobar">>}]),
%% All rules
Samples2 = populate_records(
[#{<<"topics">> => [<<"eq testall1/${username}">>]},
#{<<"topics">> => [<<"testall2/${clientid}">>, <<"testall3/#">>]}],
#{<<"permission">> => <<"allow">>,
<<"action">> => <<"all">>,
<<"username">> => <<"username">>}),
ok = setup_samples(Samples2),
ok = setup_config(#{}),
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{deny, subscribe, <<"testall1/username">>},
{allow, subscribe, <<"testall1/${username}">>},
{allow, subscribe, <<"testall2/clientid">>},
{allow, subscribe, <<"testall3/foobar">>},
{allow, subscribe, <<"testall3/+/foobar">>},
{allow, subscribe, <<"testall3/#">>},
{deny, publish, <<"testall1/username">>},
{allow, publish, <<"testall1/${username}">>},
{allow, publish, <<"testall2/clientid">>},
{allow, publish, <<"testall3/foobar">>},
{deny, subscribe, <<"testall2/username">>},
{deny, subscribe, <<"testall1/clientid">>},
{deny, subscribe, <<"testall4/foobar">>},
{deny, publish, <<"testall2/username">>},
{deny, publish, <<"testall1/clientid">>},
{deny, publish, <<"testall4/foobar">>}]).
t_complex_selector(_) ->
ClientInfo = #{clientid => clientid,
username => "username",
peerhost => {127,0,0,1},
zone => default,
listener => {tcp, default}
},
Samples = [#{<<"x">> => #{<<"u">> => <<"username">>,
<<"c">> => [#{<<"c">> => <<"clientid">>}],
<<"y">> => 1},
<<"permission">> => <<"allow">>,
<<"action">> => <<"publish">>,
<<"topics">> => [<<"t">>]
}],
ok = setup_samples(Samples),
ok = setup_config(
#{<<"selector">> => #{<<"x">> => #{<<"u">> => <<"${username}">>,
<<"c">> => [#{<<"c">> => <<"${clientid}">>}],
<<"y">> => 1}
}
}),
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{allow, publish, <<"t">>}]).
%%------------------------------------------------------------------------------
%% Helpers
%%------------------------------------------------------------------------------
populate_records(AclRecords, AdditionalData) ->
[maps:merge(Record, AdditionalData) || Record <- AclRecords].
setup_samples(AclRecords) ->
ok = reset_samples(),
{{true, _}, _} = mc_worker_api:insert(?MONGO_CLIENT, <<"acl">>, AclRecords),
ok.
reset_samples() ->
{true, _} = mc_worker_api:delete(?MONGO_CLIENT, <<"acl">>, #{}),
ok.
setup_config(SpecialParams) ->
emqx_authz_test_lib:setup_config(
raw_mongo_authz_config(),
SpecialParams).
raw_mongo_authz_config() ->
#{
<<"type">> => <<"mongodb">>,
<<"enable">> => <<"true">>,
<<"mongo_type">> => <<"single">>,
<<"database">> => <<"mqtt">>,
<<"collection">> => <<"acl">>,
<<"server">> => mongo_server(),
<<"selector">> => #{<<"username">> => <<"${username}">>}
}.
mongo_server() ->
iolist_to_binary(
io_lib:format(
"~s:~b",
[?MONGO_HOST, ?MONGO_PORT])).
mongo_config() ->
[
{database, <<"mqtt">>},
{host, ?MONGO_HOST},
{port, ?MONGO_PORT},
{register, ?MONGO_CLIENT}
].
start_apps(Apps) ->
lists:foreach(fun application:ensure_all_started/1, Apps).
stop_apps(Apps) ->
lists:foreach(fun application:stop/1, Apps).

View File

@ -81,7 +81,7 @@ t_topic_rules(_Config) ->
ok = setup_sample(#{}),
ok = setup_config(#{}),
ok = test_samples(
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{deny, subscribe, <<"#">>},
{deny, subscribe, <<"subs">>},
@ -98,7 +98,7 @@ t_topic_rules(_Config) ->
ok = setup_sample(Sample0),
ok = setup_config(#{}),
ok = test_samples(
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{allow, publish, <<"testpub1/username">>},
{allow, publish, <<"testpub2/clientid">>},
@ -123,7 +123,7 @@ t_topic_rules(_Config) ->
ok = setup_sample(Sample1),
ok = setup_config(#{}),
ok = test_samples(
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{allow, subscribe, <<"testsub1/username">>},
{allow, subscribe, <<"testsub2/clientid">>},
@ -149,7 +149,7 @@ t_topic_rules(_Config) ->
ok = setup_sample(Sample2),
ok = setup_config(#{}),
ok = test_samples(
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{allow, subscribe, <<"testall1/username">>},
{allow, subscribe, <<"testall2/clientid">>},
@ -183,7 +183,7 @@ t_lookups(_Config) ->
ok = setup_sample(ByClientid),
ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${clientid}">>}),
ok = test_samples(
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{allow, subscribe, <<"a">>},
{deny, subscribe, <<"b">>}]),
@ -194,7 +194,7 @@ t_lookups(_Config) ->
ok = setup_sample(ByPeerhost),
ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${peerhost}">>}),
ok = test_samples(
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{allow, subscribe, <<"a">>},
{deny, subscribe, <<"b">>}]),
@ -205,7 +205,7 @@ t_lookups(_Config) ->
ok = setup_sample(ByCN),
ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${cert_common_name}">>}),
ok = test_samples(
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{allow, subscribe, <<"a">>},
{deny, subscribe, <<"b">>}]),
@ -217,7 +217,7 @@ t_lookups(_Config) ->
ok = setup_sample(ByDN),
ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${cert_subject}">>}),
ok = test_samples(
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[{allow, subscribe, <<"a">>},
{deny, subscribe, <<"b">>}]).
@ -255,22 +255,6 @@ t_redis_error(_Config) ->
%% Helpers
%%------------------------------------------------------------------------------
test_samples(ClientInfo, Samples) ->
lists:foreach(
fun({Expected, Action, Topic}) ->
ct:pal(
"client_info: ~p, action: ~p, topic: ~p, expected: ~p",
[ClientInfo, Action, Topic, Expected]),
?assertEqual(
Expected,
emqx_access_control:authorize(
ClientInfo,
Action,
Topic))
end,
Samples).
setup_sample(AuthzData) ->
{ok, _} = q(["FLUSHDB"]),
ok = lists:foreach(

View File

@ -17,6 +17,7 @@
-module(emqx_authz_test_lib).
-include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl").
-compile(nowarn_export_all).
-compile(export_all).
@ -34,6 +35,27 @@ reset_authorizers(Nomatch, ChacheEnabled) ->
<<"sources">> => []}),
ok.
setup_config(BaseConfig, SpecialParams) ->
ok = reset_authorizers(deny, false),
Config = maps:merge(BaseConfig, SpecialParams),
{ok, _} = emqx_authz:update(?CMD_REPLACE, [Config]),
ok.
test_samples(ClientInfo, Samples) ->
lists:foreach(
fun({Expected, Action, Topic}) ->
ct:pal(
"client_info: ~p, action: ~p, topic: ~p, expected: ~p",
[ClientInfo, Action, Topic, Expected]),
?assertEqual(
Expected,
emqx_access_control:authorize(
ClientInfo,
Action,
Topic))
end,
Samples).
is_tcp_server_available(Host, Port) ->
case gen_tcp:connect(Host, Port, [], ?DEFAULT_CHECK_AVAIL_TIMEOUT) of
{ok, Socket} ->