feat: bootstrap dashboard users from dashboard.bootstrap_users_file

This commit is contained in:
zhongwencool 2022-10-28 09:31:45 +08:00
parent dd95a26270
commit 04c0caefac
6 changed files with 106 additions and 11 deletions

View File

@ -17,6 +17,9 @@
- Enhanced log security in ACL modules, sensitive data will be obscured. [#9242](https://github.com/emqx/emqx/pull/9242). - Enhanced log security in ACL modules, sensitive data will be obscured. [#9242](https://github.com/emqx/emqx/pull/9242).
- Add `dashboard.bootstrap_users_file` configuration to bulk import default user&password when EMQX first starts.
## Bug fixes ## Bug fixes
- Fix that after uploading a backup file with an UTF8 filename, HTTP API `GET /data/export` fails with status code 500 [#9224](https://github.com/emqx/emqx/pull/9224). - Fix that after uploading a backup file with an UTF8 filename, HTTP API `GET /data/export` fails with status code 500 [#9224](https://github.com/emqx/emqx/pull/9224).

View File

@ -17,6 +17,9 @@
- 增强 ACL 模块中的日志安全性,敏感数据将被模糊化。[#9242](https://github.com/emqx/emqx/pull/9242)。 - 增强 ACL 模块中的日志安全性,敏感数据将被模糊化。[#9242](https://github.com/emqx/emqx/pull/9242)。
- 增加 `dashboard.bootstrap_users_file` 配置可以在EMQX第一次启动时批量导入默认的用户/密码。
## 修复 ## 修复
- 修复若上传的备份文件名中包含 UTF8 字符,`GET /data/export` HTTP 接口返回 500 错误 [#9224](https://github.com/emqx/emqx/pull/9224)。 - 修复若上传的备份文件名中包含 UTF8 字符,`GET /data/export` HTTP 接口返回 500 错误 [#9224](https://github.com/emqx/emqx/pull/9224)。

View File

@ -17,6 +17,15 @@ dashboard.default_user.login = admin
## Value: String ## Value: String
dashboard.default_user.password = public dashboard.default_user.password = public
## Initialize users file
## Is used to add an administrative user to Dashboard when emqx is first launched,
## the format is:
## ```
##username1:password1
##username2:password2
## ```
dashboard.bootstrap_users_file = {{ platform_etc_dir }}/bootstrap_users.txt
##-------------------------------------------------------------------- ##--------------------------------------------------------------------
## HTTP Listener ## HTTP Listener

View File

@ -10,6 +10,11 @@
{override_env, "ADMIN_PASSWORD"} {override_env, "ADMIN_PASSWORD"}
]}. ]}.
{mapping, "dashboard.bootstrap_users_file", "emqx_dashboard.bootstrap_users_file", [
{datatype, string},
hidden
]}.
{mapping, "dashboard.listener.http", "emqx_dashboard.listeners", [ {mapping, "dashboard.listener.http", "emqx_dashboard.listeners", [
{datatype, [integer, ip]} {datatype, [integer, ip]}
]}. ]}.

View File

