diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 6c0c511fb..8509dc245 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -2302,6 +2302,8 @@ ciphers_schema(Default) -> #{ default => default_ciphers(Default), converter => fun + (undefined) -> + []; (<<>>) -> []; ("") -> @@ -2649,6 +2651,8 @@ parse_ka_int(Bin, Name, Min, Max) -> throw(#{reason => lists:flatten(Msg), value => I}) end. +user_lookup_fun_tr(undefined, Opts) -> + user_lookup_fun_tr(<<"emqx_tls_psk:lookup">>, Opts); user_lookup_fun_tr(Lookup, #{make_serializable := true}) -> fmt_user_lookup_fun(Lookup); user_lookup_fun_tr(Lookup, _) -> diff --git a/apps/emqx_dashboard/src/emqx_dashboard.erl b/apps/emqx_dashboard/src/emqx_dashboard.erl index 6299ec96b..de35c43b3 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard.erl @@ -248,9 +248,10 @@ api_key_authorize(Req, Key, Secret) -> ) end. -ensure_ssl_cert(Listeners = #{https := Https0}) -> - Https1 = emqx_tls_lib:to_server_opts(tls, Https0), - Listeners#{https => maps:from_list(Https1)}; +ensure_ssl_cert(Listeners = #{https := Https0 = #{ssl_options := SslOpts}}) -> + SslOpt1 = maps:from_list(emqx_tls_lib:to_server_opts(tls, SslOpts)), + Https1 = maps:remove(ssl_options, Https0), + Listeners#{https => maps:merge(Https1, SslOpt1)}; ensure_ssl_cert(Listeners) -> Listeners. diff --git a/apps/emqx_dashboard/src/emqx_dashboard_listener.erl b/apps/emqx_dashboard/src/emqx_dashboard_listener.erl index a48a1f657..69f02dd2c 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_listener.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_listener.erl @@ -177,14 +177,14 @@ diff_listeners(Type, Stop, Start) -> {#{Type => Stop}, #{Type => Start}}. ensure_ssl_cert(#{<<"listeners">> := #{<<"https">> := #{<<"bind">> := Bind}}} = Conf) when Bind =/= 0 -> - Https = emqx_utils_maps:deep_get([<<"listeners">>, <<"https">>], Conf, undefined), + Keys = [<<"listeners">>, <<"https">>, <<"ssl_options">>], + Ssl = emqx_utils_maps:deep_get(Keys, Conf, undefined), Opts = #{required_keys => [[<<"keyfile">>], [<<"certfile">>], [<<"cacertfile">>]]}, - case emqx_tls_lib:ensure_ssl_files(?DIR, Https, Opts) of + case emqx_tls_lib:ensure_ssl_files(?DIR, Ssl, Opts) of {ok, undefined} -> {error, <<"ssl_cert_not_found">>}; - {ok, NewHttps} -> - {ok, - emqx_utils_maps:deep_merge(Conf, #{<<"listeners">> => #{<<"https">> => NewHttps}})}; + {ok, NewSsl} -> + {ok, emqx_utils_maps:deep_put(Keys, Conf, NewSsl)}; {error, Reason} -> ?SLOG(error, Reason#{msg => "bad_ssl_config"}), {error, Reason} diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl index 31ad4f831..c252c9e37 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl @@ -21,7 +21,8 @@ roots/0, fields/1, namespace/0, - desc/1 + desc/1, + https_converter/2 ]). namespace() -> dashboard. @@ -63,7 +64,8 @@ fields("dashboard") -> desc => ?DESC(bootstrap_users_file), required => false, default => <<>>, - deprecated => {since, "5.1.0"} + deprecated => {since, "5.1.0"}, + importance => ?IMPORTANCE_HIDDEN } )} ]; @@ -82,7 +84,8 @@ fields("listeners") -> ?R_REF("https"), #{ desc => "SSL listeners", - required => {false, recursively} + required => {false, recursively}, + converter => fun ?MODULE:https_converter/2 } )} ]; @@ -95,11 +98,37 @@ fields("http") -> fields("https") -> [ enable(false), - bind(18084) - | common_listener_fields() ++ server_ssl_opts() - ]. + bind(18084), + ssl_options() + | common_listener_fields() ++ + hidden_server_ssl_options() + ]; +fields("ssl_options") -> + server_ssl_options(). -server_ssl_opts() -> +ssl_options() -> + {"ssl_options", + ?HOCON( + ?R_REF("ssl_options"), + #{ + required => true, + importance => ?IMPORTANCE_HIGH + } + )}. + +hidden_server_ssl_options() -> + lists:map( + fun({K, V}) -> + {K, V#{ + importance => ?IMPORTANCE_HIDDEN, + default => undefined, + required => false + }} + end, + server_ssl_options() + ). + +server_ssl_options() -> Opts0 = emqx_schema:server_ssl_opts_schema(#{}, true), exclude_fields(["fail_if_no_peer_cert"], Opts0). @@ -241,7 +270,7 @@ cors(desc) -> ?DESC(cors); cors(_) -> undefined. %% TODO: change it to string type -%% It will be up to the dashboard package which languagues to support +%% It will be up to the dashboard package which languages to support i18n_lang(type) -> ?ENUM([en, zh]); i18n_lang(default) -> en; i18n_lang('readOnly') -> true; @@ -257,3 +286,13 @@ validate_sample_interval(Second) -> Msg = "must be between 1 and 60 and be a divisor of 60.", {error, Msg} end. + +https_converter(Conf = #{<<"ssl_options">> := _}, _Opts) -> + Conf; +https_converter(Conf = #{}, _Opts) -> + Keys = lists:map(fun({K, _}) -> list_to_binary(K) end, server_ssl_options()), + SslOpts = maps:with(Keys, Conf), + Conf1 = maps:without(Keys, Conf), + Conf1#{<<"ssl_options">> => SslOpts}; +https_converter(Conf, _Opts) -> + Conf. diff --git a/apps/emqx_dashboard/test/emqx_dashboard_https_SUITE.erl b/apps/emqx_dashboard/test/emqx_dashboard_https_SUITE.erl index df5ed6e09..a09f8fa3b 100644 --- a/apps/emqx_dashboard/test/emqx_dashboard_https_SUITE.erl +++ b/apps/emqx_dashboard/test/emqx_dashboard_https_SUITE.erl @@ -49,7 +49,7 @@ t_update_conf(_Config) -> Conf = #{ dashboard => #{ listeners => #{ - https => #{bind => 18084}, + https => #{bind => 18084, ssl_options => #{depth => 5}}, http => #{bind => 18083} } } @@ -64,6 +64,12 @@ t_update_conf(_Config) -> get, http_api_path(["clients"]), Headers ), Raw = emqx:get_raw_config([<<"dashboard">>]), + ?assertEqual( + 5, + emqx_utils_maps:deep_get( + [<<"listeners">>, <<"https">>, <<"ssl_options">>, <<"depth">>], Raw + ) + ), ?assertEqual(Client1, Client2), ?check_trace( begin @@ -120,7 +126,7 @@ t_default_ssl_cert(_Config) -> validate_https(Conf, 512, default_ssl_cert(), verify_none), ok. -t_normal_ssl_cert(_Config) -> +t_compatibility_ssl_cert(_Config) -> MaxConnection = 1000, Conf = #{ dashboard => #{ @@ -138,6 +144,29 @@ t_normal_ssl_cert(_Config) -> validate_https(Conf, MaxConnection, default_ssl_cert(), verify_none), ok. +t_normal_ssl_cert(_Config) -> + MaxConnection = 1024, + Conf = #{ + dashboard => #{ + listeners => #{ + https => #{ + bind => 18084, + ssl_options => #{ + cacertfile => naive_env_interpolation( + <<"${EMQX_ETC_DIR}/certs/cacert.pem">> + ), + certfile => naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/cert.pem">>), + keyfile => naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/key.pem">>), + depth => 5 + }, + max_connections => MaxConnection + } + } + } + }, + validate_https(Conf, MaxConnection, default_ssl_cert(), verify_none), + ok. + t_verify_cacertfile(_Config) -> MaxConnection = 1024, DefaultSSLCert = default_ssl_cert(), diff --git a/rel/i18n/emqx_dashboard_schema.hocon b/rel/i18n/emqx_dashboard_schema.hocon index 6bd6ab016..ac0e54601 100644 --- a/rel/i18n/emqx_dashboard_schema.hocon +++ b/rel/i18n/emqx_dashboard_schema.hocon @@ -7,7 +7,9 @@ backlog.label: """Backlog""" bind.desc: -"""Port without IP(18083) or port with specified IP(127.0.0.1:18083).""" +"""Port without IP(18083) or port with specified IP(127.0.0.1:18083). +Disabled when setting bind to `0`. +""" bind.label: """Bind""" diff --git a/scripts/conf-test/old-confs/v5.0.25.conf b/scripts/conf-test/old-confs/v5.0.25.conf index 11c6e4dc0..488bbb976 100644 --- a/scripts/conf-test/old-confs/v5.0.25.conf +++ b/scripts/conf-test/old-confs/v5.0.25.conf @@ -14,6 +14,10 @@ dashboard { listeners.http { bind = 18083 } + listeners.https { + bind = 18084 + depth = 5 + } } authentication = [ diff --git a/scripts/conf-test/run.sh b/scripts/conf-test/run.sh index f01c5ca80..9b42b2b5b 100755 --- a/scripts/conf-test/run.sh +++ b/scripts/conf-test/run.sh @@ -7,9 +7,23 @@ EMQX_ROOT="${EMQX_ROOT:-_build/$PROFILE/rel/emqx}" EMQX_WAIT_FOR_START="${EMQX_WAIT_FOR_START:-30}" export EMQX_WAIT_FOR_START +function check_dashboard_https_ssl_options_depth() { + if [[ $1 =~ v5\.0\.25 ]]; then + EXPECT_DEPTH=5 + else + EXPECT_DEPTH=10 + fi + DEPTH=$("$EMQX_ROOT"/bin/emqx eval "emqx:get_config([dashboard,listeners,https,ssl_options,depth],10)") + if [[ "$DEPTH" != "$EXPECT_DEPTH" ]]; then + echo "Bad Https depth $DEPTH, expect $EXPECT_DEPTH" + exit 1 + fi +} + start_emqx_with_conf() { echo "Starting $PROFILE with $1" "$EMQX_ROOT"/bin/emqx start + check_dashboard_https_ssl_options_depth $1 "$EMQX_ROOT"/bin/emqx stop }