emqx_dashboard_https_SUITE.erl 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_dashboard_https_SUITE).
  17. -compile(nowarn_export_all).
  18. -compile(export_all).
  19. -include_lib("eunit/include/eunit.hrl").
  20. -include_lib("snabbkaffe/include/snabbkaffe.hrl").
  21. -define(NAME, 'https:dashboard').
  22. -define(HOST_HTTPS, "https://127.0.0.1:18084").
  23. -define(HOST_HTTP, "http://127.0.0.1:18083").
  24. -define(BASE_PATH, "/api/v5").
  25. -define(OVERVIEWS, [
  26. "alarms",
  27. "banned",
  28. "stats",
  29. "metrics",
  30. "listeners",
  31. "clients",
  32. "subscriptions"
  33. ]).
  34. all() ->
  35. emqx_common_test_helpers:all(?MODULE).
  36. init_per_suite(Config) -> Config.
  37. end_per_suite(_Config) -> emqx_mgmt_api_test_util:end_suite([emqx_management]).
  38. init_per_testcase(_TestCase, Config) -> Config.
  39. end_per_testcase(_TestCase, _Config) -> emqx_mgmt_api_test_util:end_suite([emqx_management]).
  40. t_update_conf(_Config) ->
  41. Conf = #{
  42. dashboard => #{
  43. listeners => #{
  44. https => #{bind => 18084, enable => true},
  45. http => #{bind => 18083, enable => true}
  46. }
  47. }
  48. },
  49. emqx_common_test_helpers:load_config(emqx_dashboard_schema, Conf),
  50. emqx_mgmt_api_test_util:init_suite([emqx_management], fun(X) -> X end),
  51. Headers = emqx_dashboard_SUITE:auth_header_(),
  52. {ok, Client1} = emqx_dashboard_SUITE:request_dashboard(
  53. get, https_api_path(["clients"]), Headers
  54. ),
  55. {ok, Client2} = emqx_dashboard_SUITE:request_dashboard(
  56. get, http_api_path(["clients"]), Headers
  57. ),
  58. Raw = emqx:get_raw_config([<<"dashboard">>]),
  59. ?assertEqual(Client1, Client2),
  60. ?check_trace(
  61. begin
  62. Raw1 = emqx_utils_maps:deep_put(
  63. [<<"listeners">>, <<"https">>, <<"enable">>], Raw, false
  64. ),
  65. ?assertMatch({ok, _}, emqx:update_config([<<"dashboard">>], Raw1)),
  66. ?assertEqual(Raw1, emqx:get_raw_config([<<"dashboard">>])),
  67. {ok, _} = ?block_until(#{?snk_kind := regenerate_minirest_dispatch}, 10000),
  68. ok
  69. end,
  70. fun(ok, Trace) ->
  71. %% Don't start new listener, so is empty
  72. ?assertMatch([#{listeners := []}], ?of_kind(regenerate_minirest_dispatch, Trace))
  73. end
  74. ),
  75. {ok, Client3} = emqx_dashboard_SUITE:request_dashboard(
  76. get, http_api_path(["clients"]), Headers
  77. ),
  78. ?assertEqual(Client1, Client3),
  79. ?assertMatch(
  80. {error,
  81. {failed_connect, [
  82. _,
  83. {inet, [inet], econnrefused}
  84. ]}},
  85. emqx_dashboard_SUITE:request_dashboard(get, https_api_path(["clients"]), Headers)
  86. ),
  87. %% reset
  88. ?check_trace(
  89. begin
  90. ?assertMatch({ok, _}, emqx:update_config([<<"dashboard">>], Raw)),
  91. ?assertEqual(Raw, emqx:get_raw_config([<<"dashboard">>])),
  92. {ok, _} = ?block_until(#{?snk_kind := regenerate_minirest_dispatch}, 10000),
  93. ok
  94. end,
  95. fun(ok, Trace) ->
  96. %% start new listener('https:dashboard')
  97. ?assertMatch(
  98. [#{listeners := ['https:dashboard']}], ?of_kind(regenerate_minirest_dispatch, Trace)
  99. )
  100. end
  101. ),
  102. {ok, Client1} = emqx_dashboard_SUITE:request_dashboard(
  103. get, https_api_path(["clients"]), Headers
  104. ),
  105. {ok, Client2} = emqx_dashboard_SUITE:request_dashboard(
  106. get, http_api_path(["clients"]), Headers
  107. ),
  108. emqx_mgmt_api_test_util:end_suite([emqx_management]).
  109. t_default_ssl_cert(_Config) ->
  110. Conf = #{dashboard => #{listeners => #{https => #{bind => 18084, enable => true}}}},
  111. validate_https(Conf, 512, default_ssl_cert(), verify_none),
  112. ok.
  113. t_normal_ssl_cert(_Config) ->
  114. MaxConnection = 1000,
  115. Conf = #{
  116. dashboard => #{
  117. listeners => #{
  118. https => #{
  119. bind => 18084,
  120. enable => true,
  121. cacertfile => naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/cacert.pem">>),
  122. certfile => naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/cert.pem">>),
  123. keyfile => naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/key.pem">>),
  124. max_connections => MaxConnection
  125. }
  126. }
  127. }
  128. },
  129. validate_https(Conf, MaxConnection, default_ssl_cert(), verify_none),
  130. ok.
  131. t_verify_cacertfile(_Config) ->
  132. MaxConnection = 1024,
  133. DefaultSSLCert = default_ssl_cert(),
  134. SSLCert = DefaultSSLCert#{cacertfile => <<"">>},
  135. %% default #{verify => verify_none}
  136. Conf = #{
  137. dashboard => #{
  138. listeners => #{
  139. https => #{
  140. bind => 18084,
  141. enable => true,
  142. cacertfile => <<"">>,
  143. max_connections => MaxConnection
  144. }
  145. }
  146. }
  147. },
  148. validate_https(Conf, MaxConnection, SSLCert, verify_none),
  149. %% verify_peer but cacertfile is empty
  150. VerifyPeerConf1 = emqx_utils_maps:deep_put(
  151. [dashboard, listeners, https, verify],
  152. Conf,
  153. verify_peer
  154. ),
  155. emqx_common_test_helpers:load_config(emqx_dashboard_schema, VerifyPeerConf1),
  156. ?assertMatch({error, [?NAME]}, emqx_dashboard:start_listeners()),
  157. %% verify_peer and cacertfile is ok.
  158. VerifyPeerConf2 = emqx_utils_maps:deep_put(
  159. [dashboard, listeners, https, cacertfile],
  160. VerifyPeerConf1,
  161. naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/cacert.pem">>)
  162. ),
  163. validate_https(VerifyPeerConf2, MaxConnection, DefaultSSLCert, verify_peer),
  164. ok.
  165. t_bad_certfile(_Config) ->
  166. Conf = #{
  167. dashboard => #{
  168. listeners => #{
  169. https => #{
  170. bind => 18084,
  171. enable => true,
  172. certfile => <<"${EMQX_ETC_DIR}/certs/not_found_cert.pem">>
  173. }
  174. }
  175. }
  176. },
  177. emqx_common_test_helpers:load_config(emqx_dashboard_schema, Conf),
  178. ?assertMatch({error, [?NAME]}, emqx_dashboard:start_listeners()),
  179. ok.
  180. validate_https(Conf, MaxConnection, SSLCert, Verify) ->
  181. emqx_common_test_helpers:load_config(emqx_dashboard_schema, Conf),
  182. emqx_mgmt_api_test_util:init_suite([emqx_management], fun(X) -> X end),
  183. assert_ranch_options(MaxConnection, SSLCert, Verify),
  184. assert_https_request(),
  185. emqx_mgmt_api_test_util:end_suite([emqx_management]).
  186. assert_ranch_options(MaxConnections0, SSLCert, Verify) ->
  187. Middlewares = [emqx_dashboard_middleware, cowboy_router, cowboy_handler],
  188. [
  189. ?NAME,
  190. ranch_ssl,
  191. #{
  192. max_connections := MaxConnections,
  193. num_acceptors := _,
  194. socket_opts := SocketOpts
  195. },
  196. cowboy_tls,
  197. #{
  198. env := #{
  199. dispatch := {persistent_term, ?NAME},
  200. options := #{
  201. name := ?NAME,
  202. protocol := https,
  203. protocol_options := #{proxy_header := false},
  204. security := [#{basicAuth := []}, #{bearerAuth := []}],
  205. swagger_global_spec := _
  206. }
  207. },
  208. middlewares := Middlewares,
  209. proxy_header := false
  210. }
  211. ] = ranch_server:get_listener_start_args(?NAME),
  212. ?assertEqual(MaxConnections0, MaxConnections),
  213. ?assert(lists:member(inet, SocketOpts), SocketOpts),
  214. #{
  215. backlog := 1024,
  216. ciphers := Ciphers,
  217. port := 18084,
  218. send_timeout := 10000,
  219. verify := Verify,
  220. versions := Versions
  221. } = SocketMaps = maps:from_list(SocketOpts -- [inet]),
  222. %% without tlsv1.1 tlsv1
  223. ?assertMatch(['tlsv1.3', 'tlsv1.2'], Versions),
  224. ?assert(Ciphers =/= []),
  225. maps:foreach(
  226. fun(K, ConfVal) ->
  227. case maps:find(K, SocketMaps) of
  228. {ok, File} -> ?assertEqual(naive_env_interpolation(ConfVal), File);
  229. error -> ?assertEqual(<<"">>, ConfVal)
  230. end
  231. end,
  232. SSLCert
  233. ),
  234. ?assertMatch(
  235. #{
  236. env := #{dispatch := {persistent_term, ?NAME}},
  237. middlewares := Middlewares,
  238. proxy_header := false
  239. },
  240. ranch:get_protocol_options(?NAME)
  241. ),
  242. ok.
  243. assert_https_request() ->
  244. Headers = emqx_dashboard_SUITE:auth_header_(),
  245. lists:foreach(
  246. fun(Path) ->
  247. ApiPath = https_api_path([Path]),
  248. ?assertMatch(
  249. {ok, _},
  250. emqx_dashboard_SUITE:request_dashboard(get, ApiPath, Headers)
  251. )
  252. end,
  253. ?OVERVIEWS
  254. ).
  255. https_api_path(Parts) ->
  256. ?HOST_HTTPS ++ filename:join([?BASE_PATH | Parts]).
  257. http_api_path(Parts) ->
  258. ?HOST_HTTP ++ filename:join([?BASE_PATH | Parts]).
  259. naive_env_interpolation(Str0) ->
  260. Str1 = emqx_schema:naive_env_interpolation(Str0),
  261. %% assert all envs are replaced
  262. ?assertNot(lists:member($$, Str1)),
  263. Str1.
  264. default_ssl_cert() ->
  265. #{
  266. cacertfile => <<"${EMQX_ETC_DIR}/certs/cacert.pem">>,
  267. certfile => <<"${EMQX_ETC_DIR}/certs/cert.pem">>,
  268. keyfile => <<"${EMQX_ETC_DIR}/certs/key.pem">>
  269. }.