diff --git a/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.erl b/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.erl index cd38540dd..7b8ffef02 100644 --- a/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.erl +++ b/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.erl @@ -59,12 +59,8 @@ valid_role(Type, Role) -> %% =================================================================== check_rbac(?ROLE_SUPERUSER, _, _, _) -> true; -%%check_rbac(?ROLE_API_SUPERUSER, _, _, _) -> -%% true; check_rbac(?ROLE_VIEWER, <<"GET">>, _, _) -> true; -%%check_rbac(?ROLE_API_VIEWER, <<"GET">>, _, _) -> -%% true; check_rbac(?ROLE_API_PUBLISHER, <<"POST">>, <<"/publish">>, _) -> true; check_rbac(?ROLE_API_PUBLISHER, <<"POST">>, <<"/publish/bulk">>, _) -> diff --git a/apps/emqx_management/src/emqx_mgmt_auth.erl b/apps/emqx_management/src/emqx_mgmt_auth.erl index bdb5d97fa..fa48ccfea 100644 --- a/apps/emqx_management/src/emqx_mgmt_auth.erl +++ b/apps/emqx_management/src/emqx_mgmt_auth.erl @@ -38,7 +38,7 @@ -export([authorize/4]). -export([post_config_update/5]). --export([backup_tables/0]). +-export([backup_tables/0, validate_mnesia_backup/1]). %% Internal exports (RPC) -export([ @@ -82,6 +82,22 @@ mnesia(boot) -> backup_tables() -> [?APP]. +validate_mnesia_backup({schema, _Tab, CreateList} = Schema) -> + case emqx_mgmt_data_backup:default_validate_mnesia_backup(Schema) of + ok -> + ok; + _ -> + case proplists:get_value(attributes, CreateList) of + %% Since v5.4.0 the `desc` has changed to `extra` + [name, api_key, api_secret_hash, enable, desc, expired_at, created_at] -> + ok; + Fields -> + {error, {unknow_fields, Fields}} + end + end; +validate_mnesia_backup(_Other) -> + ok. + post_config_update([api_key], _Req, NewConf, _OldConf, _AppEnvs) -> #{bootstrap_file := File} = NewConf, case init_bootstrap_file(File) of diff --git a/apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl index 8243b18ff..bebf8e338 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl @@ -39,7 +39,7 @@ groups() -> [ {parallel, [parallel], [t_create, t_update, t_delete, t_authorize, t_create_unexpired_app]}, {parallel, [parallel], ?EE_CASES}, - {sequence, [], [t_bootstrap_file, t_create_failed]} + {sequence, [], [t_bootstrap_file, t_bootstrap_file_with_role, t_create_failed]} ]. init_per_suite(Config) -> @@ -86,6 +86,92 @@ t_bootstrap_file(_) -> update_file(<<>>), ok. +-if(?EMQX_RELEASE_EDITION == ee). +t_bootstrap_file_with_role(_) -> + Search = fun(Name) -> + lists:search( + fun(#{api_key := AppName}) -> + AppName =:= Name + end, + emqx_mgmt_auth:list() + ) + end, + + Bin = <<"role-1:role-1:viewer\nrole-2:role-2:administrator\nrole-3:role-3">>, + File = "./bootstrap_api_keys.txt", + ok = file:write_file(File, Bin), + update_file(File), + + ?assertMatch( + {value, #{api_key := <<"role-1">>, role := <<"viewer">>}}, + Search(<<"role-1">>) + ), + + ?assertMatch( + {value, #{api_key := <<"role-2">>, role := <<"administrator">>}}, + Search(<<"role-2">>) + ), + + ?assertMatch( + {value, #{api_key := <<"role-3">>, role := <<"administrator">>}}, + Search(<<"role-3">>) + ), + + %% bad role + BadBin = <<"role-4:secret-11:bad\n">>, + ok = file:write_file(File, BadBin), + update_file(File), + ?assertEqual( + false, + Search(<<"role-4">>) + ), + ok. +-else. +t_bootstrap_file_with_role(_) -> + Search = fun(Name) -> + lists:search( + fun(#{api_key := AppName}) -> + AppName =:= Name + end, + emqx_mgmt_auth:list() + ) + end, + + Bin = <<"role-1:role-1:administrator\nrole-2:role-2">>, + File = "./bootstrap_api_keys.txt", + ok = file:write_file(File, Bin), + update_file(File), + + ?assertMatch( + {value, #{api_key := <<"role-1">>, role := <<"administrator">>}}, + Search(<<"role-1">>) + ), + + ?assertMatch( + {value, #{api_key := <<"role-2">>, role := <<"administrator">>}}, + Search(<<"role-2">>) + ), + + %% only administrator + OtherRoleBin = <<"role-3:role-3:viewer\n">>, + ok = file:write_file(File, OtherRoleBin), + update_file(File), + ?assertEqual( + false, + Search(<<"role-3">>) + ), + + %% bad role + BadBin = <<"role-4:secret-11:bad\n">>, + ok = file:write_file(File, BadBin), + update_file(File), + ?assertEqual( + false, + Search(<<"role-4">>) + ), + ok. +-endif. + auth_authorize(Path, Key, Secret) -> FakePath = erlang:list_to_binary(emqx_dashboard_swagger:relative_uri("/fake")), FakeReq = #{method => <<"GET">>, path => FakePath}, diff --git a/changes/ee/feat-11811.en.md b/changes/ee/feat-11811.en.md new file mode 100644 index 000000000..91b9e90aa --- /dev/null +++ b/changes/ee/feat-11811.en.md @@ -0,0 +1,5 @@ +Improve the format for the REST API key bootstrap file to support initialize key with a role. + +The new form is:`api_key:api_secret:role`. + +`role` is optional and its default value is `administrator`. diff --git a/rel/i18n/emqx_mgmt_api_key_schema.hocon b/rel/i18n/emqx_mgmt_api_key_schema.hocon index 811ab8a98..e72682747 100644 --- a/rel/i18n/emqx_mgmt_api_key_schema.hocon +++ b/rel/i18n/emqx_mgmt_api_key_schema.hocon @@ -9,8 +9,11 @@ api_key.label: bootstrap_file.desc: """The bootstrap file provides API keys for EMQX. EMQX will load these keys on startup to authorize API requests. -It contains key-value pairs in the format:`api_key:api_secret`. -Each line specifies an API key and its associated secret.""" +It contains colon-separated values in the format: `api_key:api_secret:role`. +Each line specifies an API key and its associated secret, and the role of this key. +The 'role' part should be the pre-defined access scope group name, +for example, `administrator` or `viewer`. +The 'role' is introduced in 5.4, to be backward compatible, if it is missing, the key is implicitly granted `administrator` role.""" bootstrap_file.label: """Initialize api_key file."""