emqx_crl_cache_SUITE.erl 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%--------------------------------------------------------------------
  4. -module(emqx_crl_cache_SUITE).
  5. -compile(export_all).
  6. -compile(nowarn_export_all).
  7. -include_lib("eunit/include/eunit.hrl").
  8. -include_lib("common_test/include/ct.hrl").
  9. -include_lib("snabbkaffe/include/snabbkaffe.hrl").
  10. %% from ssl_manager.erl
  11. -record(state, {
  12. session_cache_client,
  13. session_cache_client_cb,
  14. session_lifetime,
  15. certificate_db,
  16. session_validation_timer,
  17. session_cache_client_max,
  18. session_client_invalidator,
  19. options,
  20. client_session_order
  21. }).
  22. -define(DEFAULT_URL, "http://localhost:9878/intermediate.crl.pem").
  23. %%--------------------------------------------------------------------
  24. %% CT boilerplate
  25. %%--------------------------------------------------------------------
  26. all() ->
  27. emqx_common_test_helpers:all(?MODULE).
  28. init_per_suite(Config) ->
  29. application:load(emqx),
  30. {ok, _} = application:ensure_all_started(ssl),
  31. emqx_config:save_schema_mod_and_names(emqx_schema),
  32. emqx_common_test_helpers:boot_modules(all),
  33. Config.
  34. end_per_suite(_Config) ->
  35. ok.
  36. init_per_testcase(TestCase, Config) when
  37. TestCase =:= t_cache;
  38. TestCase =:= t_filled_cache;
  39. TestCase =:= t_revoked
  40. ->
  41. ct:timetrap({seconds, 30}),
  42. DataDir = ?config(data_dir, Config),
  43. CRLFile = filename:join([DataDir, "intermediate-revoked.crl.pem"]),
  44. {ok, CRLPem} = file:read_file(CRLFile),
  45. [{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem),
  46. ok = snabbkaffe:start_trace(),
  47. ServerPid = start_crl_server(CRLPem),
  48. IsCached = lists:member(TestCase, [t_filled_cache, t_revoked]),
  49. ok = setup_crl_options(Config, #{is_cached => IsCached}),
  50. [
  51. {crl_pem, CRLPem},
  52. {crl_der, CRLDer},
  53. {http_server, ServerPid}
  54. | Config
  55. ];
  56. init_per_testcase(t_revoke_then_refresh, Config) ->
  57. ct:timetrap({seconds, 120}),
  58. DataDir = ?config(data_dir, Config),
  59. CRLFileNotRevoked = filename:join([DataDir, "intermediate-not-revoked.crl.pem"]),
  60. {ok, CRLPemNotRevoked} = file:read_file(CRLFileNotRevoked),
  61. [{'CertificateList', CRLDerNotRevoked, not_encrypted}] = public_key:pem_decode(
  62. CRLPemNotRevoked
  63. ),
  64. CRLFileRevoked = filename:join([DataDir, "intermediate-revoked.crl.pem"]),
  65. {ok, CRLPemRevoked} = file:read_file(CRLFileRevoked),
  66. [{'CertificateList', CRLDerRevoked, not_encrypted}] = public_key:pem_decode(CRLPemRevoked),
  67. ok = snabbkaffe:start_trace(),
  68. ServerPid = start_crl_server(CRLPemNotRevoked),
  69. ExtraVars = #{refresh_interval => <<"10s">>},
  70. ok = setup_crl_options(Config, #{is_cached => true, extra_vars => ExtraVars}),
  71. [
  72. {crl_pem_not_revoked, CRLPemNotRevoked},
  73. {crl_der_not_revoked, CRLDerNotRevoked},
  74. {crl_pem_revoked, CRLPemRevoked},
  75. {crl_der_revoked, CRLDerRevoked},
  76. {http_server, ServerPid}
  77. | Config
  78. ];
  79. init_per_testcase(t_cache_overflow, Config) ->
  80. ct:timetrap({seconds, 120}),
  81. DataDir = ?config(data_dir, Config),
  82. CRLFileRevoked = filename:join([DataDir, "intermediate-revoked.crl.pem"]),
  83. {ok, CRLPemRevoked} = file:read_file(CRLFileRevoked),
  84. ok = snabbkaffe:start_trace(),
  85. ServerPid = start_crl_server(CRLPemRevoked),
  86. ExtraVars = #{cache_capacity => <<"2">>},
  87. ok = setup_crl_options(Config, #{is_cached => false, extra_vars => ExtraVars}),
  88. [
  89. {http_server, ServerPid}
  90. | Config
  91. ];
  92. init_per_testcase(t_not_cached_and_unreachable, Config) ->
  93. ct:timetrap({seconds, 30}),
  94. DataDir = ?config(data_dir, Config),
  95. CRLFile = filename:join([DataDir, "intermediate-revoked.crl.pem"]),
  96. {ok, CRLPem} = file:read_file(CRLFile),
  97. [{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem),
  98. ok = snabbkaffe:start_trace(),
  99. application:stop(cowboy),
  100. ok = setup_crl_options(Config, #{is_cached => false}),
  101. [
  102. {crl_pem, CRLPem},
  103. {crl_der, CRLDer}
  104. | Config
  105. ];
  106. init_per_testcase(t_refresh_config, Config) ->
  107. ct:timetrap({seconds, 30}),
  108. DataDir = ?config(data_dir, Config),
  109. CRLFile = filename:join([DataDir, "intermediate-revoked.crl.pem"]),
  110. {ok, CRLPem} = file:read_file(CRLFile),
  111. [{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem),
  112. TestPid = self(),
  113. ok = meck:new(emqx_crl_cache, [non_strict, passthrough, no_history, no_link]),
  114. meck:expect(
  115. emqx_crl_cache,
  116. http_get,
  117. fun(URL, _HTTPTimeout) ->
  118. ct:pal("http get crl ~p", [URL]),
  119. TestPid ! {http_get, URL},
  120. {ok, {{"HTTP/1.0", 200, "OK"}, [], CRLPem}}
  121. end
  122. ),
  123. ok = snabbkaffe:start_trace(),
  124. ok = setup_crl_options(Config, #{is_cached => false}),
  125. [
  126. {crl_pem, CRLPem},
  127. {crl_der, CRLDer}
  128. | Config
  129. ];
  130. init_per_testcase(TestCase, Config) when
  131. TestCase =:= t_update_listener;
  132. TestCase =:= t_validations
  133. ->
  134. %% when running emqx standalone tests, we can't use those
  135. %% features.
  136. case does_module_exist(emqx_mgmt_api_test_util) of
  137. true ->
  138. ct:timetrap({seconds, 30}),
  139. DataDir = ?config(data_dir, Config),
  140. PrivDir = ?config(priv_dir, Config),
  141. CRLFile = filename:join([DataDir, "intermediate-revoked.crl.pem"]),
  142. {ok, CRLPem} = file:read_file(CRLFile),
  143. ok = snabbkaffe:start_trace(),
  144. ServerPid = start_crl_server(CRLPem),
  145. ConfFilePath = filename:join([DataDir, "emqx_just_verify.conf"]),
  146. emqx_mgmt_api_test_util:init_suite(
  147. [emqx_conf],
  148. fun emqx_mgmt_api_test_util:set_special_configs/1,
  149. #{
  150. extra_mustache_vars => #{
  151. test_data_dir => DataDir,
  152. test_priv_dir => PrivDir
  153. },
  154. conf_file_path => ConfFilePath
  155. }
  156. ),
  157. [
  158. {http_server, ServerPid}
  159. | Config
  160. ];
  161. false ->
  162. [{skip_does_not_apply, true} | Config]
  163. end;
  164. init_per_testcase(_TestCase, Config) ->
  165. ct:timetrap({seconds, 30}),
  166. DataDir = ?config(data_dir, Config),
  167. CRLFile = filename:join([DataDir, "intermediate-revoked.crl.pem"]),
  168. {ok, CRLPem} = file:read_file(CRLFile),
  169. [{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem),
  170. TestPid = self(),
  171. ok = meck:new(emqx_crl_cache, [non_strict, passthrough, no_history, no_link]),
  172. meck:expect(
  173. emqx_crl_cache,
  174. http_get,
  175. fun(URL, _HTTPTimeout) ->
  176. ct:pal("http get crl ~p", [URL]),
  177. TestPid ! {http_get, URL},
  178. {ok, {{"HTTP/1.0", 200, 'OK'}, [], CRLPem}}
  179. end
  180. ),
  181. ok = snabbkaffe:start_trace(),
  182. [
  183. {crl_pem, CRLPem},
  184. {crl_der, CRLDer}
  185. | Config
  186. ].
  187. end_per_testcase(TestCase, Config) when
  188. TestCase =:= t_cache;
  189. TestCase =:= t_filled_cache;
  190. TestCase =:= t_revoked
  191. ->
  192. ServerPid = ?config(http_server, Config),
  193. emqx_crl_cache_http_server:stop(ServerPid),
  194. emqx_common_test_helpers:stop_apps([]),
  195. clear_listeners(),
  196. application:stop(cowboy),
  197. clear_crl_cache(),
  198. ok = snabbkaffe:stop(),
  199. ok;
  200. end_per_testcase(TestCase, Config) when
  201. TestCase =:= t_revoke_then_refresh;
  202. TestCase =:= t_cache_overflow
  203. ->
  204. ServerPid = ?config(http_server, Config),
  205. emqx_crl_cache_http_server:stop(ServerPid),
  206. emqx_common_test_helpers:stop_apps([]),
  207. clear_listeners(),
  208. clear_crl_cache(),
  209. application:stop(cowboy),
  210. ok = snabbkaffe:stop(),
  211. ok;
  212. end_per_testcase(t_not_cached_and_unreachable, _Config) ->
  213. emqx_common_test_helpers:stop_apps([]),
  214. clear_listeners(),
  215. clear_crl_cache(),
  216. ok = snabbkaffe:stop(),
  217. ok;
  218. end_per_testcase(t_refresh_config, _Config) ->
  219. meck:unload([emqx_crl_cache]),
  220. clear_crl_cache(),
  221. emqx_common_test_helpers:stop_apps([]),
  222. clear_listeners(),
  223. clear_crl_cache(),
  224. application:stop(cowboy),
  225. ok = snabbkaffe:stop(),
  226. ok;
  227. end_per_testcase(TestCase, Config) when
  228. TestCase =:= t_update_listener;
  229. TestCase =:= t_validations
  230. ->
  231. Skip = proplists:get_bool(skip_does_not_apply, Config),
  232. case Skip of
  233. true ->
  234. ok;
  235. false ->
  236. ServerPid = ?config(http_server, Config),
  237. emqx_crl_cache_http_server:stop(ServerPid),
  238. emqx_mgmt_api_test_util:end_suite([emqx_conf]),
  239. clear_listeners(),
  240. ok = snabbkaffe:stop(),
  241. clear_crl_cache(),
  242. ok
  243. end;
  244. end_per_testcase(_TestCase, _Config) ->
  245. meck:unload([emqx_crl_cache]),
  246. clear_crl_cache(),
  247. ok = snabbkaffe:stop(),
  248. ok.
  249. %%--------------------------------------------------------------------
  250. %% Helper functions
  251. %%--------------------------------------------------------------------
  252. does_module_exist(Mod) ->
  253. case erlang:module_loaded(Mod) of
  254. true ->
  255. true;
  256. false ->
  257. case code:ensure_loaded(Mod) of
  258. ok ->
  259. true;
  260. {module, Mod} ->
  261. true;
  262. _ ->
  263. false
  264. end
  265. end.
  266. clear_listeners() ->
  267. emqx_config:put([listeners], #{}),
  268. emqx_config:put_raw([<<"listeners">>], #{}),
  269. ok.
  270. assert_http_get(URL) ->
  271. receive
  272. {http_get, URL} ->
  273. ok
  274. after 1000 ->
  275. ct:pal("mailbox: ~p", [process_info(self(), messages)]),
  276. error({should_have_requested, URL})
  277. end.
  278. get_crl_cache_table() ->
  279. #state{certificate_db = [_, _, _, {Ref, _}]} = sys:get_state(ssl_manager),
  280. Ref.
  281. start_crl_server(Port, CRLPem) ->
  282. {ok, LSock} = gen_tcp:listen(Port, [binary, {active, true}, reusedaddr]),
  283. spawn_link(fun() -> accept_loop(LSock, CRLPem) end),
  284. ok.
  285. accept_loop(LSock, CRLPem) ->
  286. case gen_tcp:accept(LSock) of
  287. {ok, Sock} ->
  288. Worker = spawn_link(fun() -> crl_loop(Sock, CRLPem) end),
  289. gen_tcp:controlling_process(Sock, Worker),
  290. accept_loop(LSock, CRLPem);
  291. {error, Reason} ->
  292. error({accept_error, Reason})
  293. end.
  294. crl_loop(Sock, CRLPem) ->
  295. receive
  296. {tcp, Sock, _Data} ->
  297. gen_tcp:send(Sock, CRLPem),
  298. crl_loop(Sock, CRLPem);
  299. _Msg ->
  300. ok
  301. end.
  302. drain_msgs() ->
  303. receive
  304. _Msg ->
  305. drain_msgs()
  306. after 0 ->
  307. ok
  308. end.
  309. clear_crl_cache() ->
  310. %% reset the CRL cache
  311. Ref = monitor(process, whereis(ssl_manager)),
  312. exit(whereis(ssl_manager), kill),
  313. receive
  314. {'DOWN', Ref, process, _, _} ->
  315. ok
  316. after 1_000 ->
  317. ct:fail("ssl_manager didn't die")
  318. end,
  319. ensure_ssl_manager_alive(),
  320. ok.
  321. force_cacertfile(Cacertfile) ->
  322. {SSLListeners0, OtherListeners} = lists:partition(
  323. fun(#{proto := Proto}) -> Proto =:= ssl end,
  324. emqx:get_env(listeners)
  325. ),
  326. SSLListeners =
  327. lists:map(
  328. fun(Listener = #{opts := Opts0}) ->
  329. SSLOpts0 = proplists:get_value(ssl_options, Opts0),
  330. %% it injects some garbage...
  331. SSLOpts1 = lists:keydelete(cacertfile, 1, lists:keydelete(cacertfile, 1, SSLOpts0)),
  332. SSLOpts2 = [{cacertfile, Cacertfile} | SSLOpts1],
  333. Opts1 = lists:keyreplace(ssl_options, 1, Opts0, {ssl_options, SSLOpts2}),
  334. Listener#{opts => Opts1}
  335. end,
  336. SSLListeners0
  337. ),
  338. application:set_env(emqx, listeners, SSLListeners ++ OtherListeners),
  339. ok.
  340. setup_crl_options(Config, #{is_cached := IsCached} = Opts) ->
  341. DataDir = ?config(data_dir, Config),
  342. ConfFilePath = filename:join([DataDir, "emqx.conf"]),
  343. Defaults = #{
  344. refresh_interval => <<"11m">>,
  345. cache_capacity => <<"100">>,
  346. test_data_dir => DataDir
  347. },
  348. ExtraVars0 = maps:get(extra_vars, Opts, #{}),
  349. ExtraVars = maps:merge(Defaults, ExtraVars0),
  350. emqx_common_test_helpers:start_apps(
  351. [],
  352. fun(_) -> ok end,
  353. #{
  354. extra_mustache_vars => ExtraVars,
  355. conf_file_path => ConfFilePath
  356. }
  357. ),
  358. case IsCached of
  359. true ->
  360. %% wait the cache to be filled
  361. emqx_crl_cache:refresh(?DEFAULT_URL),
  362. receive
  363. {http_get, <<?DEFAULT_URL>>} -> ok
  364. after 1_000 ->
  365. ct:pal("mailbox: ~p", [process_info(self(), messages)]),
  366. error(crl_cache_not_filled)
  367. end;
  368. false ->
  369. %% ensure cache is empty
  370. clear_crl_cache(),
  371. ok
  372. end,
  373. drain_msgs(),
  374. ok.
  375. start_crl_server(CRLPem) ->
  376. application:ensure_all_started(cowboy),
  377. {ok, ServerPid} = emqx_crl_cache_http_server:start_link(self(), 9878, CRLPem, []),
  378. receive
  379. {ServerPid, ready} -> ok
  380. after 1000 -> error(timeout_starting_http_server)
  381. end,
  382. ServerPid.
  383. request(Method, Url, QueryParams, Body) ->
  384. AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
  385. Opts = #{return_all => true},
  386. case emqx_mgmt_api_test_util:request_api(Method, Url, QueryParams, AuthHeader, Body, Opts) of
  387. {ok, {Reason, Headers, BodyR}} ->
  388. {ok, {Reason, Headers, emqx_utils_json:decode(BodyR, [return_maps])}};
  389. Error ->
  390. Error
  391. end.
  392. get_listener_via_api(ListenerId) ->
  393. Path = emqx_mgmt_api_test_util:api_path(["listeners", ListenerId]),
  394. request(get, Path, [], []).
  395. update_listener_via_api(ListenerId, NewConfig) ->
  396. Path = emqx_mgmt_api_test_util:api_path(["listeners", ListenerId]),
  397. request(put, Path, [], NewConfig).
  398. assert_successful_connection(Config) ->
  399. assert_successful_connection(Config, default).
  400. assert_successful_connection(Config, ClientNum) ->
  401. DataDir = ?config(data_dir, Config),
  402. Num =
  403. case ClientNum of
  404. default -> "";
  405. _ -> integer_to_list(ClientNum)
  406. end,
  407. ClientCert = filename:join(DataDir, "client" ++ Num ++ ".cert.pem"),
  408. ClientKey = filename:join(DataDir, "client" ++ Num ++ ".key.pem"),
  409. %% 1) At first, the cache is empty, and the CRL is fetched and
  410. %% cached on the fly.
  411. {ok, C0} = emqtt:start_link([
  412. {ssl, true},
  413. {ssl_opts, [
  414. {certfile, ClientCert},
  415. {keyfile, ClientKey}
  416. ]},
  417. {port, 8883}
  418. ]),
  419. ?tp_span(
  420. mqtt_client_connection,
  421. #{client_num => ClientNum},
  422. begin
  423. {ok, _} = emqtt:connect(C0),
  424. emqtt:stop(C0),
  425. ok
  426. end
  427. ).
  428. trace_between(Trace0, Marker1, Marker2) ->
  429. {Trace1, [_ | _]} = ?split_trace_at(#{?snk_kind := Marker2}, Trace0),
  430. {[_ | _], [_ | Trace2]} = ?split_trace_at(#{?snk_kind := Marker1}, Trace1),
  431. Trace2.
  432. of_kinds(Trace0, Kinds0) ->
  433. Kinds = sets:from_list(Kinds0, [{version, 2}]),
  434. lists:filter(
  435. fun(#{?snk_kind := K}) -> sets:is_element(K, Kinds) end,
  436. Trace0
  437. ).
  438. ensure_ssl_manager_alive() ->
  439. ?retry(
  440. _Sleep0 = 200,
  441. _Attempts0 = 50,
  442. true = is_pid(whereis(ssl_manager))
  443. ).
  444. %%--------------------------------------------------------------------
  445. %% Test cases
  446. %%--------------------------------------------------------------------
  447. t_init_empty_urls(_Config) ->
  448. Ref = get_crl_cache_table(),
  449. ?assertEqual([], ets:tab2list(Ref)),
  450. emqx_config_handler:start_link(),
  451. ?assertMatch({ok, _}, emqx_crl_cache:start_link()),
  452. receive
  453. {http_get, _} ->
  454. error(should_not_make_http_request)
  455. after 1000 -> ok
  456. end,
  457. ?assertEqual([], ets:tab2list(Ref)),
  458. emqx_config_handler:stop(),
  459. ok.
  460. t_update_config(_Config) ->
  461. emqx_config:save_schema_mod_and_names(emqx_schema),
  462. emqx_config_handler:start_link(),
  463. {ok, Pid} = emqx_crl_cache:start_link(),
  464. Conf = #{
  465. refresh_interval => <<"5m">>,
  466. http_timeout => <<"10m">>,
  467. capacity => 123
  468. },
  469. ?assertMatch({ok, _}, emqx:update_config([<<"crl_cache">>], Conf)),
  470. State = sys:get_state(Pid),
  471. ?assertEqual(
  472. #{
  473. refresh_interval => timer:minutes(5),
  474. http_timeout => timer:minutes(10),
  475. capacity => 123
  476. },
  477. #{
  478. refresh_interval => element(3, State),
  479. http_timeout => element(4, State),
  480. capacity => element(7, State)
  481. }
  482. ),
  483. emqx_config:erase(<<"crl_cache">>),
  484. emqx_config_handler:stop(),
  485. ok.
  486. t_manual_refresh(Config) ->
  487. CRLDer = ?config(crl_der, Config),
  488. Ref = get_crl_cache_table(),
  489. ?assertEqual([], ets:tab2list(Ref)),
  490. emqx_config_handler:start_link(),
  491. {ok, _} = emqx_crl_cache:start_link(),
  492. URL = "http://localhost/crl.pem",
  493. ok = snabbkaffe:start_trace(),
  494. ?wait_async_action(
  495. ?assertEqual(ok, emqx_crl_cache:refresh(URL)),
  496. #{?snk_kind := crl_cache_insert},
  497. 5_000
  498. ),
  499. ok = snabbkaffe:stop(),
  500. ?assertEqual(
  501. [{"crl.pem", [CRLDer]}],
  502. ets:tab2list(Ref)
  503. ),
  504. emqx_config_handler:stop(),
  505. ok.
  506. t_refresh_request_error(_Config) ->
  507. meck:expect(
  508. emqx_crl_cache,
  509. http_get,
  510. fun(_URL, _HTTPTimeout) ->
  511. {ok, {{"HTTP/1.0", 404, 'Not Found'}, [], <<"not found">>}}
  512. end
  513. ),
  514. emqx_config_handler:start_link(),
  515. {ok, _} = emqx_crl_cache:start_link(),
  516. URL = "http://localhost/crl.pem",
  517. ?check_trace(
  518. ?wait_async_action(
  519. ?assertEqual(ok, emqx_crl_cache:refresh(URL)),
  520. #{?snk_kind := crl_cache_insert},
  521. 5_000
  522. ),
  523. fun(Trace) ->
  524. ?assertMatch(
  525. [#{error := {bad_response, #{code := 404}}}],
  526. ?of_kind(crl_refresh_failure, Trace)
  527. ),
  528. ok
  529. end
  530. ),
  531. ok = snabbkaffe:stop(),
  532. emqx_config_handler:stop(),
  533. ok.
  534. t_refresh_invalid_response(_Config) ->
  535. meck:expect(
  536. emqx_crl_cache,
  537. http_get,
  538. fun(_URL, _HTTPTimeout) ->
  539. {ok, {{"HTTP/1.0", 200, 'OK'}, [], <<"not a crl">>}}
  540. end
  541. ),
  542. emqx_config_handler:start_link(),
  543. {ok, _} = emqx_crl_cache:start_link(),
  544. URL = "http://localhost/crl.pem",
  545. ?check_trace(
  546. ?wait_async_action(
  547. ?assertEqual(ok, emqx_crl_cache:refresh(URL)),
  548. #{?snk_kind := crl_cache_insert},
  549. 5_000
  550. ),
  551. fun(Trace) ->
  552. ?assertMatch(
  553. [#{crls := []}],
  554. ?of_kind(crl_cache_insert, Trace)
  555. ),
  556. ok
  557. end
  558. ),
  559. ok = snabbkaffe:stop(),
  560. emqx_config_handler:stop(),
  561. ok.
  562. t_refresh_http_error(_Config) ->
  563. meck:expect(
  564. emqx_crl_cache,
  565. http_get,
  566. fun(_URL, _HTTPTimeout) ->
  567. {error, timeout}
  568. end
  569. ),
  570. emqx_config_handler:start_link(),
  571. {ok, _} = emqx_crl_cache:start_link(),
  572. URL = "http://localhost/crl.pem",
  573. ?check_trace(
  574. ?wait_async_action(
  575. ?assertEqual(ok, emqx_crl_cache:refresh(URL)),
  576. #{?snk_kind := crl_cache_insert},
  577. 5_000
  578. ),
  579. fun(Trace) ->
  580. ?assertMatch(
  581. [#{error := {http_error, timeout}}],
  582. ?of_kind(crl_refresh_failure, Trace)
  583. ),
  584. ok
  585. end
  586. ),
  587. ok = snabbkaffe:stop(),
  588. emqx_config_handler:stop(),
  589. ok.
  590. t_unknown_messages(_Config) ->
  591. emqx_config_handler:start_link(),
  592. {ok, Server} = emqx_crl_cache:start_link(),
  593. gen_server:call(Server, foo),
  594. gen_server:cast(Server, foo),
  595. Server ! foo,
  596. emqx_config_handler:stop(),
  597. ok.
  598. t_evict(_Config) ->
  599. emqx_config_handler:start_link(),
  600. {ok, _} = emqx_crl_cache:start_link(),
  601. URL = "http://localhost/crl.pem",
  602. ?wait_async_action(
  603. ?assertEqual(ok, emqx_crl_cache:refresh(URL)),
  604. #{?snk_kind := crl_cache_insert},
  605. 5_000
  606. ),
  607. Ref = get_crl_cache_table(),
  608. ?assertMatch([{"crl.pem", _}], ets:tab2list(Ref)),
  609. {ok, {ok, _}} = ?wait_async_action(
  610. emqx_crl_cache:evict(URL),
  611. #{?snk_kind := crl_cache_evict}
  612. ),
  613. ?assertEqual([], ets:tab2list(Ref)),
  614. emqx_config_handler:stop(),
  615. ok.
  616. t_cache(Config) ->
  617. DataDir = ?config(data_dir, Config),
  618. ClientCert = filename:join(DataDir, "client.cert.pem"),
  619. ClientKey = filename:join(DataDir, "client.key.pem"),
  620. %% 1) At first, the cache is empty, and the CRL is fetched and
  621. %% cached on the fly.
  622. {ok, C0} = emqtt:start_link([
  623. {ssl, true},
  624. {ssl_opts, [
  625. {certfile, ClientCert},
  626. {keyfile, ClientKey}
  627. ]},
  628. {port, 8883}
  629. ]),
  630. {ok, _} = emqtt:connect(C0),
  631. receive
  632. {http_get, _} -> ok
  633. after 500 ->
  634. emqtt:stop(C0),
  635. error(should_have_checked_server)
  636. end,
  637. emqtt:stop(C0),
  638. %% 2) When another client using the cached CRL URL connects later,
  639. %% it uses the cache.
  640. {ok, C1} = emqtt:start_link([
  641. {ssl, true},
  642. {ssl_opts, [
  643. {certfile, ClientCert},
  644. {keyfile, ClientKey}
  645. ]},
  646. {port, 8883}
  647. ]),
  648. {ok, _} = emqtt:connect(C1),
  649. receive
  650. {http_get, _} ->
  651. emqtt:stop(C1),
  652. error(should_not_have_checked_server)
  653. after 500 -> ok
  654. end,
  655. emqtt:stop(C1),
  656. ok.
  657. t_cache_overflow(Config) ->
  658. %% we have capacity = 2 here.
  659. ?check_trace(
  660. begin
  661. %% First and second connections goes into the cache
  662. ?tp(first_connections, #{}),
  663. assert_successful_connection(Config, 1),
  664. assert_successful_connection(Config, 2),
  665. %% These should be cached
  666. ?tp(first_reconnections, #{}),
  667. assert_successful_connection(Config, 1),
  668. assert_successful_connection(Config, 2),
  669. %% A third client connects and evicts the oldest URL (1)
  670. ?tp(first_eviction, #{}),
  671. assert_successful_connection(Config, 3),
  672. assert_successful_connection(Config, 3),
  673. %% URL (1) connects again and needs to be re-cached; this
  674. %% time, (2) gets evicted
  675. ?tp(second_eviction, #{}),
  676. assert_successful_connection(Config, 1),
  677. %% TODO: force race condition where the same URL is fetched
  678. %% at the same time and tries to be registered
  679. ?tp(test_end, #{}),
  680. ok
  681. end,
  682. fun(Trace) ->
  683. URL1 = "http://localhost:9878/intermediate1.crl.pem",
  684. URL2 = "http://localhost:9878/intermediate2.crl.pem",
  685. URL3 = "http://localhost:9878/intermediate3.crl.pem",
  686. Kinds = [
  687. mqtt_client_connection,
  688. new_crl_url_inserted,
  689. crl_cache_ensure_timer,
  690. crl_cache_overflow
  691. ],
  692. Trace1 = of_kinds(
  693. trace_between(Trace, first_connections, first_reconnections),
  694. Kinds
  695. ),
  696. ?assertMatch(
  697. [
  698. #{
  699. ?snk_kind := mqtt_client_connection,
  700. ?snk_span := start,
  701. client_num := 1
  702. },
  703. #{
  704. ?snk_kind := new_crl_url_inserted,
  705. url := URL1
  706. },
  707. #{
  708. ?snk_kind := crl_cache_ensure_timer,
  709. url := URL1
  710. },
  711. #{
  712. ?snk_kind := mqtt_client_connection,
  713. ?snk_span := {complete, ok},
  714. client_num := 1
  715. },
  716. #{
  717. ?snk_kind := mqtt_client_connection,
  718. ?snk_span := start,
  719. client_num := 2
  720. },
  721. #{
  722. ?snk_kind := new_crl_url_inserted,
  723. url := URL2
  724. },
  725. #{
  726. ?snk_kind := crl_cache_ensure_timer,
  727. url := URL2
  728. },
  729. #{
  730. ?snk_kind := mqtt_client_connection,
  731. ?snk_span := {complete, ok},
  732. client_num := 2
  733. }
  734. ],
  735. Trace1
  736. ),
  737. Trace2 = of_kinds(
  738. trace_between(Trace, first_reconnections, first_eviction),
  739. Kinds
  740. ),
  741. ?assertMatch(
  742. [
  743. #{
  744. ?snk_kind := mqtt_client_connection,
  745. ?snk_span := start,
  746. client_num := 1
  747. },
  748. #{
  749. ?snk_kind := mqtt_client_connection,
  750. ?snk_span := {complete, ok},
  751. client_num := 1
  752. },
  753. #{
  754. ?snk_kind := mqtt_client_connection,
  755. ?snk_span := start,
  756. client_num := 2
  757. },
  758. #{
  759. ?snk_kind := mqtt_client_connection,
  760. ?snk_span := {complete, ok},
  761. client_num := 2
  762. }
  763. ],
  764. Trace2
  765. ),
  766. Trace3 = of_kinds(
  767. trace_between(Trace, first_eviction, second_eviction),
  768. Kinds
  769. ),
  770. ?assertMatch(
  771. [
  772. #{
  773. ?snk_kind := mqtt_client_connection,
  774. ?snk_span := start,
  775. client_num := 3
  776. },
  777. #{
  778. ?snk_kind := new_crl_url_inserted,
  779. url := URL3
  780. },
  781. #{
  782. ?snk_kind := crl_cache_overflow,
  783. oldest_url := URL1
  784. },
  785. #{
  786. ?snk_kind := crl_cache_ensure_timer,
  787. url := URL3
  788. },
  789. #{
  790. ?snk_kind := mqtt_client_connection,
  791. ?snk_span := {complete, ok},
  792. client_num := 3
  793. },
  794. #{
  795. ?snk_kind := mqtt_client_connection,
  796. ?snk_span := start,
  797. client_num := 3
  798. },
  799. #{
  800. ?snk_kind := mqtt_client_connection,
  801. ?snk_span := {complete, ok},
  802. client_num := 3
  803. }
  804. ],
  805. Trace3
  806. ),
  807. Trace4 = of_kinds(
  808. trace_between(Trace, second_eviction, test_end),
  809. Kinds
  810. ),
  811. ?assertMatch(
  812. [
  813. #{
  814. ?snk_kind := mqtt_client_connection,
  815. ?snk_span := start,
  816. client_num := 1
  817. },
  818. #{
  819. ?snk_kind := new_crl_url_inserted,
  820. url := URL1
  821. },
  822. #{
  823. ?snk_kind := crl_cache_overflow,
  824. oldest_url := URL2
  825. },
  826. #{
  827. ?snk_kind := crl_cache_ensure_timer,
  828. url := URL1
  829. },
  830. #{
  831. ?snk_kind := mqtt_client_connection,
  832. ?snk_span := {complete, ok},
  833. client_num := 1
  834. }
  835. ],
  836. Trace4
  837. ),
  838. ok
  839. end
  840. ).
  841. %% check that the URL in the certificate is *not* checked if the cache
  842. %% contains that URL.
  843. t_filled_cache(Config) ->
  844. DataDir = ?config(data_dir, Config),
  845. ClientCert = filename:join(DataDir, "client.cert.pem"),
  846. ClientKey = filename:join(DataDir, "client.key.pem"),
  847. {ok, C} = emqtt:start_link([
  848. {ssl, true},
  849. {ssl_opts, [
  850. {certfile, ClientCert},
  851. {keyfile, ClientKey}
  852. ]},
  853. {port, 8883}
  854. ]),
  855. {ok, _} = emqtt:connect(C),
  856. receive
  857. http_get ->
  858. emqtt:stop(C),
  859. error(should_have_used_cache)
  860. after 500 -> ok
  861. end,
  862. emqtt:stop(C),
  863. ok.
  864. %% If the CRL is not cached when the client tries to connect and the
  865. %% CRL server is unreachable, the client will be denied connection.
  866. t_not_cached_and_unreachable(Config) ->
  867. DataDir = ?config(data_dir, Config),
  868. ClientCert = filename:join(DataDir, "client.cert.pem"),
  869. ClientKey = filename:join(DataDir, "client.key.pem"),
  870. {ok, C} = emqtt:start_link([
  871. {ssl, true},
  872. {ssl_opts, [
  873. {certfile, ClientCert},
  874. {keyfile, ClientKey}
  875. ]},
  876. {port, 8883}
  877. ]),
  878. Ref = get_crl_cache_table(),
  879. ?assertEqual([], ets:tab2list(Ref)),
  880. process_flag(trap_exit, true),
  881. ?assertMatch({error, {{shutdown, {tls_alert, {bad_certificate, _}}}, _}}, emqtt:connect(C)),
  882. ok.
  883. t_revoked(Config) ->
  884. DataDir = ?config(data_dir, Config),
  885. ClientCert = filename:join(DataDir, "client-revoked.cert.pem"),
  886. ClientKey = filename:join(DataDir, "client-revoked.key.pem"),
  887. {ok, C} = emqtt:start_link([
  888. {ssl, true},
  889. {ssl_opts, [
  890. {certfile, ClientCert},
  891. {keyfile, ClientKey}
  892. ]},
  893. {port, 8883}
  894. ]),
  895. process_flag(trap_exit, true),
  896. Res = emqtt:connect(C),
  897. %% apparently, sometimes there's some race condition in
  898. %% `emqtt_sock:ssl_upgrade' when it calls
  899. %% `ssl:conetrolling_process' and a bad match happens at that
  900. %% point.
  901. case Res of
  902. {error, {{shutdown, {tls_alert, {certificate_revoked, _}}}, _}} ->
  903. ok;
  904. {error, closed} ->
  905. %% race condition?
  906. ok;
  907. _ ->
  908. ct:fail("unexpected result: ~p", [Res])
  909. end,
  910. ok.
  911. t_revoke_then_refresh(Config) ->
  912. DataDir = ?config(data_dir, Config),
  913. CRLPemRevoked = ?config(crl_pem_revoked, Config),
  914. ClientCert = filename:join(DataDir, "client-revoked.cert.pem"),
  915. ClientKey = filename:join(DataDir, "client-revoked.key.pem"),
  916. {ok, C0} = emqtt:start_link([
  917. {ssl, true},
  918. {ssl_opts, [
  919. {certfile, ClientCert},
  920. {keyfile, ClientKey}
  921. ]},
  922. {port, 8883}
  923. ]),
  924. %% At first, the CRL contains no revoked entries, so the client
  925. %% should be allowed connection.
  926. ?assertMatch({ok, _}, emqtt:connect(C0)),
  927. emqtt:stop(C0),
  928. %% Now we update the CRL on the server and wait for the cache to
  929. %% be refreshed.
  930. {true, {ok, _}} =
  931. ?wait_async_action(
  932. emqx_crl_cache_http_server:set_crl(CRLPemRevoked),
  933. #{?snk_kind := crl_refresh_timer_done},
  934. 70_000
  935. ),
  936. %% The *same client* should now be denied connection.
  937. {ok, C1} = emqtt:start_link([
  938. {ssl, true},
  939. {ssl_opts, [
  940. {certfile, ClientCert},
  941. {keyfile, ClientKey}
  942. ]},
  943. {port, 8883}
  944. ]),
  945. process_flag(trap_exit, true),
  946. ?assertMatch(
  947. {error, {{shutdown, {tls_alert, {certificate_revoked, _}}}, _}}, emqtt:connect(C1)
  948. ),
  949. ok.
  950. %% check that we can start with a non-crl listener and restart it with
  951. %% the new crl config.
  952. t_update_listener(Config) ->
  953. case proplists:get_bool(skip_does_not_apply, Config) of
  954. true ->
  955. ok;
  956. false ->
  957. do_t_update_listener(Config)
  958. end.
  959. do_t_update_listener(Config) ->
  960. DataDir = ?config(data_dir, Config),
  961. Keyfile = filename:join([DataDir, "server.key.pem"]),
  962. Certfile = filename:join([DataDir, "server.cert.pem"]),
  963. Cacertfile = filename:join([DataDir, "ca-chain.cert.pem"]),
  964. ClientCert = filename:join(DataDir, "client-revoked.cert.pem"),
  965. ClientKey = filename:join(DataDir, "client-revoked.key.pem"),
  966. %% no crl at first
  967. ListenerId = "ssl:default",
  968. {ok, {{_, 200, _}, _, ListenerData0}} = get_listener_via_api(ListenerId),
  969. ?assertMatch(
  970. #{
  971. <<"ssl_options">> :=
  972. #{
  973. <<"enable_crl_check">> := false,
  974. <<"verify">> := <<"verify_peer">>
  975. }
  976. },
  977. ListenerData0
  978. ),
  979. {ok, C0} = emqtt:start_link([
  980. {ssl, true},
  981. {ssl_opts, [
  982. {certfile, ClientCert},
  983. {keyfile, ClientKey}
  984. ]},
  985. {port, 8883}
  986. ]),
  987. %% At first, the CRL contains no revoked entries, so the client
  988. %% should be allowed connection.
  989. ?assertMatch({ok, _}, emqtt:connect(C0)),
  990. emqtt:stop(C0),
  991. %% configure crl
  992. CRLConfig =
  993. #{
  994. <<"ssl_options">> =>
  995. #{
  996. <<"keyfile">> => Keyfile,
  997. <<"certfile">> => Certfile,
  998. <<"cacertfile">> => Cacertfile,
  999. <<"enable_crl_check">> => true
  1000. }
  1001. },
  1002. ListenerData1 = emqx_utils_maps:deep_merge(ListenerData0, CRLConfig),
  1003. {ok, {_, _, ListenerData2}} = update_listener_via_api(ListenerId, ListenerData1),
  1004. ?assertMatch(
  1005. #{
  1006. <<"ssl_options">> :=
  1007. #{
  1008. <<"enable_crl_check">> := true,
  1009. <<"verify">> := <<"verify_peer">>
  1010. }
  1011. },
  1012. ListenerData2
  1013. ),
  1014. %% Now should use CRL information to block connection
  1015. process_flag(trap_exit, true),
  1016. {ok, C1} = emqtt:start_link([
  1017. {ssl, true},
  1018. {ssl_opts, [
  1019. {certfile, ClientCert},
  1020. {keyfile, ClientKey}
  1021. ]},
  1022. {port, 8883}
  1023. ]),
  1024. ?assertMatch(
  1025. {error, {{shutdown, {tls_alert, {certificate_revoked, _}}}, _}}, emqtt:connect(C1)
  1026. ),
  1027. assert_http_get(<<?DEFAULT_URL>>),
  1028. ok.
  1029. t_validations(Config) ->
  1030. case proplists:get_bool(skip_does_not_apply, Config) of
  1031. true ->
  1032. ok;
  1033. false ->
  1034. do_t_validations(Config)
  1035. end.
  1036. do_t_validations(_Config) ->
  1037. ListenerId = <<"ssl:default">>,
  1038. {ok, {{_, 200, _}, _, ListenerData0}} = get_listener_via_api(ListenerId),
  1039. ListenerData1 =
  1040. emqx_utils_maps:deep_merge(
  1041. ListenerData0,
  1042. #{
  1043. <<"ssl_options">> =>
  1044. #{
  1045. <<"enable_crl_check">> => true,
  1046. <<"verify">> => <<"verify_none">>
  1047. }
  1048. }
  1049. ),
  1050. {error, {_, _, ResRaw1}} = update_listener_via_api(ListenerId, ListenerData1),
  1051. #{<<"code">> := <<"BAD_REQUEST">>, <<"message">> := MsgRaw1} =
  1052. emqx_utils_json:decode(ResRaw1, [return_maps]),
  1053. ?assertMatch(
  1054. #{
  1055. <<"kind">> := <<"validation_error">>,
  1056. <<"reason">> :=
  1057. <<"verify must be verify_peer when CRL check is enabled">>
  1058. },
  1059. emqx_utils_json:decode(MsgRaw1, [return_maps])
  1060. ),
  1061. ok.