emqx_dashboard_https_SUITE.erl 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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("emqx_dashboard.hrl").
  21. -define(NAME, 'https:dashboard').
  22. -define(HOST, "https://127.0.0.1:18084").
  23. -define(BASE_PATH, "/api/v5").
  24. -define(OVERVIEWS, [
  25. "alarms",
  26. "banned",
  27. "stats",
  28. "metrics",
  29. "listeners",
  30. "clients",
  31. "subscriptions"
  32. ]).
  33. all() ->
  34. emqx_common_test_helpers:all(?MODULE).
  35. init_per_suite(Config) -> Config.
  36. end_per_suite(_Config) -> emqx_mgmt_api_test_util:end_suite([emqx_management]).
  37. init_per_testcase(_TestCase, Config) -> Config.
  38. end_per_testcase(_TestCase, _Config) -> emqx_mgmt_api_test_util:end_suite([emqx_management]).
  39. t_default_ssl_cert(_Config) ->
  40. Conf = #{dashboard => #{listeners => #{https => #{bind => 18084, enable => true}}}},
  41. validate_https(Conf, 512, default_ssl_cert(), verify_none),
  42. ok.
  43. t_normal_ssl_cert(_Config) ->
  44. MaxConnection = 1000,
  45. Conf = #{
  46. dashboard => #{
  47. listeners => #{
  48. https => #{
  49. bind => 18084,
  50. enable => true,
  51. cacertfile => naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/cacert.pem">>),
  52. certfile => naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/cert.pem">>),
  53. keyfile => naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/key.pem">>),
  54. max_connections => MaxConnection
  55. }
  56. }
  57. }
  58. },
  59. validate_https(Conf, MaxConnection, default_ssl_cert(), verify_none),
  60. ok.
  61. t_verify_cacertfile(_Config) ->
  62. MaxConnection = 1024,
  63. DefaultSSLCert = default_ssl_cert(),
  64. SSLCert = DefaultSSLCert#{cacertfile => <<"">>},
  65. %% default #{verify => verify_none}
  66. Conf = #{
  67. dashboard => #{
  68. listeners => #{
  69. https => #{
  70. bind => 18084,
  71. enable => true,
  72. cacertfile => <<"">>,
  73. max_connections => MaxConnection
  74. }
  75. }
  76. }
  77. },
  78. validate_https(Conf, MaxConnection, SSLCert, verify_none),
  79. %% verify_peer but cacertfile is empty
  80. VerifyPeerConf1 = emqx_utils_maps:deep_put(
  81. [dashboard, listeners, https, verify],
  82. Conf,
  83. verify_peer
  84. ),
  85. emqx_common_test_helpers:load_config(emqx_dashboard_schema, VerifyPeerConf1),
  86. ?assertMatch({error, [?NAME]}, emqx_dashboard:start_listeners()),
  87. %% verify_peer and cacertfile is ok.
  88. VerifyPeerConf2 = emqx_utils_maps:deep_put(
  89. [dashboard, listeners, https, cacertfile],
  90. VerifyPeerConf1,
  91. naive_env_interpolation(<<"${EMQX_ETC_DIR}/certs/cacert.pem">>)
  92. ),
  93. validate_https(VerifyPeerConf2, MaxConnection, DefaultSSLCert, verify_peer),
  94. ok.
  95. t_bad_certfile(_Config) ->
  96. Conf = #{
  97. dashboard => #{
  98. listeners => #{
  99. https => #{
  100. bind => 18084,
  101. enable => true,
  102. certfile => <<"${EMQX_ETC_DIR}/certs/not_found_cert.pem">>
  103. }
  104. }
  105. }
  106. },
  107. emqx_common_test_helpers:load_config(emqx_dashboard_schema, Conf),
  108. ?assertMatch({error, [?NAME]}, emqx_dashboard:start_listeners()),
  109. ok.
  110. validate_https(Conf, MaxConnection, SSLCert, Verify) ->
  111. emqx_common_test_helpers:load_config(emqx_dashboard_schema, Conf),
  112. emqx_mgmt_api_test_util:init_suite([emqx_management], fun(X) -> X end),
  113. assert_ranch_options(MaxConnection, SSLCert, Verify),
  114. assert_https_request(),
  115. emqx_mgmt_api_test_util:end_suite([emqx_management]).
  116. assert_ranch_options(MaxConnections0, SSLCert, Verify) ->
  117. Middlewares = [emqx_dashboard_middleware, cowboy_router, cowboy_handler],
  118. [
  119. ?NAME,
  120. ranch_ssl,
  121. #{
  122. max_connections := MaxConnections,
  123. num_acceptors := _,
  124. socket_opts := SocketOpts
  125. },
  126. cowboy_tls,
  127. #{
  128. env := #{
  129. dispatch := {persistent_term, ?NAME},
  130. options := #{
  131. name := ?NAME,
  132. protocol := https,
  133. protocol_options := #{proxy_header := false},
  134. security := [#{basicAuth := []}, #{bearerAuth := []}],
  135. swagger_global_spec := _
  136. }
  137. },
  138. middlewares := Middlewares,
  139. proxy_header := false
  140. }
  141. ] = ranch_server:get_listener_start_args(?NAME),
  142. ?assertEqual(MaxConnections0, MaxConnections),
  143. ?assert(lists:member(inet, SocketOpts), SocketOpts),
  144. #{
  145. backlog := 1024,
  146. ciphers := Ciphers,
  147. port := 18084,
  148. send_timeout := 10000,
  149. verify := Verify,
  150. versions := Versions
  151. } = SocketMaps = maps:from_list(SocketOpts -- [inet]),
  152. %% without tlsv1.1 tlsv1
  153. ?assertMatch(['tlsv1.3', 'tlsv1.2'], Versions),
  154. ?assert(Ciphers =/= []),
  155. maps:foreach(
  156. fun(K, ConfVal) ->
  157. case maps:find(K, SocketMaps) of
  158. {ok, File} -> ?assertEqual(naive_env_interpolation(ConfVal), File);
  159. error -> ?assertEqual(<<"">>, ConfVal)
  160. end
  161. end,
  162. SSLCert
  163. ),
  164. ?assertMatch(
  165. #{
  166. env := #{dispatch := {persistent_term, ?NAME}},
  167. middlewares := Middlewares,
  168. proxy_header := false
  169. },
  170. ranch:get_protocol_options(?NAME)
  171. ),
  172. ok.
  173. assert_https_request() ->
  174. Headers = emqx_dashboard_SUITE:auth_header_(),
  175. lists:foreach(
  176. fun(Path) ->
  177. ApiPath = api_path([Path]),
  178. ?assertMatch(
  179. {ok, _},
  180. emqx_dashboard_SUITE:request_dashboard(get, ApiPath, Headers)
  181. )
  182. end,
  183. ?OVERVIEWS
  184. ).
  185. api_path(Parts) ->
  186. ?HOST ++ filename:join([?BASE_PATH | Parts]).
  187. naive_env_interpolation(Str0) ->
  188. Str1 = emqx_schema:naive_env_interpolation(Str0),
  189. %% assert all envs are replaced
  190. ?assertNot(lists:member($$, Str1)),
  191. Str1.
  192. default_ssl_cert() ->
  193. #{
  194. cacertfile => <<"${EMQX_ETC_DIR}/certs/cacert.pem">>,
  195. certfile => <<"${EMQX_ETC_DIR}/certs/cert.pem">>,
  196. keyfile => <<"${EMQX_ETC_DIR}/certs/key.pem">>
  197. }.