Skip to content

Commit 02720ba

Browse files
committed
MB-61292: Handle encryption state on file open
Anytime logger opens a new file, initialize encryption state for the disk sink. Add an encryption header if disk sink should be encrypted. Also handle existing files and append cases when log files are opened for continuation. Change-Id: I08aa6bef2284575c895e013428f3b2d811537307 Reviewed-on: https://review.couchbase.org/c/ns_server/+/216152 Tested-by: Navdeep S Boparai <[email protected]> Well-Formed: Build Bot <[email protected]> Reviewed-by: Timofey Barmin <[email protected]>
1 parent f8960ab commit 02720ba

File tree

4 files changed

+155
-40
lines changed

4 files changed

+155
-40
lines changed

apps/ale/src/ale.erl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
%% Callbacks for encryption
3030
create_no_deks_snapshot/0,
3131
file_encrypt_state_match/2,
32+
is_file_encr_by_ds/2,
33+
is_file_encrypted/1,
3234
file_encrypt_init/2,
35+
file_encrypt_cont/3,
3336
file_encrypt_chunk/2,
3437

3538
with_configuration_batching/1,
@@ -211,10 +214,22 @@ file_encrypt_state_match(DS, EncrState) ->
211214
Callback = get_encryption_cb(file_encrypt_state_match),
212215
Callback(DS, EncrState).
213216

217+
is_file_encr_by_ds(Path, DS) ->
218+
Callback = get_encryption_cb(is_file_encr_by_ds),
219+
Callback(Path, DS).
220+
221+
is_file_encrypted(Path) ->
222+
Callback = get_encryption_cb(is_file_encrypted),
223+
Callback(Path).
224+
214225
file_encrypt_init(FileName, DS) ->
215226
Callback = get_encryption_cb(file_encrypt_init),
216227
Callback(FileName, DS).
217228

229+
file_encrypt_cont(FileName, Offset, DS) ->
230+
Callback = get_encryption_cb(file_encrypt_cont),
231+
Callback(FileName, Offset, DS).
232+
218233
file_encrypt_chunk(Data, EncrState) ->
219234
Callback = get_encryption_cb(file_encrypt_chunk),
220235
Callback(Data, EncrState).
@@ -431,10 +446,22 @@ default_encr_disabled_cbs() ->
431446
fun(_DS, _EncrState) ->
432447
true
433448
end,
449+
is_file_encr_by_ds =>
450+
fun(_Path, _DS) ->
451+
true
452+
end,
453+
is_file_encrypted =>
454+
fun(_Path) ->
455+
false
456+
end,
434457
file_encrypt_init =>
435458
fun(_FileName, _DS) ->
436459
{<<>>, #{}}
437460
end,
461+
file_encrypt_cont =>
462+
fun(_FileName, _Offset, _DS) ->
463+
#{}
464+
end,
438465
file_encrypt_chunk =>
439466
fun(Data, EncrState) ->
440467
{Data, EncrState}

apps/ale/src/ale_disk_sink.erl

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -308,20 +308,57 @@ maybe_rotate_files(#worker_state{file_size = FileSize,
308308
maybe_rotate_files(State) ->
309309
State.
310310

311+
maybe_write_header(_SinkName, _File, <<>>) ->
312+
ok;
313+
maybe_write_header(SinkName, File, Header) ->
314+
time_stat(SinkName, write_time,
315+
fun () ->
316+
ok = file:write(File, Header)
317+
end).
318+
319+
update_file_encr_state(true = _ActiveKeyMatch,
320+
#worker_state{path = Path} = State, DS) ->
321+
{ok, File, #file_info{size = Size, inode = Inode}} = open_file(Path),
322+
ContEncrState =
323+
ale:file_encrypt_cont(filename:basename(Path), Size, DS),
324+
State#worker_state{file = File,
325+
file_size = Size,
326+
file_inode = Inode,
327+
encr_state = ContEncrState};
328+
update_file_encr_state(false = _ActiveKeyMatch, State, _DS) ->
329+
do_rotate_files(State).
330+
331+
open_with_encr_state(true = _IsNewFile,
332+
#worker_state{sink_name = SinkName,
333+
path = Path} = State, DS) ->
334+
{ok, File, #file_info{size = 0, inode = Inode}} = open_file(Path),
335+
{Header, EncrState} =
336+
ale:file_encrypt_init(filename:basename(Path), DS),
337+
maybe_write_header(SinkName, File, Header),
338+
State#worker_state{file = File,
339+
file_size = byte_size(Header),
340+
file_inode = Inode,
341+
encr_state = EncrState};
342+
open_with_encr_state(false = _IsNewFile,
343+
#worker_state{path = Path} = State, DS) ->
344+
ActiveKeyMatch = ale:is_file_encr_by_ds(Path, DS),
345+
update_file_encr_state(ActiveKeyMatch, State, DS).
346+
311347
open_log_file(#worker_state{path = Path,
312-
file = OldFile} = State) ->
348+
file = OldFile,
349+
sink_name = SinkName} = State) ->
313350
case OldFile of
314351
undefined ->
315352
ok;
316353
_ ->
317354
file:close(OldFile)
318355
end,
319356

