feat(crl): add refresh config API
This commit is contained in:
parent
b08d1651ad
commit
0ca7492515
|
@ -23,6 +23,7 @@
|
||||||
, start_link/1
|
, start_link/1
|
||||||
, refresh/1
|
, refresh/1
|
||||||
, evict/1
|
, evict/1
|
||||||
|
, refresh_config/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
{ refresh_timers = #{} :: #{binary() => timer:tref()}
|
{ refresh_timers = #{} :: #{binary() => timer:tref()}
|
||||||
, refresh_interval = timer:minutes(15) :: timer:time()
|
, refresh_interval = timer:minutes(15) :: timer:time()
|
||||||
, http_timeout = ?HTTP_TIMEOUT :: timer:time()
|
, http_timeout = ?HTTP_TIMEOUT :: timer:time()
|
||||||
|
, extra = #{} :: map() %% for future use
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -55,20 +57,11 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
Listeners = emqx:get_env(listeners, []),
|
Config = gather_config(),
|
||||||
URLs = collect_urls(Listeners),
|
start_link(Config).
|
||||||
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
|
|
||||||
}).
|
|
||||||
|
|
||||||
start_link(Opts = #{urls := _, refresh_interval := _, http_timeout := _}) ->
|
start_link(Config = #{urls := _, refresh_interval := _, http_timeout := _}) ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, Config, []).
|
||||||
|
|
||||||
refresh(URL) ->
|
refresh(URL) ->
|
||||||
gen_server:cast(?MODULE, {refresh, URL}).
|
gen_server:cast(?MODULE, {refresh, URL}).
|
||||||
|
@ -76,6 +69,11 @@ refresh(URL) ->
|
||||||
evict(URL) ->
|
evict(URL) ->
|
||||||
gen_server:cast(?MODULE, {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
|
%% gen_server behaviour
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -116,6 +114,21 @@ handle_cast({refresh, URL}, State0) ->
|
||||||
?LOG(debug, "fetched crl response for ~p", [URL]),
|
?LOG(debug, "fetched crl response for ~p", [URL]),
|
||||||
{noreply, ensure_timer(URL, State0)}
|
{noreply, ensure_timer(URL, State0)}
|
||||||
end;
|
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) ->
|
handle_cast(_Cast, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
|
@ -186,6 +199,7 @@ ensure_timer(URL, State = #state{refresh_interval = Timeout}) ->
|
||||||
ensure_timer(URL, State, Timeout).
|
ensure_timer(URL, State, Timeout).
|
||||||
|
|
||||||
ensure_timer(URL, State = #state{refresh_timers = RefreshTimers0}, 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),
|
MTimer = maps:get(URL, RefreshTimers0, undefined),
|
||||||
emqx_misc:cancel_timer(MTimer),
|
emqx_misc:cancel_timer(MTimer),
|
||||||
RefreshTimers = RefreshTimers0#{URL => emqx_misc:start_timer(
|
RefreshTimers = RefreshTimers0#{URL => emqx_misc:start_timer(
|
||||||
|
@ -209,3 +223,20 @@ collect_urls(Listeners) ->
|
||||||
end,
|
end,
|
||||||
CRLOpts1),
|
CRLOpts1),
|
||||||
lists:usort(CRLURLs).
|
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_pem, CRLPem}
|
||||||
, {crl_der, CRLDer}
|
, {crl_der, CRLDer}
|
||||||
| Config];
|
| 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) ->
|
init_per_testcase(_TestCase, Config) ->
|
||||||
DataDir = ?config(data_dir, Config),
|
DataDir = ?config(data_dir, Config),
|
||||||
CRLFile = filename:join([DataDir, "crl.pem"]),
|
CRLFile = filename:join([DataDir, "crl.pem"]),
|
||||||
|
@ -86,6 +109,7 @@ end_per_testcase(TestCase, Config)
|
||||||
]),
|
]),
|
||||||
application:stop(cowboy),
|
application:stop(cowboy),
|
||||||
clear_crl_cache(),
|
clear_crl_cache(),
|
||||||
|
ok = snabbkaffe:stop(),
|
||||||
ok;
|
ok;
|
||||||
end_per_testcase(t_not_cached_and_unreachable, _Config) ->
|
end_per_testcase(t_not_cached_and_unreachable, _Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]),
|
emqx_ct_helpers:stop_apps([]),
|
||||||
|
@ -95,10 +119,34 @@ end_per_testcase(t_not_cached_and_unreachable, _Config) ->
|
||||||
]}
|
]}
|
||||||
]),
|
]),
|
||||||
clear_crl_cache(),
|
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;
|
ok;
|
||||||
end_per_testcase(_TestCase, _Config) ->
|
end_per_testcase(_TestCase, _Config) ->
|
||||||
meck:unload([emqx_crl_cache]),
|
meck:unload([emqx_crl_cache]),
|
||||||
clear_crl_cache(),
|
clear_crl_cache(),
|
||||||
|
ok = snabbkaffe:stop(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -422,6 +470,44 @@ t_filled_cache(Config) ->
|
||||||
emqtt:disconnect(C),
|
emqtt:disconnect(C),
|
||||||
ok.
|
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
|
%% If the CRL is not cached when the client tries to connect and the
|
||||||
%% CRL server is unreachable, the client will be denied connection.
|
%% CRL server is unreachable, the client will be denied connection.
|
||||||
t_not_cached_and_unreachable(Config) ->
|
t_not_cached_and_unreachable(Config) ->
|
||||||
|
|
Loading…
Reference in New Issue