summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRonny <ronny@apache.org>2022-10-27 09:54:28 +0200
committerGitHub <noreply@github.com>2022-10-27 09:54:28 +0200
commitd512a15b9a6434bad081141e06b22f3f16c42ba3 (patch)
treed307691ef39fd41befbd48135e5b7bca84c091af
parent8a71e3db8a43d8f7e916cbe5f7e5b8be507501c2 (diff)
downloadcouchdb-d512a15b9a6434bad081141e06b22f3f16c42ba3.tar.gz
Implement global password hasher process (#4240)
Implement a global password hasher process. The new behavior reduces the hashing calls from 2 * N (N equals the number of `couch_server` processes) down to 2 calls. The first call is triggered by the change in the config file and the second call through `config:set` to write the hashed result back into the config file. If we want to reduce this to one call only, we need to implement some more intelligence into the config part, to prevent the triggers for such calls. The password hasher is implemented as a `gen_server` and started with the `couch_primary_services` supervisior. Fixes #4236.
-rw-r--r--src/couch/src/couch_password_hasher.erl73
-rw-r--r--src/couch/src/couch_primary_sup.erl4
-rw-r--r--src/couch/src/couch_server.erl27
3 files changed, 83 insertions, 21 deletions
diff --git a/src/couch/src/couch_password_hasher.erl b/src/couch/src/couch_password_hasher.erl
new file mode 100644
index 000000000..000497694
--- /dev/null
+++ b/src/couch/src/couch_password_hasher.erl
@@ -0,0 +1,73 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_password_hasher).
+
+-behaviour(gen_server).
+
+-include_lib("couch/include/couch_db.hrl").
+
+-export([start_link/0]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ code_change/3
+]).
+
+-export([hash/1]).
+
+-record(state, {}).
+
+%%%===================================================================
+%%% Public functions
+%%%===================================================================
+
+-spec hash(Persist :: boolean()) -> Reply :: term().
+hash(Persist) ->
+ gen_server:cast(?MODULE, {hash_passwords, Persist}).
+
+%%%===================================================================
+%%% Spawning and gen_server implementation
+%%%===================================================================
+
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+init(_Args) ->
+ hash_admin_passwords(true),
+ {ok, #state{}}.
+
+handle_call(Msg, _From, #state{} = State) ->
+ {stop, {invalid_call, Msg}, {invalid_call, Msg}, State}.
+
+handle_cast({hash_passwords, Persist}, State) ->
+ hash_admin_passwords(Persist),
+ {noreply, State};
+handle_cast(Msg, State) ->
+ {stop, {invalid_cast, Msg}, State}.
+
+code_change(_OldVsn, #state{} = State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+hash_admin_passwords(Persist) ->
+ lists:foreach(
+ fun({User, ClearPassword}) ->
+ HashedPassword = couch_passwords:hash_admin_password(ClearPassword),
+ config:set("admins", User, ?b2l(HashedPassword), Persist)
+ end,
+ couch_passwords:get_unhashed_admins()
+ ).
diff --git a/src/couch/src/couch_primary_sup.erl b/src/couch/src/couch_primary_sup.erl
index 4f2917f98..1eae87160 100644
--- a/src/couch/src/couch_primary_sup.erl
+++ b/src/couch/src/couch_primary_sup.erl
@@ -21,7 +21,9 @@ init([]) ->
Children =
[
{couch_task_status, {couch_task_status, start_link, []}, permanent, brutal_kill, worker,
- [couch_task_status]}
+ [couch_task_status]},
+ {couch_password_hasher, {couch_password_hasher, start_link, []}, permanent, brutal_kill,
+ worker, [couch_password_hasher]}
] ++ couch_servers(),
{ok, {{one_for_one, 10, 3600}, Children}}.
diff --git a/src/couch/src/couch_server.erl b/src/couch/src/couch_server.erl
index 7c96c9953..6486c56c7 100644
--- a/src/couch/src/couch_server.erl
+++ b/src/couch/src/couch_server.erl
@@ -253,18 +253,6 @@ is_admin(User, ClearPwd) ->
has_admins() ->
config:get("admins") /= [].
-hash_admin_passwords() ->
- hash_admin_passwords(true).
-
-hash_admin_passwords(Persist) ->
- lists:foreach(
- fun({User, ClearPassword}) ->
- HashedPassword = couch_passwords:hash_admin_password(ClearPassword),
- config:set("admins", User, ?b2l(HashedPassword), Persist)
- end,
- couch_passwords:get_unhashed_admins()
- ).
-
close_db_if_idle(DbName) ->
case ets:lookup(couch_dbs(DbName), DbName) of
[#entry{}] ->
@@ -307,7 +295,6 @@ init([N]) ->
),
ok = config:listen_for_changes(?MODULE, N),
ok = couch_file:init_delete_dir(RootDir),
- hash_admin_passwords(),
ets:new(couch_dbs(N), [
set,
protected,
@@ -376,20 +363,20 @@ handle_config_change("couchdb", "max_dbs_open", _, _, N) ->
handle_config_change("couchdb_engines", _, _, _, N) ->
gen_server:call(couch_server(N), reload_engines),
{ok, N};
-handle_config_change("admins", _, _, Persist, N) ->
- % spawn here so couch event manager doesn't deadlock
- spawn(fun() -> hash_admin_passwords(Persist) end),
+handle_config_change("admins", _, _, Persist, 1 = N) ->
+ % async hashing on couch_server with number 1 only
+ couch_password_hasher:hash(Persist),
{ok, N};
-handle_config_change("httpd", "authentication_handlers", _, _, N) ->
+handle_config_change("httpd", "authentication_handlers", _, _, 1 = N) ->
couch_httpd:stop(),
{ok, N};
-handle_config_change("httpd", "bind_address", _, _, N) ->
+handle_config_change("httpd", "bind_address", _, _, 1 = N) ->
couch_httpd:stop(),
{ok, N};
-handle_config_change("httpd", "port", _, _, N) ->
+handle_config_change("httpd", "port", _, _, 1 = N) ->
couch_httpd:stop(),
{ok, N};
-handle_config_change("httpd", "max_connections", _, _, N) ->
+handle_config_change("httpd", "max_connections", _, _, 1 = N) ->
couch_httpd:stop(),
{ok, N};
handle_config_change(_, _, _, _, N) ->