You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ro...@apache.org on 2022/10/27 07:54:36 UTC

[couchdb] branch main updated: Implement global password hasher process (#4240)

This is an automated email from the ASF dual-hosted git repository.

ronny pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/main by this push:
     new d512a15b9 Implement global password hasher process (#4240)
d512a15b9 is described below

commit d512a15b9a6434bad081141e06b22f3f16c42ba3
Author: Ronny <ro...@apache.org>
AuthorDate: Thu Oct 27 09:54:28 2022 +0200

    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.
---
 src/couch/src/couch_password_hasher.erl | 73 +++++++++++++++++++++++++++++++++
 src/couch/src/couch_primary_sup.erl     |  4 +-
 src/couch/src/couch_server.erl          | 27 ++++--------
 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) ->