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;