feat: sha3_256 dashboard account's password (#6084)

This commit is contained in:
zhongwencool 2021-11-09 15:41:28 +08:00 committed by GitHub
parent 0f8ad29e91
commit eea789451b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 39 deletions

View File

@ -13,8 +13,9 @@
%% See the License for the specific language governing permissions and %% See the License for the specific language governing permissions and
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(ADMIN, emqx_admin).
-record(emqx_admin, { -record(?ADMIN, {
username :: binary(), username :: binary(),
pwdhash :: binary(), pwdhash :: binary(),
tags :: list() | binary(), tags :: list() | binary(),
@ -22,17 +23,16 @@
extra = [] :: term() %% not used so far, for future extension extra = [] :: term() %% not used so far, for future extension
}). }).
-define(ADMIN, emqx_admin).
-record(emqx_admin_jwt, { -define(ADMIN_JWT, emqx_admin_jwt).
-record(?ADMIN_JWT, {
token :: binary(), token :: binary(),
username :: binary(), username :: binary(),
exptime :: integer(), exptime :: integer(),
extra = [] :: term() %% not used so far, fur future extension extra = [] :: term() %% not used so far, fur future extension
}). }).
-define(ADMIN_JWT, emqx_admin_jwt).
-define(EMPTY_KEY(Key), ((Key == undefined) orelse (Key == <<>>))). -define(EMPTY_KEY(Key), ((Key == undefined) orelse (Key == <<>>))).
-define(DASHBOARD_SHARD, emqx_dashboard_shard). -define(DASHBOARD_SHARD, emqx_dashboard_shard).

View File

