diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 37d5350a5..521293f7a 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -94,7 +94,8 @@ validate_keepalive_multiplier/1, non_empty_string/1, validations/0, - naive_env_interpolation/1 + naive_env_interpolation/1, + validate_server_ssl_opts/1 ]). -export([qos/0]). @@ -958,7 +959,7 @@ fields("mqtt_wss_listener") -> {"ssl_options", sc( ref("listener_wss_opts"), - #{} + #{validator => fun validate_server_ssl_opts/1} )}, {"websocket", sc( @@ -2426,8 +2427,21 @@ server_ssl_opts_schema(Defaults, IsRanchListener) -> ] ]. +validate_server_ssl_opts(#{<<"fail_if_no_peer_cert">> := true, <<"verify">> := Verify}) -> + validate_verify(Verify); +validate_server_ssl_opts(#{fail_if_no_peer_cert := true, verify := Verify}) -> + validate_verify(Verify); +validate_server_ssl_opts(_SSLOpts) -> + ok. + +validate_verify(verify_peer) -> + ok; +validate_verify(_) -> + {error, "verify must be verify_peer when fail_if_no_peer_cert is true"}. + mqtt_ssl_listener_ssl_options_validator(Conf) -> Checks = [ + fun validate_server_ssl_opts/1, fun ocsp_outer_validator/1, fun crl_outer_validator/1 ], diff --git a/apps/emqx/test/emqx_schema_tests.erl b/apps/emqx/test/emqx_schema_tests.erl index ad2341460..58f9a94d5 100644 --- a/apps/emqx/test/emqx_schema_tests.erl +++ b/apps/emqx/test/emqx_schema_tests.erl @@ -106,6 +106,67 @@ bad_cipher_test() -> ), ok. +fail_if_no_peer_cert_test_() -> + Sc = #{ + roots => [mqtt_ssl_listener], + fields => #{mqtt_ssl_listener => emqx_schema:fields("mqtt_ssl_listener")} + }, + Opts = #{atom_key => false, required => false}, + OptsAtomKey = #{atom_key => true, required => false}, + InvalidConf = #{ + <<"bind">> => <<"0.0.0.0:9883">>, + <<"ssl_options">> => #{ + <<"fail_if_no_peer_cert">> => true, + <<"verify">> => <<"verify_none">> + } + }, + InvalidListener = #{<<"mqtt_ssl_listener">> => InvalidConf}, + ValidListener = #{ + <<"mqtt_ssl_listener">> => InvalidConf#{ + <<"ssl_options">> => + #{ + <<"fail_if_no_peer_cert">> => true, + <<"verify">> => <<"verify_peer">> + } + } + }, + ValidListener1 = #{ + <<"mqtt_ssl_listener">> => InvalidConf#{ + <<"ssl_options">> => + #{ + <<"fail_if_no_peer_cert">> => false, + <<"verify">> => <<"verify_none">> + } + } + }, + Reason = "verify must be verify_peer when fail_if_no_peer_cert is true", + [ + ?_assertThrow( + {_Sc, [#{kind := validation_error, reason := Reason}]}, + hocon_tconf:check_plain(Sc, InvalidListener, Opts) + ), + ?_assertThrow( + {_Sc, [#{kind := validation_error, reason := Reason}]}, + hocon_tconf:check_plain(Sc, InvalidListener, OptsAtomKey) + ), + ?_assertMatch( + #{mqtt_ssl_listener := #{}}, + hocon_tconf:check_plain(Sc, ValidListener, OptsAtomKey) + ), + ?_assertMatch( + #{mqtt_ssl_listener := #{}}, + hocon_tconf:check_plain(Sc, ValidListener1, OptsAtomKey) + ), + ?_assertMatch( + #{<<"mqtt_ssl_listener">> := #{}}, + hocon_tconf:check_plain(Sc, ValidListener, Opts) + ), + ?_assertMatch( + #{<<"mqtt_ssl_listener">> := #{}}, + hocon_tconf:check_plain(Sc, ValidListener1, Opts) + ) + ]. + validate(Schema, Data0) -> Sc = #{ roots => [ssl_opts], diff --git a/apps/emqx_gateway/src/emqx_gateway_schema.erl b/apps/emqx_gateway/src/emqx_gateway_schema.erl index 8c80fc1fa..3c5706e82 100644 --- a/apps/emqx_gateway/src/emqx_gateway_schema.erl +++ b/apps/emqx_gateway/src/emqx_gateway_schema.erl @@ -120,7 +120,10 @@ fields(ssl_listener) -> {ssl_options, sc( hoconsc:ref(emqx_schema, "listener_ssl_opts"), - #{desc => ?DESC(ssl_listener_options)} + #{ + desc => ?DESC(ssl_listener_options), + validator => fun emqx_schema:validate_server_ssl_opts/1 + } )} ]; fields(udp_listener) -> @@ -132,7 +135,13 @@ fields(udp_listener) -> fields(dtls_listener) -> [{acceptors, sc(integer(), #{default => 16, desc => ?DESC(dtls_listener_acceptors)})}] ++ fields(udp_listener) ++ - [{dtls_options, sc(ref(dtls_opts), #{desc => ?DESC(dtls_listener_dtls_opts)})}]; + [ + {dtls_options, + sc(ref(dtls_opts), #{ + desc => ?DESC(dtls_listener_dtls_opts), + validator => fun emqx_schema:validate_server_ssl_opts/1 + })} + ]; fields(udp_opts) -> [ {active_n, diff --git a/changes/ce/fix-10952.en.md b/changes/ce/fix-10952.en.md new file mode 100644 index 000000000..20792906f --- /dev/null +++ b/changes/ce/fix-10952.en.md @@ -0,0 +1,8 @@ +Disallow enabling `fail_if_no_peer_cert` in listener SSL options if `verify_none` is set. + +Setting `fail_if_no_peer_cert = true` and `verify = verify_none` caused connection errors +due to incompatible options. +This fix validates the options when creating or updating a listener to avoid these errors. + +Note: any old listener configuration with `fail_if_no_peer_cert = true` and `verify = verify_none` +that was previously allowed will fail to load after applying this fix and must be manually fixed.