286 lines
9.5 KiB
Erlang
286 lines
9.5 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2020-2023 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_https_SUITE).
|
|
|
|
-compile(nowarn_export_all).
|
|
-compile(export_all).
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
|
|
|
-define(NAME, 'https:dashboard').
|
|
-define(HOST_HTTPS, "https://127.0.0.1:18084").
|
|
-define(HOST_HTTP, "http://127.0.0.1:18083").
|
|
-define(BASE_PATH, "/api/v5").
|
|
-define(OVERVIEWS, [
|
|
"alarms",
|
|
"banned",
|
|
"stats",
|
|
"metrics",
|
|
"listeners",
|
|
"clients",
|
|
"subscriptions"
|
|
]).
|
|
|
|
all() ->
|
|
emqx_common_test_helpers:all(?MODULE).
|
|
|
|
init_per_suite(Config) -> Config.
|
|
end_per_suite(_Config) -> emqx_mgmt_api_test_util:end_suite([emqx_management]).
|
|
|
|
init_per_testcase(_TestCase, Config) -> Config.
|
|
end_per_testcase(_TestCase, _Config) -> emqx_mgmt_api_test_util:end_suite([emqx_management]).
|
|
|
|
t_update_conf(_Config) ->
|
|
Conf = #{
|
|
dashboard => #{
|
|
listeners => #{
|
|
https => #{bind => 18084},
|
|
http => #{bind => 18083}
|
|
}
|
|
}
|
|
},
|
|
emqx_common_test_helpers:load_config(emqx_dashboard_schema, Conf),
|
|
emqx_mgmt_api_test_util:init_suite([emqx_management], fun(X) -> X end),
|
|
Headers = emqx_dashboard_SUITE:auth_header_(),
|
|
{ok, Client1} = emqx_dashboard_SUITE:request_dashboard(
|
|
get, https_api_path(["clients"]), Headers
|
|
),
|
|
{ok, Client2} = emqx_dashboard_SUITE:request_dashboard(
|
|
get, http_api_path(["clients"]), Headers
|
|
),
|
|
Raw = emqx:get_raw_config([<<"dashboard">>]),
|
|
?assertEqual(Client1, Client2),
|
|
?check_trace(
|
|
begin
|
|
Raw1 = emqx_utils_maps:deep_put(
|
|
[<<"listeners">>, <<"https">>, <<"bind">>], Raw, 0
|
|
),
|
|
?assertMatch({ok, _}, emqx:update_config([<<"dashboard">>], Raw1)),
|
|
?assertEqual(Raw1, emqx:get_raw_config([<<"dashboard">>])),
|
|
{ok, _} = ?block_until(#{?snk_kind := regenerate_minirest_dispatch}, 10000),
|
|
ok
|
|
end,
|
|
fun(ok, Trace) ->
|
|
%% Don't start new listener, so is empty
|
|
?assertMatch([#{listeners := []}], ?of_kind(regenerate_minirest_dispatch, Trace))
|
|
end
|
|
),
|
|
{ok, Client3} = emqx_dashboard_SUITE:request_dashboard(
|
|
get, http_api_path(["clients"]), Headers
|
|
),
|
|
?assertEqual(Client1, Client3),
|
|
?assertMatch(
|
|
{error,
|
|
{failed_connect, [
|
|
_,
|
|
{inet, [inet], econnrefused}
|
|
]}},
|
|
emqx_dashboard_SUITE:request_dashboard(get, https_api_path(["clients"]), Headers)
|
|
),
|
|
%% reset
|
|
?check_trace(
|
|
begin
|
|
?assertMatch({ok, _}, emqx:update_config([<<"dashboard">>], Raw)),
|
|
?assertEqual(Raw, emqx:get_raw_config([<<"dashboard">>])),
|
|
{ok, _} = ?block_until(#{?snk_kind := regenerate_minirest_dispatch}, 10000),
|
|
ok
|
|
end,
|
|
fun(ok, Trace) ->
|
|
%% start new listener('https:dashboard')
|
|
?assertMatch(
|
|
[#{listeners := ['https:dashboard']}], ?of_kind(regenerate_minirest_dispatch, Trace)
|
|
)
|
|
end
|
|
),
|
|
{ok, Client1} = emqx_dashboard_SUITE:request_dashboard(
|
|
get, https_api_path(["clients"]), Headers
|
|
),
|
|
{ok, Client2} = emqx_dashboard_SUITE:request_dashboard(
|
|
get, http_api_path(["clients"]), Headers
|
|
),
|
|
emqx_mgmt_api_test_util:end_suite([emqx_management]).
|
|
|
|
t_default_ssl_cert(_Config) ->
|
|
Conf = #{dashboard => #{listeners => #{https => #{bind => 18084}}}},
|
|
validate_https(Conf, 512, default_ssl_cert(), verify_none),
|
|
ok.
|
|
|
|
t_normal_ssl_cert(_Config) ->
|
|
MaxConnection = 1000,
|
|
Conf = #{
|
|
dashboard => #{
|
|
listeners => #{
|
|
https => #{
|
|
bind => 18084,
|
|
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">>),
|
|
max_connections => MaxConnection
|
|
}
|
|
}
|
|
}
|
|
},
|
|
validate_https(Conf, MaxConnection, default_ssl_cert(), verify_none),
|
|
ok.
|
|
|
|
t_verify_cacertfile(_Config) ->
|
|
MaxConnection = 1024,
|
|
DefaultSSLCert = default_ssl_cert(),
|
|
SSLCert = DefaultSSLCert#{cacertfile => <<"">>},
|
|
%% default #{verify => verify_none}
|
|
Conf = #{
|
|
dashboard => #{
|
|
listeners => #{
|
|
https => #{
|
|
bind => 18084,
|
|
cacertfile => <<"">>,
|
|
max_connections => MaxConnection
|
|
}
|
|
}
|
|
}
|
|
},
|
|
validate_https(Conf, MaxConnection, SSLCert, verify_none),
|
|
%% verify_peer but cacertfile is empty
|
|
VerifyPeerConf1 = emqx_utils_maps:deep_put(
|
|
[dashboard, listeners, https, verify],
|
|
Conf,
|
|
verify_peer
|
|
),
|
|
emqx_common_test_helpers:load_config(emqx_dashboard_schema, VerifyPeerConf1),
|
|
?assertMatch({error, [?NAME]}, emqx_dashboard:start_listeners()),
|
|
%% verify_peer and cacertfile is ok.
|
|
VerifyPeerConf2 = emqx_utils_maps:deep_put(
|
|
[dashboard, listeners, https, cacertfile],
|
|
VerifyPeerConf1,
|
|
naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/cacert.pem">>)
|
|
),
|
|
validate_https(VerifyPeerConf2, MaxConnection, DefaultSSLCert, verify_peer),
|
|
ok.
|
|
|
|
t_bad_certfile(_Config) ->
|
|
Conf = #{
|
|
dashboard => #{
|
|
listeners => #{
|
|
https => #{
|
|
bind => 18084,
|
|
certfile => <<"${EMQX_ETC_DIR}/certs/not_found_cert.pem">>
|
|
}
|
|
}
|
|
}
|
|
},
|
|
emqx_common_test_helpers:load_config(emqx_dashboard_schema, Conf),
|
|
?assertMatch({error, [?NAME]}, emqx_dashboard:start_listeners()),
|
|
ok.
|
|
|
|
validate_https(Conf, MaxConnection, SSLCert, Verify) ->
|
|
emqx_common_test_helpers:load_config(emqx_dashboard_schema, Conf),
|
|
emqx_mgmt_api_test_util:init_suite([emqx_management], fun(X) -> X end),
|
|
assert_ranch_options(MaxConnection, SSLCert, Verify),
|
|
assert_https_request(),
|
|
emqx_mgmt_api_test_util:end_suite([emqx_management]).
|
|
|
|
assert_ranch_options(MaxConnections0, SSLCert, Verify) ->
|
|
Middlewares = [emqx_dashboard_middleware, cowboy_router, cowboy_handler],
|
|
[
|
|
?NAME,
|
|
ranch_ssl,
|
|
#{
|
|
max_connections := MaxConnections,
|
|
num_acceptors := _,
|
|
socket_opts := SocketOpts
|
|
},
|
|
cowboy_tls,
|
|
#{
|
|
env := #{
|
|
dispatch := {persistent_term, ?NAME},
|
|
options := #{
|
|
name := ?NAME,
|
|
protocol := https,
|
|
protocol_options := #{proxy_header := false},
|
|
security := [#{basicAuth := []}, #{bearerAuth := []}],
|
|
swagger_global_spec := _
|
|
}
|
|
},
|
|
middlewares := Middlewares,
|
|
proxy_header := false
|
|
}
|
|
] = ranch_server:get_listener_start_args(?NAME),
|
|
?assertEqual(MaxConnections0, MaxConnections),
|
|
?assert(lists:member(inet, SocketOpts), SocketOpts),
|
|
#{
|
|
backlog := 1024,
|
|
ciphers := Ciphers,
|
|
port := 18084,
|
|
send_timeout := 10000,
|
|
verify := Verify,
|
|
versions := Versions
|
|
} = SocketMaps = maps:from_list(SocketOpts -- [inet]),
|
|
%% without tlsv1.1 tlsv1
|
|
?assertMatch(['tlsv1.3', 'tlsv1.2'], Versions),
|
|
?assert(Ciphers =/= []),
|
|
maps:foreach(
|
|
fun(K, ConfVal) ->
|
|
case maps:find(K, SocketMaps) of
|
|
{ok, File} -> ?assertEqual(naive_env_interpolation(ConfVal), File);
|
|
error -> ?assertEqual(<<"">>, ConfVal)
|
|
end
|
|
end,
|
|
SSLCert
|
|
),
|
|
?assertMatch(
|
|
#{
|
|
env := #{dispatch := {persistent_term, ?NAME}},
|
|
middlewares := Middlewares,
|
|
proxy_header := false
|
|
},
|
|
ranch:get_protocol_options(?NAME)
|
|
),
|
|
ok.
|
|
|
|
assert_https_request() ->
|
|
Headers = emqx_dashboard_SUITE:auth_header_(),
|
|
lists:foreach(
|
|
fun(Path) ->
|
|
ApiPath = https_api_path([Path]),
|
|
?assertMatch(
|
|
{ok, _},
|
|
emqx_dashboard_SUITE:request_dashboard(get, ApiPath, Headers)
|
|
)
|
|
end,
|
|
?OVERVIEWS
|
|
).
|
|
|
|
https_api_path(Parts) ->
|
|
?HOST_HTTPS ++ filename:join([?BASE_PATH | Parts]).
|
|
|
|
http_api_path(Parts) ->
|
|
?HOST_HTTP ++ filename:join([?BASE_PATH | Parts]).
|
|
|
|
naive_env_interpolation(Str0) ->
|
|
Str1 = emqx_schema:naive_env_interpolation(Str0),
|
|
%% assert all envs are replaced
|
|
?assertNot(lists:member($$, Str1)),
|
|
Str1.
|
|
|
|
default_ssl_cert() ->
|
|
#{
|
|
cacertfile => <<"${EMQX_ETC_DIR}/certs/cacert.pem">>,
|
|
certfile => <<"${EMQX_ETC_DIR}/certs/cert.pem">>,
|
|
keyfile => <<"${EMQX_ETC_DIR}/certs/key.pem">>
|
|
}.
|