From 3c5c76fcfc3a490c449d31dd2586209cb1376dc1 Mon Sep 17 00:00:00 2001 From: zmstone Date: Thu, 2 May 2024 16:52:17 +0200 Subject: [PATCH] refactor: simplify https listener config for dashboard --- apps/emqx_dashboard/etc/emqx_dashboard.conf | 15 +++- .../src/emqx_dashboard_schema.erl | 42 ++++-------- .../test/emqx_mgmt_api_configs_SUITE.erl | 36 ++++++---- .../dashboard-with-https.conf.example | 68 ++++++------------- rel/i18n/emqx_dashboard_schema.hocon | 5 +- 5 files changed, 72 insertions(+), 94 deletions(-) diff --git a/apps/emqx_dashboard/etc/emqx_dashboard.conf b/apps/emqx_dashboard/etc/emqx_dashboard.conf index 67e3f61ec..5b37aa3a1 100644 --- a/apps/emqx_dashboard/etc/emqx_dashboard.conf +++ b/apps/emqx_dashboard/etc/emqx_dashboard.conf @@ -1,5 +1,16 @@ dashboard { - listeners.http { - bind = 18083 + listeners { + http { + ## Comment out 'bind' (or set bind=0) to disable listener. + bind = 18083 + } + https { + ## Uncomment to enable + # bind = 18084 + ssl_options { + certfile = "${EMQX_ETC_DIR}/certs/cert.pem" + keyfile = "${EMQX_ETC_DIR}/certs/key.pem" + } + } } } diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl index 50e35459d..e8bbbbdc1 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl @@ -101,8 +101,7 @@ fields("https") -> enable(false), bind(18084), ssl_options() - | common_listener_fields() ++ - hidden_server_ssl_options() + | common_listener_fields() ]; fields("ssl_options") -> server_ssl_options(). @@ -118,30 +117,8 @@ ssl_options() -> } )}. -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). - -exclude_fields([], Fields) -> - Fields; -exclude_fields([FieldName | Rest], Fields) -> - %% assert field exists - case lists:keytake(FieldName, 1, Fields) of - {value, _, New} -> exclude_fields(Rest, New); - false -> error({FieldName, Fields}) - end. + emqx_schema:server_ssl_opts_schema(#{}, true). common_listener_fields() -> [ @@ -217,6 +194,7 @@ enable(Bool) -> #{ default => Bool, required => false, + %% deprecated because we use port number =:= 0 to disable deprecated => {since, "5.1.0"}, importance => ?IMPORTANCE_HIDDEN, desc => ?DESC(listener_enable) @@ -296,15 +274,19 @@ validate_sample_interval(Second) -> {error, Msg} end. -https_converter(Conf = #{<<"ssl_options">> := _}, _Opts) -> +https_converter(undefined, _Opts) -> + %% no https listener configured + undefined; +https_converter(Conf, Opts) -> + convert_ssl_layout(Conf, Opts). + +convert_ssl_layout(Conf = #{<<"ssl_options">> := _}, _Opts) -> Conf; -https_converter(Conf = #{}, _Opts) -> +convert_ssl_layout(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. + Conf1#{<<"ssl_options">> => SslOpts}. -if(?EMQX_RELEASE_EDITION == ee). sso_fields() -> diff --git a/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl index 6d4d94013..d00abd92e 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl @@ -222,7 +222,7 @@ t_dashboard(_Config) -> {ok, Dashboard = #{<<"listeners">> := Listeners}} = get_config("dashboard"), Https1 = #{enable => true, bind => 18084}, ?assertMatch( - {error, {"HTTP/1.1", 400, _}}, + {ok, _}, update_config("dashboard", Dashboard#{<<"listeners">> => Listeners#{<<"https">> => Https1}}) ), @@ -241,17 +241,20 @@ t_dashboard(_Config) -> update_config("dashboard", Dashboard2) ), - KeyFile = emqx_common_test_helpers:app_path(emqx, filename:join(["etc", "certs", "key.pem"])), - CertFile = emqx_common_test_helpers:app_path(emqx, filename:join(["etc", "certs", "cert.pem"])), - CacertFile = emqx_common_test_helpers:app_path( - emqx, filename:join(["etc", "certs", "cacert.pem"]) - ), + FilePath = fun(Name) -> + iolist_to_binary( + emqx_common_test_helpers:app_path(emqx, filename:join(["etc", "certs", Name])) + ) + end, + KeyFile = FilePath("key.pem"), + CertFile = FilePath("cert.pem"), + CacertFile = FilePath("cacert.pem"), Https3 = #{ <<"bind">> => 18084, <<"ssl_options">> => #{ - <<"keyfile">> => list_to_binary(KeyFile), - <<"cacertfile">> => list_to_binary(CacertFile), - <<"certfile">> => list_to_binary(CertFile) + <<"keyfile">> => KeyFile, + <<"cacertfile">> => CacertFile, + <<"certfile">> => CertFile } }, Dashboard3 = Dashboard#{<<"listeners">> => Listeners#{<<"https">> => Https3}}, @@ -260,16 +263,23 @@ t_dashboard(_Config) -> Dashboard4 = Dashboard#{<<"listeners">> => Listeners#{<<"https">> => #{<<"bind">> => 0}}}, ?assertMatch({ok, _}, update_config("dashboard", Dashboard4)), {ok, Dashboard41} = get_config("dashboard"), - ?assertEqual( - Https3#{<<"bind">> => 0}, + ?assertMatch( + #{ + <<"bind">> := 0, + <<"ssl_options">> := + #{ + <<"keyfile">> := KeyFile, + <<"cacertfile">> := CacertFile, + <<"certfile">> := CertFile + } + }, read_conf([<<"dashboard">>, <<"listeners">>, <<"https">>]), Dashboard41 ), ?assertMatch({ok, _}, update_config("dashboard", Dashboard)), - {ok, Dashboard1} = get_config("dashboard"), - ?assertNotEqual(Dashboard, Dashboard1), + ?assertEqual(Dashboard, Dashboard1), timer:sleep(1500), ok. diff --git a/rel/config/examples/dashboard-with-https.conf.example b/rel/config/examples/dashboard-with-https.conf.example index 8e15077e0..cac87e0fb 100644 --- a/rel/config/examples/dashboard-with-https.conf.example +++ b/rel/config/examples/dashboard-with-https.conf.example @@ -10,14 +10,32 @@ dashboard { cors = false listeners.https { - ## Port or Address to listen on, 0 means disable bind = "0.0.0.0:18084" ## or just a port number, e.g. 18084 + ssl_options { + ## PEM format certificates chain. + ## Server certificate as the first one, + ## followed by its immediate issuer certificate + ## then the issuer's issuer certificate, and so on. + ## Root CA certificate is optional. + ## The path prefix (only prefix) can be an environment variable. + certfile = "${EMQX_ETC_DIR}/certs/cert.pem" + + ## PEM format private key + keyfile = "${EMQX_ETC_DIR}/certs/key.pem" + + ## Optional. When need to verify client certificates, list trusted client's root CA certificates in this file + # cacertfile = "${EMQX_ETC_DIR}/certs/cacert.pem" + + ## Optional. Force client to send their certificate chain during TLS handshake. + # fail_if_no_peer_cert = true + } + ## Socket acceptor pool size for TCP protocols num_acceptors = 8 - ## Maximum number of simultaneous connections + ## Maximum number of concurrent connections max_connections = 512 ## Defines the maximum length that the queue of pending connections can grow to @@ -32,51 +50,7 @@ dashboard { ## Disable IPv4-to-IPv6 mapping for the listener ipv6_v6only = false - ## Enable support for `HAProxy` header + ## Enable support for ProxyProtocol v2 header proxy_header = false - - ## Trusted PEM format CA certificates bundle file - cacertfile = "data/certs/cacert.pem" - - ## PEM format certificates chain file - certfile = "data/certs/cert.pem" - - ## PEM format private key file - keyfile = "data/certs/key.pem" - - ## Enable or disable peer verification - verify = verify_none ## use verify_peer to enable - - ## Enable TLS session reuse - reuse_sessions = true - - ## Maximum number of non-self-issued intermediate certificates that can follow the peer certificate in a valid certification path - depth = 10 - - ## Which versions are to be supported - versions = [tlsv1.3, tlsv1.2] - - ## TLS cipher suite names - ## Note: By default, all available suites are supported, you do not need to set this - ciphers = ["TLS_AES_256_GCM_SHA384","TLS_AES_128_GCM_SHA256"] - - ## Allows a client and a server to renegotiate the parameters of the SSL connection on the fly - secure_renegotiate = true - - ## Log level for SSL communication - ## Type: emergency | alert | critical | error | warning | notice | info | debug | none | all - log_level = notice - - ## Hibernate the SSL process after idling for amount of time reducing its memory footprint - hibernate_after = 5s - - ## Forces the cipher to be set based on the server-specified order instead of the client-specified order - honor_cipher_order = true - - ## Setting this to false to disable client-initiated renegotiation - client_renegotiation = true - - ## Maximum time duration allowed for the handshake to complete - handshake_timeout = 15s } } diff --git a/rel/i18n/emqx_dashboard_schema.hocon b/rel/i18n/emqx_dashboard_schema.hocon index 4ee5f32d8..82d0ff536 100644 --- a/rel/i18n/emqx_dashboard_schema.hocon +++ b/rel/i18n/emqx_dashboard_schema.hocon @@ -7,8 +7,9 @@ backlog.label: """Backlog""" bind.desc: -"""Port without IP(18083) or port with specified IP(127.0.0.1:18083). -Disabled when setting bind to `0`.""" +"""Bind the listener to a specified address and port number, for example `127.0.0.1:18083`. +If configured with just the port number (e.g. `18083`) it's equivalent to binding to all addresses `0.0.0.0`. +The listener is disabled if `bind` is `0`.""" bind.label: """Bind"""