diff options
author | Ronny <ronny@apache.org> | 2022-10-27 09:54:28 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-27 09:54:28 +0200 |
commit | d512a15b9a6434bad081141e06b22f3f16c42ba3 (patch) | |
tree | d307691ef39fd41befbd48135e5b7bca84c091af | |
parent | 8a71e3db8a43d8f7e916cbe5f7e5b8be507501c2 (diff) | |
download | couchdb-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.erl | 73 | ||||
-rw-r--r-- | src/couch/src/couch_primary_sup.erl | 4 | ||||
-rw-r--r-- | 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) -> |