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

refactor(schema): make a client ssl options schema

client and server ssl options share some common fields
this commit make an abstraction for the common fields
then export server_ssl_options_schema/2 and client_ssl_options_schema/1
for other schema modules to call
Zaiming Shi 4 лет назад
Родитель
Сommit
97e1cf65b7

+ 101 - 67
apps/emqx/src/emqx_schema.erl

@@ -72,7 +72,7 @@
 
 -export([namespace/0, roots/0, roots/1, fields/1]).
 -export([conf_get/2, conf_get/3, keys/2, filter/1]).
--export([ssl_opts_schema/2, ciphers_schema/1, default_ciphers/1]).
+-export([server_ssl_opts_schema/2, client_ssl_opts_schema/1, ciphers_schema/1, default_ciphers/1]).
 
 namespace() -> undefined.
 
@@ -462,7 +462,7 @@ fields("mqtt_ssl_listener") ->
           #{})
       }
     , {"ssl",
-       sc(ref("ssl_opts"),
+       sc(ref("listener_ssl_opts"),
           #{})
       }
     ] ++ mqtt_listener();
@@ -484,7 +484,7 @@ fields("mqtt_wss_listener") ->
           #{})
       }
     , {"ssl",
-       sc(ref("wss_ssl_opts"),
+       sc(ref("listener_wss_opts"),
           #{})
       }
     , {"websocket",
@@ -631,21 +631,23 @@ fields("tcp_opts") ->
       }
     ];
 
-fields("ssl_opts") ->
-    ssl_opts_schema(
+fields("listener_ssl_opts") ->
+    server_ssl_opts_schema(
       #{ depth => 10
        , reuse_sessions => true
        , versions => tcp
        , ciphers => tcp_all
        }, false);
 
-fields("wss_ssl_opts") ->
-    ssl_opts_schema(
+fields("listener_wss_opts") ->
+    server_ssl_opts_schema(
       #{ depth => 10
        , reuse_sessions => true
        , versions => tcp
        , ciphers => tcp_all
        }, true);
+fields(ssl_client_opts) ->
+    client_ssl_opts_schema(#{});
 
 fields("deflate_opts") ->
     [ {"level",
@@ -908,10 +910,10 @@ conf_get(Key, Conf, Default) ->
 filter(Opts) ->
     [{K, V} || {K, V} <- Opts, V =/= undefined].
 
-%% @doc This function defines the SSL opts only for TLS server (listners).
-%% When it's for ranch listener, an extra field `handshake_timeout' is added.
--spec ssl_opts_schema(map(), boolean()) -> hocon_schema:field_schema().
-ssl_opts_schema(Defaults, IsRanchListener) ->
+%% @private This function defines the SSL opts which are commonly used by
+%% SSL listener and client.
+-spec common_ssl_opts_schema(map()) -> hocon_schema:field_schema().
+common_ssl_opts_schema(Defaults) ->
     D = fun (Field) -> maps:get(to_atom(Field), Defaults, undefined) end,
     Df = fun (Field, Default) -> maps:get(to_atom(Field), Defaults, Default) end,
     [ {"enable",
@@ -939,55 +941,11 @@ ssl_opts_schema(Defaults, IsRanchListener) ->
           #{ default => Df("verify", verify_none)
            })
       }
-    , {"fail_if_no_peer_cert",
-       sc(boolean(),
-          #{ default => Df("fail_if_no_peer_cert", false)
-           , desc =>
-"""
-Used together with {verify, verify_peer} by an TLS/DTLS server. 
-If set to true, the server fails if the client does not have a 
-certificate to send, that is, sends an empty certificate. 
-If set to false, it fails only if the client sends an invalid 
-certificate (an empty certificate is considered valid).
-"""
-           })
-      }
-    , {"secure_renegotiate",
-       sc(boolean(),
-          #{ default => Df("secure_renegotiate", true)
-           , desc => """
-SSL parameter renegotiation is a feature that allows a client and a server 
-to renegotiate the parameters of the SSL connection on the fly. 
-RFC 5746 defines a more secure way of doing this. By enabling secure renegotiation, 
-you drop support for the insecure renegotiation, prone to MitM attacks.
-"""
-           })
-      }
-    , {"client_renegotiation",
-       sc(boolean(),
-          #{ default => Df("client_renegotiation", true)
-           , desc => """
-In protocols that support client-initiated renegotiation, 
-the cost of resources of such an operation is higher for the server than the client. 
-This can act as a vector for denial of service attacks. 
-The SSL application already takes measures to counter-act such attempts, 
-but client-initiated renegotiation can be strictly disabled by setting this option to false. 
-The default value is true. Note that disabling renegotiation can result in 
-long-lived connections becoming unusable due to limits on 
-the number of messages the underlying cipher suite can encipher.
-"""
-           })
-      }
     , {"reuse_sessions",
        sc(boolean(),
           #{ default => Df("reuse_sessions", true)
            })
       }
