From 830cdffe16c412aa487d585d69549698c277932f Mon Sep 17 00:00:00 2001 From: k32 <10274441+k32@users.noreply.github.com> Date: Mon, 29 Mar 2021 14:48:29 +0200 Subject: [PATCH] fix(emqx_auth_mnesia): add missing combinations of permissions Allow to define different access for pub and sub actions --- apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl | 1 + .../src/emqx_acl_mnesia_cli.erl | 30 +++++-- .../test/emqx_acl_mnesia_SUITE.erl | 2 + .../src/emqx_mgmt_data_backup.erl | 21 ++++- .../test/emqx_auth_mnesia_migration_SUITE.erl | 81 +++++++++++++++++++ .../v4.1.json | 48 +++++++++++ .../v4.2.json | 53 ++++++++++++ 7 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE.erl create mode 100644 apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/v4.1.json create mode 100644 apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/v4.2.json diff --git a/apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl index c657e54a0..ec8670a83 100644 --- a/apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl +++ b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl @@ -31,6 +31,7 @@ init() -> ok = ekka_mnesia:create_table(emqx_acl, [ + {type, bag}, {disc_copies, [node()]}, {attributes, record_info(fields, emqx_acl)}, {storage_properties, [{ets, [{read_concurrency, true}]}]}]), diff --git a/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl index ef852d04d..9d69402e5 100644 --- a/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl +++ b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl @@ -39,13 +39,19 @@ -spec(add_acl(login() | all, emqx_topic:topic(), pub | sub | pubsub, allow | deny) -> ok | {error, any()}). add_acl(Login, Topic, Action, Access) -> - Acls = #?TABLE{ - filter = {Login, Topic}, - action = Action, - access = Access, - created_at = erlang:system_time(millisecond) - }, - ret(mnesia:transaction(fun mnesia:write/1, [Acls])). + Filter = {Login, Topic}, + Acl = #?TABLE{ + filter = Filter, + action = Action, + access = Access, + created_at = erlang:system_time(millisecond) + }, + ret(mnesia:transaction( + fun() -> + OldRecords = mnesia:wread({?TABLE, Filter}), + maybe_delete_shadowed_records(Action, OldRecords), + mnesia:write(Acl) + end)). %% @doc Lookup acl by login -spec(lookup_acl(login() | all) -> list()). @@ -233,3 +239,13 @@ print_acl({all, Topic, Action, Access, _}) -> "Acl($all topic = ~p action = ~p access = ~p)~n", [Topic, Action, Access] ). + +maybe_delete_shadowed_records(_, []) -> + ok; +maybe_delete_shadowed_records(Action1, [Rec = #emqx_acl{action = Action2} | Rest]) -> + if Action1 =:= Action2 orelse Action1 =:= pubsub -> + ok = mnesia:delete_object(Rec); + true -> + ok + end, + maybe_delete_shadowed_records(Action1, Rest). diff --git a/apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl b/apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl index f900fb91a..07fbf4211 100644 --- a/apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl +++ b/apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl @@ -124,6 +124,7 @@ t_acl_cli(_Config) -> ?assertEqual(0, length(emqx_acl_mnesia_cli:cli(["list"]))), + emqx_acl_mnesia_cli:cli(["add", "clientid", "test_clientid", "topic/A", "pub", "deny"]), emqx_acl_mnesia_cli:cli(["add", "clientid", "test_clientid", "topic/A", "pub", "allow"]), R1 = emqx_ctl:format("Acl(clientid = ~p topic = ~p action = ~p access = ~p)~n", [<<"test_clientid">>, <<"topic/A">>, pub, allow]), @@ -136,6 +137,7 @@ t_acl_cli(_Config) -> ?assertEqual([R2], emqx_acl_mnesia_cli:cli(["show", "username", "test_username"])), ?assertEqual([R2], emqx_acl_mnesia_cli:cli(["list", "username"])), + emqx_acl_mnesia_cli:cli(["add", "_all", "#", "pub", "allow"]), emqx_acl_mnesia_cli:cli(["add", "_all", "#", "pubsub", "deny"]), ?assertMatch(["Acl($all topic = <<\"#\">> action = pubsub access = deny)\n"], emqx_acl_mnesia_cli:cli(["list", "_all"]) diff --git a/apps/emqx_management/src/emqx_mgmt_data_backup.erl b/apps/emqx_management/src/emqx_mgmt_data_backup.erl index 76d16e89f..a455e18c8 100644 --- a/apps/emqx_management/src/emqx_mgmt_data_backup.erl +++ b/apps/emqx_management/src/emqx_mgmt_data_backup.erl @@ -441,9 +441,11 @@ import_acl_mnesia(Acls, _) -> do_import_acl_mnesia(Acls). -else. import_auth_mnesia(Auths, FromVersion) when FromVersion =:= "4.0" orelse - FromVersion =:= "4.1" orelse - FromVersion =:= "4.2" -> + FromVersion =:= "4.1" -> do_import_auth_mnesia_by_old_data(Auths); +import_auth_mnesia(Auths, "4.2") -> + %% 4.2 contains a bug where password is not base64-encoded + do_import_auth_mnesia_4_2(Auths); import_auth_mnesia(Auths, _) -> do_import_auth_mnesia(Auths). @@ -454,6 +456,17 @@ import_acl_mnesia(Acls, FromVersion) when FromVersion =:= "4.0" orelse import_acl_mnesia(Acls, _) -> do_import_acl_mnesia(Acls). + +do_import_auth_mnesia_4_2(Auths) -> + case ets:info(emqx_user) of + undefined -> ok; + _ -> + CreatedAt = erlang:system_time(millisecond), + lists:foreach(fun(#{<<"login">> := Login, + <<"password">> := Password}) -> + mnesia:dirty_write({emqx_user, {username, Login}, Password, CreatedAt}) + end, Auths) + end. -endif. do_import_auth_mnesia_by_old_data(Auths) -> @@ -466,6 +479,8 @@ do_import_auth_mnesia_by_old_data(Auths) -> mnesia:dirty_write({emqx_user, {username, Login}, base64:decode(Password), CreatedAt}) end, Auths) end. + + do_import_auth_mnesia(Auths) -> case ets:info(emqx_user) of undefined -> ok; @@ -648,4 +663,4 @@ covert_empty_headers(Headers) -> [] -> #{}; Other -> Other end. --endif. \ No newline at end of file +-endif. diff --git a/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE.erl b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE.erl new file mode 100644 index 000000000..42ab0d907 --- /dev/null +++ b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE.erl @@ -0,0 +1,81 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 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_auth_mnesia_migration_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +-include_lib("emqx/include/emqx.hrl"). +-include_lib("emqx/include/emqx_mqtt.hrl"). +-include_lib("emqx_auth_mnesia/include/emqx_auth_mnesia.hrl"). + +all() -> + [t_import_4_2, t_import_4_1]. + +init_per_suite(Config) -> + emqx_ct_helpers:start_apps([emqx_management, emqx_dashboard, emqx_auth_mnesia]), + ekka_mnesia:start(), + emqx_mgmt_auth:mnesia(boot), + mnesia:clear_table(emqx_acl), + mnesia:clear_table(emqx_user), + Config. + +end_per_suite(_Config) -> + emqx_ct_helpers:stop_apps([emqx_modules, emqx_management, emqx_dashboard, emqx_management, emqx_auth_mnesia]), + ekka_mnesia:ensure_stopped(). + +init_per_testcase(_, Config) -> + Config. + +end_per_testcase(_, _Config) -> + mnesia:clear_table(emqx_acl), + mnesia:clear_table(emqx_user), + ok. + +t_import_4_2(Config) -> + test_import(Config, "v4.2.json"). + +t_import_4_1(Config) -> + test_import(Config, "v4.1.json"). + +test_import(Config, File) -> + Filename = filename:join(proplists:get_value(data_dir, Config), File), + ?assertMatch(ok, emqx_mgmt_data_backup:import(Filename)), + Records = lists:sort(ets:tab2list(emqx_acl)), + %% Check importing of records related to emqx_auth_mnesia + ?assertMatch([#emqx_acl{ + filter = {{username,<<"emqx_c">>}, <<"Topic/A">>}, + action = pub, + access = allow + }, + #emqx_acl{ + filter = {{username,<<"emqx_c">>}, <<"Topic/A">>}, + action = sub, + access = allow + }], + lists:sort(Records)), + ?assertMatch([#emqx_user{ + login = {username, <<"emqx_c">>} + }], ets:tab2list(emqx_user)), + Req = #{clientid => "blah", + username => <<"emqx_c">>, + password => "emqx_p" + }, + ?assertMatch({stop, #{auth_result := success}}, + emqx_auth_mnesia:check(Req, #{}, #{hash_type => sha256})). diff --git a/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/v4.1.json b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/v4.1.json new file mode 100644 index 000000000..04a7a273f --- /dev/null +++ b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/v4.1.json @@ -0,0 +1,48 @@ +{ + "acl_mnesia": [ + { + "action": "sub", + "allow": true, + "login": "emqx_c", + "topic": "Topic/A" + }, + { + "action": "pub", + "allow": true, + "login": "emqx_c", + "topic": "Topic/A" + } + ], + "apps": [ + { + "desc": "Application user", + "expired": "undefined", + "id": "admin", + "name": "Default", + "secret": "public", + "status": true + } + ], + "auth_clientid": [], + "auth_mnesia": [ + { + "is_superuser": false, + "login": "emqx_c", + "password": "Y2ViNWU5MTdmNzkzMGFlOGYwZGMzY2ViNDk2YTQyOGY3ZTY0NDczNmVlYmNhMzZhMmI4ZjZiYmFjNzU2MTcxYQ==" + } + ], + "auth_username": [], + "blacklist": [], + "date": "2021-03-30 09:11:29", + "resources": [], + "rules": [], + "schemas": [], + "users": [ + { + "password": "t89PhgOb15rSCdpxm7Obp7QGcyY=", + "tags": "administrator", + "username": "admin" + } + ], + "version": "4.1" +} diff --git a/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/v4.2.json b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/v4.2.json new file mode 100644 index 000000000..57958aa58 --- /dev/null +++ b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/v4.2.json @@ -0,0 +1,53 @@ +{ + "schemas": [], + "acl_mnesia": [ + { + "allow": true, + "action": "sub", + "topic": "Topic/A", + "login": "emqx_c" + }, + { + "allow": true, + "action": "pub", + "topic": "Topic/A", + "login": "emqx_c" + } + ], + "auth_mnesia": [ + { + "is_superuser": false, + "password": "ceb5e917f7930ae8f0dc3ceb496a428f7e644736eebca36a2b8f6bbac756171a", + "login": "emqx_c" + } + ], + "auth_username": [], + "auth_clientid": [], + "users": [ + { + "tags": "viewer", + "password": "oVqjR1wOi2u4DtsuXNctYt6+SKE=", + "username": "test" + }, + { + "tags": "administrator", + "password": "9SO4rEEZ6rNwA4vAwp3cnXgQsAM=", + "username": "admin" + } + ], + "apps": [ + { + "expired": "undefined", + "status": true, + "desc": "Application user", + "name": "Default", + "secret": "public", + "id": "admin" + } + ], + "blacklist": [], + "resources": [], + "rules": [], + "date": "2021-03-26 09:51:38", + "version": "4.2" +}