fix(secret): dedicate a specific loader module for file secrets

To make code employing `emqx_secret` easier to follow.
This commit is contained in:
Andrew Mayorov 2023-10-30 21:48:42 +07:00
parent 44b4205561
commit d278486416
No known key found for this signature in database
GPG Key ID: 2837C62ACFBFED5D
4 changed files with 84 additions and 50 deletions

View File

@ -25,9 +25,6 @@
%% HOCON Schema API %% HOCON Schema API
-export([convert_secret/2]). -export([convert_secret/2]).
%% Target of `emqx_secret:wrap/3`
-export([load/1]).
%% @doc Secret value. %% @doc Secret value.
-type t() :: binary(). -type t() :: binary().
@ -74,31 +71,15 @@ convert_secret(Secret, #{}) ->
end. end.
-spec wrap(source()) -> emqx_secret:t(t()). -spec wrap(source()) -> emqx_secret:t(t()).
wrap(Source) -> wrap(<<"file://", Filename/binary>>) ->
emqx_secret:wrap(?MODULE, load, Source). emqx_secret:wrap_load({file, Filename});
wrap(Secret) ->
emqx_secret:wrap(Secret).
-spec source(emqx_secret:t(t())) -> source(). -spec source(emqx_secret:t(t())) -> source().
source(Secret) when is_function(Secret) -> source(Secret) when is_function(Secret) ->
emqx_secret:term(Secret); source(emqx_secret:term(Secret));
source({file, Filename}) ->
<<"file://", Filename/binary>>;
source(Secret) -> source(Secret) ->
Secret. Secret.
%%
-spec load(source()) -> t().
load(<<"file://", Filename/binary>>) ->
load_file(Filename);
load(Secret) ->
Secret.
load_file(Filename) ->
case file:read_file(Filename) of
{ok, Secret} ->
string:trim(Secret, trailing, [$\n]);
{error, Reason} ->
throw(#{
msg => failed_to_read_secret_file,
path => Filename,
reason => emqx_utils:explain_posix(Reason)
})
end.

View File

@ -19,12 +19,16 @@
-module(emqx_secret). -module(emqx_secret).
%% API: %% API:
-export([wrap/1, wrap/3, unwrap/1, term/1]). -export([wrap/1, wrap_load/1, unwrap/1, term/1]).
-export_type([t/1]). -export_type([t/1]).
-opaque t(T) :: T | fun(() -> t(T)). -opaque t(T) :: T | fun(() -> t(T)).
%% Secret loader module.
%% Any changes related to processing of secrets should be made there.
-define(LOADER, emqx_secret_loader).
%%================================================================================ %%================================================================================
%% API funcions %% API funcions
%%================================================================================ %%================================================================================
@ -37,12 +41,12 @@ wrap(Term) ->
Term Term
end. end.
%% @doc Wrap a function call over a term in a secret closure. %% @doc Wrap a loader function call over a term in a secret closure.
%% This is slightly more flexible form of `wrap/1` with the same basic purpose. %% This is slightly more flexible form of `wrap/1` with the same basic purpose.
-spec wrap(module(), atom(), _Term) -> t(_). -spec wrap_load(emqx_secret_loader:source()) -> t(_).
wrap(Module, Function, Term) -> wrap_load(Source) ->
fun() -> fun() ->
apply(Module, Function, [Term]) apply(?LOADER, load, [Source])
end. end.
%% @doc Unwrap a secret closure, revealing the secret. %% @doc Unwrap a secret closure, revealing the secret.

View File

@ -0,0 +1,42 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% 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(emqx_secret_loader).
%% API
-export([load/1]).
-export([file/1]).
-export_type([source/0]).
-type source() :: {file, file:filename_all()}.
-spec load(source()) -> binary() | no_return().
load({file, Filename}) ->
file(Filename).
-spec file(file:filename_all()) -> binary() | no_return().
file(Filename) ->
case file:read_file(Filename) of
{ok, Secret} ->
string:trim(Secret, trailing);
{error, Reason} ->
throw(#{
msg => failed_to_read_secret_file,
path => Filename,
reason => emqx_utils:explain_posix(Reason)
})
end.

View File

@ -16,8 +16,6 @@
-module(emqx_secret_tests). -module(emqx_secret_tests).
-export([ident/1]).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
wrap_unwrap_test() -> wrap_unwrap_test() ->
@ -32,16 +30,30 @@ unwrap_immediate_test() ->
emqx_secret:unwrap(42) emqx_secret:unwrap(42)
). ).
wrap_unwrap_external_test() -> wrap_unwrap_load_test_() ->
Secret = <<"foobaz">>,
{
setup,
fun() -> write_temp_file(Secret) end,
fun(Filename) -> file:delete(Filename) end,
fun(Filename) ->
?_assertEqual(
Secret,
emqx_secret:unwrap(emqx_secret:wrap_load({file, Filename}))
)
end
}.
wrap_load_term_test() ->
?assertEqual( ?assertEqual(
ident({foo, bar}), {file, "no/such/file/i/swear"},
emqx_secret:unwrap(emqx_secret:wrap(?MODULE, ident, {foo, bar})) emqx_secret:term(emqx_secret:wrap_load({file, "no/such/file/i/swear"}))
). ).
wrap_unwrap_transform_test() -> wrap_unwrap_missing_file_test() ->
?assertEqual( ?assertThrow(
<<"this_was_an_atom">>, #{msg := failed_to_read_secret_file, reason := "No such file or directory"},
emqx_secret:unwrap(emqx_secret:wrap(erlang, atom_to_binary, this_was_an_atom)) emqx_secret:unwrap(emqx_secret:wrap_load({file, "no/such/file/i/swear"}))
). ).
wrap_term_test() -> wrap_term_test() ->
@ -50,12 +62,6 @@ wrap_term_test() ->
emqx_secret:term(emqx_secret:wrap(42)) emqx_secret:term(emqx_secret:wrap(42))
). ).
wrap_external_term_test() ->
?assertEqual(
this_was_an_atom,
emqx_secret:term(emqx_secret:wrap(erlang, atom_to_binary, this_was_an_atom))
).
external_fun_term_error_test() -> external_fun_term_error_test() ->
Term = {foo, bar}, Term = {foo, bar},
?assertError( ?assertError(
@ -63,7 +69,8 @@ external_fun_term_error_test() ->
emqx_secret:term(fun() -> Term end) emqx_secret:term(fun() -> Term end)
). ).
%% write_temp_file(Bytes) ->
Ts = erlang:system_time(millisecond),
ident(X) -> Filename = filename:join("/tmp", ?MODULE_STRING ++ integer_to_list(-Ts)),
X. ok = file:write_file(Filename, Bytes),
Filename.