Merge pull request #7527 from zmstone/0405-5.0-provide-defaults-for-ssl-files

5.0 provide defaults for ssl files
This commit is contained in:
Zaiming (Stone) Shi 2022-04-19 11:51:39 +01:00 committed by GitHub
commit fb7c7dffb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 55 additions and 63 deletions

View File

@ -2071,7 +2071,13 @@ common_ssl_opts_schema(Defaults) ->
%% @doc Make schema for SSL listener options. %% @doc Make schema for SSL listener options.
%% When it's for ranch listener, an extra field `handshake_timeout' is added. %% When it's for ranch listener, an extra field `handshake_timeout' is added.
-spec server_ssl_opts_schema(map(), boolean()) -> hocon_schema:field_schema(). -spec server_ssl_opts_schema(map(), boolean()) -> hocon_schema:field_schema().
server_ssl_opts_schema(Defaults, IsRanchListener) -> server_ssl_opts_schema(Defaults1, IsRanchListener) ->
Defaults0 = #{
cacertfile => emqx:cert_file("cacert.pem"),
certfile => emqx:cert_file("cert.pem"),
keyfile => emqx:cert_file("key.pem")
},
Defaults = maps:merge(Defaults0, Defaults1),
D = fun(Field) -> maps:get(to_atom(Field), Defaults, undefined) end, D = fun(Field) -> maps:get(to_atom(Field), Defaults, undefined) end,
Df = fun(Field, Default) -> maps:get(to_atom(Field), Defaults, Default) end, Df = fun(Field, Default) -> maps:get(to_atom(Field), Defaults, Default) end,
common_ssl_opts_schema(Defaults) ++ common_ssl_opts_schema(Defaults) ++
@ -2148,7 +2154,15 @@ server_ssl_opts_schema(Defaults, IsRanchListener) ->
%% @doc Make schema for SSL client. %% @doc Make schema for SSL client.
-spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema(). -spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema().
client_ssl_opts_schema(Defaults) -> client_ssl_opts_schema(Defaults1) ->
%% assert
true = lists:all(fun(K) -> is_atom(K) end, maps:keys(Defaults1)),
Defaults0 = #{
cacertfile => emqx:cert_file("cacert.pem"),
certfile => emqx:cert_file("client-cert.pem"),
keyfile => emqx:cert_file("client-key.pem")
},
Defaults = maps:merge(Defaults0, Defaults1),
common_ssl_opts_schema(Defaults) ++ common_ssl_opts_schema(Defaults) ++
[ [
{"server_name_indication", {"server_name_indication",

View File

@ -31,7 +31,8 @@
-export([ -export([
ensure_ssl_files/2, ensure_ssl_files/2,
delete_ssl_files/3, delete_ssl_files/3,
file_content_as_options/1 drop_invalid_certs/1,
is_valid_pem_file/1
]). ]).
-export([ -export([
@ -40,10 +41,11 @@
-include("logger.hrl"). -include("logger.hrl").
-define(IS_TRUE(Val), ((Val =:= true) or (Val =:= <<"true">>))). -define(IS_TRUE(Val), ((Val =:= true) orelse (Val =:= <<"true">>))).
-define(IS_FALSE(Val), ((Val =:= false) or (Val =:= <<"false">>))). -define(IS_FALSE(Val), ((Val =:= false) orelse (Val =:= <<"false">>))).
-define(SSL_FILE_OPT_NAMES, [<<"keyfile">>, <<"certfile">>, <<"cacertfile">>]). -define(SSL_FILE_OPT_NAMES, [<<"keyfile">>, <<"certfile">>, <<"cacertfile">>]).
-define(SSL_FILE_OPT_NAMES_A, [keyfile, certfile, cacertfile]).
%% non-empty string %% non-empty string
-define(IS_STRING(L), (is_list(L) andalso L =/= [] andalso is_integer(hd(L)))). -define(IS_STRING(L), (is_list(L) andalso L =/= [] andalso is_integer(hd(L)))).
@ -398,35 +400,37 @@ pem_file_name(Dir, Key, Pem) ->
hex_str(Bin) -> hex_str(Bin) ->
iolist_to_binary([io_lib:format("~2.16.0b", [X]) || <<X:8>> <= Bin]). iolist_to_binary([io_lib:format("~2.16.0b", [X]) || <<X:8>> <= Bin]).
%% @doc Returns 'true' when the file is a valid pem, otherwise {error, Reason}.
is_valid_pem_file(Path) -> is_valid_pem_file(Path) ->
case file:read_file(Path) of case file:read_file(Path) of
{ok, Pem} -> is_pem(Pem) orelse {error, not_pem}; {ok, Pem} -> is_pem(Pem) orelse {error, not_pem};
{error, Reason} -> {error, Reason} {error, Reason} -> {error, Reason}
end. end.
%% @doc This is to return SSL file content in management APIs. %% @doc Input and output are both HOCON-checked maps, with invalid SSL
file_content_as_options(undefined) -> %% file options dropped.
undefined; %% This is to give a feedback to the front-end or management API caller
file_content_as_options(#{<<"enable">> := False} = SSL) when ?IS_FALSE(False) -> %% so they are forced to upload a cert file, or use an existing file path.
{ok, maps:without(?SSL_FILE_OPT_NAMES, SSL)}; -spec drop_invalid_certs(map()) -> map().
file_content_as_options(#{<<"enable">> := True} = SSL) when ?IS_TRUE(True) -> drop_invalid_certs(#{enable := False} = SSL) when ?IS_FALSE(False) ->
file_content_as_options(?SSL_FILE_OPT_NAMES, SSL). maps:without(?SSL_FILE_OPT_NAMES_A, SSL);
drop_invalid_certs(#{<<"enable">> := False} = SSL) when ?IS_FALSE(False) ->
maps:without(?SSL_FILE_OPT_NAMES, SSL);
drop_invalid_certs(#{enable := True} = SSL) when ?IS_TRUE(True) ->
drop_invalid_certs(?SSL_FILE_OPT_NAMES_A, SSL);
drop_invalid_certs(#{<<"enable">> := True} = SSL) when ?IS_TRUE(True) ->
drop_invalid_certs(?SSL_FILE_OPT_NAMES, SSL).
file_content_as_options([], SSL) -> drop_invalid_certs([], SSL) ->
{ok, SSL}; SSL;
file_content_as_options([Key | Keys], SSL) -> drop_invalid_certs([Key | Keys], SSL) ->
case maps:get(Key, SSL, undefined) of case maps:get(Key, SSL, undefined) of
undefined -> undefined ->
file_content_as_options(Keys, SSL); drop_invalid_certs(Keys, SSL);
Path -> Path ->
case file:read_file(Path) of case is_valid_pem_file(Path) of
{ok, Bin} -> true -> SSL;
file_content_as_options(Keys, SSL#{Key => Bin}); {error, _} -> maps:without([Key], SSL)
{error, Reason} ->
{error, #{
file_path => Path,
reason => Reason
}}
end end
end. end.

View File

@ -1202,25 +1202,8 @@ fill_defaults(Configs) when is_list(Configs) ->
fill_defaults(Config) -> fill_defaults(Config) ->
emqx_authn:check_config(Config, #{only_fill_defaults => true}). emqx_authn:check_config(Config, #{only_fill_defaults => true}).
convert_certs(#{ssl := #{enable := true} = SSLOpts} = Config) -> convert_certs(#{ssl := SSL} = Config) when SSL =/= undefined ->
NSSLOpts = lists:foldl( Config#{ssl := emqx_tls_lib:drop_invalid_certs(SSL)};
fun(K, Acc) ->
case maps:get(K, Acc, undefined) of
undefined ->
Acc;
Filename ->
case file:read_file(Filename) of
{ok, Bin} ->
Acc#{K => Bin};
{error, _} ->
Acc#{K => Filename}
end
end
end,
SSLOpts,
[certfile, keyfile, cacertfile]
),
Config#{ssl => NSSLOpts};
convert_certs(Config) -> convert_certs(Config) ->
Config. Config.

View File

@ -214,7 +214,7 @@ sources(get, _) ->
]) ])
end; end;
(Source, AccIn) -> (Source, AccIn) ->
lists:append(AccIn, [read_certs(Source)]) lists:append(AccIn, [drop_invalid_certs(Source)])
end, end,
[], [],
get_raw_sources() get_raw_sources()
@ -248,7 +248,7 @@ source(get, #{bindings := #{type := Type}}) ->
}} }}
end; end;
[Source] -> [Source] ->
{200, read_certs(Source)} {200, drop_invalid_certs(Source)}
end; end;
source(put, #{bindings := #{type := <<"file">>}, body := #{<<"type">> := <<"file">>} = Body}) -> source(put, #{bindings := #{type := <<"file">>}, body := #{<<"type">> := <<"file">>} = Body}) ->
update_authz_file(Body); update_authz_file(Body);
@ -498,15 +498,9 @@ update_config(Cmd, Sources) ->
}} }}
end. end.
read_certs(#{<<"ssl">> := SSL} = Source) -> drop_invalid_certs(#{<<"ssl">> := SSL} = Source) when SSL =/= undefined ->
case emqx_tls_lib:file_content_as_options(SSL) of Source#{<<"ssl">> => emqx_tls_lib:drop_invalid_certs(SSL)};
{error, Reason} -> drop_invalid_certs(Source) ->
?SLOG(error, Reason#{msg => failed_to_read_ssl_file}),
throw(failed_to_read_ssl_file);
{ok, NewSSL} ->
Source#{<<"ssl">> => NewSSL}
end;
read_certs(Source) ->
Source. Source.
parameters_field() -> parameters_field() ->

View File

@ -94,9 +94,6 @@
>> >>
}). }).
-define(MATCH_RSA_KEY, <<"-----BEGIN RSA PRIVATE KEY", _/binary>>).
-define(MATCH_CERT, <<"-----BEGIN CERTIFICATE", _/binary>>).
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -279,9 +276,9 @@ t_api(_) ->
<<"type">> := <<"mongodb">>, <<"type">> := <<"mongodb">>,
<<"ssl">> := #{ <<"ssl">> := #{
<<"enable">> := <<"true">>, <<"enable">> := <<"true">>,
<<"cacertfile">> := ?MATCH_CERT, <<"cacertfile">> := _,
<<"certfile">> := ?MATCH_CERT, <<"certfile">> := _,
<<"keyfile">> := ?MATCH_RSA_KEY, <<"keyfile">> := _,
<<"verify">> := <<"verify_none">> <<"verify">> := <<"verify_none">>
} }
}, },
@ -313,9 +310,9 @@ t_api(_) ->
<<"type">> := <<"mongodb">>, <<"type">> := <<"mongodb">>,
<<"ssl">> := #{ <<"ssl">> := #{
<<"enable">> := <<"true">>, <<"enable">> := <<"true">>,
<<"cacertfile">> := ?MATCH_CERT, <<"cacertfile">> := _,
<<"certfile">> := ?MATCH_CERT, <<"certfile">> := _,
<<"keyfile">> := ?MATCH_RSA_KEY, <<"keyfile">> := _,
<<"verify">> := <<"verify_none">> <<"verify">> := <<"verify_none">>
} }
}, },