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
-export([convert_secret/2]).
%% Target of `emqx_secret:wrap/3`
-export([load/1]).
%% @doc Secret value.
-type t() :: binary().
@ -74,31 +71,15 @@ convert_secret(Secret, #{}) ->
end.
-spec wrap(source()) -> emqx_secret:t(t()).
wrap(Source) ->
emqx_secret:wrap(?MODULE, load, Source).
wrap(<<"file://", Filename/binary>>) ->
emqx_secret:wrap_load({file, Filename});
wrap(Secret) ->
emqx_secret:wrap(Secret).
-spec source(emqx_secret:t(t())) -> source().
source(Secret) when is_function(Secret) ->
emqx_secret:term(Secret);
source(emqx_secret:term(Secret));
source({file, Filename}) ->
<<"file://", Filename/binary>>;
source(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).
%% API:
-export([wrap/1, wrap/3, unwrap/1, term/1]).
-export([wrap/1, wrap_load/1, unwrap/1, term/1]).
-export_type([t/1]).
-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
%%================================================================================
@ -37,12 +41,12 @@ wrap(Term) ->
Term
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.
-spec wrap(module(), atom(), _Term) -> t(_).
wrap(Module, Function, Term) ->
-spec wrap_load(emqx_secret_loader:source()) -> t(_).
wrap_load(Source) ->
fun() ->
apply(Module, Function, [Term])
apply(?LOADER, load, [Source])
end.
%% @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).
-export([ident/1]).
-include_lib("eunit/include/eunit.hrl").
wrap_unwrap_test() ->
@ -32,16 +30,30 @@ unwrap_immediate_test() ->
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(
ident({foo, bar}),
emqx_secret:unwrap(emqx_secret:wrap(?MODULE, ident, {foo, bar}))
{file, "no/such/file/i/swear"},
emqx_secret:term(emqx_secret:wrap_load({file, "no/such/file/i/swear"}))
).
wrap_unwrap_transform_test() ->
?assertEqual(
<<"this_was_an_atom">>,
emqx_secret:unwrap(emqx_secret:wrap(erlang, atom_to_binary, this_was_an_atom))
wrap_unwrap_missing_file_test() ->
?assertThrow(
#{msg := failed_to_read_secret_file, reason := "No such file or directory"},
emqx_secret:unwrap(emqx_secret:wrap_load({file, "no/such/file/i/swear"}))
).
wrap_term_test() ->
@ -50,12 +62,6 @@ wrap_term_test() ->
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() ->
Term = {foo, bar},
?assertError(
@ -63,7 +69,8 @@ external_fun_term_error_test() ->
emqx_secret:term(fun() -> Term end)
).
%%
ident(X) ->
X.
write_temp_file(Bytes) ->
Ts = erlang:system_time(millisecond),
Filename = filename:join("/tmp", ?MODULE_STRING ++ integer_to_list(-Ts)),
ok = file:write_file(Filename, Bytes),
Filename.