Просмотр исходного кода

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
Thales Macedo Garitezi 2 лет назад
Родитель
Сommit
d3d52695d5

+ 8 - 0
apps/emqx/src/emqx_schema.erl

@@ -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(),

+ 14 - 0
apps/emqx/src/emqx_tls_lib.erl

@@ -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.

+ 23 - 0
apps/emqx/test/emqx_tls_lib_tests.erl

@@ -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() ->

+ 6 - 0
rel/i18n/emqx_schema.hocon

@@ -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>"""