feat(tls): automatically add `cacerts` to client opts

`public_key:cacerts_get/0` was introduced in OTP 25 and allows us to
load the system trusted CA certificates.

https://www.erlang.org/doc/man/public_key.html#cacerts_get-0
This commit is contained in:
Thales Macedo Garitezi 2023-07-21 17:04:31 -03:00
parent 1874cd1223
commit d3d52695d5
4 changed files with 51 additions and 0 deletions

View File

@ -2017,6 +2017,14 @@ common_ssl_opts_schema(Defaults, Type) ->
desc => ?DESC(common_ssl_opts_schema_cacertfile) desc => ?DESC(common_ssl_opts_schema_cacertfile)
} }
)}, )},
{"cacerts",
sc(
boolean(),
#{
default => false,
desc => ?DESC(common_ssl_opts_schema_cacerts)
}
)},
{"certfile", {"certfile",
sc( sc(
binary(), binary(),

View File

@ -478,11 +478,13 @@ to_server_opts(Type, Opts) ->
Versions = integral_versions(Type, maps:get(versions, Opts, undefined)), Versions = integral_versions(Type, maps:get(versions, Opts, undefined)),
Ciphers = integral_ciphers(Versions, maps:get(ciphers, Opts, undefined)), Ciphers = integral_ciphers(Versions, maps:get(ciphers, Opts, undefined)),
Path = fun(Key) -> resolve_cert_path_for_read_strict(maps:get(Key, Opts, undefined)) end, Path = fun(Key) -> resolve_cert_path_for_read_strict(maps:get(Key, Opts, undefined)) end,
CACerts = get_cacerts(maps:get(cacerts, Opts, false)),
ensure_valid_options( ensure_valid_options(
maps:to_list(Opts#{ maps:to_list(Opts#{
keyfile => Path(keyfile), keyfile => Path(keyfile),
certfile => Path(certfile), certfile => Path(certfile),
cacertfile => Path(cacertfile), cacertfile => Path(cacertfile),
cacerts => CACerts,
ciphers => Ciphers, ciphers => Ciphers,
versions => Versions versions => Versions
}), }),
@ -511,11 +513,13 @@ to_client_opts(Type, Opts) ->
SNI = ensure_sni(Get(server_name_indication)), SNI = ensure_sni(Get(server_name_indication)),
Versions = integral_versions(Type, Get(versions)), Versions = integral_versions(Type, Get(versions)),
Ciphers = integral_ciphers(Versions, Get(ciphers)), Ciphers = integral_ciphers(Versions, Get(ciphers)),
CACerts = get_cacerts(GetD(cacerts, false)),
ensure_valid_options( ensure_valid_options(
[ [
{keyfile, KeyFile}, {keyfile, KeyFile},
{certfile, CertFile}, {certfile, CertFile},
{cacertfile, CAFile}, {cacertfile, CAFile},
{cacerts, CACerts},
{verify, Verify}, {verify, Verify},
{server_name_indication, SNI}, {server_name_indication, SNI},
{versions, Versions}, {versions, Versions},
@ -661,3 +665,13 @@ ensure_ssl_file_key(SSL, RequiredKeyPaths) ->
[] -> ok; [] -> ok;
Miss -> {error, #{reason => ssl_file_option_not_found, which_options => Miss}} Miss -> {error, #{reason => ssl_file_option_not_found, which_options => Miss}}
end. end.
get_cacerts(true = _UseSystemCACerts) ->
try
public_key:cacerts_get()
catch
_:_ ->
undefined
end;
get_cacerts(false = _UseSystemCACerts) ->
undefined.

View File

@ -229,6 +229,7 @@ to_client_opts_test() ->
Versions13Only = ['tlsv1.3'], Versions13Only = ['tlsv1.3'],
Options = #{ Options = #{
enable => true, enable => true,
cacerts => true,
verify => "Verify", verify => "Verify",
server_name_indication => "SNI", server_name_indication => "SNI",
ciphers => "Ciphers", ciphers => "Ciphers",
@ -263,6 +264,28 @@ to_client_opts_test() ->
emqx_tls_lib:to_client_opts(tls, Options#{depth := undefined, password := ""}) emqx_tls_lib:to_client_opts(tls, Options#{depth := undefined, password := ""})
) )
) )
),
Expected4 = lists:usort(maps:keys(Options) -- [enable, cacerts]),
?assertEqual(
Expected4,
lists:usort(
proplists:get_keys(
emqx_tls_lib:to_client_opts(tls, Options#{cacerts := false})
)
)
),
emqx_common_test_helpers:with_mock(
public_key,
cacerts_get,
fun() -> ok = {error, enoent} end,
fun() ->
?assertNot(
lists:member(
cacerts,
proplists:get_keys(emqx_tls_lib:to_client_opts(tls, Options))
)
)
end
). ).
to_server_opts_test() -> to_server_opts_test() ->

View File

@ -262,6 +262,12 @@ already established connections."""
common_ssl_opts_schema_cacertfile.label: common_ssl_opts_schema_cacertfile.label:
"""CACertfile""" """CACertfile"""
common_ssl_opts_schema_cacerts.desc:
"""When enabled, uses the system trusted CA certificates for establishing to TLS connections."""
common_ssl_opts_schema_cacerts.label:
"""Use System CA Certificates"""
fields_ws_opts_mqtt_path.desc: fields_ws_opts_mqtt_path.desc:
"""WebSocket's MQTT protocol path. So the address of EMQX Broker's WebSocket is: """WebSocket's MQTT protocol path. So the address of EMQX Broker's WebSocket is:
<code>ws://{ip}:{port}/mqtt</code>""" <code>ws://{ip}:{port}/mqtt</code>"""