From 569d54a4c017e5b5960c1cac09a106f18f322225 Mon Sep 17 00:00:00 2001 From: Turtle Date: Mon, 26 Jul 2021 13:37:28 +0800 Subject: [PATCH] feat(dashboard): Update the configuration file to hocon --- apps/emqx/src/emqx_schema.erl | 1 + apps/emqx_dashboard/etc/emqx_dashboard.conf | 165 ++++------------- .../emqx_dashboard/include/emqx_dashboard.hrl | 2 +- .../emqx_dashboard/priv/emqx_dashboard.schema | 152 ---------------- .../emqx_dashboard/src/emqx_dashboard.app.src | 2 +- .../src/emqx_dashboard.appup.src | 16 -- apps/emqx_dashboard/src/emqx_dashboard.erl | 168 +++++++++--------- .../src/emqx_dashboard_admin.erl | 4 +- .../src/emqx_dashboard_schema.erl | 55 ++++++ apps/emqx_management/src/emqx_mgmt_http.erl | 2 +- rebar.config.erl | 1 + scripts/merge-config.escript | 22 +-- 12 files changed, 189 insertions(+), 401 deletions(-) delete mode 100644 apps/emqx_dashboard/priv/emqx_dashboard.schema delete mode 100644 apps/emqx_dashboard/src/emqx_dashboard.appup.src create mode 100644 apps/emqx_dashboard/src/emqx_dashboard_schema.erl diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index cfaffc6c7..19ca12142 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -91,6 +91,7 @@ includes() -> , "emqx_bridge_mqtt" , "emqx_modules" , "emqx_management" + , "emqx_dashboard" , "emqx_gateway" ]. -endif. diff --git a/apps/emqx_dashboard/etc/emqx_dashboard.conf b/apps/emqx_dashboard/etc/emqx_dashboard.conf index 4f28ec84e..b94c4f50c 100644 --- a/apps/emqx_dashboard/etc/emqx_dashboard.conf +++ b/apps/emqx_dashboard/etc/emqx_dashboard.conf @@ -1,130 +1,41 @@ ##-------------------------------------------------------------------- -## EMQ X Dashboard +## Dashboard for EMQ X ##-------------------------------------------------------------------- -## Default user's login name. -## -## Value: String -dashboard.default_user.login = admin - -## Default user's password. -## -## Value: String -dashboard.default_user.password = public - -##-------------------------------------------------------------------- -## HTTP Listener - -## The port that the Dashboard HTTP listener will bind. -## -## Value: Port -## -## Examples: 18083 -dashboard.listener.http.port = 18083 - -## The acceptor pool for external Dashboard HTTP listener. -## -## Value: Number -dashboard.listener.http.acceptors = 4 - -## Maximum number of concurrent Dashboard HTTP connections. -## -## Value: Number -dashboard.listener.http.max_clients = 512 - -## Set up the socket for IPv6. -## -## Value: false | true -dashboard.listener.http.inet6 = false - -## Listen on IPv4 and IPv6 (false) or only on IPv6 (true). Use with inet6. -## -## Value: false | true -dashboard.listener.http.ipv6_v6only = false - -##-------------------------------------------------------------------- -## HTTPS Listener - -## The port that the Dashboard HTTPS listener will bind. -## -## Value: Port -## -## Examples: 18084 -## dashboard.listener.https.port = 18084 - -## The acceptor pool for external Dashboard HTTPS listener. -## -## Value: Number -## dashboard.listener.https.acceptors = 2 - -## Maximum number of concurrent Dashboard HTTPS connections. -## -## Value: Number -## dashboard.listener.https.max_clients = 512 - -## Set up the socket for IPv6. -## -## Value: false | true -## dashboard.listener.https.inet6 = false - -## Listen on IPv4 and IPv6 (false) or only on IPv6 (true). Use with inet6. -## -## Value: false | true -## dashboard.listener.https.ipv6_v6only = false - -## Path to the file containing the user's private PEM-encoded key. -## -## Value: File -## dashboard.listener.https.keyfile = "etc/certs/key.pem" - -## Path to a file containing the user certificate. -## -## Value: File -## dashboard.listener.https.certfile = "etc/certs/cert.pem" - -## Path to the file containing PEM-encoded CA certificates. -## -## Value: File -## dashboard.listener.https.cacertfile = "etc/certs/cacert.pem" - -## See: 'listener.ssl..dhfile' in emq.conf -## -## Value: File -## dashboard.listener.https.dhfile = "{{ platform_etc_dir }}/certs/dh-params.pem" - -## See: 'listener.ssl..verify' in emq.conf -## -## Value: verify_peer | verify_none -## dashboard.listener.https.verify = verify_peer - -## See: 'listener.ssl..fail_if_no_peer_cert' in emq.conf -## -## Value: false | true -## dashboard.listener.https.fail_if_no_peer_cert = true - -## TLS versions only to protect from POODLE attack. -## -## Value: String, seperated by ',' -## NOTE: Do not use tlsv1.3 if emqx is running on OTP-22 or earlier -## dashboard.listener.https.tls_versions = "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" - -## See: 'listener.ssl..ciphers' in emq.conf -## -## Value: Ciphers -## dashboard.listener.https.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" - -## See: 'listener.ssl..secure_renegotiate' in emq.conf -## -## Value: on | off -## dashboard.listener.https.secure_renegotiate = off - -## See: 'listener.ssl..reuse_sessions' in emq.conf -## -## Value: on | off -## dashboard.listener.https.reuse_sessions = on - -## See: 'listener.ssl..honor_cipher_order' in emq.conf -## -## Value: on | off -## dashboard.listener.https.honor_cipher_order = on - +emqx_dashboard:{ + default_username: "admin" + default_password: "public" + listeners: [ + { + num_acceptors: 4 + max_connections: 512 + protocol: http + port: 18083 + backlog: 512 + send_timeout: 15s + send_timeout_close: true + inet6: false + ipv6_v6only: false + } +## , +## { +## protocol: https +## port: 8081 +## 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 +## } + ] +} diff --git a/apps/emqx_dashboard/include/emqx_dashboard.hrl b/apps/emqx_dashboard/include/emqx_dashboard.hrl index 891a723f7..cc3d9b3d6 100644 --- a/apps/emqx_dashboard/include/emqx_dashboard.hrl +++ b/apps/emqx_dashboard/include/emqx_dashboard.hrl @@ -14,7 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- --record(mqtt_admin, {username, password, tags}). +-record(mqtt_admin, {username, password, tags, role = undefined}). -type(mqtt_admin() :: #mqtt_admin{}). diff --git a/apps/emqx_dashboard/priv/emqx_dashboard.schema b/apps/emqx_dashboard/priv/emqx_dashboard.schema deleted file mode 100644 index 203bb7358..000000000 --- a/apps/emqx_dashboard/priv/emqx_dashboard.schema +++ /dev/null @@ -1,152 +0,0 @@ -%%-*- mode: erlang -*- -%% emqx_dashboard config mapping - -{mapping, "dashboard.default_user.login", "emqx_dashboard.default_user_username", [ - {datatype, string} -]}. - -{mapping, "dashboard.default_user.password", "emqx_dashboard.default_user_passwd", [ - {datatype, string}, - {override_env, "EMQX_ADMIN_PASSWORD"} -]}. - -{mapping, "dashboard.listener.http.port", "emqx_dashboard.listeners", [ - {datatype, integer} -]}. - -{mapping, "dashboard.listener.http.acceptors", "emqx_dashboard.listeners", [ - {default, 4}, - {datatype, integer} -]}. - -{mapping, "dashboard.listener.http.max_clients", "emqx_dashboard.listeners", [ - {default, 512}, - {datatype, integer} -]}. - -{mapping, "dashboard.listener.http.access.$id", "emqx_dashboard.listeners", [ - {datatype, string} -]}. - -{mapping, "dashboard.listener.http.inet6", "emqx_dashboard.listeners", [ - {default, false}, - {datatype, {enum, [true, false]}} -]}. - -{mapping, "dashboard.listener.http.ipv6_v6only", "emqx_dashboard.listeners", [ - {default, false}, - {datatype, {enum, [true, false]}} -]}. - -{mapping, "dashboard.listener.https.port", "emqx_dashboard.listeners", [ - {datatype, integer} -]}. - -{mapping, "dashboard.listener.https.acceptors", "emqx_dashboard.listeners", [ - {default, 8}, - {datatype, integer} -]}. - -{mapping, "dashboard.listener.https.max_clients", "emqx_dashboard.listeners", [ - {default, 64}, - {datatype, integer} -]}. - -{mapping, "dashboard.listener.https.inet6", "emqx_dashboard.listeners", [ - {default, false}, - {datatype, {enum, [true, false]}} -]}. - -{mapping, "dashboard.listener.https.ipv6_v6only", "emqx_dashboard.listeners", [ - {default, false}, - {datatype, {enum, [true, false]}} -]}. - -{mapping, "dashboard.listener.https.tls_versions", "emqx_dashboard.listeners", [ - {datatype, string} -]}. - -{mapping, "dashboard.listener.https.dhfile", "emqx_dashboard.listeners", [ - {datatype, string} -]}. - -{mapping, "dashboard.listener.https.keyfile", "emqx_dashboard.listeners", [ - {datatype, string} -]}. - -{mapping, "dashboard.listener.https.certfile", "emqx_dashboard.listeners", [ - {datatype, string} -]}. - -{mapping, "dashboard.listener.https.cacertfile", "emqx_dashboard.listeners", [ - {datatype, string} -]}. - -{mapping, "dashboard.listener.https.verify", "emqx_dashboard.listeners", [ - {datatype, string} -]}. - -{mapping, "dashboard.listener.https.fail_if_no_peer_cert", "emqx_dashboard.listeners", [ - {datatype, {enum, [true, false]}} -]}. - -{mapping, "dashboard.listener.https.ciphers", "emqx_dashboard.listeners", [ - {datatype, string} -]}. - -{mapping, "dashboard.listener.https.secure_renegotiate", "emqx_dashboard.listeners", [ - {datatype, flag} -]}. - -{mapping, "dashboard.listener.https.reuse_sessions", "emqx_dashboard.listeners", [ - {default, on}, - {datatype, flag} -]}. - -{mapping, "dashboard.listener.https.honor_cipher_order", "emqx_dashboard.listeners", [ - {datatype, flag} -]}. - -{translation, "emqx_dashboard.listeners", fun(Conf) -> - Filter = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end, - LisOpts = fun(Prefix) -> - Filter([{num_acceptors, cuttlefish:conf_get(Prefix ++ ".acceptors", Conf)}, - {max_connections, cuttlefish:conf_get(Prefix ++ ".max_clients", Conf)}, - {inet6, cuttlefish:conf_get(Prefix ++ ".inet6", Conf)}, - {ipv6_v6only, cuttlefish:conf_get(Prefix ++ ".ipv6_v6only", Conf)}]) - end, - - SplitFun = fun(undefined) -> undefined; (S) -> string:tokens(S, ",") end, - - SslOpts = fun(Prefix) -> - Versions = case SplitFun(cuttlefish:conf_get(Prefix ++ ".tls_versions", Conf, undefined)) of - undefined -> undefined; - L -> [list_to_atom(V) || V <- L] - end, - Filter([{versions, Versions}, - {ciphers, SplitFun(cuttlefish:conf_get(Prefix ++ ".ciphers", Conf, undefined))}, - {dhfile, cuttlefish:conf_get(Prefix ++ ".dhfile", Conf, undefined)}, - {keyfile, cuttlefish:conf_get(Prefix ++ ".keyfile", Conf, undefined)}, - {certfile, cuttlefish:conf_get(Prefix ++ ".certfile", Conf, undefined)}, - {cacertfile, cuttlefish:conf_get(Prefix ++ ".cacertfile", Conf, undefined)}, - {verify, cuttlefish:conf_get(Prefix ++ ".verify", Conf, undefined)}, - {fail_if_no_peer_cert, cuttlefish:conf_get(Prefix ++ ".fail_if_no_peer_cert", Conf, undefined)}, - {secure_renegotiate, cuttlefish:conf_get(Prefix ++ ".secure_renegotiate", Conf, undefined)}, - {reuse_sessions, cuttlefish:conf_get(Prefix ++ ".reuse_sessions", Conf, undefined)}, - {honor_cipher_order, cuttlefish:conf_get(Prefix ++ ".honor_cipher_order", Conf, undefined)}]) - end, - lists:append( - lists:map( - fun(Proto) -> - Prefix = "dashboard.listener." ++ atom_to_list(Proto), - case cuttlefish:conf_get(Prefix ++ ".port", Conf, undefined) of - undefined -> []; - Port -> - [{Proto, Port, case Proto of - http -> LisOpts(Prefix); - https -> LisOpts(Prefix) ++ SslOpts(Prefix) - end}] - end - end, [http, https])) -end}. - diff --git a/apps/emqx_dashboard/src/emqx_dashboard.app.src b/apps/emqx_dashboard/src/emqx_dashboard.app.src index 1604198dc..788677a33 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.app.src +++ b/apps/emqx_dashboard/src/emqx_dashboard.app.src @@ -1,6 +1,6 @@ {application, emqx_dashboard, [{description, "EMQ X Web Dashboard"}, - {vsn, "4.4.0"}, % strict semver, bump manually! + {vsn, "5.0.0"}, % strict semver, bump manually! {modules, []}, {registered, [emqx_dashboard_sup]}, {applications, [kernel,stdlib,mnesia,minirest]}, diff --git a/apps/emqx_dashboard/src/emqx_dashboard.appup.src b/apps/emqx_dashboard/src/emqx_dashboard.appup.src deleted file mode 100644 index 678bd3b22..000000000 --- a/apps/emqx_dashboard/src/emqx_dashboard.appup.src +++ /dev/null @@ -1,16 +0,0 @@ -%% -*- mode: erlang -*- -{VSN, - [ {"4.3.0", - %% load all plugins - %% NOTE: this depends on the fact that emqx_dashboard is always - %% the last application gets upgraded - [ {apply, {emqx_plugins, load, []}} - ]}, - {<<".*">>, []} - ], - [ {"4.3.0", - [ {apply, {emqx_plugins, load, []}} - ]}, - {<<".*">>, []} - ] -}. diff --git a/apps/emqx_dashboard/src/emqx_dashboard.erl b/apps/emqx_dashboard/src/emqx_dashboard.erl index 8e81b979f..e7b227f77 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard.erl @@ -16,114 +16,112 @@ -module(emqx_dashboard). --include_lib("emqx/include/emqx.hrl"). --include_lib("emqx/include/logger.hrl"). +-define(APP, ?MODULE). -%%-import(proplists, [get_value/3]). -export([ start_listeners/0 , stop_listeners/0 , start_listener/1 - , stop_listener/1 - ]). + , stop_listener/1]). -%% for minirest --export([ filter/1 - , is_authorized/1 - ]). +%% Authorization +-export([authorize_appid/1]). --define(APP, ?MODULE). + +-define(BASE_PATH, "/api/v5"). %%-------------------------------------------------------------------- -%% Start/Stop listeners. +%% Start/Stop Listeners %%-------------------------------------------------------------------- start_listeners() -> - lists:foreach(fun(Listener) -> start_listener(Listener) end, listeners()). - -%% Start HTTP Listener -start_listener(_) -> ok. -%% TODO: V5 API -%%start_listener({Proto, Port, Options}) when Proto == http -> -%% Dispatch = [{"/", cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}}, -%% {"/static/[...]", cowboy_static, {priv_dir, emqx_dashboard, "www/static"}}, -%% {"/api/v4/[...]", minirest, http_handlers()}], -%% minirest:start_http(listener_name(Proto), ranch_opts(Port, Options), Dispatch); -%% -%%start_listener({Proto, Port, Options}) when Proto == https -> -%% Dispatch = [{"/", cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}}, -%% {"/static/[...]", cowboy_static, {priv_dir, emqx_dashboard, "www/static"}}, -%% {"/api/v4/[...]", minirest, http_handlers()}], -%% minirest:start_https(listener_name(Proto), ranch_opts(Port, Options), Dispatch). -%% -%%ranch_opts(Port, Options0) -> -%% NumAcceptors = get_value(num_acceptors, Options0, 4), -%% MaxConnections = get_value(max_connections, Options0, 512), -%% 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), -%% #{num_acceptors => NumAcceptors, -%% max_connections => MaxConnections, -%% socket_opts => [{port, Port} | Options]}. + lists:foreach(fun start_listener/1, listeners()). stop_listeners() -> - lists:foreach(fun(Listener) -> stop_listener(Listener) end, listeners()). + lists:foreach(fun stop_listener/1, listeners()). -stop_listener(_) -> - ok. -%% TODO: V5 API -%%stop_listener({Proto, _Port, _}) -> -%% minirest:stop_http(listener_name(Proto)). +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"}, + servers => [#{url => ?BASE_PATH}], + components => #{ + schemas => #{}, + securitySchemes => #{ + application => #{ + 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"}}], + Minirest = #{ + protocol => Proto, + base_path => ?BASE_PATH, + apps => apps(), + authorization => Authorization, + security => [#{application => []}], + swagger_global_spec => GlobalSpec, + dispatch => Dispatch}, + MinirestOptions = maps:merge(Minirest, RanchOptions), + {ok, _} = minirest:start(listener_name(Proto), MinirestOptions), + io:format("Start ~p listener on ~p successfully.~n", [listener_name(Proto), Port]). + +apps() -> + [App || {App, _, _} <- application:loaded_applications(), + case re:run(atom_to_list(App), "^emqx") of + {match,[{0,4}]} -> true; + _ -> 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, _}) -> + io:format("Stop dashboard listener on ~s successfully.~n",[format(Port)]), + minirest:stop(listener_name(Proto)). listeners() -> - application:get_env(?APP, listeners, []). + [{Protocol, Port, maps:to_list(maps:without([protocol, port], Map))} + || Map = #{protocol := Protocol,port := Port} + <- emqx_config:get([emqx_dashboard, listeners], [])]. -%%listener_name(Proto) -> -%% list_to_atom(atom_to_list(Proto) ++ ":dashboard"). +listener_name(Proto) -> + list_to_atom(atom_to_list(Proto) ++ ":dashboard"). -%%-------------------------------------------------------------------- -%% HTTP Handlers and Dispatcher -%%-------------------------------------------------------------------- - -%%http_handlers() -> -%% Plugins = lists:map(fun(Plugin) -> Plugin#plugin.name end, emqx_plugins:list()), -%% [{"/api/v4/", -%% minirest:handler(#{apps => Plugins ++ [emqx_modules], -%% filter => fun ?MODULE:filter/1}), -%% [{authorization, fun ?MODULE:is_authorized/1}]}]. - -%%-------------------------------------------------------------------- -%% Basic Authorization -%%-------------------------------------------------------------------- - -is_authorized(Req) -> - is_authorized(binary_to_list(cowboy_req:path(Req)), Req). - -is_authorized("/api/v4/auth", _Req) -> - true; -is_authorized(_Path, Req) -> +authorize_appid(Req) -> case cowboy_req:parse_header(<<"authorization">>, Req) of {basic, Username, Password} -> case emqx_dashboard_admin:check(iolist_to_binary(Username), iolist_to_binary(Password)) of - ok -> true; - {error, Reason} -> - ?LOG(error, "[Dashboard] Authorization Failure: username=~s, reason=~p", - [Username, Reason]), - false + ok -> + ok; + {error, _} -> + {401, #{<<"WWW-Authenticate">> => + <<"Basic Realm=\"minirest-server\"">>}, + <<"UNAUTHORIZED">>} end; - _ -> false + _ -> + {401, #{<<"WWW-Authenticate">> => + <<"Basic Realm=\"minirest-server\"">>}, + <<"UNAUTHORIZED">>} end. -filter(#{app := emqx_modules}) -> true; -filter(#{app := App}) -> - case emqx_plugins:find_plugin(App) of - false -> false; - Plugin -> Plugin#plugin.active - 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_admin.erl b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl index 202914982..fdec41b2b 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_admin.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl @@ -182,7 +182,7 @@ check(Username, Password) -> init([]) -> %% Add default admin user - _ = add_default_user(binenv(default_user_username), binenv(default_user_passwd)), + _ = add_default_user(binenv(default_username), binenv(default_password)), {ok, state}. handle_call(_Req, _From, State) -> @@ -217,7 +217,7 @@ salt() -> <>. binenv(Key) -> - iolist_to_binary(application:get_env(emqx_dashboard, Key, "")). + iolist_to_binary(emqx_config:get([emqx_dashboard, Key], "")). add_default_user(Username, Password) when ?EMPTY_KEY(Username) orelse ?EMPTY_KEY(Password) -> igonre; diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl new file mode 100644 index 000000000..45ad345fb --- /dev/null +++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl @@ -0,0 +1,55 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_dashboard_schema). + +-include_lib("typerefl/include/types.hrl"). + +-export([ structs/0 + , fields/1]). + +structs() -> ["emqx_dashboard"]. + +fields("emqx_dashboard") -> + [ {listeners, hoconsc:array(hoconsc:union([hoconsc:ref(?MODULE, "http"), + hoconsc:ref(?MODULE, "https")]))} + , {default_username, fun default_username/1} + , {default_password, fun default_password/1} + ]; + +fields("http") -> + [ {"protocol", hoconsc:enum([http, https])} + , {"port", emqx_schema:t(integer(), undefined, 8081)} + , {"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)} + , {"inet6", emqx_schema:t(boolean(), undefined, false)} + , {"ipv6_v6only", emqx_schema:t(boolean(), undefined, false)} + ]; + +fields("https") -> + emqx_schema:ssl(#{enable => true}) ++ fields("http"). + +default_username(type) -> string(); +default_username(default) -> "admin"; +default_username(nullable) -> false; +default_username(_) -> undefined. + +default_password(type) -> string(); +default_password(default) -> "public"; +default_password(nullable) -> false; +default_password(_) -> undefined. diff --git a/apps/emqx_management/src/emqx_mgmt_http.erl b/apps/emqx_management/src/emqx_mgmt_http.erl index c431fb4c3..6d768b261 100644 --- a/apps/emqx_management/src/emqx_mgmt_http.erl +++ b/apps/emqx_management/src/emqx_mgmt_http.erl @@ -63,7 +63,7 @@ start_listener({Proto, Port, Options}) -> swagger_global_spec => GlobalSpec}, MinirestOptions = maps:merge(Minirest, RanchOptions), {ok, _} = minirest:start(listener_name(Proto), MinirestOptions), - io:format("Start ~p listener on ~p successfully.", [listener_name(Proto), Port]). + io:format("Start ~p listener on ~p successfully.~n", [listener_name(Proto), Port]). apps() -> Apps = [App || {App, _, _} <- application:loaded_applications(), diff --git a/rebar.config.erl b/rebar.config.erl index 49679d783..eddd6bac1 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -269,6 +269,7 @@ relx_apps(ReleaseType) -> , emqx_bridge_mqtt , emqx_modules , emqx_management + , emqx_dashboard , emqx_retainer , emqx_statsd ] diff --git a/scripts/merge-config.escript b/scripts/merge-config.escript index 503e6faa8..78739c1e9 100755 --- a/scripts/merge-config.escript +++ b/scripts/merge-config.escript @@ -15,22 +15,12 @@ main(_) -> {ok, Bin} = file:read_file(BaseConf), Apps = filelib:wildcard("emqx_*", "apps/"), Conf = lists:foldl(fun(App, Acc) -> - case lists:member(App, ["emqx_exhook", - "emqx_exproto", - "emqx_lwm2m", - "emqx_sn", - "emqx_coap", - "emqx_stomp", - "emqx_dashboard"]) of - true -> Acc; - false -> - Filename = filename:join([apps, App, "etc", App]) ++ ".conf", - case filelib:is_regular(Filename) of - true -> - {ok, Bin1} = file:read_file(Filename), - [Acc, io_lib:nl(), Bin1]; - false -> Acc - end + Filename = filename:join([apps, App, "etc", App]) ++ ".conf", + case filelib:is_regular(Filename) of + true -> + {ok, Bin1} = file:read_file(Filename), + [Acc, io_lib:nl(), Bin1]; + false -> Acc end end, Bin, Apps), ok = file:write_file("apps/emqx/etc/emqx.conf.all", Conf).