From c4e279bb76f3320359c4c5de485fc65e0e95df22 Mon Sep 17 00:00:00 2001 From: DDDHuang <44492639+DDDHuang@users.noreply.github.com> Date: Wed, 1 Sep 2021 16:44:34 +0800 Subject: [PATCH] fix: support https (#5606) * fix: support https --- apps/emqx_dashboard/etc/emqx_dashboard.conf | 41 +++---- apps/emqx_dashboard/src/emqx_dashboard.erl | 111 ++++++++++-------- .../emqx_dashboard/src/emqx_dashboard_app.erl | 2 +- .../src/emqx_dashboard_schema.erl | 5 +- rebar.config | 2 +- 5 files changed, 85 insertions(+), 76 deletions(-) diff --git a/apps/emqx_dashboard/etc/emqx_dashboard.conf b/apps/emqx_dashboard/etc/emqx_dashboard.conf index 31c95a9ee..70b1d1d71 100644 --- a/apps/emqx_dashboard/etc/emqx_dashboard.conf +++ b/apps/emqx_dashboard/etc/emqx_dashboard.conf @@ -11,35 +11,30 @@ emqx_dashboard { token_expired_time = 60m listeners = [ { + protocol = http num_acceptors = 4 max_connections = 512 - protocol = http port = 18083 backlog = 512 - send_timeout = 15s - send_timeout_close = true + send_timeout = 5s inet6 = false ipv6_v6only = false } -## , -## { -## protocol: https -## port: 18084 -## acceptors: 2 -## backlog: 512 -## send_timeout: 15s -## send_timeout_close: true -## inet6: false -## ipv6_v6only: false -## certfile = "etc/certs/cert.pem" -## keyfile = "etc/certs/key.pem" -## cacertfile = "etc/certs/cacert.pem" -## verify = verify_peer -## tls_versions = "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" -## ciphers = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" -## fail_if_no_peer_cert = true -## inet6 = false -## ipv6_v6only = false -## } + # , + # { + # protocol = https + # port = 18084 + # num_acceptors = 2 + # backlog = 512 + # send_timeout = 5s + # inet6 = false + # ipv6_v6only = false + # certfile = "etc/certs/cert.pem" + # keyfile = "etc/certs/key.pem" + # cacertfile = "etc/certs/cacert.pem" + # verify = verify_peer + # versions = ["tlsv1.3","tlsv1.2","tlsv1.1","tlsv1"] + # ciphers = ["TLS_AES_256_GCM_SHA384","TLS_AES_128_GCM_SHA256","TLS_CHACHA20_POLY1305_SHA256","TLS_AES_128_CCM_SHA256","TLS_AES_128_CCM_8_SHA256","ECDHE-ECDSA-AES256-GCM-SHA384","ECDHE-RSA-AES256-GCM-SHA384","ECDHE-ECDSA-AES256-SHA384","ECDHE-RSA-AES256-SHA384","ECDHE-ECDSA-DES-CBC3-SHA","ECDH-ECDSA-AES256-GCM-SHA384","ECDH-RSA-AES256-GCM-SHA384","ECDH-ECDSA-AES256-SHA384","ECDH-RSA-AES256-SHA384","DHE-DSS-AES256-GCM-SHA384","DHE-DSS-AES256-SHA256","AES256-GCM-SHA384","AES256-SHA256","ECDHE-ECDSA-AES128-GCM-SHA256","ECDHE-RSA-AES128-GCM-SHA256","ECDHE-ECDSA-AES128-SHA256","ECDHE-RSA-AES128-SHA256","ECDH-ECDSA-AES128-GCM-SHA256","ECDH-RSA-AES128-GCM-SHA256","ECDH-ECDSA-AES128-SHA256","ECDH-RSA-AES128-SHA256","DHE-DSS-AES128-GCM-SHA256","DHE-DSS-AES128-SHA256","AES128-GCM-SHA256","AES128-SHA256","ECDHE-ECDSA-AES256-SHA","ECDHE-RSA-AES256-SHA","DHE-DSS-AES256-SHA","ECDH-ECDSA-AES256-SHA","ECDH-RSA-AES256-SHA","AES256-SHA","ECDHE-ECDSA-AES128-SHA","ECDHE-RSA-AES128-SHA","DHE-DSS-AES128-SHA","ECDH-ECDSA-AES128-SHA","ECDH-RSA-AES128-SHA","AES128-SHA"] + # } ] } diff --git a/apps/emqx_dashboard/src/emqx_dashboard.erl b/apps/emqx_dashboard/src/emqx_dashboard.erl index fb0e25564..603d8009b 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard.erl @@ -20,9 +20,7 @@ -export([ start_listeners/0 - , stop_listeners/0 - , start_listener/1 - , stop_listener/1]). + , stop_listeners/0]). %% Authorization -export([authorize_appid/1]). @@ -36,15 +34,8 @@ %%-------------------------------------------------------------------- start_listeners() -> - lists:foreach(fun start_listener/1, listeners()). - -stop_listeners() -> - lists:foreach(fun stop_listener/1, listeners()). - -start_listener({Proto, Port, Options}) -> {ok, _} = application:ensure_all_started(minirest), Authorization = {?MODULE, authorize_appid}, - RanchOptions = ranch_opts(Port, Options), GlobalSpec = #{ openapi => "3.0.0", info => #{title => "EMQ X Dashboard API", version => "5.0.0"}, @@ -56,20 +47,33 @@ start_listener({Proto, Port, Options}) -> type => apiKey, name => "authorization", in => header}}}}, - Dispatch = [{"/", cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}}, - {"/static/[...]", cowboy_static, {priv_dir, emqx_dashboard, "www/static"}}, - {'_', cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}}], - Minirest = #{ - protocol => Proto, + Dispatch = [ + {"/", cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}}, + {"/static/[...]", cowboy_static, {priv_dir, emqx_dashboard, "www/static"}}, + {'_', cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}} + ], + BaseMinirest = #{ base_path => ?BASE_PATH, modules => minirest_api:find_api_modules(apps()), authorization => Authorization, security => [#{application => []}], swagger_global_spec => GlobalSpec, - dispatch => Dispatch}, - MinirestOptions = maps:merge(Minirest, RanchOptions), - {ok, _} = minirest:start(listener_name(Proto), MinirestOptions), - ?ULOG("Start ~p listener on ~p successfully.~n", [listener_name(Proto), Port]). + dispatch => Dispatch + }, + [begin + Minirest = maps:put(protocol, Protocol, BaseMinirest), + {ok, _} = minirest:start(Name, RanchOptions, Minirest), + ?ULOG("Start listener ~s on ~p successfully.~n", [Name, Port]) + end || {Name, Protocol, Port, RanchOptions} <- listeners()]. + +stop_listeners() -> + [begin + ok = minirest:stop(Name), + ?ULOG("Stop listener ~s on ~p successfully.~n", [Name, Port]) + end || {Name, _, Port, _} <- listeners()]. + +%%-------------------------------------------------------------------- +%% internal apps() -> [App || {App, _, _} <- application:loaded_applications(), @@ -78,30 +82,48 @@ apps() -> _ -> false end]. -ranch_opts(Port, Options0) -> - Options = lists:foldl( - fun - ({K, _V}, Acc) when K =:= max_connections orelse K =:= num_acceptors -> Acc; - ({inet6, true}, Acc) -> [inet6 | Acc]; - ({inet6, false}, Acc) -> Acc; - ({ipv6_v6only, true}, Acc) -> [{ipv6_v6only, true} | Acc]; - ({ipv6_v6only, false}, Acc) -> Acc; - ({K, V}, Acc)-> - [{K, V} | Acc] - end, [], Options0), - maps:from_list([{port, Port} | Options]). - -stop_listener({Proto, Port, _}) -> - ?ULOG("Stop dashboard listener on ~s successfully.~n", [format(Port)]), - minirest:stop(listener_name(Proto)). - listeners() -> - [{Protocol, Port, maps:to_list(maps:without([protocol, port], Map))} - || Map = #{protocol := Protocol,port := Port} - <- emqx:get_config([emqx_dashboard, listeners], [])]. + [begin + Protocol = maps:get(protocol, ListenerOptions, http), + Port = maps:get(port, ListenerOptions, 18083), + Name = listener_name(Protocol, Port), + RanchOptions = ranch_opts(maps:without([protocol], ListenerOptions)), + {Name, Protocol, Port, RanchOptions} + end || ListenerOptions <- emqx_config:get([emqx_dashboard, listeners], [])]. -listener_name(Proto) -> - list_to_atom(atom_to_list(Proto) ++ ":dashboard"). +ranch_opts(RanchOptions) -> + Keys = [ {ack_timeout, handshake_timeout} + , connection_type + , max_connections + , num_acceptors + , shutdown + , socket], + {S, R} = lists:foldl(fun key_take/2, {RanchOptions, #{}}, Keys), + R#{socket_opts => maps:fold(fun key_only/3, [], S)}. + + +key_take({K, K1}, {All, R}) -> + case maps:get(K, All, undefined) of + undefined -> + {All, R}; + V -> + {maps:remove(K, All), R#{K1 => V}} + end; +key_take(K, {All, R}) -> + case maps:get(K, All, undefined) of + undefined -> + {All, R}; + V -> + {maps:remove(K, All), R#{K => V}} + end. + +key_only(K , true , S) -> [K | S]; +key_only(_K, false, S) -> S; +key_only(K , V , S) -> [{K, V} | S]. + +listener_name(Protocol, Port) -> + Name = "dashboard:" ++ atom_to_list(Protocol) ++ ":" ++ integer_to_list(Port), + list_to_atom(Name). authorize_appid(Req) -> case cowboy_req:parse_header(<<"authorization">>, Req) of @@ -127,10 +149,3 @@ authorize_appid(Req) -> #{code => <<"UNAUTHORIZED">>, message => <<"POST '/login'">>}} end. - -format(Port) when is_integer(Port) -> - io_lib:format("0.0.0.0:~w", [Port]); -format({Addr, Port}) when is_list(Addr) -> - io_lib:format("~s:~w", [Addr, Port]); -format({Addr, Port}) when is_tuple(Addr) -> - io_lib:format("~s:~w", [inet:ntoa(Addr), Port]). diff --git a/apps/emqx_dashboard/src/emqx_dashboard_app.erl b/apps/emqx_dashboard/src/emqx_dashboard_app.erl index edcc19d8b..4e1b0caec 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_app.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_app.erl @@ -27,7 +27,7 @@ start(_StartType, _StartArgs) -> {ok, Sup} = emqx_dashboard_sup:start_link(), ok = ekka_rlog:wait_for_shards([?DASHBOARD_SHARD], infinity), - emqx_dashboard:start_listeners(), + _ = emqx_dashboard:start_listeners(), emqx_dashboard_cli:load(), ok = emqx_dashboard_admin:add_default_user(), {ok, Sup}. diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl index e0ff21ada..7dfbc923b 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl @@ -37,14 +37,13 @@ fields("http") -> , {"num_acceptors", emqx_schema:t(integer(), undefined, 4)} , {"max_connections", emqx_schema:t(integer(), undefined, 512)} , {"backlog", emqx_schema:t(integer(), undefined, 1024)} - , {"send_timeout", emqx_schema:t(emqx_schema:duration(), undefined, "15s")} - , {"send_timeout_close", emqx_schema:t(boolean(), undefined, true)} + , {"send_timeout", emqx_schema:t(emqx_schema:duration(), undefined, "5s")} , {"inet6", emqx_schema:t(boolean(), undefined, false)} , {"ipv6_v6only", emqx_schema:t(boolean(), undefined, false)} ]; fields("https") -> - emqx_schema:ssl(#{enable => true}) ++ fields("http"). + proplists:delete("fail_if_no_peer_cert", emqx_schema:ssl(#{})) ++ fields("http"). default_username(type) -> string(); default_username(default) -> "admin"; diff --git a/rebar.config b/rebar.config index c1710bda2..4e622b738 100644 --- a/rebar.config +++ b/rebar.config @@ -51,7 +51,7 @@ , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}} , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.8"}}} , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}} - , {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.0"}}} + , {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.1"}}} , {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.1"}}} , {replayq, "0.3.3"} , {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}