-    , {"honor_cipher_order",
-       sc(boolean(),
-          #{ default => Df("honor_cipher_order", true)
-           })
-      }
     , {"depth",
        sc(integer(),
           #{default => Df("depth", 10)
@@ -1002,18 +960,6 @@ the number of messages the underlying cipher suite can encipher.
 keyfile is password-protected."""
            })
       }
-    , {"dhfile",
-       sc(string(),
-          #{ default => D("dhfile")
-           , nullable => true
-           , desc =>
-"""Path to a file containing PEM-encoded Diffie Hellman parameters 
-to be used by the server if a cipher suite using Diffie Hellman 
-key exchange is negotiated. If not specified, default parameters 
-are used.<br>
-NOTE: The dhfile option is not supported by TLS 1.3."""
-           })
-      }
     , {"versions",
        sc(hoconsc:array(typerefl:atom()),
           #{ default => default_tls_vsns(maps:get(versions, Defaults, tcp))
@@ -1032,6 +978,71 @@ In case PSK cipher suites are intended, make sure to configured
            , converter => fun ?MODULE:parse_user_lookup_fun/1
            })
       }
+    , {"secure_renegotiate",
+       sc(boolean(),
+          #{ default => Df("secure_renegotiate", true)
+           , desc => """
+SSL parameter renegotiation is a feature that allows a client and a server 
+to renegotiate the parameters of the SSL connection on the fly. 
+RFC 5746 defines a more secure way of doing this. By enabling secure renegotiation, 
+you drop support for the insecure renegotiation, prone to MitM attacks.
+"""
+           })
+      }
+    ].
+
+%% @doc Make schema for SSL listener options.
+%% When it's for ranch listener, an extra field `handshake_timeout' is added.
+-spec server_ssl_opts_schema(map(), boolean()) -> hocon_schema:field_schema().
+server_ssl_opts_schema(Defaults, IsRanchListener) ->
+    D = fun (Field) -> maps:get(to_atom(Field), Defaults, undefined) end,
+    Df = fun (Field, Default) -> maps:get(to_atom(Field), Defaults, Default) end,
+    common_ssl_opts_schema(Defaults) ++
+    [ {"dhfile",
+       sc(string(),
+          #{ default => D("dhfile")
+           , nullable => true
+           , desc =>
+"""Path to a file containing PEM-encoded Diffie Hellman parameters 
+to be used by the server if a cipher suite using Diffie Hellman 
+key exchange is negotiated. If not specified, default parameters 
+are used.<br>
+NOTE: The dhfile option is not supported by TLS 1.3."""
+           })
+      }
+    , {"fail_if_no_peer_cert",
+       sc(boolean(),
+          #{ default => Df("fail_if_no_peer_cert", false)
+           , desc =>
+"""
+Used together with {verify, verify_peer} by an TLS/DTLS server. 
+If set to true, the server fails if the client does not have a 
+certificate to send, that is, sends an empty certificate. 
+If set to false, it fails only if the client sends an invalid 
+certificate (an empty certificate is considered valid).
+"""
+           })
+      }
+    , {"honor_cipher_order",
+       sc(boolean(),
+          #{ default => Df("honor_cipher_order", true)
+           })
+      }
+    , {"client_renegotiation",
+       sc(boolean(),
+          #{ default => Df("client_renegotiation", true)
+           , desc => """
+In protocols that support client-initiated renegotiation, 
+the cost of resources of such an operation is higher for the server than the client. 
+This can act as a vector for denial of service attacks. 
+The SSL application already takes measures to counter-act such attempts, 
+but client-initiated renegotiation can be strictly disabled by setting this option to false. 
+The default value is true. Note that disabling renegotiation can result in 
+long-lived connections becoming unusable due to limits on 
+the number of messages the underlying cipher suite can encipher.
+"""
+           })
+      }
     | [ {"handshake_timeout",
          sc(duration(),
             #{ default => Df("handshake_timeout", "15s")
@@ -1040,6 +1051,29 @@ In case PSK cipher suites are intended, make sure to configured
        || IsRanchListener]
     ].
 
+%% @doc Make schema for SSL client.
+-spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema().
+client_ssl_opts_schema(Defaults) ->
+    common_ssl_opts_schema(Defaults) ++
+    [ { "server_name_indication",
+        sc(hoconsc:union([disable, string()]),
+           #{ default => disable
+            , desc =>
+"""Specify the host name to be used in TLS Server Name Indication extension.<br>
+For instance, when connecting to \"server.example.net\", the genuine server 
+which accedpts the connection and performs TSL handshake may differ from the 
+host the TLS client initially connects to, e.g. when connecting to an IP address 
+or when the host has multiple resolvable DNS records <br>
+If not specified, it will default to the host name string which is used 
+to establish the connection, unless it is IP addressed used.<br>
+The host name is then also used in the host name verification of the peer 
+certificate.<br> The special value 'disable' prevents the Server Name
+Indication extension from being sent and disables the hostname 
+verification check."""
+            })}
+    ].
+
+
 default_tls_vsns(dtls) ->
     [<<"dtlsv1.2">>, <<"dtlsv1">>];
 default_tls_vsns(tcp) ->

+ 7 - 7
apps/emqx/test/emqx_schema_tests.erl

@@ -20,7 +20,7 @@
 -include_lib("snabbkaffe/include/snabbkaffe.hrl").
 
 ssl_opts_dtls_test() ->
-    Sc = emqx_schema:ssl_opts_schema(#{versions => dtls,
+    Sc = emqx_schema:server_ssl_opts_schema(#{versions => dtls,
                                            ciphers => dtls}, false),
     Checked = validate(Sc, #{<<"versions">> => [<<"dtlsv1.2">>, <<"dtlsv1">>]}),
     ?assertMatch(#{versions := ['dtlsv1.2', 'dtlsv1'],
@@ -28,7 +28,7 @@ ssl_opts_dtls_test() ->
                   }, Checked).
 
 ssl_opts_tls_1_3_test() ->
-    Sc = emqx_schema:ssl_opts_schema(#{}, false),
+    Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
     Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>]}),
     ?assertNot(maps:is_key(handshake_timeout, Checked)),
     ?assertMatch(#{versions := ['tlsv1.3'],
@@ -36,7 +36,7 @@ ssl_opts_tls_1_3_test() ->
                   }, Checked).
 
 ssl_opts_tls_for_ranch_test() ->
-    Sc = emqx_schema:ssl_opts_schema(#{}, true),
+    Sc = emqx_schema:server_ssl_opts_schema(#{}, true),
     Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>]}),
     ?assertMatch(#{versions := ['tlsv1.3'],
                    ciphers := [_ | _],
@@ -44,7 +44,7 @@ ssl_opts_tls_for_ranch_test() ->
                   }, Checked).
 
 ssl_opts_cipher_array_test() ->
-    Sc = emqx_schema:ssl_opts_schema(#{}, false),
+    Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
     Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>],
                              <<"ciphers">> => [<<"TLS_AES_256_GCM_SHA384">>,
                                                <<"ECDHE-ECDSA-AES256-GCM-SHA384">>]}),
@@ -53,7 +53,7 @@ ssl_opts_cipher_array_test() ->
                   }, Checked).
 
 ssl_opts_cipher_comma_separated_string_test() ->
-    Sc = emqx_schema:ssl_opts_schema(#{}, false),
+    Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
     Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>],
                              <<"ciphers">> => <<"TLS_AES_256_GCM_SHA384,ECDHE-ECDSA-AES256-GCM-SHA384">>}),
     ?assertMatch(#{versions := ['tlsv1.3'],
@@ -61,7 +61,7 @@ ssl_opts_cipher_comma_separated_string_test() ->
                   }, Checked).
 
 ssl_opts_tls_psk_test() ->
-    Sc = emqx_schema:ssl_opts_schema(#{}, false),
+    Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
     Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.2">>]}),
     ?assertMatch(#{versions := ['tlsv1.2']}, Checked),
     #{ciphers := Ciphers} = Checked,
@@ -72,7 +72,7 @@ ssl_opts_tls_psk_test() ->
 
 bad_cipher_test() ->
     ok = snabbkaffe:start_trace(),
-    Sc = emqx_schema:ssl_opts_schema(#{}, false),
+    Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
     ?assertThrow({_Sc, [{validation_error, _Error}]},
               [validate(Sc, #{<<"versions">> => [<<"tlsv1.2">>],
                         <<"ciphers">> => [<<"foo">>]})]),

+ 4 - 43
apps/emqx_connector/src/emqx_connector_schema_lib.erl

@@ -53,19 +53,12 @@
 
 -export([roots/0, fields/1]).
 
-roots() -> ["ssl"].
-
-fields("ssl") ->
-    [ {enable, #{type => boolean(), default => false}}
-    , {cacertfile, fun cacertfile/1}
-    , {keyfile, fun keyfile/1}
-    , {certfile, fun certfile/1}
-    , {verify, fun verify/1}
-    , {server_name_indicator, fun server_name_indicator/1}
-    ].
+roots() -> [].
+
+fields(_) -> [].
 
 ssl_fields() ->
-    [ {ssl, #{type => hoconsc:ref(?MODULE, "ssl"),
+    [ {ssl, #{type => hoconsc:ref(emqx_schema, ssl_client_opts),
               default => #{<<"enable">> => false}
              }
       }
@@ -107,22 +100,6 @@ auto_reconnect(type) -> boolean();
 auto_reconnect(default) -> true;
 auto_reconnect(_) -> undefined.
 
-cacertfile(type) -> string();
-cacertfile(nullable) -> true;
-cacertfile(_) -> undefined.
-
-keyfile(type) -> string();
-keyfile(nullable) -> true;
-keyfile(_) -> undefined.
-
-certfile(type) -> string();
-certfile(nullable) -> true;
-certfile(_) -> undefined.
-
-verify(type) -> boolean();
-verify(default) -> false;
-verify(_) -> undefined.
-
 servers(type) -> servers();
 servers(validator) -> [?NOT_EMPTY("the value of the field 'servers' cannot be empty")];
 servers(_) -> undefined.
@@ -151,19 +128,3 @@ to_servers(Str) ->
                      [{host, Ip}, {port, list_to_integer(Port)}]
              end
          end, string:tokens(Str, " , "))}.
-
-server_name_indicator(type) -> string();
-server_name_indicator(default) -> disable;
-server_name_indicator(desc) ->
-"""Specify the host name to be used in TLS Server Name Indication extension.<br>
-For instance, when connecting to \"server.example.net\", the genuine server 
-which accedpts the connection and performs TSL handshake may differ from the 
-host the TLS client initially connects to, e.g. when connecting to an IP address 
-or when the host has multiple resolvable DNS records <br>
-If not specified, it will default to the host name string which is used 
-to establish the connection, unless it is IP addressed used.<br>
-The host name is then also used in the host name verification of the peer 
-certificate.<br> The special value 'disable' prevents the Server Name
-Indication extension from being sent and disables the hostname 
-verification check.""";
-server_name_indicator(_) -> undefined.

+ 1 - 1
apps/emqx_dashboard/src/emqx_dashboard_schema.erl

@@ -47,7 +47,7 @@ fields("http") ->
 fields("https") ->
     fields("http") ++
     proplists:delete("fail_if_no_peer_cert",
-                     emqx_schema:ssl_opts_schema(#{}, true)).
+                     emqx_schema:server_ssl_opts_schema(#{}, true)).
 
 default_username(type) -> string();
 default_username(default) -> "admin";

+ 2 - 2
apps/emqx_gateway/src/emqx_gateway_schema.erl

@@ -163,7 +163,7 @@ fields(tcp_listener) ->
 
 fields(ssl_listener) ->
     fields(tcp_listener) ++
-    [{ssl, sc_meta(hoconsc:ref(emqx_schema, "ssl_opts"),
+    [{ssl, sc_meta(hoconsc:ref(emqx_schema, "listener_ssl_opts"),
                    #{desc => "SSL listener options"})}];
 
 
@@ -188,7 +188,7 @@ fields(udp_opts) ->
     ];
 
 fields(dtls_opts) ->
-    emqx_schema:ssl_opts_schema(
+    emqx_schema:server_ssl_opts_schema(
         #{ depth => 10
          , reuse_sessions => true
          , versions => dtls

+ 1 - 4
apps/emqx_machine/src/emqx_machine_schema.erl

@@ -211,13 +211,10 @@ fields(cluster_etcd) ->
           #{ default => "1m"
            })}
     , {"ssl",
-       sc(ref(etcd_ssl_opts),
+       sc(hoconsc:ref(emqx_schema, ssl_client_opts),
           #{})}
     ];
 
-fields(etcd_ssl_opts) ->
-    emqx_schema:ssl_opts_schema(#{}, false);
-
 fields(cluster_k8s) ->
     [ {"apiserver",
        sc(string(),