320-
{ok, File, #file_info{size = Size,
321-
inode = Inode}} = open_file(Path),
322-
State#worker_state{file = File,
323-
file_size = Size,
324-
file_inode = Inode}.
357+
IsNewFile = not filelib:is_file(Path) orelse
358+
filelib:file_size(Path) =:= 0,
359+
360+
DS = ale:get_sink_ds(SinkName),
361+
open_with_encr_state(IsNewFile, State#worker_state{file = undefined}, DS).
325362

326363
check_log_file(#worker_state{path = Path,
327364
file_inode = FileInode} = State) ->
@@ -436,19 +473,17 @@ spawn_worker(WorkerState) ->
436473
worker_init(WorkerState)
437474
end).
438475

439-
worker_init(#worker_state{rotation_check_interval = RotCheckInterval,
440-
path = Path} = State0) ->
476+
worker_init(#worker_state{
477+
rotation_check_interval = RotCheckInterval} = State0) ->
441478
case RotCheckInterval > 0 of
442479
true ->
443480
erlang:send_after(RotCheckInterval, self(), check_file);
444481
false ->
445482
ok
446483
end,
447484

448-
DS = ale:create_no_deks_snapshot(),
449-
{<<>>, EncrState} = ale:file_encrypt_init(filename:basename(Path), DS),
450485
worker_loop(
451-
open_log_file(State0#worker_state{encr_state = EncrState})).
486+
open_log_file(State0)).
452487

453488
worker_loop(#worker_state{sink_name = SinkName} = State) ->
454489
NewState =
@@ -467,7 +502,7 @@ worker_loop(#worker_state{sink_name = SinkName} = State) ->
467502
#worker_state{encr_state = EncrState} = State,
468503
DS = ale:get_sink_ds(SinkName),
469504
UpdtReq = not ale:file_encrypt_state_match(DS, EncrState),
470-
UpdatedState = process_key_update_work(UpdtReq, DS, State),
505+
UpdatedState = process_key_update_work(UpdtReq, State),
471506
gen_server:reply(From, ok),
472507
UpdatedState;
473508
Msg ->
@@ -508,23 +543,10 @@ write_data(InputData, InputDataSize,
508543
encr_state = NewEncrState},
509544
maybe_rotate_files(NewState).
510545

511-
process_key_update_work(false = _UpdtReq, _DS, State) ->
546+
process_key_update_work(false = _UpdtReq, State) ->
512547
State;
513-
process_key_update_work(true = _UpdtReq, DS,
514-
#worker_state{sink_name = Name,
515-
path = Path} = State) ->
516-
{Header, EncryptState} =
517-
ale:file_encrypt_init(filename:basename(Path), DS),
518-
NewState = do_rotate_files(State),
519-
#worker_state{file = File,
520-
file_size = 0,
521-
path = Path} = NewState,
522-
time_stat(Name, write_time,
523-
fun () ->
524-
ok = file:write(File, Header)
525-
end),
526-
NewState#worker_state{file_size = byte_size(Header),
527-
encr_state = EncryptState}.
548+
process_key_update_work(true = _UpdtReq, State) ->
549+
do_rotate_files(State).
528550

529551
remove_unnecessary_log_files(LogFilePath, NumFiles) ->
530552
Dir = filename:dirname(LogFilePath),

apps/ns_server/include/ns_common.hrl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,22 @@
470470
fun(DS, EncrState) ->
471471
cb_crypto:file_encrypt_state_match(DS, EncrState)
472472
end,
473+
is_file_encr_by_ds =>
474+
fun(Path, DS) ->
475+
cb_crypto:is_file_encr_by_ds(Path, DS)
476+
end,
477+
is_file_encrypted =>
478+
fun(Path) ->
479+
cb_crypto:is_file_encrypted(Path)
480+
end,
473481
file_encrypt_init =>
474482
fun(FileName, DS) ->
475483
cb_crypto:file_encrypt_init(FileName, DS)
476484
end,
485+
file_encrypt_cont =>
486+
fun(FileName, Offset, DS) ->
487+
cb_crypto:file_encrypt_cont(FileName, Offset, DS)
488+
end,
477489
file_encrypt_chunk =>
478490
fun(Data, EncrState) ->
479491
cb_crypto:file_encrypt_chunk(Data, EncrState)

apps/ns_server/src/cb_crypto.erl

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@
2929
atomic_write_file/3,
3030
atomic_write_file/4,
3131
file_encrypt_init/2,
32+
file_encrypt_cont/3,
3233
file_encrypt_chunk/2,
3334

3435
read_file/2,
3536
file_encrypt_state_match/2,
37+
is_file_encr_by_ds/2,
38+
is_file_encrypted/1,
3639
file_decrypt_init/3,
3740
file_decrypt_next_chunk/2,
3841

