Skip to content

Commit c54fefd

Browse files
MB-61292: Pass deks to memcached
The create bucket command should contain all deks for that bucket, and its current active key id. If the active key is empty, it means that encryption is disabled for that bucket. Every time new key is generated for a bucket ns_server should push it to memcached using new command (set_encryption_key). That command informs memcached about new dek and sets that dek as active. Change-Id: I1d193e05e5258314017088eb818332fc3dcefab6 Reviewed-on: https://review.couchbase.org/c/ns_server/+/211628 Well-Formed: Build Bot <[email protected]> Reviewed-by: Navdeep S Boparai <[email protected]> Tested-by: Build Bot <[email protected]> Tested-by: Timofey Barmin <[email protected]>
1 parent cacd8d2 commit c54fefd

File tree

6 files changed

+129
-14
lines changed

6 files changed

+129
-14
lines changed

apps/ns_server/include/mc_constants.hrl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@
124124
-define(CMD_RBAC_REFRESH, 16#f7).
125125
-define(CMD_GET_ERROR_MAP, 16#fe).
126126

127+
-define(CMD_SET_ENCRYPTION_KEY, 16#2d).
128+
127129
-define(RGET, 16#30).
128130
-define(RSET, 16#31).
129131
-define(RSETQ, 16#32).

apps/ns_server/src/cb_deks.erl

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,12 @@ maybe_reencrypt_deks(Kind, Deks, NewEncryptionKeyFun) ->
265265
%% Returns false otherwise.
266266
dek_chronicle_keys_filter(?CHRONICLE_ENCR_AT_REST_SETTINGS_KEY) ->
267267
[chronicleDek];
268-
dek_chronicle_keys_filter(_Key) -> false.
268+
dek_chronicle_keys_filter(Key) ->
269+
case ns_bucket:sub_key_match(Key) of
270+
{true, Bucket, props} -> [{bucketDek, Bucket}];
271+
{true, _Bucket, _} -> false;
272+
false -> false
273+
end.
269274

270275
%% encryption_method_callback - called to determine if encryption is enabled
271276
%% or not for that type of entity.
@@ -290,13 +295,21 @@ dek_config(chronicleDek) ->
290295
encryption_method_callback => fun chronicle_local:get_encryption/1,
291296
set_active_key_callback => fun chronicle_local:set_active_dek/1,
292297
chronicle_txn_keys => [?CHRONICLE_ENCR_AT_REST_SETTINGS_KEY],
293-
required_usage => config_encryption}.
298+
required_usage => config_encryption};
299+
dek_config({bucketDek, Bucket}) ->
300+
#{name => {bucket, Bucket},
301+
encryption_method_callback => ns_bucket:get_encryption(Bucket, _),
302+
set_active_key_callback => ns_memcached:set_active_dek(Bucket, _),
303+
chronicle_txn_keys => [ns_bucket:root(),
304+
ns_bucket:sub_key(Bucket, props)],
305+
required_usage => {bucket_encryption, Bucket}}.
294306

295307
%% Returns all possible deks kinds on the node.
296308
%% The list was supposed to be static if not buckets. Buckets can be created and
297309
%% removed in real time, so the list is dynamic because of that. Note that the
298310
%% list doesn't depend if encryption is on or off.
299311
dek_kinds_list() ->
300312
dek_kinds_list(direct).
301-
dek_kinds_list(_Snapshot) ->
302-
[chronicleDek].
313+
dek_kinds_list(Snapshot) ->
314+
Buckets = ns_bucket:get_bucket_names(Snapshot),
315+
[chronicleDek] ++ [{bucketDek, B} || B <- Buckets].

apps/ns_server/src/mc_client_binary.erl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070
update_user_permissions/2,
7171
set_collections_manifest/2,
7272
get_collections_manifest/1,
73-
set_tls_config/2
73+
set_tls_config/2,
74+
set_active_encryption_key/3
7475
]).
7576

7677
-type recv_callback() :: fun((_, _, _) -> any()) | undefined.
@@ -1029,3 +1030,22 @@ set_tls_config(Sock, TLSConfigJSON) ->
10291030
Response ->
10301031
process_error_response(Response)
10311032
end.
1033+
1034+
set_active_encryption_key(Sock, Bucket, ActiveKey) ->
1035+
report_counter(?FUNCTION_NAME),
1036+
Key = iolist_to_binary(Bucket),
1037+
%% TODO: We should pass all available keys here
1038+
AllKeys = case ActiveKey of
1039+
undefined -> [];
1040+
_ -> [ActiveKey]
1041+
end,
1042+
Value = memcached_bucket_config:format_mcd_keys(ActiveKey, AllKeys),
1043+
Entry = #mc_entry{key = Key,
1044+
data = ejson:encode(Value)},
1045+
case cmd(?CMD_SET_ENCRYPTION_KEY, Sock, undefined, undefined,
1046+
{#mc_header{}, Entry}) of
1047+
{ok, #mc_header{status=?SUCCESS}, _, _} ->
1048+
ok;
1049+
Response ->
1050+
process_error_response(Response)
1051+
end.

apps/ns_server/src/memcached_bucket_config.erl

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@
1515
-include("ns_common.hrl").
1616
-include("ns_bucket.hrl").
1717
-include_lib("ns_common/include/cut.hrl").
18+
-include("cb_cluster_secrets.hrl").
19+
20+
-define(MCD_DISABLED_ENCRYPTION_KEY_ID, <<"">>).
1821

1922
-record(cfg, {type, name, config, snapshot, engine_config, params}).
2023

2124
-export([get/1,
2225
get_bucket_config/1,
2326
ensure/2,
24-
start_params/1,
27+
start_params/3,
2528
ensure_collections/2,
26-
get_current_collections_uid/1]).
29+
get_current_collections_uid/1,
30+
format_mcd_keys/2]).
2731

2832
params(membase, BucketName, BucketConfig, MemQuota, UUID) ->
2933
{DriftAheadThreshold, DriftBehindThreshold} =
@@ -301,6 +305,18 @@ ensure(Sock, #cfg{type = memcached}) ->
301305
end, not_present),
302306
ok.
303307

308+
format_mcd_keys(ActiveDek, Deks) ->
309+
DeksJsonMcd = lists:map(fun format_mcd_key/1, Deks),
310+
ActiveKeyMcd = case ActiveDek of
311+
undefined -> ?MCD_DISABLED_ENCRYPTION_KEY_ID;
312+
#{id := ActiveId} -> ActiveId
313+
end,
314+
{[{keys, DeksJsonMcd}, {active, ActiveKeyMcd}]}.
315+
316+
format_mcd_key(#{id := Id, type := 'raw-aes-gcm', info := #{key := KeyFun}}) ->
317+
Encoded = base64:encode(KeyFun()),
318+
{[{id, Id}, {cipher, <<"AES-256-GCM">>}, {key, Encoded}]}.
319+
304320
get_current_collections_uid(Sock) ->
305321
case mc_client_binary:get_collections_manifest(Sock) of
306322
{memcached_error, no_coll_manifest, _} ->
@@ -343,7 +359,7 @@ ensure_collections(Sock, #cfg{name = BucketName, snapshot = Snapshot}) ->
343359

344360
start_params(#cfg{config = BucketConfig,
345361
params = Params,
346-
engine_config = EngineConfig}) ->
362+
engine_config = EngineConfig}, ActiveDek, Deks) ->
347363
Engine = proplists:get_value(engine, EngineConfig),
348364

