Browse Source

Dont let passwords leak through crash logs

Sergey Prokhorov 6 years ago
parent
commit
b0aca87b75
4 changed files with 31 additions and 6 deletions
  1. 22 3
      src/commands/epgsql_cmd_connect.erl
  2. 3 1
      src/epgsql.erl
  3. 3 1
      src/epgsqla.erl
  4. 3 1
      src/epgsqli.erl

+ 22 - 3
src/commands/epgsql_cmd_connect.erl

@@ -4,6 +4,7 @@
 %%%
 %%%
 -module(epgsql_cmd_connect).
 -module(epgsql_cmd_connect).
 -behaviour(epgsql_command).
 -behaviour(epgsql_command).
+-export([hide_password/1]).
 -export([init/1, execute/2, handle_message/4]).
 -export([init/1, execute/2, handle_message/4]).
 -export_type([response/0, connect_error/0]).
 -export_type([response/0, connect_error/0]).
 
 
@@ -97,6 +98,18 @@ execute(PgSock, #connect{stage = auth, auth_send = {PacketId, Data}} = St) ->
     {ok, PgSock, St#connect{auth_send = undefined}}.
     {ok, PgSock, St#connect{auth_send = undefined}}.
 
 
 
 
+%% @doc this function wraps plaintext password to a lambda function, so, if
+%% epgsql_sock process crashes when executing `connect` command, password will
+%% not appear in a crash log
+hide_password(Password) when is_list(Password);
+                             is_binary(Password) ->
+    fun() ->
+            Password
+    end;
+hide_password(PasswordFun) when is_function(PasswordFun, 0) ->
+    PasswordFun.
+
+
 maybe_ssl(S, false, _, PgSock) ->
 maybe_ssl(S, false, _, PgSock) ->
     epgsql_sock:set_net_socket(gen_tcp, S, PgSock);
     epgsql_sock:set_net_socket(gen_tcp, S, PgSock);
 maybe_ssl(S, Flag, Opts, PgSock) ->
 maybe_ssl(S, Flag, Opts, PgSock) ->
@@ -164,14 +177,14 @@ auth_handle(Data, PgSock, #connect{auth_fun = Fun, auth_state = AuthSt} = St) ->
 
 
 %% AuthenticationCleartextPassword
 %% AuthenticationCleartextPassword
 auth_cleartext(init, _AuthState, #connect{opts = Opts}) ->
 auth_cleartext(init, _AuthState, #connect{opts = Opts}) ->
-    Password = maps:get(password, Opts),
+    Password = get_password(Opts),
     {send, ?PASSWORD, [Password, 0], undefined};
     {send, ?PASSWORD, [Password, 0], undefined};
 auth_cleartext(_, _, _) -> unknown.
 auth_cleartext(_, _, _) -> unknown.
 
 
 %% AuthenticationMD5Password
 %% AuthenticationMD5Password
 auth_md5(init, Salt, #connect{opts = Opts}) ->
 auth_md5(init, Salt, #connect{opts = Opts}) ->
     User = maps:get(username, Opts),
     User = maps:get(username, Opts),
-    Password = maps:get(password, Opts),
+    Password = get_password(Opts),
     Digest1 = hex(erlang:md5([Password, User])),
     Digest1 = hex(erlang:md5([Password, User])),
     Str = ["md5", hex(erlang:md5([Digest1, Salt])), 0],
     Str = ["md5", hex(erlang:md5([Digest1, Salt])), 0],
     {send, ?PASSWORD, Str, undefined};
     {send, ?PASSWORD, Str, undefined};
@@ -186,7 +199,7 @@ auth_scram(init, undefined, #connect{opts = Opts}) ->
     {send, ?SASL_ANY_RESPONSE, SaslInitialResponse, {auth_request, Nonce}};
     {send, ?SASL_ANY_RESPONSE, SaslInitialResponse, {auth_request, Nonce}};
 auth_scram(<<?AUTH_SASL_CONTINUE:?int32, ServerFirst/binary>>, {auth_request, Nonce}, #connect{opts = Opts}) ->
 auth_scram(<<?AUTH_SASL_CONTINUE:?int32, ServerFirst/binary>>, {auth_request, Nonce}, #connect{opts = Opts}) ->
     User = maps:get(username, Opts),
     User = maps:get(username, Opts),
-    Password = maps:get(password, Opts),
+    Password = get_password(Opts),
     ServerFirstParts = epgsql_scram:parse_server_first(ServerFirst, Nonce),
     ServerFirstParts = epgsql_scram:parse_server_first(ServerFirst, Nonce),
     {ClientFinalMessage, ServerProof} = epgsql_scram:get_client_final(ServerFirstParts, Nonce, User, Password),
     {ClientFinalMessage, ServerProof} = epgsql_scram:get_client_final(ServerFirstParts, Nonce, User, Password),
     {send, ?SASL_ANY_RESPONSE, ClientFinalMessage, {server_final, ServerProof}};
     {send, ?SASL_ANY_RESPONSE, ClientFinalMessage, {server_final, ServerProof}};
@@ -239,6 +252,12 @@ handle_message(?ERROR, Err, Sock, #connect{stage = Stage} = _State) when Stage =
 handle_message(_, _, _, _) ->
 handle_message(_, _, _, _) ->
     unknown.
     unknown.
 
 
+
+get_password(Opts) ->
+    PasswordFun = maps:get(password, Opts),
+    PasswordFun().
+
+
 hex(Bin) ->
 hex(Bin) ->
     HChar = fun(N) when N < 10 -> $0 + N;
     HChar = fun(N) when N < 10 -> $0 + N;
                (N) when N < 16 -> $W + N
                (N) when N < 16 -> $W + N

+ 3 - 1
src/epgsql.erl

@@ -154,9 +154,11 @@ connect(Host, Username, Password, Opts) ->
         -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}.
         -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}.
 connect(C, Host, Username, Password, Opts0) ->
 connect(C, Host, Username, Password, Opts0) ->
     Opts = to_map(Opts0),
     Opts = to_map(Opts0),
+    Opts1 = maps:remove(password, Opts),
+    Password1 = epgsql_cmd_connect:hide_password(Password),
     %% TODO connect timeout
     %% TODO connect timeout
     case epgsql_sock:sync_command(
     case epgsql_sock:sync_command(
-           C, epgsql_cmd_connect, {Host, Username, Password, Opts}) of
+           C, epgsql_cmd_connect, {Host, Username, Password1, Opts1}) of
         connected ->
         connected ->
             %% If following call fails for you, try to add {codecs, []} connect option
             %% If following call fails for you, try to add {codecs, []} connect option
             {ok, _} = maybe_update_typecache(C, Opts),
             {ok, _} = maybe_update_typecache(C, Opts),

+ 3 - 1
src/epgsqla.erl

@@ -49,8 +49,10 @@ connect(Host, Username, Password, Opts) ->
               string(), string(), epgsql:connect_opts()) -> reference().
               string(), string(), epgsql:connect_opts()) -> reference().
 connect(C, Host, Username, Password, Opts) ->
 connect(C, Host, Username, Password, Opts) ->
     Opts1 = epgsql:to_map(Opts),
     Opts1 = epgsql:to_map(Opts),
+    Opts2 = maps:remove(password, Opts1),
+    Password1 = epgsql_cmd_connect:hide_password(Password),
     complete_connect(
     complete_connect(
-      C, cast(C, epgsql_cmd_connect, {Host, Username, Password, Opts1}), Opts1).
+      C, cast(C, epgsql_cmd_connect, {Host, Username, Password1, Opts2}), Opts2).
 
 
 -spec close(epgsql:connection()) -> ok.
 -spec close(epgsql:connection()) -> ok.
 close(C) ->
 close(C) ->

+ 3 - 1
src/epgsqli.erl

@@ -48,8 +48,10 @@ connect(Host, Username, Password, Opts) ->
               string(), string(), epgsql:connect_opts()) -> reference().
               string(), string(), epgsql:connect_opts()) -> reference().
 connect(C, Host, Username, Password, Opts) ->
 connect(C, Host, Username, Password, Opts) ->
     Opts1 = epgsql:to_map(Opts),
     Opts1 = epgsql:to_map(Opts),
+    Opts2 = maps:remove(password, Opts1),
+    Password1 = epgsql_cmd_connect:hide_password(Password),
     epgsqla:complete_connect(
     epgsqla:complete_connect(
-      C, incremental(C, epgsql_cmd_connect, {Host, Username, Password, Opts1}), Opts1).
+      C, incremental(C, epgsql_cmd_connect, {Host, Username, Password1, Opts2}), Opts2).
 
 
 -spec close(epgsql:connection()) -> ok.
 -spec close(epgsql:connection()) -> ok.
 close(C) ->
 close(C) ->