Skip to content

Commit 195ae9b

Browse files
MB-61292: [gosecrets] Add read-only mode for gosecrets
so it doesn't create any keys or tokens on disk when it is started for read-only purposes The biggest concern here is the following scenario: Two parallel gosecrets instances can race on creating keys on disk. In this case on of the instances can end up having one key in memory, and another key on disk, which is catastrophic. By adding read-only mode we can avoid this situation, because only ns_server will start gosecrets in read-write mode, so all other instances will be read-only and will not create any keys. Change-Id: I7ce935622ab8ca1cade6d6d66df79bcf7d08a896 Reviewed-on: https://review.couchbase.org/c/ns_server/+/221039 Reviewed-by: Navdeep S Boparai <[email protected]> Well-Formed: Build Bot <[email protected]> Tested-by: Timofey Barmin <[email protected]>
1 parent 73411c1 commit 195ae9b

File tree

6 files changed

+165
-46
lines changed

6 files changed

+165
-46
lines changed

apps/ns_babysitter/src/cb_gosecrets_runner.erl

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
-include_lib("kernel/include/file.hrl").
1818
-endif.
1919

20-
-export([start_link/2, start_link/3, extract_hidden_pass/0,
20+
-export([start_link/3, start_link/4, extract_hidden_pass/0,
2121
extract_hidden_pass/1, stop/0]).
2222
-export([init/1, handle_call/3, handle_cast/2,
2323
handle_info/2, terminate/2, code_change/3]).
@@ -147,17 +147,17 @@ remove_old_integrity_tokens(Name, Paths) ->
147147
get_key_id_in_use(Name) ->
148148
gen_server:call(Name, get_key_id_in_use, infinity).
149149

150-
start_link(Logger, PasswordPromptAllowed) ->
151-
start_link(Logger, PasswordPromptAllowed, gosecrets_cfg_path()).
150+
start_link(Logger, PasswordPromptAllowed, ReadOnly) ->
151+
start_link(Logger, PasswordPromptAllowed, ReadOnly, gosecrets_cfg_path()).
152152

153-
start_link(Logger, PasswordPromptAllowed, ConfigPath) ->
153+
start_link(Logger, PasswordPromptAllowed, ReadOnly, ConfigPath) ->
154154
gen_server:start_link({local, ?MODULE}, ?MODULE,
155-
[ConfigPath, Logger, PasswordPromptAllowed], []).
155+
[ConfigPath, Logger, PasswordPromptAllowed, ReadOnly], []).
156156

157157
stop() ->
158158
gen_server:call(?MODULE, stop, infinity).
159159

160-
prompt_the_password(State, Retries) ->
160+
prompt_the_password(State, Retries, ReadOnly) ->
161161
StdIn =
162162
case application:get_env(handle_ctrl_c) of
163163
{ok, true} ->
@@ -166,7 +166,7 @@ prompt_the_password(State, Retries) ->
166166
undefined
167167
end,
168168
try
169-
prompt_the_password(State, Retries, StdIn)
169+
prompt_the_password(State, Retries, ReadOnly, StdIn)
170170
after
171171
case StdIn of
172172
undefined ->
@@ -176,12 +176,12 @@ prompt_the_password(State, Retries) ->
176176
end
177177
end.
178178

179-
prompt_the_password(State, MaxRetries, StdIn) ->
179+
prompt_the_password(State, MaxRetries, ReadOnly, StdIn) ->
180180
case open_udp_socket(State) of
181181
{ok, Socket} ->
182182
try
183183
save_port_file(Socket),
184-
prompt_the_password(State, MaxRetries, StdIn,
184+
prompt_the_password(State, MaxRetries, ReadOnly, StdIn,
185185
Socket, _RetriesLeft = MaxRetries)
186186
after
187187
file:delete(port_file_path()),
@@ -191,7 +191,7 @@ prompt_the_password(State, MaxRetries, StdIn) ->
191191
{error, {udp_socket_open_failed, Error}}
192192
end.
193193

194-
prompt_the_password(State, MaxRetries, StdIn, Socket, RetriesLeft) ->
194+
prompt_the_password(State, MaxRetries, ReadOnly, StdIn, Socket, RetriesLeft) ->
195195
{ok, {Addr, Port}} = inet:sockname(Socket),
196196
log(info, "Waiting for the master password to be supplied (UDP: ~p:~b). "
197197
"Attempt ~p (~p attempts left)",
@@ -201,7 +201,7 @@ prompt_the_password(State, MaxRetries, StdIn, Socket, RetriesLeft) ->
201201
log(error, "Password prompt interrupted: ~p", [M], State),
202202
{error, interrupted};
203203
{udp, Socket, FromAddr, FromPort, Password} ->
204-
case call_init(?HIDE(Password), State) of
204+
case call_init(?HIDE(Password), ReadOnly, State) of
205205
ok ->
206206
gen_udp:send(Socket, FromAddr, FromPort, <<"ok">>),
207207
ok;
@@ -210,7 +210,7 @@ prompt_the_password(State, MaxRetries, StdIn, Socket, RetriesLeft) ->
210210
State),
211211
gen_udp:send(Socket, FromAddr, FromPort, <<"retry">>),
212212
timer:sleep(1000),
213-
prompt_the_password(State, MaxRetries, StdIn,
213+
prompt_the_password(State, MaxRetries, ReadOnly, StdIn,
214214
Socket, RetriesLeft - 1);
215215
{Reply, _Reason} when Reply == error;
216216
Reply == wrong_password ->
@@ -219,7 +219,7 @@ prompt_the_password(State, MaxRetries, StdIn, Socket, RetriesLeft) ->
219219
end
220220
end.
221221

222-
init([GosecretsCfgPath, Logger, PasswordPromptAllowed]) ->
222+
init([GosecretsCfgPath, Logger, PasswordPromptAllowed, ReadOnly]) ->
223223
State = #state{config = GosecretsCfgPath,
224224
logger = Logger},
225225

@@ -235,7 +235,7 @@ init([GosecretsCfgPath, Logger, PasswordPromptAllowed]) ->
235235
HiddenPass = extract_hidden_pass(),
236236

237237
init_gosecrets(HiddenPass, _MaxRetries = 3, PasswordPromptAllowed,
238-
NewState),
238+
ReadOnly, NewState),
239239

240240
{ok, NewState}.
241241

@@ -250,14 +250,15 @@ save_config(CfgPath, Cfg, State) ->
250250
erlang:error({write_failed, CfgPath, Error})
251251
end.
252252

253-
init_gosecrets(HiddenPass, MaxRetries, PasswordPromptAllowed, State) ->
254-
case call_init(HiddenPass, State) of
253+
init_gosecrets(HiddenPass, MaxRetries, PasswordPromptAllowed, ReadOnly,
254+
State) ->
255+
case call_init(HiddenPass, ReadOnly, State) of
255256
ok -> ok;
256257
{wrong_password, ErrorMsg} ->
257258
case PasswordPromptAllowed and should_prompt_the_password(State) of
258259
true ->
259260
try
260-
case prompt_the_password(State, MaxRetries) of
261+
case prompt_the_password(State, MaxRetries, ReadOnly) of
261262
ok ->
262263
ok;
263264
{error, Error} ->
@@ -284,8 +285,8 @@ init_gosecrets(HiddenPass, MaxRetries, PasswordPromptAllowed, State) ->
284285
erlang:error({gosecrets_init_failed, Error})
285286
end.
286287

287-
call_init(HiddenPass, State) ->
288-
case call_gosecrets({init, HiddenPass}, State) of
288+
call_init(HiddenPass, ReadOnly, State) ->
289+
case call_gosecrets({init, ReadOnly, HiddenPass}, State) of
289290
ok ->
290291
memorize_hidden_pass(HiddenPass),
291292
log(info, "Init complete. Password (if used) accepted.", [], State),
@@ -465,7 +466,7 @@ gosecret_do_process_exit(Port, {'EXIT', Port, Reason}) ->
465466
gosecret_do_process_exit(_Port, {'EXIT', _, Reason}) ->
466467
exit(Reason).
467468

468-
encode({init, HiddenPass}) ->
469+
encode({init_read_only, HiddenPass}) ->
469470
BinaryPassword = encode_password(HiddenPass),
470471
<<1, BinaryPassword/binary>>;
471472
encode(get_keys_ref) ->
@@ -518,7 +519,15 @@ encode({remove_old_integrity_tokens, Paths}) ->
518519
PathsBin = list_to_binary([encode_param(P) || P <- Paths]),
519520
<<18, PathsBin/binary>>;
520521
encode(get_key_id_in_use) ->
521-
<<19>>.
522+
<<19>>;
523+
encode({init, IsReadOnly, HiddenPass}) ->
524+
BinaryPassword = encode_password(HiddenPass),
525+
ReadOnlyBin = case IsReadOnly of
526+
true -> <<1>>;
527+
false -> <<0>>
528+
end,
529+
<<20, (encode_param(ReadOnlyBin))/binary,
530+
(encode_param(BinaryPassword))/binary>>.
522531

523532
encode_param(B) when is_atom(B) ->
524533
encode_param(atom_to_binary(B));
@@ -1157,7 +1166,7 @@ upgrade_from_7_2_no_password_test() ->
11571166
undefined,
11581167
fun (CfgPath) ->
11591168
ok = file:write_file(DKeyPath, base64:decode(DKeyNoPass)),
1160-
{ok, Pid} = start_link(default, true, CfgPath),
1169+
{ok, Pid} = start_link(default, true, true, CfgPath),
11611170
try
11621171
Data = rand:bytes(512),
11631172
{ok, Encrypted} = encrypt(Pid, Data),
@@ -1186,7 +1195,7 @@ upgrade_from_7_2_with_password_test() ->
11861195
undefined,
11871196
fun (CfgPath) ->
11881197
ok = file:write_file(DKeyPath, base64:decode(DKeyNoPass)),
1189-
{ok, Pid} = start_link(default, true, CfgPath),
1198+
{ok, Pid} = start_link(default, true, true, CfgPath),
11901199
try
11911200
Data = rand:bytes(512),
11921201
{ok, Encrypted} = encrypt(Pid, Data),
@@ -1396,7 +1405,7 @@ with_gosecrets(Cfg, Fun, ResetMemorizePassword) ->
13961405
with_tmp_cfg(
13971406
Cfg,
13981407
fun (CfgPath) ->
1399-
{ok, Pid} = start_link(default, true, CfgPath),
1408+
{ok, Pid} = start_link(default, true, false, CfgPath),
14001409
try
14011410
Fun(CfgPath, Pid)
14021411
after

apps/ns_babysitter/src/ns_babysitter.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ init_master_password() ->
127127
%% cb_gosecrets_runner starts again (with normal logger this time)
128128
%% Note that cb_gosecrets_runner that starts later never asks for
129129
%% password, it uses the one that is set here.
130-
cb_gosecrets_runner:start_link(dummy_logger(Self), true),
130+
cb_gosecrets_runner:start_link(dummy_logger(Self), true, true),
131131
ok = cb_gosecrets_runner:stop()
132132
end).
133133

apps/ns_babysitter/src/ns_babysitter_sup.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ child_specs() ->
2828
case ns_config_default:init_is_enterprise() of
2929
true ->
3030
[{cb_gosecrets_runner,
31-
{cb_gosecrets_runner, start_link, [default, false]},
31+
{cb_gosecrets_runner, start_link, [default, false, false]},
3232
permanent, 1000, worker, []}];
3333
false ->
3434
[]

0 commit comments

Comments
 (0)