From fd66cb6b3dc70a64b556bb1e5bd9fa05f20df571 Mon Sep 17 00:00:00 2001 From: Mike Wallace Date: Wed, 10 Feb 2016 14:59:50 +0000 Subject: [PATCH] Avoid logging creds on couch_replicator termination When couch_replicator terminates with an error we log the #rep record which can contain credentials for the source or target of a replication, either in the url directly or in an Authorization header. This commit adds a function to strip credentials from the #httpdb records in the #rep record and replace them with ****. Specifically this concerns the url and headers fields of the #rep.source and #rep.target #httpdb records. We also add the format_status/2 callback and strip creds from the #rep_state record in the gen_server state to prevent the creds in the state getting logged in the event of a crash. Closes COUCHDB-2949 This closes #25 --- src/couch_replicator.erl | 44 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/couch_replicator.erl b/src/couch_replicator.erl index ff18223..29225e3 100644 --- a/src/couch_replicator.erl +++ b/src/couch_replicator.erl @@ -24,6 +24,7 @@ % gen_server callbacks -export([init/1, terminate/2, code_change/3]). -export([handle_call/3, handle_cast/2, handle_info/2]). +-export([format_status/2]). -export([details/1]). @@ -502,6 +503,42 @@ code_change(_OldVsn, #rep_state{}=State, _Extra) -> {ok, State}. +headers_strip_creds([], Acc) -> + lists:reverse(Acc); +headers_strip_creds([{Key, Value0} | Rest], Acc) -> + Value = case string:to_lower(Key) of + "authorization" -> + "****"; + _ -> + Value0 + end, + headers_strip_creds(Rest, [{Key, Value} | Acc]). + + +httpdb_strip_creds(#httpdb{url = Url, headers = Headers} = HttpDb) -> + HttpDb#httpdb{ + url = couch_util:url_strip_password(Url), + headers = headers_strip_creds(Headers, []) + }. + + +rep_strip_creds(#rep{source = Source, target = Target} = Rep) -> + Rep#rep{ + source = httpdb_strip_creds(Source), + target = httpdb_strip_creds(Target) + }. + + +state_strip_creds(#rep_state{rep_details = Rep, source = Source, target = Target} = State) -> + % #rep_state contains the source and target at the top level and also + % in the nested #rep_details record + State#rep_state{ + rep_details = rep_strip_creds(Rep), + source = httpdb_strip_creds(Source), + target = httpdb_strip_creds(Target) + }. + + terminate(normal, #rep_state{rep_details = #rep{id = RepId} = Rep, checkpoint_history = CheckpointHistory} = State) -> terminate_cleanup(State), @@ -516,8 +553,9 @@ terminate(shutdown, #rep_state{rep_details = #rep{id = RepId}} = State) -> terminate(shutdown, {error, Class, Error, Stack, InitArgs}) -> #rep{id=RepId} = InitArgs, couch_stats:increment_counter([couch_replicator, failed_starts]), + CleanInitArgs = rep_strip_creds(InitArgs), couch_log:error("~p:~p: Replication failed to start for args ~p: ~p", - [Class, Error, InitArgs, Stack]), + [Class, Error, CleanInitArgs, Stack]), case Error of {unauthorized, DbUri} -> NotifyError = {unauthorized, <<"unauthorized to access or create database ", DbUri/binary>>}; @@ -548,6 +586,10 @@ terminate_cleanup(State) -> couch_replicator_api_wrap:db_close(State#rep_state.target). +format_status(_Opt, [_PDict, State]) -> + [{data, [{"State", state_strip_creds(State)}]}]. + + do_last_checkpoint(#rep_state{seqs_in_progress = [], highest_seq_done = {_Ts, ?LOWEST_SEQ}} = State) -> {stop, normal, cancel_timer(State)};