fix(emqx_plugin_libs_ssl): handle relative cert paths

This commit is contained in:
Zaiming (Stone) Shi 2022-04-05 14:20:23 +02:00
parent 66873af319
commit 0948417db8
2 changed files with 93 additions and 37 deletions

View File

@ -16,12 +16,13 @@
-module(emqx_plugin_libs_ssl). -module(emqx_plugin_libs_ssl).
-export([save_files_return_opts/2, -export([
save_files_return_opts/2,
save_files_return_opts/3, save_files_return_opts/3,
save_file/2 save_file/2
]). ]).
-type file_input_key() :: atom() | binary(). %% <<"file">> | <<"filename">> -type file_input_key() :: atom() | binary().
-type file_input() :: #{file_input_key() => binary()}. -type file_input() :: #{file_input_key() => binary()}.
%% options are below paris %% options are below paris
@ -39,6 +40,8 @@
-type opt_value() :: term(). -type opt_value() :: term().
-type opts() :: [{opt_key(), opt_value()}]. -type opts() :: [{opt_key(), opt_value()}].
-include_lib("emqx/include/logger.hrl").
%% @doc Parse ssl options input. %% @doc Parse ssl options input.
%% If the input contains file content, save the files in the given dir. %% If the input contains file content, save the files in the given dir.
%% Returns ssl options for Erlang's ssl application. %% Returns ssl options for Erlang's ssl application.
@ -48,8 +51,11 @@
%% In case it's a map, the file is saved in EMQX's `data_dir' %% In case it's a map, the file is saved in EMQX's `data_dir'
%% (unless `SubDir' is an absolute path). %% (unless `SubDir' is an absolute path).
%% NOTE: This function is now deprecated, use emqx_tls_lib:ensure_ssl_files/2 instead. %% NOTE: This function is now deprecated, use emqx_tls_lib:ensure_ssl_files/2 instead.
-spec save_files_return_opts(opts_input(), atom() | string() | binary(), -spec save_files_return_opts(
string() | binary()) -> opts(). opts_input(),
atom() | string() | binary(),
string() | binary()
) -> opts().
save_files_return_opts(Options, SubDir, ResId) -> save_files_return_opts(Options, SubDir, ResId) ->
Dir = filename:join([emqx:data_dir(), SubDir, ResId]), Dir = filename:join([emqx:data_dir(), SubDir, ResId]),
save_files_return_opts(Options, Dir). save_files_return_opts(Options, Dir).
@ -70,16 +76,26 @@ save_files_return_opts(Options, Dir) ->
KeyFile = Get(keyfile), KeyFile = Get(keyfile),
CertFile = Get(certfile), CertFile = Get(certfile),
CAFile = Get(cacertfile), CAFile = Get(cacertfile),
Key = do_save_file(KeyFile, Dir), Key = maybe_save_file(KeyFile, Dir),
Cert = do_save_file(CertFile, Dir), Cert = maybe_save_file(CertFile, Dir),
CA = do_save_file(CAFile, Dir), CA = maybe_save_file(CAFile, Dir),
Verify = GetD(verify, verify_none), Verify = GetD(verify, verify_none),
SNI = Get(server_name_indication), SNI =
case Get(<<"server_name_indication">>) of
undefined -> undefined;
SNI0 -> ensure_str(SNI0)
end,
Versions = emqx_tls_lib:integral_versions(Get(versions)), Versions = emqx_tls_lib:integral_versions(Get(versions)),
Ciphers = emqx_tls_lib:integral_ciphers(Versions, Get(ciphers)), Ciphers = emqx_tls_lib:integral_ciphers(Versions, Get(ciphers)),
filter([{keyfile, Key}, {certfile, Cert}, {cacertfile, CA}, filter([
{verify, Verify}, {server_name_indication, SNI}, {keyfile, Key},
{versions, Versions}, {ciphers, Ciphers}]). {certfile, Cert},
{cacertfile, CA},
{verify, Verify},
{server_name_indication, SNI},
{versions, Versions},
{ciphers, Ciphers}
]).
%% @doc Save a key or certificate file in data dir, %% @doc Save a key or certificate file in data dir,
%% and return path of the saved file. %% and return path of the saved file.
@ -87,32 +103,54 @@ save_files_return_opts(Options, Dir) ->
-spec save_file(file_input(), atom() | string() | binary()) -> string(). -spec save_file(file_input(), atom() | string() | binary()) -> string().
save_file(Param, SubDir) -> save_file(Param, SubDir) ->
Dir = filename:join([emqx:data_dir(), SubDir]), Dir = filename:join([emqx:data_dir(), SubDir]),
do_save_file(Param, Dir). maybe_save_file(Param, Dir).
filter([]) -> []; filter([]) -> [];
filter([{_, undefined} | T]) -> filter(T); filter([{_, undefined} | T]) -> filter(T);
filter([{_, ""} | T]) -> filter(T); filter([{_, ""} | T]) -> filter(T);
filter([H | T]) -> [H | filter(T)]. filter([H | T]) -> [H | filter(T)].
do_save_file(#{filename := FileName, file := Content}, Dir) maybe_save_file(#{filename := FileName, file := Content}, Dir) when
when FileName =/= undefined andalso Content =/= undefined -> FileName =/= undefined andalso Content =/= undefined
do_save_file(ensure_str(FileName), iolist_to_binary(Content), Dir); ->
do_save_file(FilePath, _) when is_list(FilePath) -> maybe_save_file(ensure_str(FileName), iolist_to_binary(Content), Dir);
maybe_save_file(FilePath, _) when is_list(FilePath) ->
FilePath; FilePath;
do_save_file(FilePath, _) when is_binary(FilePath) -> maybe_save_file(FilePath, _) when is_binary(FilePath) ->
ensure_str(FilePath); ensure_str(FilePath);
do_save_file(_, _) -> "". maybe_save_file(_, _) ->
"".
do_save_file("", _, _Dir) -> ""; %% ignore %% no filename, ignore
do_save_file(_, <<>>, _Dir) -> ""; %% ignore maybe_save_file("", _, _Dir) ->
do_save_file(FileName, Content, Dir) -> "";
%% no content, see if file exists
maybe_save_file(FileName, <<>>, Dir) ->
{ok, Cwd} = file:get_cwd(),
%% NOTE: when FileName is an absolute path, filename:join has no effect
CwdFile = ensure_str(filename:join([Cwd, FileName])),
DataDirFile = ensure_str(filename:join([Dir, FileName])),
Possibles =
case CwdFile =:= DataDirFile of
true -> [CwdFile];
false -> [CwdFile, DataDirFile]
end,
case find_exist_file(FileName, Possibles) of
false -> erlang:throw({bad_cert_file, Possibles});
Found -> Found
end;
maybe_save_file(FileName, Content, Dir) ->
FullFilename = filename:join([Dir, FileName]), FullFilename = filename:join([Dir, FileName]),
ok = filelib:ensure_dir(FullFilename), ok = filelib:ensure_dir(FullFilename),
case file:write_file(FullFilename, Content) of case file:write_file(FullFilename, Content) of
ok -> ok ->
ensure_str(FullFilename); ensure_str(FullFilename);
{error, Reason} -> {error, Reason} ->
logger:error("failed_to_save_ssl_file ~ts: ~0p", [FullFilename, Reason]), ?SLOG(error, #{
msg => "failed_to_save_ssl_file",
filename => FullFilename,
reason => Reason
}),
error({"failed_to_save_ssl_file", FullFilename, Reason}) error({"failed_to_save_ssl_file", FullFilename, Reason})
end. end.
@ -122,8 +160,18 @@ ensure_str(B) when is_binary(B) -> unicode:characters_to_list(B, utf8).
-spec fuzzy_map_get(atom() | binary(), map(), any()) -> any(). -spec fuzzy_map_get(atom() | binary(), map(), any()) -> any().
fuzzy_map_get(Key, Options, Default) -> fuzzy_map_get(Key, Options, Default) ->
case maps:find(Key, Options) of case maps:find(Key, Options) of
{ok, Val} -> Val; {ok, Val} ->
Val;
error when is_atom(Key) -> error when is_atom(Key) ->
fuzzy_map_get(atom_to_binary(Key, utf8), Options, Default); fuzzy_map_get(atom_to_binary(Key, utf8), Options, Default);
error -> Default error ->
Default
end.
find_exist_file(_Name, []) ->
false;
find_exist_file(Name, [F | Rest]) ->
case filelib:is_regular(F) of
true -> F;
false -> find_exist_file(Name, Rest)
end. end.

View File

@ -42,7 +42,8 @@ prop_file_or_content() ->
{prop_cert_file_name(), proper_types:binary()}]). {prop_cert_file_name(), proper_types:binary()}]).
prop_cert_file_name() -> prop_cert_file_name() ->
proper_types:oneof(["certname1", <<"certname2">>, "", <<>>, undefined]). File = code:which(?MODULE), %% existing
proper_types:oneof(["", <<>>, undefined, File]).
prop_tls_versions() -> prop_tls_versions() ->
proper_types:oneof(["tlsv1.3", proper_types:oneof(["tlsv1.3",
@ -76,3 +77,10 @@ file_or_content({Name, Content}) ->
#{<<"file">> => Content, <<"filename">> => Name}; #{<<"file">> => Content, <<"filename">> => Name};
file_or_content(Name) -> file_or_content(Name) ->
Name. Name.
bad_cert_file_test() ->
Input = #{<<"keyfile">> =>
#{<<"filename">> => "notafile",
<<"file">> => ""}},
?assertThrow({bad_cert_file, _},
emqx_plugin_libs_ssl:save_files_return_opts(Input, "test-data")).