You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2020/04/22 20:13:05 UTC
[couchdb] 01/01: Simplify aegis design
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a commit to branch aegis_key_cache_rnewson
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit a37f763f38e324f3c2515e7ba2d96394b8c29ca6
Author: Robert Newson <rn...@apache.org>
AuthorDate: Wed Apr 22 15:59:38 2020 +0100
Simplify aegis design
---
src/aegis/src/aegis.erl | 45 ++++----------
src/aegis/src/aegis_key_manager.erl | 98 ++-----------------------------
src/aegis/src/aegis_noop_key_manager.erl | 16 ++---
src/aegis/src/aegis_server.erl | 50 ++++++++++------
src/aegis/src/aegis_sup.erl | 5 --
src/aegis/test/aegis_key_manager_test.erl | 84 --------------------------
src/aegis/test/aegis_server_test.erl | 26 ++++----
src/fabric/src/fabric2_fdb.erl | 4 +-
8 files changed, 68 insertions(+), 260 deletions(-)
diff --git a/src/aegis/src/aegis.erl b/src/aegis/src/aegis.erl
index 83535bf..1f8286a 100644
--- a/src/aegis/src/aegis.erl
+++ b/src/aegis/src/aegis.erl
@@ -14,12 +14,9 @@
-include_lib("fabric/include/fabric2.hrl").
--define(WRAPPED_KEY, {?DB_AEGIS, 1}).
-
-
-export([
- create/2,
- open/2,
+ init_db/2,
+ open_db/2,
decrypt/2,
decrypt/3,
@@ -27,45 +24,26 @@
wrap_fold_fun/2
]).
-create(#{} = Db, Options) ->
- #{
- tx := Tx,
- db_prefix := DbPrefix
- } = Db,
-
- {ok, AegisConfig} = aegis_server:generate_key(Db, Options),
-
- FDBKey = erlfdb_tuple:pack(?WRAPPED_KEY, DbPrefix),
- Packed = erlfdb_tuple:pack({AegisConfig}),
- ok = erlfdb:set(Tx, FDBKey, Packed),
-
+init_db(#{} = Db, Options) ->
Db#{
- aegis => AegisConfig
+ is_encrypted := aegis_server:init_db(Db, Options)
}.
-open(#{} = Db, _Options) ->
- #{
- tx := Tx,
- db_prefix := DbPrefix
- } = Db,
-
- FDBKey = erlfdb_tuple:pack(?WRAPPED_KEY, DbPrefix),
- Packed = erlfdb:wait(erlfdb:get(Tx, FDBKey)),
- {AegisConfig} = erlfdb_tuple:unpack(Packed),
-
+open_db(#{} = Db, Options) ->
Db#{
- aegis => AegisConfig
+ is_encrypted := aegis_server:open_db(Db, Options)
}.
encrypt(#{} = _Db, _Key, <<>>) ->
<<>>;
-encrypt(#{aegis := false}, _Key, Value) when is_binary(Value) ->
+encrypt(#{is_encrypted := false}, _Key, Value) when is_binary(Value) ->
Value;
-encrypt(#{} = Db, Key, Value) when is_binary(Key), is_binary(Value) ->
+encrypt(#{is_encrypted := true} = Db, Key, Value)
+ when is_binary(Key), is_binary(Value) ->
aegis_server:encrypt(Db, Key, Value).
@@ -77,10 +55,11 @@ decrypt(#{} = Db, Rows) when is_list(Rows) ->
decrypt(#{} = _Db, _Key, <<>>) ->
<<>>;
-decrypt(#{aegis := false}, _Key, Value) when is_binary(Value) ->
+decrypt(#{is_encrypted := false}, _Key, Value) when is_binary(Value) ->
Value;
-decrypt(#{} = Db, Key, Value) when is_binary(Key), is_binary(Value) ->
+decrypt(#{is_encrypted := true} = Db, Key, Value)
+ when is_binary(Key), is_binary(Value) ->
aegis_server:decrypt(Db, Key, Value).
diff --git a/src/aegis/src/aegis_key_manager.erl b/src/aegis/src/aegis_key_manager.erl
index 1d24de5..593f4fc 100644
--- a/src/aegis/src/aegis_key_manager.erl
+++ b/src/aegis/src/aegis_key_manager.erl
@@ -12,104 +12,14 @@
-module(aegis_key_manager).
--behaviour(gen_server).
-
-vsn(1).
--type key() :: binary().
--type aegis_config() :: term().
--type key_manager_state() :: term().
-
-
--callback init() -> key_manager_state().
-
-
--callback generate_key(
- St :: key_manager_state(),
+-callback init_db(
Db :: #{},
- DbOptions :: list()) ->
- {ok, key(), aegis_config()} | false.
+ DbOptions :: list()) -> {ok, binary()} | false.
--callback unwrap_key(
- St :: key_manager_state(),
+-callback open_db(
Db :: #{},
- AegisConfig :: aegis_config()) ->
- {ok, key(), aegis_config()}.
-
-
-%% aegis_key_manager API
--export([
- start_link/0,
- generate_key/2,
- unwrap_key/2
-]).
-
-
-%% gen_server callbacks
--export([
- init/1,
- terminate/2,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- code_change/3
-]).
-
-
-
-start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-
-
-generate_key(#{} = Db, Options) ->
- gen_server:call(?MODULE, {generate_key, Db, Options}).
-
-
-unwrap_key(#{} = Db, AegisConfig) ->
- gen_server:call(?MODULE, {unwrap_key, Db, AegisConfig}).
-
-
-
-
-init([]) ->
- process_flag(sensitive, true),
- Store = ets:new(?MODULE, [set, private]),
- State = ?AEGIS_KEY_MANAGER:init(),
- true = ets:insert(Store, {?AEGIS_KEY_MANAGER, State}),
- {ok, Store}.
-
-
-terminate(_Reason, _Store) ->
- ok.
-
-
-handle_call({Method, Db, Opts}, From, Store)
- when Method == generate_key; Method == unwrap_key ->
- [{?AEGIS_KEY_MANAGER, State}] = ets:lookup(Store, ?AEGIS_KEY_MANAGER),
- erlang:spawn(fun() ->
- process_flag(sensitive, true),
- try
- ?AEGIS_KEY_MANAGER:Method(State, Db, Opts)
- of
- Resp ->
- gen_server:reply(From, Resp)
- catch
- _:Error ->
- gen_server:reply(From, {error, Error})
- end
- end),
- {noreply, Store}.
-
-
-handle_cast(_Msg, Store) ->
- {noreply, Store}.
-
-
-handle_info(_Msg, Store) ->
- {noreply, Store}.
-
-
-code_change(_OldVsn, Store, _Extra) ->
- {ok, Store}.
+ DbOptions :: list()) -> {ok, binary()} | false.
diff --git a/src/aegis/src/aegis_noop_key_manager.erl b/src/aegis/src/aegis_noop_key_manager.erl
index bb6bd72..9c78986 100644
--- a/src/aegis/src/aegis_noop_key_manager.erl
+++ b/src/aegis/src/aegis_noop_key_manager.erl
@@ -17,20 +17,14 @@
-export([
- init/0,
- generate_key/3,
- unwrap_key/3
+ init_db/2,
+ open_db/2
]).
-
-init() ->
- [].
-
-
-generate_key([], #{} = _Db, _Options) ->
+init_db(#{} = _Db, _Options) ->
false.
-unwrap_key([], #{} = _Db, _AegisConfig) ->
- erlang:error(invalid_operation).
+open_db(#{} = _Db, _Options) ->
+ false.
diff --git a/src/aegis/src/aegis_server.erl b/src/aegis/src/aegis_server.erl
index f4973c2..04cbf0e 100644
--- a/src/aegis/src/aegis_server.erl
+++ b/src/aegis/src/aegis_server.erl
@@ -23,7 +23,8 @@
%% aegis_server API
-export([
start_link/0,
- generate_key/2,
+ init_db/2,
+ open_db/2,
encrypt/3,
decrypt/3
]).
@@ -40,8 +41,8 @@
%% workers callbacks
-export([
- do_generate_key/2,
- do_unwrap_key/1,
+ do_init_db/2,
+ do_open_db/2,
do_encrypt/5,
do_decrypt/5
]).
@@ -58,10 +59,14 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
--spec generate_key(Db :: #{}, Options :: list()) ->
- {ok, term() | false} | {error, term()}.
-generate_key(#{} = Db, Options) ->
- gen_server:call(?MODULE, {generate_key, Db, Options}).
+-spec init_db(Db :: #{}, Options :: list()) -> boolean().
+init_db(#{} = Db, Options) ->
+ gen_server:call(?MODULE, {init_db, Db, Options}).
+
+
+-spec open_db(Db :: #{}, Options :: list()) -> boolean().
+open_db(#{} = Db, Options) ->
+ gen_server:call(?MODULE, {open_db, Db, Options}).
-spec encrypt(Db :: #{}, Key :: binary(), Value :: binary()) -> binary().
@@ -97,23 +102,32 @@ terminate(_Reason, St) ->
dict:fold(fun(_AegisConfig, WaitList, _) ->
lists:foreach(fun(#{from := From}) ->
- gen_server:reply(From, {error, decryption_aborted})
+ gen_server:reply(From, {error, operation_aborted})
end, WaitList)
end, ok, Waiters),
dict:fold(fun(Ref, From, _) ->
erlang:demonitor(Ref),
- gen_server:reply(From, {error, decryption_aborted})
+ gen_server:reply(From, {error, operation_aborted})
end, ok, Openers),
ok.
-handle_call({generate_key, Db, Options}, From, #{openers := Openers} = St) ->
+handle_call({init_db, Db, Options}, From, #{openers := Openers} = St) ->
+ #{
+ uuid := UUID
+ } = Db,
+
+ {_, Ref} = erlang:spawn_monitor(?MODULE, do_init_db, [Db, Options]),
+ Openers1 = dict:store(Ref, {UUID, From}, Openers),
+ {noreply, St#{openers := Openers1}, ?TIMEOUT};
+
+handle_call({open_db, Db, Options}, From, #{openers := Openers} = St) ->
#{
uuid := UUID
} = Db,
- {_, Ref} = erlang:spawn_monitor(?MODULE, do_generate_key, [Db, Options]),
+ {_, Ref} = erlang:spawn_monitor(?MODULE, do_open_db, [Db, Options]),
Openers1 = dict:store(Ref, {UUID, From}, Openers),
{noreply, St#{openers := Openers1}, ?TIMEOUT};
@@ -135,10 +149,10 @@ handle_cast(_Msg, St) ->
handle_info({'DOWN', Ref, _, _Pid, false}, #{openers := Openers} = St) ->
{{_UUID, From}, Openers1} = dict:take(Ref, Openers),
- gen_server:reply(From, {ok, false}),
+ gen_server:reply(From, false),
{noreply, St#{openers := Openers1}, ?TIMEOUT};
-handle_info({'DOWN', Ref, _, _Pid, {ok, DbKey, AegisConfig}}, St) ->
+handle_info({'DOWN', Ref, _, _Pid, {ok, DbKey}}, St) ->
#{
cache := Cache,
openers := Openers,
@@ -149,7 +163,7 @@ handle_info({'DOWN', Ref, _, _Pid, {ok, DbKey, AegisConfig}}, St) ->
case dict:take(Ref, Openers) of
{{UUID, From}, Openers1} ->
ok = insert(Cache, UUID, DbKey),
- gen_server:reply(From, {ok, AegisConfig}),
+ gen_server:reply(From, true),
{noreply, St#{openers := Openers1}, ?TIMEOUT};
error ->
{UUID, Unwrappers1} = dict:take(Ref, Unwrappers),
@@ -202,10 +216,10 @@ code_change(_OldVsn, St, _Extra) ->
%% workers functions
-do_generate_key(#{} = Db, Options) ->
+do_init_db(#{} = Db, Options) ->
process_flag(sensitive, true),
try
- aegis_key_manager:generate_key(Db, Options)
+ ?AEGIS_KEY_MANAGER:init_db(Db, Options)
of
Resp ->
exit(Resp)
@@ -215,10 +229,10 @@ do_generate_key(#{} = Db, Options) ->
end.
-do_unwrap_key(#{aegis := AegisConfig} = Db) ->
+do_open_db(#{} = Db, Options) ->
process_flag(sensitive, true),
try
- aegis_key_manager:unwrap_key(Db, AegisConfig)
+ ?AEGIS_KEY_MANAGER:open_db(Db, Options)
of
Resp ->
exit(Resp)
diff --git a/src/aegis/src/aegis_sup.erl b/src/aegis/src/aegis_sup.erl
index 08dce4d..6d3ee83 100644
--- a/src/aegis/src/aegis_sup.erl
+++ b/src/aegis/src/aegis_sup.erl
@@ -38,11 +38,6 @@ init([]) ->
},
Children = [
#{
- id => aegis_key_manager,
- start => {aegis_key_manager, start_link, []},
- shutdown => 5000
- },
- #{
id => aegis_server,
start => {aegis_server, start_link, []},
shutdown => 5000
diff --git a/src/aegis/test/aegis_key_manager_test.erl b/src/aegis/test/aegis_key_manager_test.erl
deleted file mode 100644
index fa46817..0000000
--- a/src/aegis/test/aegis_key_manager_test.erl
+++ /dev/null
@@ -1,84 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-% http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(aegis_key_manager_test).
-
-
--include_lib("eunit/include/eunit.hrl").
--include_lib("couch/include/couch_eunit.hrl").
-
-
--define(ROOT_KEY, <<7:256>>).
--define(CB_REPLY, {ok, <<5:256>>, {my_wrapped_key, <<5:320>>}}).
-
-
-
-basic_test_() ->
- {
- setup,
- fun setup/0,
- fun teardown/1,
- [
- {"provide state and pass through normal reply for generate_key",
- fun generate_key/0},
- {"provide state and pass through disable reply for generate_key",
- fun generate_key_encryption_disabled/0},
- {"provide state and pass through normal reply for unwrap_key",
- fun unwrap_key/0},
- {"provide state and pass through error for unwrap_key",
- fun unwrap_key_error/0}
- ]
- }.
-
-
-setup() ->
- %% mock callback first, so aegis_key_manager store expected state
- meck:new([?AEGIS_KEY_MANAGER], [passthrough]),
- ok = meck:expect(?AEGIS_KEY_MANAGER, init, 0, ?ROOT_KEY),
- test_util:start_couch([fabric]).
-
-
-teardown(Ctx) ->
- test_util:stop_couch(Ctx),
- meck:unload().
-
-
-generate_key() ->
- ok = meck:expect(?AEGIS_KEY_MANAGER, generate_key, fun(St, _, _) ->
- ?assertEqual(?ROOT_KEY, St),
- ?CB_REPLY
- end),
- ?assertEqual(?CB_REPLY, aegis_key_manager:generate_key(#{}, [])).
-
-
-generate_key_encryption_disabled() ->
- ok = meck:expect(?AEGIS_KEY_MANAGER, generate_key, fun(St, _, _) ->
- ?assertEqual(?ROOT_KEY, St),
- false
- end),
- ?assertEqual(false, aegis_key_manager:generate_key(#{}, [])).
-
-
-unwrap_key() ->
- ok = meck:expect(?AEGIS_KEY_MANAGER, unwrap_key, fun(St, _, _) ->
- ?assertEqual(?ROOT_KEY, St),
- ?CB_REPLY
- end),
- ?assertEqual(?CB_REPLY, aegis_key_manager:unwrap_key(#{}, [])).
-
-
-unwrap_key_error() ->
- ok = meck:expect(?AEGIS_KEY_MANAGER, unwrap_key, fun(St, _, _) ->
- ?assertEqual(?ROOT_KEY, St),
- erlang:error(unwrap_failed)
- end),
- ?assertEqual({error,unwrap_failed}, aegis_key_manager:unwrap_key(#{}, [])).
diff --git a/src/aegis/test/aegis_server_test.erl b/src/aegis/test/aegis_server_test.erl
index b9b4588..b1082f8 100644
--- a/src/aegis/test/aegis_server_test.erl
+++ b/src/aegis/test/aegis_server_test.erl
@@ -16,7 +16,7 @@
-include_lib("couch/include/couch_eunit.hrl").
-define(SERVER, aegis_server).
--define(DB, #{aegis => <<0:320>>, uuid => <<0:64>>}).
+-define(DB, #{uuid => <<0:64>>}).
-define(VALUE, <<0:8192>>).
-define(ENCRYPTED, <<1:8, 0:320, 0:4096>>).
-define(TIMEOUT, 10000).
@@ -29,8 +29,8 @@ basic_test_() ->
fun setup/0,
fun teardown/1,
[
- {"cache unwrapped key on generate_key",
- {timeout, ?TIMEOUT, fun test_generate_key/0}},
+ {"cache unwrapped key on init_db",
+ {timeout, ?TIMEOUT, fun test_init_db/0}},
{"cache unwrapped key on encrypt",
{timeout, ?TIMEOUT, fun test_encrypt/0}},
{"cache unwrapped key on decrypt",
@@ -44,7 +44,7 @@ basic_test_() ->
setup() ->
Ctx = test_util:start_couch([fabric]),
meck:new([aegis_server, aegis_key_manager], [passthrough]),
- ok = meck:expect(aegis_key_manager, generate_key, fun(Db, _) ->
+ ok = meck:expect(aegis_key_manager, init_db, fun(Db, _) ->
DbKey = <<0:256>>,
#{aegis := AegisConfig} = Db,
{ok, DbKey, AegisConfig}
@@ -68,10 +68,10 @@ teardown(Ctx) ->
test_util:stop_couch(Ctx).
-test_generate_key() ->
- {ok, WrappedKey1} = aegis_server:generate_key(?DB, []),
+test_init_db() ->
+ {ok, WrappedKey1} = aegis_server:init_db(?DB, []),
?assertEqual(<<0:320>>, WrappedKey1),
- ?assertEqual(1, meck:num_calls(aegis_key_manager, generate_key, 2)).
+ ?assertEqual(1, meck:num_calls(aegis_key_manager, init_db, 2)).
test_encrypt() ->
@@ -174,14 +174,14 @@ disabled_test_() ->
foreach,
fun() ->
Ctx = setup(),
- ok = meck:delete(aegis_key_manager, generate_key, 2),
- ok = meck:expect(aegis_key_manager, generate_key, 2, false),
+ ok = meck:delete(aegis_key_manager, init_db, 2),
+ ok = meck:expect(aegis_key_manager, init_db, 2, false),
Ctx
end,
fun teardown/1,
[
{"accept false from key managers",
- {timeout, ?TIMEOUT, fun test_disabled_generate_key/0}},
+ {timeout, ?TIMEOUT, fun test_disabled_init_db/0}},
{"pass through on encrypt when encryption disabled",
{timeout, ?TIMEOUT, fun test_disabled_encrypt/0}},
{"pass through on decrypt when encryption disabled",
@@ -190,9 +190,9 @@ disabled_test_() ->
}.
-test_disabled_generate_key() ->
- ?assertEqual({ok, false}, aegis_server:generate_key(?DB, [])),
- ?assertEqual(1, meck:num_calls(aegis_key_manager, generate_key, 2)).
+test_disabled_init_db() ->
+ ?assertEqual({ok, false}, aegis_server:init_db(?DB, [])),
+ ?assertEqual(1, meck:num_calls(aegis_key_manager, init_db, 2)).
test_disabled_encrypt() ->
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index 96f60e6..bb5afcd 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -236,7 +236,7 @@ create(#{} = Db0, Options) ->
db_options => Options1
},
- aegis:create(Db2, Options).
+ aegis:init_db(Db2, Options).
open(#{} = Db0, Options) ->
@@ -281,7 +281,7 @@ open(#{} = Db0, Options) ->
},
Db3 = load_config(Db2),
- Db4 = aegis:open(Db3, Options),
+ Db4 = aegis:open_db(Db3, Options),
case {UUID, Db4} of
{undefined, _} -> ok;