349365
StaticConfigString =
@@ -368,7 +384,13 @@ start_params(#cfg{config = BucketConfig,
368384
end
369385
end, Params),
370386

371-
ExtraParams = [P || P <- [StaticConfigString, ExtraConfigString], P =/= ""],
387+
EncodedDeks = binary_to_list(ejson:encode(format_mcd_keys(ActiveDek,
388+
Deks))),
389+
390+
DeksConfigString = "encryption=" ++ EncodedDeks,
391+
392+
ExtraParams = [P || P <- [StaticConfigString, ExtraConfigString,
393+
DeksConfigString], P =/= ""],
372394
{Engine, string:join(DynamicParams ++ ExtraParams, ";")}.
373395

374396
get_bucket_config(#cfg{config = BucketConfig}) ->

apps/ns_server/src/ns_bucket.erl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@
192192
get_commits_from_snapshot/2,
193193
update_bucket_config/2,
194194
update_buckets_config/1,
195-
all_keys/1]).
195+
all_keys/1,
196+
get_encryption/2]).
196197

197198
-import(json_builder,
198199
[to_binary/1,
@@ -2517,6 +2518,22 @@ validate_encryption_secret(SecretId, Bucket, Snapshot) ->
25172518
{error, not_allowed} -> {error, secret_not_allowed}
25182519
end.
25192520

2521+
get_encryption(BucketName, Snapshot) ->
2522+
case get_bucket(BucketName, Snapshot) of
2523+
{ok, BucketConfig} ->
2524+
case proplists:get_value(encryption_secret_id, BucketConfig,
2525+
?SECRET_ID_NOT_SET) of
2526+
?SECRET_ID_NOT_SET -> {ok, disabled};
2527+
Id ->
2528+
case lists:member(node(), get_servers(BucketConfig)) of
2529+
true -> {ok, {secret, Id}};
2530+
false -> {error, not_found}
2531+
end
2532+
end;
2533+
not_present ->
2534+
{error, not_found}
2535+
end.
2536+
25202537
-ifdef(TEST).
25212538
min_live_copies_test() ->
25222539
?assertEqual(min_live_copies([node1], []), undefined),

apps/ns_server/src/ns_memcached.erl

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
-include("ns_common.hrl").
2929
-include("rbac.hrl").
3030
-include_lib("ns_common/include/cut.hrl").
31+
-include("cb_cluster_secrets.hrl").
3132

3233
-define(CHECK_INTERVAL, 10000).
3334
-define(CHECK_WARMUP_INTERVAL, 500).
@@ -42,6 +43,7 @@
4243
-define(GET_KEYS_TIMEOUT, ?get_timeout(get_keys, 60000)).
4344
-define(GET_KEYS_OUTER_TIMEOUT, ?get_timeout(get_keys_outer, 70000)).
4445
-define(MAGMA_CREATION_TIMEOUT, ?get_timeout(magma_creation, 300000)).
46+
-define(DEKS_TIMEOUT, ?get_timeout(deks, 60000)).
4547

4648
-define(RECBUF, ?get_param(recbuf, 64 * 1024)).
4749
-define(SNDBUF, ?get_param(sndbuf, 64 * 1024)).
@@ -132,7 +134,8 @@
132134
get_collections_uid/1,
133135
maybe_add_impersonate_user_frame_info/2,
134136
delete_bucket/2,
135-
get_config_stats/2
137+
get_config_stats/2,
138+
set_active_dek/2
136139
]).
137140

