Skip to content

Commit 96db79e

Browse files
MB-61292: GC DEKs when attempting to delete secret
Scenario: 1. Bucket encryption is enabled, secret s is used as a key 2. User disables encryption for that bucket 3. The bucket still contains encrypted data, so deletion of the secret s does not work 4. Ns-server runs a compaction (scheduled) 5. Now deletion of secret should work because the bucket doesn't have any encrypted data Step 5 doesn't work without this patch Change-Id: I439fdae425a4096531d0893664420ffac8f956ff Reviewed-on: https://review.couchbase.org/c/ns_server/+/216785 Tested-by: Timofey Barmin <[email protected]> Reviewed-by: Navdeep S Boparai <[email protected]> Well-Formed: Build Bot <[email protected]>
1 parent 9774a42 commit 96db79e

File tree

1 file changed

+46
-5
lines changed

1 file changed

+46
-5
lines changed

apps/ns_server/src/cb_cluster_secrets.erl

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
-define(DEK_TIMER_RETRY_TIME_S, ?get_param(dek_retry_interval, 60)).
2828
-define(DEK_DROP_RETRY_TIME_S(Kind),
2929
?get_param({dek_removal_min_interval, Kind}, 60*60*3)).
30+
-define(MIN_DEK_GC_INTERVAL_S, ?get_param(min_dek_gc_interval, 60)).
3031

3132
-ifndef(TEST).
3233
-define(MIN_RECHECK_ROTATION_INTERVAL, ?get_param(min_rotation_recheck_interval,
@@ -138,6 +139,7 @@
138139
deks := [cb_deks:dek()],
139140
is_enabled := boolean(),
140141
deks_being_dropped := [cb_deks:dek_id()],
142+
last_deks_gc_datetime := undefined | calendar:datetime(),
141143
last_drop_timestamp := undefined | non_neg_integer()}.
142144

143145
%%%===================================================================
@@ -474,10 +476,16 @@ handle_call(get_node_deks_info, _From,
474476
K#{info => maps:remove(key, Info)}
475477
end, Keys)
476478
end,
477-
#state{deks = Deks} = NewState,
479+
480+
%% Run gc for deks; it is usefull in case if a compaction has been run
481+
%% recently.
482+
NewState2 = maps:fold(fun (Kind, _, Acc) ->
483+
maybe_garbage_collect_deks(Kind, Acc)
484+
end, NewState, NewState#state.deks),
485+
#state{deks = Deks} = NewState2,
478486
Res = maps:map(fun (_K, #{deks := Keys}) -> StripKeyMaterial(Keys) end,
479487
Deks),
480-
{reply, Res, NewState}
488+
{reply, Res, NewState2}
481489
else
482490
[_ | _] ->
483491
%% There are still unfinished jobs. That means deks in our state
@@ -896,10 +904,37 @@ maybe_update_deks(Kind, #state{deks = CurDeks} = OldState) ->
896904
{ok, OldState#state{deks = maps:remove(Kind, CurDeks)}}
897905
end.
898906

907+
-spec maybe_garbage_collect_deks(cb_deks:dek_kind(), #state{}) -> #state{}.
908+
maybe_garbage_collect_deks(Kind, #state{deks = DeksInfo} = State) ->
909+
ShouldRun =
910+
case maps:find(Kind, DeksInfo) of
911+
{ok, #{last_deks_gc_datetime := undefined}} ->
912+
true;
913+
{ok, #{last_deks_gc_datetime := DT}} ->
914+
%% The goal is to not call it too often
915+
Deadline = misc:datetime_add(DT, ?MIN_DEK_GC_INTERVAL_S),
916+
calendar:universal_time() > Deadline;
917+
error ->
918+
false
919+
end,
920+
case ShouldRun of
921+
true ->
922+
case garbage_collect_deks(Kind, State) of
923+
{ok, NewState} -> NewState;
924+
{error, NewState, Error} ->
925+
?log_error("Garbage collecting DEKs failed: ~p", [Error]),
926+
NewState
927+
end;
928+
false ->
929+
?log_debug("Skipping garbage collect for ~p", [Kind]),
930+
State
931+
end.
932+
899933
%% Remove DEKs that are not being used anymore
900934
-spec garbage_collect_deks(cb_deks:dek_kind(), #state{}) ->
901935
{ok, #state{}} | {error, #state{}, term()}.
902936
garbage_collect_deks(Kind, #state{deks = DeksInfo} = State) ->
937+
?log_debug("Garbage collecting ~p DEKs", [Kind]),
903938
case maps:find(Kind, DeksInfo) of
904939
{ok, #{active_id := _ActiveId,
905940
deks := []}} ->
@@ -908,10 +943,14 @@ garbage_collect_deks(Kind, #state{deks = DeksInfo} = State) ->
908943
{ok, #{is_enabled := true, deks := [_]}} ->
909944
%% If encryption is enabled, one dek is a minimum. Can't be removed.
910945
{ok, State};
911-
{ok, #{}} ->
946+
{ok, #{} = KindDeks} ->
912947
case call_dek_callback(get_ids_in_use_callback, Kind, []) of
913948
{succ, {ok, IdList}} ->
914-
retire_unused_deks(Kind, IdList, State);
949+
NewKindDeks = KindDeks#{last_deks_gc_datetime =>
950+
calendar:universal_time()},
951+
NewState = State#state{deks = DeksInfo#{
952+
Kind => NewKindDeks}},
953+
retire_unused_deks(Kind, IdList, NewState);
915954
{succ, {error, not_found}} ->
916955
%% The entity that uses deks does not exist.
917956
%% Ignoring it here because we assume that deks will
@@ -1033,11 +1072,13 @@ read_deks(Kind, #state{deks = AllDeks} = State) ->
10331072
CurDeksDropped = maps:get(deks_being_dropped, CurKindDeks, []),
10341073
CurDeksDroppedCleaned = CurDeksDropped -- (CurDeksDropped -- DekIds),
10351074
CurLastDropTS = maps:get(last_drop_timestamp, CurKindDeks, undefined),
1075+
GCDeksDT = maps:get(last_deks_gc_datetime, CurKindDeks, undefined),
10361076
KindDeks = #{active_id => ActiveDek,
10371077
deks => Deks,
10381078
is_enabled => IsEnabled,
10391079
deks_being_dropped => CurDeksDroppedCleaned,
1040-
last_drop_timestamp => CurLastDropTS},
1080+
last_drop_timestamp => CurLastDropTS,
1081+
last_deks_gc_datetime => GCDeksDT},
10411082
functools:chain(State#state{deks = AllDeks#{Kind => KindDeks}},
10421083
[restart_dek_cleanup_timer(_),
10431084
restart_dek_rotation_timer(_)]).

0 commit comments

Comments
 (0)