emqx/apps/emqx_plugin_libs/src/emqx_plugin_libs_ssl.erl

129 lines
5.3 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2021 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_plugin_libs_ssl).
-export([save_files_return_opts/2,
save_files_return_opts/3,
save_file/2
]).
-type file_input_key() :: atom() | binary(). %% <<"file">> | <<"filename">>
-type file_input() :: #{file_input_key() => binary()}.
%% options are below paris
%% <<"keyfile">> => file_input()
%% <<"certfile">> => file_input()
%% <<"cafile">> => file_input() %% backward compatible
%% <<"cacertfile">> => file_input()
%% <<"verify">> => boolean()
%% <<"tls_versions">> => binary()
%% <<"ciphers">> => binary()
-type opts_key() :: binary() | atom().
-type opts_input() :: #{opts_key() => term()}.
-type opt_key() :: keyfile | certfile | cacertfile | verify | versions | ciphers.
-type opt_value() :: term().
-type opts() :: [{opt_key(), opt_value()}].
%% @doc Parse ssl options input.
%% If the input contains file content, save the files in the given dir.
%% Returns ssl options for Erlang's ssl application.
%%
%% For SSL files in the input Option, it can either be a file path
%% or a map like `#{filename := FileName, file := Content}`.
%% In case it's a map, the file is saved in EMQ X's `data_dir'
%% (unless `SubDir' is an absolute path).
%% 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(),
string() | binary()) -> opts().
save_files_return_opts(Options, SubDir, ResId) ->
Dir = filename:join([emqx:data_dir(), SubDir, ResId]),
save_files_return_opts(Options, Dir).
%% @doc Parse ssl options input.
%% If the input contains file content, save the files in the given dir.
%% Returns ssl options for Erlang's ssl application.
%%
%% For SSL files in the input Option, it can either be a file path
%% or a map like `#{filename := FileName, file := Content}`.
%% In case it's a map, the file is saved in EMQ X's `data_dir'
%% (unless `SubDir' is an absolute path).
%% NOTE: This function is now deprecated, use emqx_tls_lib:ensure_ssl_files/2 instead.
-spec save_files_return_opts(opts_input(), file:name_all()) -> opts().
save_files_return_opts(Options, Dir) ->
GetD = fun(Key, Default) -> fuzzy_map_get(Key, Options, Default) end,
Get = fun(Key) -> GetD(Key, undefined) end,
KeyFile = Get(keyfile),
CertFile = Get(certfile),
CAFile = Get(cacertfile),
Key = do_save_file(KeyFile, Dir),
Cert = do_save_file(CertFile, Dir),
CA = do_save_file(CAFile, Dir),
Verify = GetD(verify, verify_none),
SNI = Get(server_name_indication),
Versions = emqx_tls_lib:integral_versions(Get(tls_versions)),
Ciphers = emqx_tls_lib:integral_ciphers(Versions, Get(ciphers)),
filter([{keyfile, Key}, {certfile, Cert}, {cacertfile, CA},
{verify, Verify}, {server_name_indication, SNI}, {versions, Versions}, {ciphers, Ciphers}]).
%% @doc Save a key or certificate file in data dir,
%% and return path of the saved file.
%% empty string is returned if the input is empty.
-spec save_file(file_input(), atom() | string() | binary()) -> string().
save_file(Param, SubDir) ->
Dir = filename:join([emqx:data_dir(), SubDir]),
do_save_file(Param, Dir).
filter([]) -> [];
filter([{_, undefined} | T]) -> filter(T);
filter([{_, ""} | T]) -> filter(T);
filter([H | T]) -> [H | filter(T)].
do_save_file(#{filename := FileName, file := Content}, Dir)
when FileName =/= undefined andalso Content =/= undefined ->
do_save_file(ensure_str(FileName), iolist_to_binary(Content), Dir);
do_save_file(FilePath, _) when is_list(FilePath) ->
FilePath;
do_save_file(FilePath, _) when is_binary(FilePath) ->
ensure_str(FilePath);
do_save_file(_, _) -> "".
do_save_file("", _, _Dir) -> ""; %% ignore
do_save_file(_, <<>>, _Dir) -> ""; %% ignore
do_save_file(FileName, Content, Dir) ->
FullFilename = filename:join([Dir, FileName]),
ok = filelib:ensure_dir(FullFilename),
case file:write_file(FullFilename, Content) of
ok ->
ensure_str(FullFilename);
{error, Reason} ->
logger:error("failed_to_save_ssl_file ~ts: ~0p", [FullFilename, Reason]),
error({"failed_to_save_ssl_file", FullFilename, Reason})
end.
ensure_str(L) when is_list(L) -> L;
ensure_str(B) when is_binary(B) -> unicode:characters_to_list(B, utf8).
-spec fuzzy_map_get(atom() | binary(), map(), any()) -> any().
fuzzy_map_get(Key, Options, Default) ->
case maps:find(Key, Options) of
{ok, Val} -> Val;
error when is_atom(Key) ->
fuzzy_map_get(atom_to_binary(Key, utf8), Options, Default);
error -> Default
end.