summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Avdey <eiri@eiri.ca>2020-04-21 00:25:35 -0300
committerEric Avdey <eiri@eiri.ca>2020-04-21 00:25:35 -0300
commitd17604b0dc0d643e44b06926e506b5620b919cae (patch)
tree60ed38493fb3a9bcbd6d5bd2edfa24228e1d0d77
parentc971c3e85d7fc3d9bad995605efba7d8740ec217 (diff)
downloadcouchdb-d17604b0dc0d643e44b06926e506b5620b919cae.tar.gz
Convert aegis_key_manager into gen server
Convert aegis_key_manager into gen_server and make it keep private state for key manager callback module. Rename aegis_file_key_manager into aegis_example_key_manager to clarify intent and adapt it to new aegis_key_manager behaviour
-rw-r--r--src/aegis/src/aegis_example_key_manager.erl (renamed from src/aegis/src/aegis_file_key_manager.erl)21
-rw-r--r--src/aegis/src/aegis_key_manager.erl90
-rw-r--r--src/aegis/src/aegis_sup.erl5
-rw-r--r--src/aegis/test/aegis_key_manager_test.erl84
4 files changed, 186 insertions, 14 deletions
diff --git a/src/aegis/src/aegis_file_key_manager.erl b/src/aegis/src/aegis_example_key_manager.erl
index 6522c76c5..5375e59c6 100644
--- a/src/aegis/src/aegis_file_key_manager.erl
+++ b/src/aegis/src/aegis_example_key_manager.erl
@@ -10,31 +10,36 @@
% License for the specific language governing permissions and limitations under
% the License.
--module(aegis_file_key_manager).
+-module(aegis_example_key_manager).
-behaviour(aegis_key_manager).
-export([
- generate_key/2,
- unwrap_key/2
+ init/0,
+ generate_key/3,
+ unwrap_key/3
]).
--define(ROOT_KEY, <<1:256>>).
+init() ->
+ <<1:256>>.
-generate_key(#{} = _Db, _Options) ->
+
+generate_key(RootKey, #{} = _Db, _Options) ->
DbKey = crypto:strong_rand_bytes(32),
- WrappedKey = aegis_keywrap:key_wrap(?ROOT_KEY, DbKey),
+ WrappedKey = aegis_keywrap:key_wrap(RootKey, DbKey),
+
%% just an example of how to represent the arbitrary options
AegisConfig = {<<"wrapped_key">>, WrappedKey},
{ok, DbKey, AegisConfig}.
-unwrap_key(#{} = _Db, {<<"wrapped_key">>, WrappedKey} = AegisConfig) ->
- case aegis_keywrap:key_unwrap(?ROOT_KEY, WrappedKey) of
+unwrap_key(RootKey, #{} = _Db, AegisConfig) ->
+ {<<"wrapped_key">>, WrappedKey} = AegisConfig,
+ case aegis_keywrap:key_unwrap(RootKey, WrappedKey) of
fail ->
error(unwrap_failed);
DbKey ->
diff --git a/src/aegis/src/aegis_key_manager.erl b/src/aegis/src/aegis_key_manager.erl
index f53b98c32..1aa44194a 100644
--- a/src/aegis/src/aegis_key_manager.erl
+++ b/src/aegis/src/aegis_key_manager.erl
@@ -12,26 +12,104 @@
-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(Db :: #{}, DbOptions :: list()) ->
- {ok, key(), aegis_config()} | {ok, false}.
--callback unwrap_key(Db :: #{}, AegisConfig :: aegis_config()) ->
- {ok, key(), aegis_config()}.
+-callback generate_key(
+ St :: key_manager_state(),
+ Db :: #{},
+ DbOptions :: list()) ->
+ {ok, key(), aegis_config()} | {ok, false}.
+-callback unwrap_key(
+ St :: key_manager_state(),
+ 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) ->
- ?AEGIS_KEY_MANAGER:generate_key(Db, Options).
+ gen_server:call(?MODULE, {generate_key, Db, Options}).
unwrap_key(#{} = Db, AegisConfig) ->
- ?AEGIS_KEY_MANAGER: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}.
diff --git a/src/aegis/src/aegis_sup.erl b/src/aegis/src/aegis_sup.erl
index 6d3ee83d8..08dce4d3e 100644
--- a/src/aegis/src/aegis_sup.erl
+++ b/src/aegis/src/aegis_sup.erl
@@ -38,6 +38,11 @@ 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
new file mode 100644
index 000000000..3c0b9eb67
--- /dev/null
+++ b/src/aegis/test/aegis_key_manager_test.erl
@@ -0,0 +1,84 @@
+% 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),
+ {ok, false}
+ end),
+ ?assertEqual({ok, 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(#{}, [])).