diff --git a/apps/emqx/src/emqx_schema_secret.erl b/apps/emqx/src/emqx_schema_secret.erl index 865d0eac9..635285ce7 100644 --- a/apps/emqx/src/emqx_schema_secret.erl +++ b/apps/emqx/src/emqx_schema_secret.erl @@ -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. diff --git a/apps/emqx/src/emqx_secret.erl b/apps/emqx/src/emqx_secret.erl index ad0194201..dfbfa488e 100644 --- a/apps/emqx/src/emqx_secret.erl +++ b/apps/emqx/src/emqx_secret.erl @@ -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. diff --git a/apps/emqx/src/emqx_secret_loader.erl b/apps/emqx/src/emqx_secret_loader.erl new file mode 100644 index 000000000..2e99587bf --- /dev/null +++ b/apps/emqx/src/emqx_secret_loader.erl @@ -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. diff --git a/apps/emqx/test/emqx_secret_tests.erl b/apps/emqx/test/emqx_secret_tests.erl index ab0866c10..cd6588c83 100644 --- a/apps/emqx/test/emqx_secret_tests.erl +++ b/apps/emqx/test/emqx_secret_tests.erl @@ -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.