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
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 @@
{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, {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 @@
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)};