Skip to content

Commit

Permalink
MB-52422: Upgrade for scram-sha auth records
Browse files Browse the repository at this point in the history
Change-Id: I91b877e3c81987c6f3016934f52ede9f27651ce1
Reviewed-on: https://review.couchbase.org/c/ns_server/+/175849
Well-Formed: Build Bot <build@couchbase.com>
Well-Formed: Restriction Checker
Tested-by: Timofey Barmin <timofey.barmin@couchbase.com>
Reviewed-by: Artem Stemkovski <artem@couchbase.com>
  • Loading branch information
timofey-barmin committed Jun 29, 2022
1 parent 6f676f1 commit 894b19f
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 6 deletions.
8 changes: 5 additions & 3 deletions src/cluster_compat_mode.erl
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ consider_switching_compat_mode() ->
upgrades() ->
[{?VERSION_66, rbac, menelaus_users, upgrade},
{?VERSION_70, rbac, menelaus_users, upgrade},
{?VERSION_71, rbac, menelaus_users, upgrade}].
{?VERSION_71, rbac, menelaus_users, upgrade},
{?VERSION_ELIXIR, rbac, menelaus_users, upgrade}].

do_upgrades(undefined, _, _, _) ->
%% this happens during the cluster initialization. no upgrade needed
Expand All @@ -251,8 +252,9 @@ do_upgrades([], _, _, _, _) ->
do_upgrades([{Version, Name, Module, Fun} | Rest],
CurrentVersion, NewVersion, Config, NodesWanted)
when CurrentVersion < Version andalso NewVersion >= Version ->
?log_debug("Initiating ~p upgrade due to version change from ~p to ~p",
[Name, CurrentVersion, NewVersion]),
?log_debug("Initiating ~p upgrade due to version change from ~p to ~p "
"(target version: ~p)",
[Name, CurrentVersion, Version, NewVersion]),
case Module:Fun(Version, Config, NodesWanted) of
ok ->
do_upgrades(Rest, CurrentVersion, NewVersion, Config, NodesWanted);
Expand Down
32 changes: 31 additions & 1 deletion src/menelaus_users.erl
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,8 @@ upgrade_props(?VERSION_70, RecType, _Key, Props) when RecType == user;
{ok, upgrade_roles(fun maybe_upgrade_role_to_70/1, Props)};
upgrade_props(?VERSION_71, user, Key, Props) ->
{ok, add_uuid(Key, Props)};
upgrade_props(?VERSION_ELIXIR, auth, _Key, AuthProps) ->
{ok, scram_sha:fix_pre_elixir_auth_info(AuthProps)};
upgrade_props(_Vsn, _RecType, _Key, _Props) ->
skip.

Expand Down Expand Up @@ -1017,6 +1019,16 @@ upgrade_test_() ->
?assert(is_binary(proplists:get_value(uuid, Props)))
end,

CheckAuth =
fun (User, AuthType, Expected) ->
fun () ->
Props = get_props_raw(auth, {User, local}),
{Actual} = proplists:get_value(AuthType, Props, []),
?assertEqual(lists:sort(Expected),
lists:sort(Actual))
end
end,

Test =
fun (Version, Users, Checks) ->
{lists:flatten(io_lib:format("Upgrade to ~p", [Version])),
Expand Down Expand Up @@ -1063,6 +1075,24 @@ upgrade_test_() ->
Test(?VERSION_71,
SetUsers([{"user1", [admin]},
{"user2", [{bucket_admin, ["test"]}]}]),
[?cut([CheckUUID(U) || U <- ["user1", "user2"]])])]}.
[?cut([CheckUUID(U) || U <- ["user1", "user2"]])]),
Test(?VERSION_ELIXIR,
[{{auth, {"migrated-user", local}},
[{<<"hash">>, {[anything]}},
{<<"scram-sha-1">>, {[anything]}}]}],
[CheckAuth("migrated-user", <<"hash">>, [anything]),
CheckAuth("migrated-user", <<"scram-sha-1">>, [anything])]),
Test(?VERSION_ELIXIR,
[{{auth, {"not-migrated-user", local}},
[{<<"hash">>, {[anything]}},
{<<"sha1">>, {[{<<"s">>, <<"0ues3mfZqA4OjuljBI/uQY5L0jI=">>},
{<<"h">>, <<"kZlCBy+TU+meqxR7rJfg9mS1LZA=">>},
{<<"i">>, 4000}]}}]}],
[CheckAuth("not-migrated-user", <<"hash">>, [anything]),
CheckAuth("not-migrated-user", <<"scram-sha-1">>,
[{<<"c">>, <<"APXjupUS+LktBEirfdNtNtCYChk=">>},
{<<"k">>, <<"Vkelr1rzrz9tT0Z/AhLvKJVuWJs=">>},
{<<"s">>, <<"0ues3mfZqA4OjuljBI/uQY5L0jI=">>},
{<<"i">>, 4000}])])]}.

-endif.
42 changes: 40 additions & 2 deletions src/scram_sha.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@
-define(SERVER_KEY_KEY, <<"k">>).
-define(ITERATIONS_KEY, <<"i">>).

%% Pre elixir names:
-define(OLD_SALT_KEY, <<"s">>).
-define(OLD_HASH_KEY, <<"h">>).
-define(OLD_ITERATIONS_KEY, <<"i">>).

-export([start_link/0,
authenticate/1,
meta_header/0,
get_resp_headers_from_req/1,
get_fallback_salt/0,
pbkdf2/4,
build_auth/1]).
build_auth/1,
fix_pre_elixir_auth_info/1]).

%% callback for token_server
-export([init/0]).
Expand All @@ -58,6 +64,37 @@ build_auth(Password) ->
end,
[BuildAuth(Sha) || Sha <- supported_types()].

%% Convert scram-sha auth info generated by pre-elixir code to correct
%% scram-sha auth info.
%% See MB-52422 for details.
fix_pre_elixir_auth_info(Props) ->
lists:map(
fun ({ShaBin, {Params}}) when ShaBin == <<"sha1">>;
ShaBin == <<"sha256">>;
ShaBin == <<"sha512">> ->
Sha = case ShaBin of
<<"sha1">> -> sha;
<<"sha256">> -> sha256;
<<"sha512">> -> sha512
end,
NewParams =
lists:flatmap(
fun ({?OLD_ITERATIONS_KEY, I}) ->
[{?ITERATIONS_KEY, I}];
({?OLD_SALT_KEY, S}) ->
[{?SALT_KEY, S}];
({?OLD_HASH_KEY, SPasswordBase64}) ->
SPassword = base64:decode(SPasswordBase64),
ClientKey = client_key(Sha, SPassword),
StoredKey = stored_key(Sha, ClientKey),
ServerKey = server_key(Sha, SPassword),
[{?STORED_KEY_KEY, base64:encode(StoredKey)},
{?SERVER_KEY_KEY, base64:encode(ServerKey)}]
end, Params),
{auth_info_key(Sha), {NewParams}};
(KV) -> KV
end, Props).

meta_header() ->
"menelaus-auth-scram-sha_reply".

Expand Down Expand Up @@ -217,7 +254,8 @@ find_auth_info(Sha, Name) ->
{false, _} ->
undefined;
{AuthInfo, Domain} ->
case proplists:get_value(auth_info_key(Sha), AuthInfo) of
MigratedAuthInfo = fix_pre_elixir_auth_info(AuthInfo),
case proplists:get_value(auth_info_key(Sha), MigratedAuthInfo) of
undefined ->
undefined;
{Info} ->
Expand Down

0 comments on commit 894b19f

Please sign in to comment.