diff options
author | Nick Vatamaniuc <vatamane@apache.org> | 2020-02-06 12:03:28 -0500 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2020-02-06 15:43:55 -0500 |
commit | 27bb45043435828915bdcbdc130b685e5533bbd8 (patch) | |
tree | 2228faf0b61b372918fb1ff51d3f7aac66e33e2b | |
parent | 9729ab3c6b92e35b2c93ac4274d07801d90662cc (diff) | |
download | couchdb-27bb45043435828915bdcbdc130b685e5533bbd8.tar.gz |
Simplify couch_auth_cache
Since the backend port is closed by default, remove the cache service
since it was shown to cause monitor leaks, and it just consumes resources
running in the background when almost nobody uses it. The few utility scripts
that might use the backend port can just get the auth docs directly from the
db.
Issue: https://github.com/apache/couchdb/issues/2493
-rw-r--r-- | src/couch/src/couch_auth_cache.erl | 383 | ||||
-rw-r--r-- | src/couch/src/couch_secondary_sup.erl | 3 | ||||
-rw-r--r-- | src/couch/test/eunit/couch_auth_cache_tests.erl | 28 | ||||
-rw-r--r-- | test/javascript/tests/reader_acl.js | 8 | ||||
-rw-r--r-- | test/javascript/tests/security_validation.js | 7 |
5 files changed, 45 insertions, 384 deletions
diff --git a/src/couch/src/couch_auth_cache.erl b/src/couch/src/couch_auth_cache.erl index 157b0902e..c564cee00 100644 --- a/src/couch/src/couch_auth_cache.erl +++ b/src/couch/src/couch_auth_cache.erl @@ -11,37 +11,22 @@ % the License. -module(couch_auth_cache). --behaviour(gen_server). --vsn(3). --behaviour(config_listener). -% public API --export([get_user_creds/1, get_user_creds/2, update_user_creds/3]). --export([get_admin/1, add_roles/2, auth_design_doc/1]). -% gen_server API --export([start_link/0, init/1, handle_call/3, handle_info/2, handle_cast/2]). --export([code_change/3, terminate/2]). +-export([ + get_user_creds/1, + get_user_creds/2, + update_user_creds/3, + get_admin/1, + add_roles/2, + auth_design_doc/1, + ensure_users_db_exists/0 +]). --export([handle_config_change/5, handle_config_terminate/3]). --export([handle_db_event/3]). -include_lib("couch/include/couch_db.hrl"). -include_lib("couch/include/couch_js_functions.hrl"). --define(STATE, auth_state_ets). --define(BY_USER, auth_by_user_ets). --define(BY_ATIME, auth_by_atime_ets). - --define(RELISTEN_DELAY, 5000). - --record(state, { - max_cache_size = 0, - cache_size = 0, - db_notifier = nil, - event_listener = nil -}). - -spec get_user_creds(UserName::string() | binary()) -> {ok, Credentials::list(), term()} | nil. @@ -58,9 +43,9 @@ get_user_creds(Req, UserName) when is_list(UserName) -> get_user_creds(_Req, UserName) -> UserCreds = case get_admin(UserName) of nil -> - get_from_cache(UserName); + get_from_db(UserName); Props -> - case get_from_cache(UserName) of + case get_from_db(UserName) of nil -> Props; UserProps when is_list(UserProps) -> @@ -70,8 +55,8 @@ get_user_creds(_Req, UserName) -> validate_user_creds(UserCreds). update_user_creds(_Req, UserDoc, _AuthCtx) -> - DbNameList = config:get("couch_httpd_auth", "authentication_db", "_users"), - couch_util:with_db(?l2b(DbNameList), fun(UserDb) -> + ok = ensure_users_db_exists(), + couch_util:with_db(users_db(), fun(UserDb) -> {ok, _NewRev} = couch_db:update_doc(UserDb, UserDoc, []), ok end). @@ -109,21 +94,20 @@ make_admin_doc(DerivedKey, Salt, Iterations) -> {<<"password_scheme">>, <<"pbkdf2">>}, {<<"derived_key">>, ?l2b(DerivedKey)}]. -get_from_cache(UserName) -> - exec_if_auth_db( - fun(_AuthDb) -> - maybe_refresh_cache(), - case ets:lookup(?BY_USER, UserName) of - [] -> - gen_server:call(?MODULE, {fetch, UserName}, infinity); - [{UserName, {Credentials, _ATime}}] -> - couch_stats:increment_counter([couchdb, auth_cache_hits]), - gen_server:cast(?MODULE, {cache_hit, UserName}), - Credentials - end - end, - nil - ). + +get_from_db(UserName) -> + ok = ensure_users_db_exists(), + couch_util:with_db(users_db(), fun(Db) -> + DocId = <<"org.couchdb.user:", UserName/binary>>, + try + {ok, Doc} = couch_db:open_doc(Db, DocId, [conflicts]), + {DocProps} = couch_doc:to_json_obj(Doc, []), + DocProps + catch + _:_Error -> + nil + end + end). validate_user_creds(nil) -> @@ -141,313 +125,24 @@ validate_user_creds(UserCreds) -> {ok, UserCreds, nil}. -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - - -init(_) -> - ?STATE = ets:new(?STATE, [set, protected, named_table]), - ?BY_USER = ets:new(?BY_USER, [set, protected, named_table]), - ?BY_ATIME = ets:new(?BY_ATIME, [ordered_set, private, named_table]), - AuthDbName = config:get("couch_httpd_auth", "authentication_db"), - process_flag(trap_exit, true), - ok = config:listen_for_changes(?MODULE, nil), - {ok, Listener} = couch_event:link_listener( - ?MODULE, handle_db_event, nil, [{dbname, AuthDbName}] - ), - State = #state{ - event_listener = Listener, - max_cache_size = list_to_integer( - config:get("couch_httpd_auth", "auth_cache_size", "50") - ) - }, - {ok, reinit_cache(State)}. - - -handle_db_event(_DbName, created, St) -> - gen_server:call(?MODULE, reinit_cache, infinity), - {ok, St}; -handle_db_event(_DbName, compacted, St) -> - gen_server:call(?MODULE, auth_db_compacted, infinity), - {ok, St}; -handle_db_event(_, _, St) -> - {ok, St}. - - -handle_call(reinit_cache, _From, State) -> - exec_if_auth_db(fun(AuthDb) -> catch couch_db:close(AuthDb) end), - {reply, ok, reinit_cache(State)}; - -handle_call(auth_db_compacted, _From, State) -> - exec_if_auth_db( - fun(AuthDb) -> - true = ets:insert(?STATE, {auth_db, reopen_auth_db(AuthDb)}) - end - ), - {reply, ok, State}; - -handle_call({new_max_cache_size, NewSize}, - _From, #state{cache_size = Size} = State) when NewSize >= Size -> - {reply, ok, State#state{max_cache_size = NewSize}}; - -handle_call({new_max_cache_size, NewSize}, _From, State) -> - free_mru_cache_entries(State#state.cache_size - NewSize), - {reply, ok, State#state{max_cache_size = NewSize, cache_size = NewSize}}; - -handle_call({fetch, UserName}, _From, State) -> - {Credentials, NewState} = case ets:lookup(?BY_USER, UserName) of - [{UserName, {Creds, ATime}}] -> - couch_stats:increment_counter([couchdb, auth_cache_hits]), - cache_hit(UserName, Creds, ATime), - {Creds, State}; - [] -> - couch_stats:increment_counter([couchdb, auth_cache_misses]), - Creds = get_user_props_from_db(UserName), - ATime = couch_util:unique_monotonic_integer(), - State1 = add_cache_entry(UserName, Creds, ATime, State), - {Creds, State1} - end, - {reply, Credentials, NewState}; - -handle_call(refresh, _From, State) -> - exec_if_auth_db(fun refresh_entries/1), - {reply, ok, State}. - - -handle_cast({cache_hit, UserName}, State) -> - case ets:lookup(?BY_USER, UserName) of - [{UserName, {Credentials, ATime}}] -> - cache_hit(UserName, Credentials, ATime); - _ -> - ok - end, - {noreply, State}. - - -handle_info({'EXIT', LPid, _Reason}, #state{event_listener=LPid}=State) -> - erlang:send_after(5000, self(), restart_event_listener), - {noreply, State#state{event_listener=undefined}}; -handle_info(restart_event_listener, State) -> - [{auth_db_name, AuthDbName}] = ets:lookup(?STATE, auth_db_name), - {ok, NewListener} = couch_event:link_listener( - ?MODULE, handle_db_event, nil, [{dbname, AuthDbName}] - ), - {noreply, State#state{event_listener=NewListener}}; -handle_info({'DOWN', _Ref, _, _, shutdown}, State) -> - {stop, shutdown, State}; -handle_info({'DOWN', _Ref, _, _, _}, State) -> - {noreply, reinit_cache(State)}; -handle_info(restart_config_listener, State) -> - ok = config:listen_for_changes(?MODULE, nil), - {noreply, State}. - - - -terminate(_Reason, #state{event_listener = Listener}) -> - couch_event:stop_listener(Listener), - exec_if_auth_db(fun(AuthDb) -> catch couch_db:close(AuthDb) end), - true = ets:delete(?BY_USER), - true = ets:delete(?BY_ATIME), - true = ets:delete(?STATE). - - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - - -handle_config_change("couch_httpd_auth", "auth_cache_size", SizeList, _, _) -> - Size = list_to_integer(SizeList), - {ok, gen_server:call(?MODULE, {new_max_cache_size, Size}, infinity)}; -handle_config_change("couch_httpd_auth", "authentication_db", _DbName, _, _) -> - {ok, gen_server:call(?MODULE, reinit_cache, infinity)}; -handle_config_change(_, _, _, _, _) -> - {ok, nil}. - -handle_config_terminate(_, stop, _) -> - ok; -handle_config_terminate(_Server, _Reason, _State) -> - erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener). - -clear_cache(State) -> - exec_if_auth_db(fun(AuthDb) -> catch couch_db:close(AuthDb) end), - true = ets:delete_all_objects(?BY_USER), - true = ets:delete_all_objects(?BY_ATIME), - State#state{cache_size = 0}. - - -reinit_cache(#state{} = State) -> - NewState = clear_cache(State), - AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")), - true = ets:insert(?STATE, {auth_db_name, AuthDbName}), - AuthDb = open_auth_db(), - true = ets:insert(?STATE, {auth_db, AuthDb}), - couch_db:monitor(AuthDb), - NewState. - - -add_cache_entry(_, _, _, #state{max_cache_size = 0} = State) -> - State; -add_cache_entry(UserName, Credentials, ATime, State) -> - case State#state.cache_size >= State#state.max_cache_size of - true -> - free_mru_cache_entry(); - false -> - ok - end, - true = ets:insert(?BY_ATIME, {ATime, UserName}), - true = ets:insert(?BY_USER, {UserName, {Credentials, ATime}}), - State#state{cache_size = couch_util:get_value(size, ets:info(?BY_USER))}. - -free_mru_cache_entries(0) -> - ok; -free_mru_cache_entries(N) when N > 0 -> - free_mru_cache_entry(), - free_mru_cache_entries(N - 1). - -free_mru_cache_entry() -> - MruTime = ets:last(?BY_ATIME), - [{MruTime, UserName}] = ets:lookup(?BY_ATIME, MruTime), - true = ets:delete(?BY_ATIME, MruTime), - true = ets:delete(?BY_USER, UserName). - - -cache_hit(UserName, Credentials, ATime) -> - NewATime = couch_util:unique_monotonic_integer(), - true = ets:delete(?BY_ATIME, ATime), - true = ets:insert(?BY_ATIME, {NewATime, UserName}), - true = ets:insert(?BY_USER, {UserName, {Credentials, NewATime}}). - - -refresh_entries(AuthDb) -> - case reopen_auth_db(AuthDb) of - nil -> - ok; - AuthDb2 -> - AuthDbSeq = couch_db:get_update_seq(AuthDb), - AuthDb2Seq = couch_db:get_update_seq(AuthDb2), - case AuthDb2Seq > AuthDbSeq of - true -> - Fun = fun(DocInfo, _) -> refresh_entry(AuthDb2, DocInfo) end, - {ok, _} = couch_db:fold_changes(AuthDb2, AuthDbSeq, Fun, nil), - true = ets:insert(?STATE, {auth_db, AuthDb2}); - false -> - ok - end - end. - - -refresh_entry(Db, #full_doc_info{} = FDI) -> - refresh_entry(Db, couch_doc:to_doc_info(FDI)); -refresh_entry(Db, #doc_info{high_seq = DocSeq} = DocInfo) -> - case is_user_doc(DocInfo) of - {true, UserName} -> - case ets:lookup(?BY_USER, UserName) of - [] -> - ok; - [{UserName, {_OldCreds, ATime}}] -> - {ok, Doc} = couch_db:open_doc(Db, DocInfo, [conflicts, deleted]), - NewCreds = user_creds(Doc), - true = ets:insert(?BY_USER, {UserName, {NewCreds, ATime}}) - end; - false -> - ok - end, - {ok, DocSeq}. - - -user_creds(#doc{deleted = true}) -> - nil; -user_creds(#doc{} = Doc) -> - {Creds} = couch_doc:to_json_obj(Doc, []), - Creds. - - -is_user_doc(#doc_info{id = <<"org.couchdb.user:", UserName/binary>>}) -> - {true, UserName}; -is_user_doc(_) -> - false. - - -maybe_refresh_cache() -> - case cache_needs_refresh() of - true -> - ok = gen_server:call(?MODULE, refresh, infinity); - false -> - ok - end. - - -cache_needs_refresh() -> - exec_if_auth_db( - fun(AuthDb) -> - case reopen_auth_db(AuthDb) of - nil -> - false; - AuthDb2 -> - AuthDbSeq = couch_db:get_update_seq(AuthDb), - AuthDb2Seq = couch_db:get_update_seq(AuthDb2), - AuthDb2Seq > AuthDbSeq - end - end, - false - ). - - -reopen_auth_db(AuthDb) -> - case (catch couch_db:reopen(AuthDb)) of - {ok, AuthDb2} -> - AuthDb2; - _ -> - nil - end. - - -exec_if_auth_db(Fun) -> - exec_if_auth_db(Fun, ok). - -exec_if_auth_db(Fun, DefRes) -> - case ets:lookup(?STATE, auth_db) of - [{auth_db, AuthDb}] -> - Fun(AuthDb); - _ -> - DefRes - end. - - -open_auth_db() -> - [{auth_db_name, DbName}] = ets:lookup(?STATE, auth_db_name), - {ok, AuthDb} = ensure_users_db_exists(DbName, [sys_db]), - AuthDb. - +users_db() -> + DbNameList = config:get("couch_httpd_auth", "authentication_db", "_users"), + ?l2b(DbNameList). -get_user_props_from_db(UserName) -> - exec_if_auth_db( - fun(AuthDb) -> - Db = reopen_auth_db(AuthDb), - DocId = <<"org.couchdb.user:", UserName/binary>>, - try - {ok, Doc} = couch_db:open_doc(Db, DocId, [conflicts]), - {DocProps} = couch_doc:to_json_obj(Doc, []), - DocProps - catch - _:_Error -> - nil - end - end, - nil - ). -ensure_users_db_exists(DbName, Options) -> - Options1 = [?ADMIN_CTX, nologifmissing | Options], - case couch_db:open(DbName, Options1) of +ensure_users_db_exists() -> + Options = [?ADMIN_CTX, nologifmissing], + case couch_db:open(users_db(), Options) of {ok, Db} -> ensure_auth_ddoc_exists(Db, <<"_design/_auth">>), - {ok, Db}; + couch_db:close(Db); _Error -> - {ok, Db} = couch_db:create(DbName, Options1), + {ok, Db} = couch_db:create(users_db(), Options), ok = ensure_auth_ddoc_exists(Db, <<"_design/_auth">>), - {ok, Db} - end. + couch_db:close(Db) + end, + ok. + ensure_auth_ddoc_exists(Db, DDocId) -> case couch_db:open_doc(Db, DDocId) of diff --git a/src/couch/src/couch_secondary_sup.erl b/src/couch/src/couch_secondary_sup.erl index 9c7d414d0..bb7821555 100644 --- a/src/couch/src/couch_secondary_sup.erl +++ b/src/couch/src/couch_secondary_sup.erl @@ -30,8 +30,7 @@ init([]) -> {index_server, {couch_index_server, start_link, []}}, {query_servers, {couch_proc_manager, start_link, []}}, {vhosts, {couch_httpd_vhost, start_link, []}}, - {uuids, {couch_uuids, start, []}}, - {auth_cache, {couch_auth_cache, start_link, []}} + {uuids, {couch_uuids, start, []}} ], MaybeHttp = case http_enabled() of diff --git a/src/couch/test/eunit/couch_auth_cache_tests.erl b/src/couch/test/eunit/couch_auth_cache_tests.erl index 5439dd7b9..71faf77d6 100644 --- a/src/couch/test/eunit/couch_auth_cache_tests.erl +++ b/src/couch/test/eunit/couch_auth_cache_tests.erl @@ -52,7 +52,6 @@ couch_auth_cache_test_() -> fun should_drop_cache_on_auth_db_change/1, fun should_restore_cache_on_auth_db_change/1, fun should_recover_cache_after_shutdown/1, - fun should_close_old_db_on_auth_db_change/1, fun should_get_admin_from_config/1 ] } @@ -226,13 +225,6 @@ should_recover_cache_after_shutdown(DbName) -> ?assertEqual(PasswordHash, get_user_doc_password_sha(DbName, "joe")) end). -should_close_old_db_on_auth_db_change(DbName) -> - {timeout, ?DB_TIMEOUT, ?_test(begin - ?assertEqual(ok, wait_db(DbName, fun is_opened/1)), - config:set("couch_httpd_auth", "authentication_db", - ?b2l(?tempdb()), false), - ?assertEqual(ok, wait_db(DbName, fun is_closed/1)) - end)}. should_get_admin_from_config(_DbName) -> ?_test(begin @@ -251,6 +243,7 @@ update_user_doc(DbName, UserName, Password) -> update_user_doc(DbName, UserName, Password, nil). update_user_doc(DbName, UserName, Password, Rev) -> + ok = couch_auth_cache:ensure_users_db_exists(), User = iolist_to_binary(UserName), Doc = couch_doc:from_json_obj({[ {<<"_id">>, <<"org.couchdb.user:", User/binary>>}, @@ -269,17 +262,6 @@ update_user_doc(DbName, UserName, Password, Rev) -> ok = couch_db:close(AuthDb), {ok, couch_doc:rev_to_str(NewRev)}. -wait_db(Db, DbFun) -> - test_util:wait(fun() -> - case DbFun(Db) of - true -> - ok; - false -> - wait - end - end, ?DB_TIMEOUT, 500). - - hash_password(Password) -> ?l2b(couch_util:to_hex(crypto:hash(sha, iolist_to_binary([Password, ?SALT])))). @@ -324,14 +306,6 @@ delete_user_doc(DbName, UserName) -> {ok, _} = couch_db:update_doc(AuthDb, DeletedDoc, []), ok = couch_db:close(AuthDb). -is_opened(DbName) -> - {ok, AuthDb} = couch_db:open_int(DbName, [?ADMIN_CTX]), - Monitors = couch_db:monitored_by(AuthDb) -- [self()], - ok = couch_db:close(AuthDb), - Monitors /= []. - -is_closed(DbName) -> - not is_opened(DbName). make_validate_test({Old, New, "ok"} = Case) -> {test_id(Case), ?_assertEqual(ok, validate(doc(Old), doc(New)))}; diff --git a/test/javascript/tests/reader_acl.js b/test/javascript/tests/reader_acl.js index 3966b64cb..8dc28aae9 100644 --- a/test/javascript/tests/reader_acl.js +++ b/test/javascript/tests/reader_acl.js @@ -215,11 +215,7 @@ couchTests.reader_acl = function(debug) { ); usersDb.deleteDb(); - // have to delete the backside version now too :( - var req = CouchDB.newXhr(); - req.open("DELETE", "/_node/node1@127.0.0.1/" + users_db_name, false); - req.send(""); - CouchDB.maybeThrowError(req); - + // don't have to delete the backside db since in this case couch_auth_cache only read + // admin from the config section and so it never auto-created the node local db secretDb.deleteDb(); } diff --git a/test/javascript/tests/security_validation.js b/test/javascript/tests/security_validation.js index 128b90b8b..6f0bd0f42 100644 --- a/test/javascript/tests/security_validation.js +++ b/test/javascript/tests/security_validation.js @@ -325,9 +325,6 @@ couchTests.security_validation = function(debug) { adminDbB.deleteDb(); } authDb.deleteDb(); - // have to clean up authDb on the backside :( - var req = CouchDB.newXhr(); - req.open("DELETE", "/_node/node1@127.0.0.1/" + authDb_name, false); - req.send(""); - CouchDB.maybeThrowError(req); + // don't have to clean the backend authDb since this test only calls + // couch_auth_cache:get_admin/1 which doesn't auto-create the users db }; |