feat(crl): add refresh config API
This commit is contained in:
parent
b08d1651ad
commit
0ca7492515
|
@ -23,6 +23,7 @@
|
|||
, start_link/1
|
||||
, refresh/1
|
||||
, evict/1
|
||||
, refresh_config/0
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
|
@ -48,6 +49,7 @@
|
|||
{ refresh_timers = #{} :: #{binary() => timer:tref()}
|
||||
, refresh_interval = timer:minutes(15) :: timer:time()
|
||||
, http_timeout = ?HTTP_TIMEOUT :: timer:time()
|
||||
, extra = #{} :: map() %% for future use
|
||||
}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -55,20 +57,11 @@
|
|||
%%--------------------------------------------------------------------
|
||||
|
||||
start_link() ->
|
||||
Listeners = emqx:get_env(listeners, []),
|
||||
URLs = collect_urls(Listeners),
|
||||
RefreshIntervalMS0 = emqx:get_env(crl_cache_refresh_interval,
|
||||
timer:minutes(15)),
|
||||
MinimumRefreshInverval = timer:minutes(1),
|
||||
RefreshIntervalMS = max(RefreshIntervalMS0, MinimumRefreshInverval),
|
||||
HTTPTimeoutMS = emqx:get_env(crl_cache_http_timeout, ?HTTP_TIMEOUT),
|
||||
start_link(#{ urls => URLs
|
||||
, refresh_interval => RefreshIntervalMS
|
||||
, http_timeout => HTTPTimeoutMS
|
||||
}).
|
||||
Config = gather_config(),
|
||||
start_link(Config).
|
||||
|
||||
start_link(Opts = #{urls := _, refresh_interval := _, http_timeout := _}) ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).
|
||||
start_link(Config = #{urls := _, refresh_interval := _, http_timeout := _}) ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, Config, []).
|
||||
|
||||
refresh(URL) ->
|
||||
gen_server:cast(?MODULE, {refresh, URL}).
|
||||
|
@ -76,6 +69,11 @@ refresh(URL) ->
|
|||
evict(URL) ->
|
||||
gen_server:cast(?MODULE, {evict, URL}).
|
||||
|
||||
%% to pick up changes from the config
|
||||
-spec refresh_config() -> ok.
|
||||
refresh_config() ->
|
||||
gen_server:cast(?MODULE, refresh_config).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server behaviour
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -116,6 +114,21 @@ handle_cast({refresh, URL}, State0) ->
|
|||
?LOG(debug, "fetched crl response for ~p", [URL]),
|
||||
{noreply, ensure_timer(URL, State0)}
|
||||
end;
|
||||
handle_cast(refresh_config, State0) ->
|
||||
#{ urls := URLs
|
||||
, http_timeout := HTTPTimeoutMS
|
||||
, refresh_interval := RefreshIntervalMS
|
||||
} = gather_config(),
|
||||
State = lists:foldl(fun(URL, Acc) -> ensure_timer(URL, Acc, 0) end,
|
||||
State0#state{ refresh_interval = RefreshIntervalMS
|
||||
, http_timeout = HTTPTimeoutMS
|
||||
},
|
||||
URLs),
|
||||
?tp(crl_cache_refresh_config, #{ refresh_interval => RefreshIntervalMS
|
||||
, http_timeout => HTTPTimeoutMS
|
||||
, urls => URLs
|
||||
}),
|
||||
State;
|
||||
handle_cast(_Cast, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
|
@ -186,6 +199,7 @@ ensure_timer(URL, State = #state{refresh_interval = Timeout}) ->
|
|||
ensure_timer(URL, State, Timeout).
|
||||
|
||||
ensure_timer(URL, State = #state{refresh_timers = RefreshTimers0}, Timeout) ->
|
||||
?tp(crl_cache_ensure_timer, #{url => URL, timeout => Timeout}),
|
||||
MTimer = maps:get(URL, RefreshTimers0, undefined),
|
||||
emqx_misc:cancel_timer(MTimer),
|
||||
RefreshTimers = RefreshTimers0#{URL => emqx_misc:start_timer(
|
||||
|
@ -209,3 +223,20 @@ collect_urls(Listeners) ->
|
|||
end,
|
||||
CRLOpts1),
|
||||
lists:usort(CRLURLs).
|
||||
|
||||
-spec gather_config() -> #{ urls := [string()]
|
||||
, refresh_interval := timer:time()
|
||||
, http_timeout := timer:time()
|
||||
}.
|
||||
gather_config() ->
|
||||
Listeners = emqx:get_env(listeners, []),
|
||||
URLs = collect_urls(Listeners),
|
||||
RefreshIntervalMS0 = emqx:get_env(crl_cache_refresh_interval,
|
||||
timer:minutes(15)),
|
||||
MinimumRefreshInverval = timer:minutes(1),
|
||||
RefreshIntervalMS = max(RefreshIntervalMS0, MinimumRefreshInverval),
|
||||
HTTPTimeoutMS = emqx:get_env(crl_cache_http_timeout, ?HTTP_TIMEOUT),
|
||||
#{ urls => URLs
|
||||
, refresh_interval => RefreshIntervalMS
|
||||
, http_timeout => HTTPTimeoutMS
|
||||
}.
|
||||
|
|
|
@ -56,6 +56,29 @@ init_per_testcase(t_not_cached_and_unreachable, Config) ->
|
|||
[ {crl_pem, CRLPem}
|
||||
, {crl_der, CRLDer}
|
||||
| Config];
|
||||
init_per_testcase(t_refresh_config, Config) ->
|
||||
DataDir = ?config(data_dir, Config),
|
||||
CRLFile = filename:join([DataDir, "crl.pem"]),
|
||||
{ok, CRLPem} = file:read_file(CRLFile),
|
||||
[{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem),
|
||||
TestPid = self(),
|
||||
ok = meck:new(emqx_crl_cache, [non_strict, passthrough, no_history, no_link]),
|
||||
meck:expect(emqx_crl_cache, http_get,
|
||||
fun(URL, _HTTPTimeout) ->
|
||||
TestPid ! {http_get, URL},
|
||||
{ok, {{"HTTP/1.0", 200, 'OK'}, [], CRLPem}}
|
||||
end),
|
||||
OldListeners = emqx:get_env(listeners),
|
||||
OldRefreshInterval = emqx:get_env(crl_cache_refresh_interval),
|
||||
OldHTTPTimeout = emqx:get_env(crl_cache_http_timeout),
|
||||
ok = setup_crl_options(Config, #{is_cached => false}),
|
||||
[ {crl_pem, CRLPem}
|
||||
, {crl_der, CRLDer}
|
||||
, {old_configs, [ {listeners, OldListeners}
|
||||
, {crl_cache_refresh_interval, OldRefreshInterval}
|
||||
, {crl_cache_http_timeout, OldHTTPTimeout}
|
||||
]}
|
||||
| Config];
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
DataDir = ?config(data_dir, Config),
|
||||
CRLFile = filename:join([DataDir, "crl.pem"]),
|
||||
|
@ -86,6 +109,7 @@ end_per_testcase(TestCase, Config)
|
|||
]),
|
||||
application:stop(cowboy),
|
||||
clear_crl_cache(),
|
||||
ok = snabbkaffe:stop(),
|
||||
ok;
|
||||
end_per_testcase(t_not_cached_and_unreachable, _Config) ->
|
||||
emqx_ct_helpers:stop_apps([]),
|
||||
|
@ -95,10 +119,34 @@ end_per_testcase(t_not_cached_and_unreachable, _Config) ->
|
|||
]}
|
||||
]),
|
||||
clear_crl_cache(),
|
||||
ok = snabbkaffe:stop(),
|
||||
ok;
|
||||
end_per_testcase(t_refresh_config, Config) ->
|
||||
OldConfigs = ?config(old_configs, Config),
|
||||
meck:unload([emqx_crl_cache]),
|
||||
emqx_ct_helpers:stop_apps([]),
|
||||
emqx_ct_helpers:change_emqx_opts(
|
||||
ssl_twoway, [ {crl_options, [ {crl_check_enabled, false}
|
||||
, {crl_cache_urls, []}
|
||||
]}
|
||||
]),
|
||||
clear_crl_cache(),
|
||||
lists:foreach(
|
||||
fun({Key, MValue}) ->
|
||||
case MValue of
|
||||
undefined -> ok;
|
||||
Value -> application:set_env(emqx, Key, Value)
|
||||
end
|
||||
end,
|
||||
OldConfigs),
|
||||
application:stop(cowboy),
|
||||
clear_crl_cache(),
|
||||
ok = snabbkaffe:stop(),
|
||||
ok;
|
||||
end_per_testcase(_TestCase, _Config) ->
|
||||
meck:unload([emqx_crl_cache]),
|
||||
clear_crl_cache(),
|
||||
ok = snabbkaffe:stop(),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -422,6 +470,44 @@ t_filled_cache(Config) ->
|
|||
emqtt:disconnect(C),
|
||||
ok.
|
||||
|
||||
t_refresh_config(_Config) ->
|
||||
URLs = [ "http://localhost:9878/some.crl.pem"
|
||||
, "http://localhost:9878/another.crl.pem"
|
||||
],
|
||||
SortedURLs = lists:sort(URLs),
|
||||
emqx_ct_helpers:change_emqx_opts(
|
||||
ssl_twoway, [ {crl_options, [ {crl_check_enabled, true}
|
||||
, {crl_cache_urls, URLs}
|
||||
]}
|
||||
]),
|
||||
%% has to be more than 1 minute
|
||||
NewRefreshInterval = timer:seconds(64),
|
||||
NewHTTPTimeout = timer:seconds(7),
|
||||
application:set_env(emqx, crl_cache_refresh_interval, NewRefreshInterval),
|
||||
application:set_env(emqx, crl_cache_http_timeout, NewHTTPTimeout),
|
||||
?check_trace(
|
||||
?wait_async_action(
|
||||
emqx_crl_cache:refresh_config(),
|
||||
#{?snk_kind := crl_cache_refresh_config},
|
||||
_Timeout = 10_000),
|
||||
fun(Res, Trace) ->
|
||||
?assertMatch({ok, {ok, _}}, Res),
|
||||
?assertMatch(
|
||||
[#{ urls := SortedURLs
|
||||
, refresh_interval := NewRefreshInterval
|
||||
, http_timeout := NewHTTPTimeout
|
||||
}],
|
||||
?of_kind(crl_cache_refresh_config, Trace),
|
||||
#{ expected => #{ urls => SortedURLs
|
||||
, refresh_interval => NewRefreshInterval
|
||||
, http_timeout => NewHTTPTimeout
|
||||
}
|
||||
}),
|
||||
?assertEqual(SortedURLs, ?projection(url, ?of_kind(crl_cache_ensure_timer, Trace))),
|
||||
ok
|
||||
end),
|
||||
ok.
|
||||
|
||||
%% If the CRL is not cached when the client tries to connect and the
|
||||
%% CRL server is unreachable, the client will be denied connection.
|
||||
t_not_cached_and_unreachable(Config) ->
|
||||
|
|
Loading…
Reference in New Issue