@@ -114,18 +117,6 @@ atomic_write_file(Path, Bytes, DekSnapshot, Opts) when is_binary(Bytes) ->
114117
encrypt_to_file(IODevice, Filename, Bytes, MaxChunkSize, DekSnapshot)
115118
end).
116119

117-
get_key_id(undefined) ->
118-
undefined;
119-
get_key_id(#{id := KeyId} = _Key) ->
120-
KeyId.
121-
122-
-spec file_encrypt_state_match(#dek_snapshot{},
123-
#file_encr_state{}) -> boolean().
124-
file_encrypt_state_match(#dek_snapshot{active_key = ActiveKey},
125-
#file_encr_state{key = FileActiveKey}) ->
126-
127-
get_key_id(ActiveKey) =:= get_key_id(FileActiveKey).
128-
129120
-spec file_encrypt_init(string(), #dek_snapshot{}) ->
130121
{binary(), #file_encr_state{}}.
131122
file_encrypt_init(Filename,
@@ -149,6 +140,18 @@ file_encrypt_init(Filename,
149140
iv_atomic_counter = IVCounter,
150141
offset = size(Header)}}.
151142

143+
-spec file_encrypt_cont(string(), non_neg_integer(), #dek_snapshot{}) ->
144+
#file_encr_state{}.
145+
file_encrypt_cont(Filename, Offset,
146+
#dek_snapshot{active_key = ActiveKey,
147+
iv_random = IVRandom,
148+
iv_atomic_counter = IVCounter}) ->
149+
#file_encr_state{filename = iolist_to_binary(Filename),
150+
key = ActiveKey,
151+
iv_random = IVRandom,
152+
iv_atomic_counter = IVCounter,
153+
offset = Offset}.
154+
152155
-spec file_encrypt_chunk(binary(), #file_encr_state{}) ->
153156
{binary(), #file_encr_state{}}.
154157
file_encrypt_chunk(Data, #file_encr_state{key = undefined} = State) ->
@@ -164,6 +167,32 @@ file_encrypt_chunk(Data, #file_encr_state{key = Dek,
164167
NewOffset = Offset + size(ChunkWithSize),
165168
{ChunkWithSize, State#file_encr_state{offset = NewOffset}}.
166169

170+
-spec file_encrypt_state_match(#dek_snapshot{},
171+
#file_encr_state{}) -> boolean().
172+
file_encrypt_state_match(#dek_snapshot{active_key = ActiveKey},
173+
#file_encr_state{key = FileActiveKey}) ->
174+
get_key_id(ActiveKey) =:= get_key_id(FileActiveKey).
175+
176+
-spec is_file_encr_by_ds(string(), #dek_snapshot{}) -> true | false.
177+
is_file_encr_by_ds(Path, #dek_snapshot{active_key = undefined}) ->
178+
not is_file_encrypted(Path);
179+
is_file_encr_by_ds(Path, #dek_snapshot{active_key = #{id := KeyId}}) ->
180+
{ok, File} = file:open(Path, [read, raw, binary]),
181+
try
182+
header_key_match(file:read(File, ?ENCRYPTED_FILE_HEADER_LEN), KeyId)
183+
after
184+
file:close(File)
185+
end.
186+
187+
-spec is_file_encrypted(string()) -> true | false.
188+
is_file_encrypted(Path) ->
189+
{ok, File} = file:open(Path, [read, raw, binary]),
190+
try
191+
is_valid_encr_header(file:read(File, ?ENCRYPTED_FILE_HEADER_LEN))
192+
after
193+
file:close(File)
194+
end.
195+
167196
-spec read_file(string(), cb_deks:dek_kind() | fun(() -> fetch_deks_res())) ->
168197
{decrypted, binary()} | {raw, binary()} | {error, term()}.
169198
read_file(Path, GetDekSnapshotFun) when is_function(GetDekSnapshotFun, 0) ->
@@ -352,6 +381,31 @@ get_dek_rotation_interval(Type, Snapshot) ->
352381
%%% Internal functions
353382
%%%===================================================================
354383

384+
is_valid_encr_header({ok, HeaderData}) ->
385+
case parse_header(HeaderData) of
386+
{ok, _Parsed} ->
387+
true;
388+
_ ->
389+
false
390+
end;
391+
is_valid_encr_header(eof) ->
392+
false.
393+
394+
header_key_match({ok, HeaderData}, KeyId) ->
395+
case parse_header(HeaderData) of
396+
{ok, {0, KeyId, ?ENCRYPTED_FILE_HEADER_LEN, _Rest}} ->
397+
true;
398+
_ ->
399+
false
400+
end;
401+
header_key_match(eof, _KeyId) ->
402+
false.
403+
404+
get_key_id(undefined) ->
405+
undefined;
406+
get_key_id(#{id := KeyId} = _Key) ->
407+
KeyId.
408+
355409
encrypt_internal(Data, AD, IVRandom, IVAtomic, #{type := 'raw-aes-gcm',
356410
info := #{key := KeyFun}}) ->
357411
IV = new_aes_gcm_iv(IVRandom, IVAtomic),

0 commit comments

Comments
 (0)