138141
%% for ns_memcached_sockets_pool, memcached_file_refresh only
@@ -149,6 +152,11 @@
149152
%%
150153

151154
start_link(Bucket) ->
155+
%% Sync with node monitor to make sure the key is created by the time
156+
%% when ns_memcached is started. Note that ns_memcached can't call
157+
%% cb_cluster_secrets, because cb_cluster_secrets calls ns_memcached, so
158+
%% deadlock is possible.
159+
cb_cluster_secrets:sync_with_node_monitor(),
152160
gen_server:start_link({local, server(Bucket)}, ?MODULE, Bucket, []).
153161

154162

@@ -822,7 +830,6 @@ handle_info({connect_done, WorkersCount, RV}, #state{bucket = Bucket,
822830
gen_event:notify(buckets_events, {started, Bucket}),
823831
erlang:process_flag(trap_exit, true),
824832
Self = self(),
825-
826833
case RV of
827834
{ok, Sock} ->
828835
try ensure_bucket(Sock, Bucket, false) of
@@ -942,6 +949,7 @@ handle_info(Message, #state{worker_features = WF, control_queue = Q,
942949
when Message =:= check_config_soon orelse Message =:= check_config ->
943950
misc:flush(check_config_soon),
944951
misc:flush(check_config),
952+
945953
case get_worker_features() of
946954
WF ->
947955
Self = self(),
@@ -1568,9 +1576,19 @@ do_ensure_bucket(Sock, Bucket, BConf, false) ->
15681576
{ok, DBSubDir} =
15691577
ns_storage_conf:this_node_bucket_dbdir(Bucket),
15701578
ok = filelib:ensure_dir(DBSubDir),
1571-
1579+
{ok, {ActiveDekId, DekIds, IsEnabled}} =
1580+
cb_deks:list({bucketDek, Bucket}),
1581+
{ok, Deks} = cb_deks:read({bucketDek, Bucket}, DekIds),
1582+
{value, ActiveDek} =
1583+
case IsEnabled of
1584+
true ->
1585+
lists:search(fun (#{id := Id}) -> Id == ActiveDekId end,
1586+
Deks);
1587+
_ ->
1588+
{value, undefined}
1589+
end,
15721590
{Engine, ConfigString} =
1573-
memcached_bucket_config:start_params(BConf),
1591+
memcached_bucket_config:start_params(BConf, ActiveDek, Deks),
15741592

15751593
BucketConfig = memcached_bucket_config:get_bucket_config(BConf),
15761594
Timeout = case ns_bucket:node_kv_backend_type(BucketConfig) of
@@ -1845,6 +1863,29 @@ set_tls_config(Config) ->
18451863
end
18461864
end).
18471865

1866+
set_active_dek(TypeOrBucket, ActiveDek) ->
1867+
?log_debug("Setting active encryption key id for ~p: ~p...",
1868+
[TypeOrBucket, maps:get(id, ActiveDek)]),
1869+
1870+
RV = perform_very_long_call(
1871+
fun (Sock) ->
1872+
case mc_client_binary:set_active_encryption_key(Sock,
1873+
TypeOrBucket,
1874+
ActiveDek) of
1875+
ok -> {reply, ok};
1876+
{memcached_error, S, Msg} ->
1877+
?log_error("Setting encryption key for ~p failed: ~p",
1878+
[TypeOrBucket, {S, Msg}]),
1879+
{reply, {error, {S, Msg}}}
1880+
end
1881+
end),
1882+
1883+
case RV of
1884+
ok -> ok;
1885+
{error, couldnt_connect_to_memcached} -> {error, retry};
1886+
{error, E} -> {error, E}
1887+
end.
1888+
18481889
get_bucket_stats(RootKey, StatKey, SubKey) ->
18491890
perform_very_long_call(
18501891
fun(Sock) ->

0 commit comments

Comments
 (0)