@ -167,7 +167,7 @@ check(_, undefined) ->
check(Username, Password) -> check(Username, Password) ->
case lookup_user(Username) of case lookup_user(Username) of
[#?ADMIN{pwdhash = <<Salt:4/binary, Hash/binary>>}] -> [#?ADMIN{pwdhash = <<Salt:4/binary, Hash/binary>>}] ->
case Hash =:= md5_hash(Salt, Password) of case Hash =:= sha3_hash(Salt, Password) of
true -> ok; true -> ok;
false -> {error, <<"BAD_USERNAME_OR_PASSWORD">>} false -> {error, <<"BAD_USERNAME_OR_PASSWORD">>}
end; end;
@ -201,16 +201,11 @@ destroy_token_by_username(Username, Token) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
hash(Password) -> hash(Password) ->
SaltBin = salt(), SaltBin = emqx_dashboard_token:salt(),
<<SaltBin/binary, (md5_hash(SaltBin, Password))/binary>>. <<SaltBin/binary, (sha3_hash(SaltBin, Password))/binary>>.
md5_hash(SaltBin, Password) -> sha3_hash(SaltBin, Password) ->
erlang:md5(<<SaltBin/binary, Password/binary>>). crypto:hash('sha3_256', <<SaltBin/binary, Password/binary>>).
salt() ->
_ = emqx_misc:rand_seed(),
Salt = rand:uniform(16#ffffffff),
<<Salt:32>>.
add_default_user() -> add_default_user() ->
add_default_user(binenv(default_username), binenv(default_password)). add_default_user(binenv(default_username), binenv(default_password)).

View File

@ -40,7 +40,7 @@
%% gen server part %% gen server part
-behaviour(gen_server). -behaviour(gen_server).
-export([start_link/0]). -export([start_link/0, salt/0]).
-export([ init/1 -export([ init/1
, handle_call/3 , handle_call/3
@ -75,6 +75,12 @@ destroy(Token) when is_binary(Token)->
destroy_by_username(Username) -> destroy_by_username(Username) ->
do_destroy_by_username(Username). do_destroy_by_username(Username).
%% @doc create 4 bytes salt.
-spec(salt() -> binary()).
salt() ->
<<X:16/big-unsigned-integer>> = crypto:strong_rand_bytes(2),
iolist_to_binary(io_lib:format("~4.16.0b", [X])).
mnesia(boot) -> mnesia(boot) ->
ok = mria:create_table(?TAB, [ ok = mria:create_table(?TAB, [
{type, set}, {type, set},
@ -110,7 +116,9 @@ do_verify(Token)->
case ExpTime > erlang:system_time(millisecond) of case ExpTime > erlang:system_time(millisecond) of
true -> true ->
NewJWT = JWT#?ADMIN_JWT{exptime = jwt_expiration_time()}, NewJWT = JWT#?ADMIN_JWT{exptime = jwt_expiration_time()},
{atomic, Res} = mria:transaction(?DASHBOARD_SHARD, fun mnesia:write/1, [NewJWT]), {atomic, Res} = mria:transaction(?DASHBOARD_SHARD,
fun mnesia:write/1,
[NewJWT]),
Res; Res;
_ -> _ ->
{error, token_timeout} {error, token_timeout}
@ -145,7 +153,7 @@ lookup_by_username(Username) ->
List. List.
jwk(Username, Password, Salt) -> jwk(Username, Password, Salt) ->
Key = erlang:md5(<<Salt/binary, Username/binary, Password/binary>>), Key = crypto:hash(md5, <<Salt/binary, Username/binary, Password/binary>>),
#{ #{
<<"kty">> => <<"oct">>, <<"kty">> => <<"oct">>,
<<"k">> => jose_base64url:encode(Key) <<"k">> => jose_base64url:encode(Key)
@ -157,11 +165,6 @@ jwt_expiration_time() ->
token_ttl() -> token_ttl() ->
emqx_conf:get([emqx_dashboard, token_expired_time], ?EXPTIME). emqx_conf:get([emqx_dashboard, token_expired_time], ?EXPTIME).
salt() ->
_ = emqx_misc:rand_seed(),
Salt = rand:uniform(16#ffffffff),
<<Salt:32>>.
format(Token, Username, ExpTime) -> format(Token, Username, ExpTime) ->
#?ADMIN_JWT{ #?ADMIN_JWT{
token = Token, token = Token,

View File

@ -37,7 +37,18 @@
-define(BASE_PATH, "api"). -define(BASE_PATH, "api").
-define(OVERVIEWS, ['alarms/activated', 'alarms/deactivated', banned, brokers, stats, metrics, listeners, clients, subscriptions, routes, plugins]). -define(OVERVIEWS, ['alarms/activated',
'alarms/deactivated',
banned,
brokers,
stats,
metrics,
listeners,
clients,
subscriptions,
routes,
plugins
]).
all() -> all() ->
%% TODO: V5 API %% TODO: V5 API
@ -45,7 +56,8 @@ all() ->
[t_cli, t_lookup_by_username_jwt, t_clean_expired_jwt]. [t_cli, t_lookup_by_username_jwt, t_clean_expired_jwt].
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_common_test_helpers:start_apps([emqx_management, emqx_dashboard],fun set_special_configs/1), emqx_common_test_helpers:start_apps([emqx_management, emqx_dashboard],
fun set_special_configs/1),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
@ -53,7 +65,8 @@ end_per_suite(_Config) ->
mria:stop(). mria:stop().
set_special_configs(emqx_management) -> set_special_configs(emqx_management) ->
emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}], Listeners = [#{protocol => http, port => 8081}],
emqx_config:put([emqx_management], #{listeners => Listeners,
applications =>[#{id => "admin", secret => "public"}]}), applications =>[#{id => "admin", secret => "public"}]}),
ok; ok;
set_special_configs(_) -> set_special_configs(_) ->
@ -62,27 +75,33 @@ set_special_configs(_) ->
t_overview(_) -> t_overview(_) ->
mnesia:clear_table(?ADMIN), mnesia:clear_table(?ADMIN),
emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, <<"tag">>), emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, <<"tag">>),
[?assert(request_dashboard(get, api_path(erlang:atom_to_list(Overview)), auth_header_()))|| Overview <- ?OVERVIEWS]. [?assert(request_dashboard(get, api_path(erlang:atom_to_list(Overview)),
auth_header_()))|| Overview <- ?OVERVIEWS].
t_admins_add_delete(_) -> t_admins_add_delete(_) ->
mnesia:clear_table(?ADMIN), mnesia:clear_table(?ADMIN),
ok = emqx_dashboard_admin:add_user(<<"username">>, <<"password">>, <<"tag">>), Tag = <<"tag">>,
ok = emqx_dashboard_admin:add_user(<<"username1">>, <<"password1">>, <<"tag1">>), ok = emqx_dashboard_admin:add_user(<<"username">>, <<"password">>, Tag),
ok = emqx_dashboard_admin:add_user(<<"username1">>, <<"password1">>, Tag),
Admins = emqx_dashboard_admin:all_users(), Admins = emqx_dashboard_admin:all_users(),
?assertEqual(2, length(Admins)), ?assertEqual(2, length(Admins)),
ok = emqx_dashboard_admin:remove_user(<<"username1">>), ok = emqx_dashboard_admin:remove_user(<<"username1">>),
Users = emqx_dashboard_admin:all_users(), Users = emqx_dashboard_admin:all_users(),
?assertEqual(1, length(Users)), ?assertEqual(1, length(Users)),
ok = emqx_dashboard_admin:change_password(<<"username">>, <<"password">>, <<"pwd">>), ok = emqx_dashboard_admin:change_password(<<"username">>,
<<"password">>,
<<"pwd">>),
timer:sleep(10), timer:sleep(10),
?assert(request_dashboard(get, api_path("brokers"), auth_header_("username", "pwd"))), Header = auth_header_("username", "pwd"),
?assert(request_dashboard(get, api_path("brokers"), Header)),
ok = emqx_dashboard_admin:remove_user(<<"username">>), ok = emqx_dashboard_admin:remove_user(<<"username">>),
?assertNotEqual(true, request_dashboard(get, api_path("brokers"), auth_header_("username", "pwd"))). ?assertNotEqual(true, request_dashboard(get, api_path("brokers"), Header)).
t_rest_api(_Config) -> t_rest_api(_Config) ->
mnesia:clear_table(?ADMIN), mnesia:clear_table(?ADMIN),
emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, <<"administrator">>), Tag = <<"administrator">>,
emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, Tag),
{ok, Res0} = http_get("users"), {ok, Res0} = http_get("users"),
?assertEqual([#{<<"username">> => <<"admin">>, ?assertEqual([#{<<"username">> => <<"admin">>,
@ -93,11 +112,15 @@ t_rest_api(_Config) ->
end, end,
[AssertSuccess(R) [AssertSuccess(R)
|| R <- [ http_put("users/admin", #{<<"tags">> => <<"a_new_tag">>}) || R <- [ http_put("users/admin", #{<<"tags">> => <<"a_new_tag">>})
, http_post("users", #{<<"username">> => <<"usera">>, <<"password">> => <<"passwd">>}) , http_post("users", #{<<"username">> => <<"usera">>,
, http_post("auth", #{<<"username">> => <<"usera">>, <<"password">> => <<"passwd">>}) <<"password">> => <<"passwd">>})
, http_post("auth", #{<<"username">> => <<"usera">>,
<<"password">> => <<"passwd">>})
, http_delete("users/usera") , http_delete("users/usera")
, http_put("users/admin/change_pwd", #{<<"old_pwd">> => <<"public">>, <<"new_pwd">> => <<"newpwd">>}) , http_put("users/admin/change_pwd", #{<<"old_pwd">> => <<"public">>,
, http_post("auth", #{<<"username">> => <<"admin">>, <<"password">> => <<"newpwd">>}) <<"new_pwd">> => <<"newpwd">>})
, http_post("auth", #{<<"username">> => <<"admin">>,
<<"password">> => <<"newpwd">>})
]], ]],
ok. ok.
@ -106,11 +129,11 @@ t_cli(_Config) ->
emqx_dashboard_cli:admins(["add", "username", "password"]), emqx_dashboard_cli:admins(["add", "username", "password"]),
[#?ADMIN{ username = <<"username">>, pwdhash = <<Salt:4/binary, Hash/binary>>}] = [#?ADMIN{ username = <<"username">>, pwdhash = <<Salt:4/binary, Hash/binary>>}] =
emqx_dashboard_admin:lookup_user(<<"username">>), emqx_dashboard_admin:lookup_user(<<"username">>),
?assertEqual(Hash, erlang:md5(<<Salt/binary, <<"password">>/binary>>)), ?assertEqual(Hash, crypto:hash(sha3_256, <<Salt/binary, <<"password">>/binary>>)),
emqx_dashboard_cli:admins(["passwd", "username", "newpassword"]), emqx_dashboard_cli:admins(["passwd", "username", "newpassword"]),
[#?ADMIN{username = <<"username">>, pwdhash = <<Salt1:4/binary, Hash1/binary>>}] = [#?ADMIN{username = <<"username">>, pwdhash = <<Salt1:4/binary, Hash1/binary>>}] =
emqx_dashboard_admin:lookup_user(<<"username">>), emqx_dashboard_admin:lookup_user(<<"username">>),
?assertEqual(Hash1, erlang:md5(<<Salt1/binary, <<"newpassword">>/binary>>)), ?assertEqual(Hash1, crypto:hash(sha3_256, <<Salt1/binary, <<"newpassword">>/binary>>)),
emqx_dashboard_cli:admins(["del", "username"]), emqx_dashboard_cli:admins(["del", "username"]),
[] = emqx_dashboard_admin:lookup_user(<<"username">>), [] = emqx_dashboard_admin:lookup_user(<<"username">>),
emqx_dashboard_cli:admins(["add", "admin1", "pass1"]), emqx_dashboard_cli:admins(["add", "admin1", "pass1"]),