feat: sha3_256 dashboard account's password (#6084)
This commit is contained in:
parent
0f8ad29e91
commit
eea789451b
|
@ -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).
|
||||||
|
|
|
@ -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)).
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"]),
|
||||||
|
|
Loading…
Reference in New Issue