@ -1,6 +1,6 @@
{application, emqx_dashboard, {application, emqx_dashboard,
[{description, "EMQ X Web Dashboard"}, [{description, "EMQ X Web Dashboard"},
{vsn, "4.3.18"}, % strict semver, bump manually! {vsn, "4.3.19"}, % strict semver, bump manually!
{modules, []}, {modules, []},
{registered, [emqx_dashboard_sup]}, {registered, [emqx_dashboard_sup]},
{applications, [kernel,stdlib,mnesia,minirest]}, {applications, [kernel,stdlib,mnesia,minirest]},

View File

@ -23,6 +23,7 @@
-include("emqx_dashboard.hrl"). -include("emqx_dashboard.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-define(DEFAULT_PASSWORD, <<"public">>). -define(DEFAULT_PASSWORD, <<"public">>).
-define(BOOTSTRAP_USER_TAG, <<"bootstrap user">>).
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}). -copy_mnesia({mnesia, [copy]}).
@ -43,6 +44,7 @@
, change_password/3 , change_password/3
, all_users/0 , all_users/0
, check/2 , check/2
, add_bootstrap_users/0
]). ]).
%% gen_server Function Exports %% gen_server Function Exports
@ -195,6 +197,75 @@ check(Username, Password) ->
{error, <<"Username/Password error">>} {error, <<"Username/Password error">>}
end. end.
add_bootstrap_users() ->
Bootstrap = application:get_env(emqx_dashboard, bootstrap_users_file, undefined),
Size = mnesia:table_info(mqtt_admin, size),
add_bootstrap_users(Bootstrap, Size).
add_bootstrap_users(undefined, _) -> ok;
add_bootstrap_users(_File, Size)when Size > 0 -> ok;
add_bootstrap_users(File, 0) ->
case file:open(File, [read, binary]) of
{ok, Dev} ->
{ok, MP} = re:compile(<<"(\.+):(\.+$)">>, [ungreedy]),
case add_bootstrap_users(File, Dev, MP) of
ok -> ok;
Error ->
%% if failed add bootstrap users, we should clear all bootstrap users
mnesia:transaction(fun clear_bootstrap_users/0, []),
Error
end;
{error, Reason} = Error ->
?LOG(error,
"failed to open the dashboard bootstrap users file(~s) for ~p",
[File, Reason]
),
Error
end.
add_bootstrap_users(File, Dev, MP) ->
try
add_bootstrap_user(File, Dev, MP, 1)
catch
throw:Error -> {error, Error};
Type:Reason:Stacktrace ->
{error, {Type, Reason, Stacktrace}}
after
file:close(Dev)
end.
add_bootstrap_user(File, Dev, MP, Line) ->
case file:read_line(Dev) of
{ok, Bin} ->
case re:run(Bin, MP, [global, {capture, all_but_first, binary}]) of
{match, [[Username, Password]]} ->
case add_user(Username, Password, ?BOOTSTRAP_USER_TAG) of
ok ->
add_bootstrap_user(File, Dev, MP, Line + 1);
Reason ->
throw(#{file => File, line => Line, content => Bin, reason => Reason})
end;
_ ->
?LOG(error,
"failed to bootstrap users file(~s) for Line(~w): ~ts",
[File, Line, Bin]
),
throw(#{file => File, line => Line, content => Bin, reason => "invalid format"})
end;
eof ->
ok;
Error ->
throw(#{file => File, line => Line, reason => Error})
end.
clear_bootstrap_users() ->
FoldFun =
fun(#mqtt_admin{tags = ?BOOTSTRAP_USER_TAG} = User, Acc) ->
mnesia:delete_object(User), Acc;
(_, Acc) -> Acc
end,
mnesia:foldl(FoldFun, ok, mqtt_admin).
bad_login_penalty() -> bad_login_penalty() ->
timer:sleep(2000), timer:sleep(2000),
ok. ok.
@ -207,6 +278,8 @@ is_valid_pwd(<<Salt:4/binary, Hash/binary>>, Password) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
case add_bootstrap_users() of
ok ->
case binenv(default_user_username) of case binenv(default_user_username) of
<<>> -> ok; <<>> -> ok;
UserName -> UserName ->
@ -216,7 +289,9 @@ init([]) ->
ok = ensure_default_user_passwd_hashed_in_pt(PasswordHash), ok = ensure_default_user_passwd_hashed_in_pt(PasswordHash),
ok = maybe_warn_default_pwd() ok = maybe_warn_default_pwd()
end, end,
{ok, state}. {ok, state};
Error -> {stop, Error}
end.
handle_call(_Req, _From, State) -> handle_call(_Req, _From, State) ->
{reply, error, State}. {reply, error, State}.