| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- %%--------------------------------------------------------------------
- %% 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, ssl_options => #{depth => 5}},
- 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(
- 5,
- emqx_utils_maps:deep_get(
- [<<"listeners">>, <<"https">>, <<"ssl_options">>, <<"depth">>], Raw
- )
- ),
- ?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_compatibility_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_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(),
- 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">>
- }.
|