Merge pull request #10128 from thalesmg/ocsp-v50-mkII
feat: add ocsp stapling support to mqtt ssl listener (5.0)
This commit is contained in:
commit
91a57faa95
|
@ -1753,6 +1753,63 @@ server_ssl_opts_schema_gc_after_handshake {
|
|||
}
|
||||
}
|
||||
|
||||
server_ssl_opts_schema_enable_ocsp_stapling {
|
||||
desc {
|
||||
en: "Whether to enable Online Certificate Status Protocol (OCSP) stapling for the listener."
|
||||
" If set to true, requires defining the OCSP responder URL and issuer PEM path."
|
||||
zh: "是否为监听器启用 OCSP Stapling 功能。 如果设置为 true,"
|
||||
"需要定义 OCSP Responder 的 URL 和证书签发者的 PEM 文件路径。"
|
||||
}
|
||||
label: {
|
||||
en: "Enable OCSP Stapling"
|
||||
zh: "启用 OCSP Stapling"
|
||||
}
|
||||
}
|
||||
|
||||
server_ssl_opts_schema_ocsp_responder_url {
|
||||
desc {
|
||||
en: "URL for the OCSP responder to check the server certificate against."
|
||||
zh: "用于检查服务器证书的 OCSP Responder 的 URL。"
|
||||
}
|
||||
label: {
|
||||
en: "OCSP Responder URL"
|
||||
zh: "OCSP Responder 的 URL"
|
||||
}
|
||||
}
|
||||
|
||||
server_ssl_opts_schema_ocsp_issuer_pem {
|
||||
desc {
|
||||
en: "PEM-encoded certificate of the OCSP issuer for the server certificate."
|
||||
zh: "服务器证书的 OCSP 签发者的 PEM 编码证书。"
|
||||
}
|
||||
label: {
|
||||
en: "OCSP Issuer Certificate"
|
||||
zh: "OCSP 签发者证书"
|
||||
}
|
||||
}
|
||||
|
||||
server_ssl_opts_schema_ocsp_refresh_interval {
|
||||
desc {
|
||||
en: "The period to refresh the OCSP response for the server."
|
||||
zh: "为服务器刷新OCSP响应的周期。"
|
||||
}
|
||||
label: {
|
||||
en: "OCSP Refresh Interval"
|
||||
zh: "OCSP 刷新间隔"
|
||||
}
|
||||
}
|
||||
|
||||
server_ssl_opts_schema_ocsp_refresh_http_timeout {
|
||||
desc {
|
||||
en: "The timeout for the HTTP request when checking OCSP responses."
|
||||
zh: "检查 OCSP 响应时,HTTP 请求的超时。"
|
||||
}
|
||||
label: {
|
||||
en: "OCSP Refresh HTTP Timeout"
|
||||
zh: "OCSP 刷新 HTTP 超时"
|
||||
}
|
||||
}
|
||||
|
||||
fields_listeners_tcp {
|
||||
desc {
|
||||
en: """TCP listeners."""
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.14.5"}}},
|
||||
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}},
|
||||
{hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.37.0"}}},
|
||||
{emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.5.2"}}},
|
||||
{pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}},
|
||||
{recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}},
|
||||
{snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "1.0.0"}}}
|
||||
|
@ -43,7 +44,7 @@
|
|||
{meck, "0.9.2"},
|
||||
{proper, "1.4.0"},
|
||||
{bbmustache, "1.10.0"},
|
||||
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.8.2"}}}
|
||||
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.8.5"}}}
|
||||
]},
|
||||
{extra_src_dirs, [{"test", [recursive]}]}
|
||||
]}
|
||||
|
|
|
@ -87,6 +87,10 @@
|
|||
remove_handlers/0
|
||||
]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-export([erase_schema_mod_and_names/0]).
|
||||
-endif.
|
||||
|
||||
-include("logger.hrl").
|
||||
-include_lib("hocon/include/hoconsc.hrl").
|
||||
|
||||
|
@ -501,6 +505,11 @@ save_schema_mod_and_names(SchemaMod) ->
|
|||
names => lists:usort(OldNames ++ RootNames)
|
||||
}).
|
||||
|
||||
-ifdef(TEST).
|
||||
erase_schema_mod_and_names() ->
|
||||
persistent_term:erase(?PERSIS_SCHEMA_MODS).
|
||||
-endif.
|
||||
|
||||
-spec get_schema_mod() -> #{binary() => atom()}.
|
||||
get_schema_mod() ->
|
||||
maps:get(mods, persistent_term:get(?PERSIS_SCHEMA_MODS, #{mods => #{}})).
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
%% You may obtain a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing, software
|
||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
%% See the License for the specific language governing permissions and
|
||||
%% limitations under the License.
|
||||
%%
|
||||
%% @doc Never update this module, create a v2 instead.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_const_v1).
|
||||
|
||||
-export([make_sni_fun/1]).
|
||||
|
||||
make_sni_fun(ListenerID) ->
|
||||
fun(SN) -> emqx_ocsp_cache:sni_fun(SN, ListenerID) end.
|
|
@ -35,7 +35,8 @@ init([]) ->
|
|||
child_spec(emqx_hooks, worker),
|
||||
child_spec(emqx_stats, worker),
|
||||
child_spec(emqx_metrics, worker),
|
||||
child_spec(emqx_authn_authz_metrics_sup, supervisor)
|
||||
child_spec(emqx_authn_authz_metrics_sup, supervisor),
|
||||
child_spec(emqx_ocsp_cache, worker)
|
||||
]
|
||||
}}.
|
||||
|
||||
|
|
|
@ -484,8 +484,12 @@ esockd_opts(ListenerId, Type, Opts0) ->
|
|||
},
|
||||
maps:to_list(
|
||||
case Type of
|
||||
tcp -> Opts3#{tcp_options => tcp_opts(Opts0)};
|
||||
ssl -> Opts3#{ssl_options => ssl_opts(Opts0), tcp_options => tcp_opts(Opts0)}
|
||||
tcp ->
|
||||
Opts3#{tcp_options => tcp_opts(Opts0)};
|
||||
ssl ->
|
||||
OptsWithSNI = inject_sni_fun(ListenerId, Opts0),
|
||||
SSLOpts = ssl_opts(OptsWithSNI),
|
||||
Opts3#{ssl_options => SSLOpts, tcp_options => tcp_opts(Opts0)}
|
||||
end
|
||||
).
|
||||
|
||||
|
@ -785,3 +789,8 @@ quic_listener_optional_settings() ->
|
|||
max_binding_stateless_operations,
|
||||
stateless_operation_expiration_ms
|
||||
].
|
||||
|
||||
inject_sni_fun(ListenerId, Conf = #{ssl_options := #{ocsp := #{enable_ocsp_stapling := true}}}) ->
|
||||
emqx_ocsp_cache:inject_sni_fun(ListenerId, Conf);
|
||||
inject_sni_fun(_ListenerId, Conf) ->
|
||||
Conf.
|
||||
|
|
|
@ -0,0 +1,532 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
%% You may obtain a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing, software
|
||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
%% See the License for the specific language governing permissions and
|
||||
%% limitations under the License.
|
||||
%%
|
||||
%% @doc EMQX OCSP cache.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_ocsp_cache).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include_lib("public_key/include/public_key.hrl").
|
||||
-include_lib("ssl/src/ssl_handshake.hrl").
|
||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([
|
||||
start_link/0,
|
||||
sni_fun/2,
|
||||
fetch_response/1,
|
||||
register_listener/2,
|
||||
inject_sni_fun/2
|
||||
]).
|
||||
|
||||
%% gen_server API
|
||||
-export([
|
||||
init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
code_change/3
|
||||
]).
|
||||
|
||||
%% internal export; only for mocking in tests
|
||||
-export([http_get/2]).
|
||||
|
||||
-define(CACHE_TAB, ?MODULE).
|
||||
-define(CALL_TIMEOUT, 20_000).
|
||||
-define(RETRY_TIMEOUT, 5_000).
|
||||
-define(REFRESH_TIMER(LID), {refresh_timer, LID}).
|
||||
-ifdef(TEST).
|
||||
-define(MIN_REFRESH_INTERVAL, timer:seconds(5)).
|
||||
-else.
|
||||
-define(MIN_REFRESH_INTERVAL, timer:minutes(1)).
|
||||
-endif.
|
||||
|
||||
%% Allow usage of OTP certificate record fields (camelCase).
|
||||
-elvis([
|
||||
{elvis_style, atom_naming_convention, #{
|
||||
regex => "^([a-z][a-z0-9]*_?)([a-zA-Z0-9]*_?)*$",
|
||||
enclosed_atoms => ".*"
|
||||
}}
|
||||
]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% API
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
sni_fun(_ServerName, ListenerID) ->
|
||||
Res =
|
||||
try
|
||||
fetch_response(ListenerID)
|
||||
catch
|
||||
_:_ -> error
|
||||
end,
|
||||
case Res of
|
||||
{ok, Response} ->
|
||||
[
|
||||
{certificate_status, #certificate_status{
|
||||
status_type = ?CERTIFICATE_STATUS_TYPE_OCSP,
|
||||
response = Response
|
||||
}}
|
||||
];
|
||||
error ->
|
||||
[]
|
||||
end.
|
||||
|
||||
fetch_response(ListenerID) ->
|
||||
case do_lookup(ListenerID) of
|
||||
{ok, DERResponse} ->
|
||||
{ok, DERResponse};
|
||||
{error, invalid_listener_id} ->
|
||||
error;
|
||||
{error, not_cached} ->
|
||||
?tp(ocsp_cache_miss, #{listener_id => ListenerID}),
|
||||
?SLOG(debug, #{
|
||||
msg => "fetching_new_ocsp_response",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
http_fetch(ListenerID)
|
||||
end.
|
||||
|
||||
register_listener(ListenerID, Opts) ->
|
||||
gen_server:call(?MODULE, {register_listener, ListenerID, Opts}, ?CALL_TIMEOUT).
|
||||
|
||||
-spec inject_sni_fun(emqx_listeners:listener_id(), map()) -> map().
|
||||
inject_sni_fun(ListenerID, Conf0) ->
|
||||
SNIFun = emqx_const_v1:make_sni_fun(ListenerID),
|
||||
Conf = emqx_map_lib:deep_merge(Conf0, #{ssl_options => #{sni_fun => SNIFun}}),
|
||||
ok = ?MODULE:register_listener(ListenerID, Conf),
|
||||
Conf.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server behaviour
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
init(_Args) ->
|
||||
logger:set_process_metadata(#{domain => [emqx, ocsp, cache]}),
|
||||
emqx_tables:new(?CACHE_TAB, [
|
||||
named_table,
|
||||
public,
|
||||
{heir, whereis(emqx_kernel_sup), none},
|
||||
{read_concurrency, true}
|
||||
]),
|
||||
?tp(ocsp_cache_init, #{}),
|
||||
{ok, #{}}.
|
||||
|
||||
handle_call({http_fetch, ListenerID}, _From, State) ->
|
||||
case do_lookup(ListenerID) of
|
||||
{ok, DERResponse} ->
|
||||
{reply, {ok, DERResponse}, State};
|
||||
{error, invalid_listener_id} ->
|
||||
{reply, error, State};
|
||||
{error, not_cached} ->
|
||||
Conf = undefined,
|
||||
with_refresh_params(ListenerID, Conf, {reply, error, State}, fun(Params) ->
|
||||
case do_http_fetch_and_cache(ListenerID, Params) of
|
||||
error -> {reply, error, ensure_timer(ListenerID, State, ?RETRY_TIMEOUT)};
|
||||
{ok, Response} -> {reply, {ok, Response}, ensure_timer(ListenerID, State)}
|
||||
end
|
||||
end)
|
||||
end;
|
||||
handle_call({register_listener, ListenerID, Conf}, _From, State0) ->
|
||||
?SLOG(debug, #{
|
||||
msg => "registering_ocsp_cache",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
RefreshInterval0 = emqx_map_lib:deep_get([ssl_options, ocsp, refresh_interval], Conf),
|
||||
RefreshInterval = max(RefreshInterval0, ?MIN_REFRESH_INTERVAL),
|
||||
State = State0#{{refresh_interval, ListenerID} => RefreshInterval},
|
||||
%% we need to pass the config along because this might be called
|
||||
%% during the listener's `post_config_update', hence the config is
|
||||
%% not yet "commited" and accessible when we need it.
|
||||
Message = {refresh, ListenerID, Conf},
|
||||
{reply, ok, ensure_timer(ListenerID, Message, State, 0)};
|
||||
handle_call(Call, _From, State) ->
|
||||
{reply, {error, {unknown_call, Call}}, State}.
|
||||
|
||||
handle_cast(_Cast, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({timeout, TRef, {refresh, ListenerID}}, State0) ->
|
||||
case maps:get(?REFRESH_TIMER(ListenerID), State0, undefined) of
|
||||
TRef ->
|
||||
?tp(ocsp_refresh_timer, #{listener_id => ListenerID}),
|
||||
?SLOG(debug, #{
|
||||
msg => "refreshing_ocsp_response",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
Conf = undefined,
|
||||
handle_refresh(ListenerID, Conf, State0);
|
||||
_ ->
|
||||
{noreply, State0}
|
||||
end;
|
||||
handle_info({timeout, TRef, {refresh, ListenerID, Conf}}, State0) ->
|
||||
case maps:get(?REFRESH_TIMER(ListenerID), State0, undefined) of
|
||||
TRef ->
|
||||
?tp(ocsp_refresh_timer, #{listener_id => ListenerID}),
|
||||
?SLOG(debug, #{
|
||||
msg => "refreshing_ocsp_response",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
handle_refresh(ListenerID, Conf, State0);
|
||||
_ ->
|
||||
{noreply, State0}
|
||||
end;
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
code_change(_Vsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
http_fetch(ListenerID) ->
|
||||
%% TODO: configurable call timeout?
|
||||
gen_server:call(?MODULE, {http_fetch, ListenerID}, ?CALL_TIMEOUT).
|
||||
|
||||
with_listener_config(ListenerID, ConfPath, ErrorResp, Fn) ->
|
||||
case emqx_listeners:parse_listener_id(ListenerID) of
|
||||
{ok, #{type := Type, name := Name}} ->
|
||||
case emqx_config:get_listener_conf(Type, Name, ConfPath, not_found) of
|
||||
not_found ->
|
||||
?SLOG(error, #{
|
||||
msg => "listener_config_missing",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
ErrorResp;
|
||||
Config ->
|
||||
Fn(Config)
|
||||
end;
|
||||
_Err ->
|
||||
?SLOG(error, #{
|
||||
msg => "listener_id_not_found",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
ErrorResp
|
||||
end.
|
||||
|
||||
cache_key(ListenerID) ->
|
||||
with_listener_config(ListenerID, [ssl_options], error, fun
|
||||
(#{certfile := ServerCertPemPath}) ->
|
||||
#'Certificate'{
|
||||
tbsCertificate =
|
||||
#'TBSCertificate'{
|
||||
signature = Signature
|
||||
}
|
||||
} = read_server_cert(ServerCertPemPath),
|
||||
{ok, {ocsp_response, Signature}};
|
||||
(OtherConfig) ->
|
||||
?SLOG(error, #{
|
||||
msg => "listener_config_inconsistent",
|
||||
listener_id => ListenerID,
|
||||
config => OtherConfig
|
||||
}),
|
||||
error
|
||||
end).
|
||||
|
||||
do_lookup(ListenerID) ->
|
||||
CacheKey = cache_key(ListenerID),
|
||||
case CacheKey of
|
||||
error ->
|
||||
{error, invalid_listener_id};
|
||||
{ok, Key} ->
|
||||
%% Respond immediately if a concurrent call already fetched it.
|
||||
case ets:lookup(?CACHE_TAB, Key) of
|
||||
[{_, DERResponse}] ->
|
||||
?tp(ocsp_cache_hit, #{listener_id => ListenerID}),
|
||||
{ok, DERResponse};
|
||||
[] ->
|
||||
{error, not_cached}
|
||||
end
|
||||
end.
|
||||
|
||||
read_server_cert(ServerCertPemPath0) ->
|
||||
ServerCertPemPath = to_bin(ServerCertPemPath0),
|
||||
case ets:lookup(ssl_pem_cache, ServerCertPemPath) of
|
||||
[{_, [{'Certificate', ServerCertDer, _} | _]}] ->
|
||||
public_key:der_decode('Certificate', ServerCertDer);
|
||||
[] ->
|
||||
case file:read_file(ServerCertPemPath) of
|
||||
{ok, ServerCertPem} ->
|
||||
[{'Certificate', ServerCertDer, _} | _] =
|
||||
public_key:pem_decode(ServerCertPem),
|
||||
public_key:der_decode('Certificate', ServerCertDer);
|
||||
{error, Error1} ->
|
||||
error({bad_server_cert_file, Error1})
|
||||
end
|
||||
end.
|
||||
|
||||
handle_refresh(ListenerID, Conf, State0) ->
|
||||
%% no point in retrying if the config is inconsistent or non
|
||||
%% existent.
|
||||
State1 = maps:without([{refresh_interval, ListenerID}, ?REFRESH_TIMER(ListenerID)], State0),
|
||||
with_refresh_params(ListenerID, Conf, {noreply, State1}, fun(Params) ->
|
||||
case do_http_fetch_and_cache(ListenerID, Params) of
|
||||
error ->
|
||||
?SLOG(debug, #{
|
||||
msg => "failed_to_fetch_ocsp_response",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
{noreply, ensure_timer(ListenerID, State0, ?RETRY_TIMEOUT)};
|
||||
{ok, _Response} ->
|
||||
?SLOG(debug, #{
|
||||
msg => "fetched_ocsp_response",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
{noreply, ensure_timer(ListenerID, State0)}
|
||||
end
|
||||
end).
|
||||
|
||||
with_refresh_params(ListenerID, Conf, ErrorRet, Fn) ->
|
||||
case get_refresh_params(ListenerID, Conf) of
|
||||
error ->
|
||||
ErrorRet;
|
||||
{ok, Params} ->
|
||||
try
|
||||
Fn(Params)
|
||||
catch
|
||||
Kind:Error ->
|
||||
?SLOG(error, #{
|
||||
msg => "error_fetching_ocsp_response",
|
||||
listener_id => ListenerID,
|
||||
error => {Kind, Error}
|
||||
}),
|
||||
ErrorRet
|
||||
end
|
||||
end.
|
||||
|
||||
get_refresh_params(ListenerID, undefined = _Conf) ->
|
||||
%% during normal periodic refreshes, we read from the emqx config.
|
||||
with_listener_config(ListenerID, [ssl_options], error, fun
|
||||
(
|
||||
#{
|
||||
ocsp := #{
|
||||
issuer_pem := IssuerPemPath,
|
||||
responder_url := ResponderURL,
|
||||
refresh_http_timeout := HTTPTimeout
|
||||
},
|
||||
certfile := ServerCertPemPath
|
||||
}
|
||||
) ->
|
||||
{ok, #{
|
||||
issuer_pem => IssuerPemPath,
|
||||
responder_url => ResponderURL,
|
||||
refresh_http_timeout => HTTPTimeout,
|
||||
server_certfile => ServerCertPemPath
|
||||
}};
|
||||
(OtherConfig) ->
|
||||
?SLOG(error, #{
|
||||
msg => "listener_config_inconsistent",
|
||||
listener_id => ListenerID,
|
||||
config => OtherConfig
|
||||
}),
|
||||
error
|
||||
end);
|
||||
get_refresh_params(_ListenerID, #{
|
||||
ssl_options := #{
|
||||
ocsp := #{
|
||||
issuer_pem := IssuerPemPath,
|
||||
responder_url := ResponderURL,
|
||||
refresh_http_timeout := HTTPTimeout
|
||||
},
|
||||
certfile := ServerCertPemPath
|
||||
}
|
||||
}) ->
|
||||
{ok, #{
|
||||
issuer_pem => IssuerPemPath,
|
||||
responder_url => ResponderURL,
|
||||
refresh_http_timeout => HTTPTimeout,
|
||||
server_certfile => ServerCertPemPath
|
||||
}};
|
||||
get_refresh_params(_ListenerID, _Conf) ->
|
||||
error.
|
||||
|
||||
do_http_fetch_and_cache(ListenerID, Params) ->
|
||||
#{
|
||||
issuer_pem := IssuerPemPath,
|
||||
responder_url := ResponderURL,
|
||||
refresh_http_timeout := HTTPTimeout,
|
||||
server_certfile := ServerCertPemPath
|
||||
} = Params,
|
||||
IssuerPem =
|
||||
case file:read_file(IssuerPemPath) of
|
||||
{ok, IssuerPem0} -> IssuerPem0;
|
||||
{error, Error0} -> error({bad_issuer_pem_file, Error0})
|
||||
end,
|
||||
ServerCert = read_server_cert(ServerCertPemPath),
|
||||
Request = build_ocsp_request(IssuerPem, ServerCert),
|
||||
?tp(ocsp_http_fetch, #{
|
||||
listener_id => ListenerID,
|
||||
responder_url => ResponderURL,
|
||||
timeout => HTTPTimeout
|
||||
}),
|
||||
RequestURI = iolist_to_binary([ResponderURL, Request]),
|
||||
Resp = ?MODULE:http_get(RequestURI, HTTPTimeout),
|
||||
case Resp of
|
||||
{ok, {{_, 200, _}, _, Body}} ->
|
||||
?SLOG(debug, #{
|
||||
msg => "caching_ocsp_response",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
%% if we got this far, the certfile is correct.
|
||||
{ok, CacheKey} = cache_key(ListenerID),
|
||||
true = ets:insert(?CACHE_TAB, {CacheKey, Body}),
|
||||
?tp(ocsp_http_fetch_and_cache, #{
|
||||
listener_id => ListenerID,
|
||||
headers => true
|
||||
}),
|
||||
{ok, Body};
|
||||
{ok, {200, Body}} ->
|
||||
?SLOG(debug, #{
|
||||
msg => "caching_ocsp_response",
|
||||
listener_id => ListenerID
|
||||
}),
|
||||
%% if we got this far, the certfile is correct.
|
||||
{ok, CacheKey} = cache_key(ListenerID),
|
||||
true = ets:insert(?CACHE_TAB, {CacheKey, Body}),
|
||||
?tp(ocsp_http_fetch_and_cache, #{
|
||||
listener_id => ListenerID,
|
||||
headers => false
|
||||
}),
|
||||
{ok, Body};
|
||||
{ok, {{_, Code, _}, _, Body}} ->
|
||||
?tp(
|
||||
error,
|
||||
ocsp_http_fetch_bad_code,
|
||||
#{
|
||||
listener_id => ListenerID,
|
||||
body => Body,
|
||||
code => Code,
|
||||
headers => true
|
||||
}
|
||||
),
|
||||
?SLOG(error, #{
|
||||
msg => "error_fetching_ocsp_response",
|
||||
listener_id => ListenerID,
|
||||
code => Code,
|
||||
body => Body
|
||||
}),
|
||||
error;
|
||||
{ok, {Code, Body}} ->
|
||||
?tp(
|
||||
error,
|
||||
ocsp_http_fetch_bad_code,
|
||||
#{
|
||||
listener_id => ListenerID,
|
||||
body => Body,
|
||||
code => Code,
|
||||
headers => false
|
||||
}
|
||||
),
|
||||
?SLOG(error, #{
|
||||
msg => "error_fetching_ocsp_response",
|
||||
listener_id => ListenerID,
|
||||
code => Code,
|
||||
body => Body
|
||||
}),
|
||||
error;
|
||||
{error, Error} ->
|
||||
?tp(
|
||||
error,
|
||||
ocsp_http_fetch_error,
|
||||
#{
|
||||
listener_id => ListenerID,
|
||||
error => Error
|
||||
}
|
||||
),
|
||||
?SLOG(error, #{
|
||||
msg => "error_fetching_ocsp_response",
|
||||
listener_id => ListenerID,
|
||||
error => Error
|
||||
}),
|
||||
error
|
||||
end.
|
||||
|
||||
http_get(URL, HTTPTimeout) ->
|
||||
httpc:request(
|
||||
get,
|
||||
{URL, [{"connection", "close"}]},
|
||||
[{timeout, HTTPTimeout}],
|
||||
[{body_format, binary}]
|
||||
).
|
||||
|
||||
ensure_timer(ListenerID, State) ->
|
||||
Timeout = maps:get({refresh_interval, ListenerID}, State, timer:minutes(5)),
|
||||
ensure_timer(ListenerID, State, Timeout).
|
||||
|
||||
ensure_timer(ListenerID, State, Timeout) ->
|
||||
ensure_timer(ListenerID, {refresh, ListenerID}, State, Timeout).
|
||||
|
||||
ensure_timer(ListenerID, Message, State, Timeout) ->
|
||||
emqx_misc:cancel_timer(maps:get(?REFRESH_TIMER(ListenerID), State, undefined)),
|
||||
State#{
|
||||
?REFRESH_TIMER(ListenerID) => emqx_misc:start_timer(
|
||||
Timeout,
|
||||
Message
|
||||
)
|
||||
}.
|
||||
|
||||
build_ocsp_request(IssuerPem, ServerCert) ->
|
||||
[{'Certificate', IssuerDer, _} | _] = public_key:pem_decode(IssuerPem),
|
||||
#'Certificate'{
|
||||
tbsCertificate =
|
||||
#'TBSCertificate'{
|
||||
serialNumber = SerialNumber,
|
||||
issuer = Issuer
|
||||
}
|
||||
} = ServerCert,
|
||||
#'Certificate'{
|
||||
tbsCertificate =
|
||||
#'TBSCertificate'{
|
||||
subjectPublicKeyInfo =
|
||||
#'SubjectPublicKeyInfo'{subjectPublicKey = IssuerPublicKeyDer}
|
||||
}
|
||||
} = public_key:der_decode('Certificate', IssuerDer),
|
||||
IssuerDNHash = crypto:hash(sha, public_key:der_encode('Name', Issuer)),
|
||||
IssuerPKHash = crypto:hash(sha, IssuerPublicKeyDer),
|
||||
Req = #'OCSPRequest'{
|
||||
tbsRequest =
|
||||
#'TBSRequest'{
|
||||
version = 0,
|
||||
requestList =
|
||||
[
|
||||
#'Request'{
|
||||
reqCert =
|
||||
#'CertID'{
|
||||
hashAlgorithm =
|
||||
#'AlgorithmIdentifier'{
|
||||
algorithm = ?'id-sha1',
|
||||
%% ???
|
||||
parameters = <<5, 0>>
|
||||
},
|
||||
issuerNameHash = IssuerDNHash,
|
||||
issuerKeyHash = IssuerPKHash,
|
||||
serialNumber = SerialNumber
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
ReqDer = public_key:der_encode('OCSPRequest', Req),
|
||||
base64:encode_to_string(ReqDer).
|
||||
|
||||
to_bin(Str) when is_list(Str) -> list_to_binary(Str);
|
||||
to_bin(Bin) when is_binary(Bin) -> Bin.
|
|
@ -43,6 +43,7 @@
|
|||
-type cipher() :: map().
|
||||
-type port_number() :: 1..65536.
|
||||
-type server_parse_option() :: #{default_port => port_number(), no_port => boolean()}.
|
||||
-type url() :: binary().
|
||||
|
||||
-typerefl_from_string({duration/0, emqx_schema, to_duration}).
|
||||
-typerefl_from_string({duration_s/0, emqx_schema, to_duration_s}).
|
||||
|
@ -56,6 +57,7 @@
|
|||
-typerefl_from_string({ip_port/0, emqx_schema, to_ip_port}).
|
||||
-typerefl_from_string({cipher/0, emqx_schema, to_erl_cipher_suite}).
|
||||
-typerefl_from_string({comma_separated_atoms/0, emqx_schema, to_comma_separated_atoms}).
|
||||
-typerefl_from_string({url/0, emqx_schema, to_url}).
|
||||
|
||||
-export([
|
||||
validate_heap_size/1,
|
||||
|
@ -81,7 +83,8 @@
|
|||
to_bar_separated_list/1,
|
||||
to_ip_port/1,
|
||||
to_erl_cipher_suite/1,
|
||||
to_comma_separated_atoms/1
|
||||
to_comma_separated_atoms/1,
|
||||
to_url/1
|
||||
]).
|
||||
|
||||
-export([
|
||||
|
@ -108,7 +111,8 @@
|
|||
bar_separated_list/0,
|
||||
ip_port/0,
|
||||
cipher/0,
|
||||
comma_separated_atoms/0
|
||||
comma_separated_atoms/0,
|
||||
url/0
|
||||
]).
|
||||
|
||||
-export([namespace/0, roots/0, roots/1, fields/1, desc/1, tags/0]).
|
||||
|
@ -810,7 +814,7 @@ fields("mqtt_ssl_listener") ->
|
|||
{"ssl_options",
|
||||
sc(
|
||||
ref("listener_ssl_opts"),
|
||||
#{}
|
||||
#{validator => fun mqtt_ssl_listener_ssl_options_validator/1}
|
||||
)}
|
||||
];
|
||||
fields("mqtt_ws_listener") ->
|
||||
|
@ -1294,6 +1298,49 @@ fields("listener_quic_ssl_opts") ->
|
|||
);
|
||||
fields("ssl_client_opts") ->
|
||||
client_ssl_opts_schema(#{});
|
||||
fields("ocsp") ->
|
||||
[
|
||||
{"enable_ocsp_stapling",
|
||||
sc(
|
||||
boolean(),
|
||||
#{
|
||||
default => false,
|
||||
desc => ?DESC("server_ssl_opts_schema_enable_ocsp_stapling")
|
||||
}
|
||||
)},
|
||||
{"responder_url",
|
||||
sc(
|
||||
url(),
|
||||
#{
|
||||
required => false,
|
||||
desc => ?DESC("server_ssl_opts_schema_ocsp_responder_url")
|
||||
}
|
||||
)},
|
||||
{"issuer_pem",
|
||||
sc(
|
||||
binary(),
|
||||
#{
|
||||
required => false,
|
||||
desc => ?DESC("server_ssl_opts_schema_ocsp_issuer_pem")
|
||||
}
|
||||
)},
|
||||
{"refresh_interval",
|
||||
sc(
|
||||
duration(),
|
||||
#{
|
||||
default => <<"5m">>,
|
||||
desc => ?DESC("server_ssl_opts_schema_ocsp_refresh_interval")
|
||||
}
|
||||
)},
|
||||
{"refresh_http_timeout",
|
||||
sc(
|
||||
duration(),
|
||||
#{
|
||||
default => <<"15s">>,
|
||||
desc => ?DESC("server_ssl_opts_schema_ocsp_refresh_http_timeout")
|
||||
}
|
||||
)}
|
||||
];
|
||||
fields("deflate_opts") ->
|
||||
[
|
||||
{"level",
|
||||
|
@ -2017,6 +2064,8 @@ desc("trace") ->
|
|||
"Real-time filtering logs for the ClientID or Topic or IP for debugging.";
|
||||
desc("shared_subscription_group") ->
|
||||
"Per group dispatch strategy for shared subscription";
|
||||
desc("ocsp") ->
|
||||
"Per listener OCSP Stapling configuration.";
|
||||
desc(_) ->
|
||||
undefined.
|
||||
|
||||
|
@ -2199,14 +2248,62 @@ server_ssl_opts_schema(Defaults, IsRanchListener) ->
|
|||
)}
|
||||
] ++
|
||||
[
|
||||
Field
|
||||
|| not IsRanchListener,
|
||||
Field <- [
|
||||
{"gc_after_handshake",
|
||||
sc(boolean(), #{
|
||||
default => false,
|
||||
desc => ?DESC(server_ssl_opts_schema_gc_after_handshake)
|
||||
})}
|
||||
|| not IsRanchListener
|
||||
})},
|
||||
{"ocsp",
|
||||
sc(
|
||||
ref("ocsp"),
|
||||
#{
|
||||
required => false,
|
||||
validator => fun ocsp_inner_validator/1
|
||||
}
|
||||
)}
|
||||
]
|
||||
].
|
||||
|
||||
mqtt_ssl_listener_ssl_options_validator(Conf) ->
|
||||
Checks = [
|
||||
fun ocsp_outer_validator/1
|
||||
],
|
||||
case emqx_misc:pipeline(Checks, Conf, not_used) of
|
||||
{ok, _, _} ->
|
||||
ok;
|
||||
{error, Reason, _NotUsed} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
ocsp_outer_validator(#{<<"ocsp">> := #{<<"enable_ocsp_stapling">> := true}} = Conf) ->
|
||||
%% outer mqtt listener ssl server config
|
||||
ServerCertPemPath = maps:get(<<"certfile">>, Conf, undefined),
|
||||
case ServerCertPemPath of
|
||||
undefined ->
|
||||
{error, "Server certificate must be defined when using OCSP stapling"};
|
||||
_ ->
|
||||
%% check if issuer pem is readable and/or valid?
|
||||
ok
|
||||
end;
|
||||
ocsp_outer_validator(_Conf) ->
|
||||
ok.
|
||||
|
||||
ocsp_inner_validator(#{enable_ocsp_stapling := _} = Conf) ->
|
||||
ocsp_inner_validator(emqx_map_lib:binary_key_map(Conf));
|
||||
ocsp_inner_validator(#{<<"enable_ocsp_stapling">> := false} = _Conf) ->
|
||||
ok;
|
||||
ocsp_inner_validator(#{<<"enable_ocsp_stapling">> := true} = Conf) ->
|
||||
assert_required_field(
|
||||
Conf, <<"responder_url">>, "The responder URL is required for OCSP stapling"
|
||||
),
|
||||
assert_required_field(
|
||||
Conf, <<"issuer_pem">>, "The issuer PEM path is required for OCSP stapling"
|
||||
),
|
||||
ok.
|
||||
|
||||
%% @doc Make schema for SSL client.
|
||||
-spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema().
|
||||
client_ssl_opts_schema(Defaults) ->
|
||||
|
@ -2408,6 +2505,15 @@ to_comma_separated_binary(Str) ->
|
|||
to_comma_separated_atoms(Str) ->
|
||||
{ok, lists:map(fun to_atom/1, string:tokens(Str, ", "))}.
|
||||
|
||||
to_url(Str) ->
|
||||
case emqx_http_lib:uri_parse(Str) of
|
||||
{ok, URIMap} ->
|
||||
URIString = emqx_http_lib:normalize(URIMap),
|
||||
{ok, iolist_to_binary(URIString)};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
to_bar_separated_list(Str) ->
|
||||
{ok, string:tokens(Str, "| ")}.
|
||||
|
||||
|
@ -2865,3 +2971,11 @@ is_quic_ssl_opts(Name) ->
|
|||
%% , "handshake_timeout"
|
||||
%% , "gc_after_handshake"
|
||||
]).
|
||||
|
||||
assert_required_field(Conf, Key, ErrorMessage) ->
|
||||
case maps:get(Key, Conf, undefined) of
|
||||
undefined ->
|
||||
throw(ErrorMessage);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
|
|
@ -47,8 +47,18 @@
|
|||
-define(IS_TRUE(Val), ((Val =:= true) orelse (Val =:= <<"true">>))).
|
||||
-define(IS_FALSE(Val), ((Val =:= false) orelse (Val =:= <<"false">>))).
|
||||
|
||||
-define(SSL_FILE_OPT_NAMES, [<<"keyfile">>, <<"certfile">>, <<"cacertfile">>]).
|
||||
-define(SSL_FILE_OPT_NAMES_A, [keyfile, certfile, cacertfile]).
|
||||
-define(SSL_FILE_OPT_PATHS, [
|
||||
[<<"keyfile">>],
|
||||
[<<"certfile">>],
|
||||
[<<"cacertfile">>],
|
||||
[<<"ocsp">>, <<"issuer_pem">>]
|
||||
]).
|
||||
-define(SSL_FILE_OPT_PATHS_A, [
|
||||
[keyfile],
|
||||
[certfile],
|
||||
[cacertfile],
|
||||
[ocsp, issuer_pem]
|
||||
]).
|
||||
|
||||
%% non-empty string
|
||||
-define(IS_STRING(L), (is_list(L) andalso L =/= [] andalso is_integer(hd(L)))).
|
||||
|
@ -298,20 +308,20 @@ ensure_ssl_files(Dir, SSL, Opts) ->
|
|||
RequiredKeys = maps:get(required_keys, Opts, []),
|
||||
case ensure_ssl_file_key(SSL, RequiredKeys) of
|
||||
ok ->
|
||||
Keys = ?SSL_FILE_OPT_NAMES ++ ?SSL_FILE_OPT_NAMES_A,
|
||||
ensure_ssl_files(Dir, SSL, Keys, Opts);
|
||||
KeyPaths = ?SSL_FILE_OPT_PATHS ++ ?SSL_FILE_OPT_PATHS_A,
|
||||
ensure_ssl_files(Dir, SSL, KeyPaths, Opts);
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
ensure_ssl_files(_Dir, SSL, [], _Opts) ->
|
||||
{ok, SSL};
|
||||
ensure_ssl_files(Dir, SSL, [Key | Keys], Opts) ->
|
||||
case ensure_ssl_file(Dir, Key, SSL, maps:get(Key, SSL, undefined), Opts) of
|
||||
ensure_ssl_files(Dir, SSL, [KeyPath | KeyPaths], Opts) ->
|
||||
case ensure_ssl_file(Dir, KeyPath, SSL, emqx_map_lib:deep_get(KeyPath, SSL, undefined), Opts) of
|
||||
{ok, NewSSL} ->
|
||||
ensure_ssl_files(Dir, NewSSL, Keys, Opts);
|
||||
ensure_ssl_files(Dir, NewSSL, KeyPaths, Opts);
|
||||
{error, Reason} ->
|
||||
{error, Reason#{which_options => [Key]}}
|
||||
{error, Reason#{which_options => [KeyPath]}}
|
||||
end.
|
||||
|
||||
%% @doc Compare old and new config, delete the ones in old but not in new.
|
||||
|
@ -321,12 +331,12 @@ delete_ssl_files(Dir, NewOpts0, OldOpts0) ->
|
|||
{ok, NewOpts} = ensure_ssl_files(Dir, NewOpts0, #{dry_run => DryRun}),
|
||||
{ok, OldOpts} = ensure_ssl_files(Dir, OldOpts0, #{dry_run => DryRun}),
|
||||
Get = fun
|
||||
(_K, undefined) -> undefined;
|
||||
(K, Opts) -> maps:get(K, Opts, undefined)
|
||||
(_KP, undefined) -> undefined;
|
||||
(KP, Opts) -> emqx_map_lib:deep_get(KP, Opts, undefined)
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(Key) -> delete_old_file(Get(Key, NewOpts), Get(Key, OldOpts)) end,
|
||||
?SSL_FILE_OPT_NAMES ++ ?SSL_FILE_OPT_NAMES_A
|
||||
fun(KeyPath) -> delete_old_file(Get(KeyPath, NewOpts), Get(KeyPath, OldOpts)) end,
|
||||
?SSL_FILE_OPT_PATHS ++ ?SSL_FILE_OPT_PATHS_A
|
||||
),
|
||||
%% try to delete the dir if it is empty
|
||||
_ = file:del_dir(pem_dir(Dir)),
|
||||
|
@ -346,29 +356,33 @@ delete_old_file(_New, Old) ->
|
|||
?SLOG(error, #{msg => "failed_to_delete_ssl_file", file_path => Old, reason => Reason})
|
||||
end.
|
||||
|
||||
ensure_ssl_file(_Dir, _Key, SSL, undefined, _Opts) ->
|
||||
ensure_ssl_file(_Dir, _KeyPath, SSL, undefined, _Opts) ->
|
||||
{ok, SSL};
|
||||
ensure_ssl_file(Dir, Key, SSL, MaybePem, Opts) ->
|
||||
ensure_ssl_file(Dir, KeyPath, SSL, MaybePem, Opts) ->
|
||||
case is_valid_string(MaybePem) of
|
||||
true ->
|
||||
DryRun = maps:get(dry_run, Opts, false),
|
||||
do_ensure_ssl_file(Dir, Key, SSL, MaybePem, DryRun);
|
||||
do_ensure_ssl_file(Dir, KeyPath, SSL, MaybePem, DryRun);
|
||||
false ->
|
||||
{error, #{reason => invalid_file_path_or_pem_string}}
|
||||
end.
|
||||
|
||||
do_ensure_ssl_file(Dir, Key, SSL, MaybePem, DryRun) ->
|
||||
do_ensure_ssl_file(Dir, KeyPath, SSL, MaybePem, DryRun) ->
|
||||
case is_pem(MaybePem) of
|
||||
true ->
|
||||
case save_pem_file(Dir, Key, MaybePem, DryRun) of
|
||||
{ok, Path} -> {ok, SSL#{Key => Path}};
|
||||
{error, Reason} -> {error, Reason}
|
||||
case save_pem_file(Dir, KeyPath, MaybePem, DryRun) of
|
||||
{ok, Path} ->
|
||||
NewSSL = emqx_map_lib:deep_put(KeyPath, SSL, Path),
|
||||
{ok, NewSSL};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
false ->
|
||||
case is_valid_pem_file(MaybePem) of
|
||||
true ->
|
||||
{ok, SSL};
|
||||
{error, enoent} when DryRun -> {ok, SSL};
|
||||
{error, enoent} when DryRun ->
|
||||
{ok, SSL};
|
||||
{error, Reason} ->
|
||||
{error, #{
|
||||
pem_check => invalid_pem,
|
||||
|
@ -398,8 +412,8 @@ is_pem(MaybePem) ->
|
|||
%% To make it simple, the file is always overwritten.
|
||||
%% Also a potentially half-written PEM file (e.g. due to power outage)
|
||||
%% can be corrected with an overwrite.
|
||||
save_pem_file(Dir, Key, Pem, DryRun) ->
|
||||
Path = pem_file_name(Dir, Key, Pem),
|
||||
save_pem_file(Dir, KeyPath, Pem, DryRun) ->
|
||||
Path = pem_file_name(Dir, KeyPath, Pem),
|
||||
case filelib:ensure_dir(Path) of
|
||||
ok when DryRun ->
|
||||
{ok, Path};
|
||||
|
@ -422,11 +436,14 @@ is_generated_file(Filename) ->
|
|||
_ -> false
|
||||
end.
|
||||
|
||||
pem_file_name(Dir, Key, Pem) ->
|
||||
pem_file_name(Dir, KeyPath, Pem) ->
|
||||
<<CK:8/binary, _/binary>> = crypto:hash(md5, Pem),
|
||||
Suffix = hex_str(CK),
|
||||
FileName = binary:replace(ensure_bin(Key), <<"file">>, <<"-", Suffix/binary>>),
|
||||
filename:join([pem_dir(Dir), FileName]).
|
||||
Segments = lists:map(fun ensure_bin/1, KeyPath),
|
||||
Filename0 = iolist_to_binary(lists:join(<<"_">>, Segments)),
|
||||
Filename1 = binary:replace(Filename0, <<"file">>, <<>>),
|
||||
Filename = <<Filename1/binary, "-", Suffix/binary>>,
|
||||
filename:join([pem_dir(Dir), Filename]).
|
||||
|
||||
pem_dir(Dir) ->
|
||||
filename:join([emqx:mutable_certs_dir(), Dir]).
|
||||
|
@ -465,24 +482,26 @@ is_valid_pem_file(Path) ->
|
|||
%% so they are forced to upload a cert file, or use an existing file path.
|
||||
-spec drop_invalid_certs(map()) -> map().
|
||||
drop_invalid_certs(#{enable := False} = SSL) when ?IS_FALSE(False) ->
|
||||
maps:without(?SSL_FILE_OPT_NAMES_A, SSL);
|
||||
lists:foldl(fun emqx_map_lib:deep_remove/2, SSL, ?SSL_FILE_OPT_PATHS_A);
|
||||
drop_invalid_certs(#{<<"enable">> := False} = SSL) when ?IS_FALSE(False) ->
|
||||
maps:without(?SSL_FILE_OPT_NAMES, SSL);
|
||||
lists:foldl(fun emqx_map_lib:deep_remove/2, SSL, ?SSL_FILE_OPT_PATHS);
|
||||
drop_invalid_certs(#{enable := True} = SSL) when ?IS_TRUE(True) ->
|
||||
do_drop_invalid_certs(?SSL_FILE_OPT_NAMES_A, SSL);
|
||||
do_drop_invalid_certs(?SSL_FILE_OPT_PATHS_A, SSL);
|
||||
drop_invalid_certs(#{<<"enable">> := True} = SSL) when ?IS_TRUE(True) ->
|
||||
do_drop_invalid_certs(?SSL_FILE_OPT_NAMES, SSL).
|
||||
do_drop_invalid_certs(?SSL_FILE_OPT_PATHS, SSL).
|
||||
|
||||
do_drop_invalid_certs([], SSL) ->
|
||||
SSL;
|
||||
do_drop_invalid_certs([Key | Keys], SSL) ->
|
||||
case maps:get(Key, SSL, undefined) of
|
||||
do_drop_invalid_certs([KeyPath | KeyPaths], SSL) ->
|
||||
case emqx_map_lib:deep_get(KeyPath, SSL, undefined) of
|
||||
undefined ->
|
||||
do_drop_invalid_certs(Keys, SSL);
|
||||
do_drop_invalid_certs(KeyPaths, SSL);
|
||||
PemOrPath ->
|
||||
case is_pem(PemOrPath) orelse is_valid_pem_file(PemOrPath) of
|
||||
true -> do_drop_invalid_certs(Keys, SSL);
|
||||
{error, _} -> do_drop_invalid_certs(Keys, maps:without([Key], SSL))
|
||||
true ->
|
||||
do_drop_invalid_certs(KeyPaths, SSL);
|
||||
{error, _} ->
|
||||
do_drop_invalid_certs(KeyPaths, emqx_map_lib:deep_remove(KeyPath, SSL))
|
||||
end
|
||||
end.
|
||||
|
||||
|
@ -565,9 +584,10 @@ ensure_bin(A) when is_atom(A) -> atom_to_binary(A, utf8).
|
|||
|
||||
ensure_ssl_file_key(_SSL, []) ->
|
||||
ok;
|
||||
ensure_ssl_file_key(SSL, RequiredKeys) ->
|
||||
Filter = fun(Key) -> not maps:is_key(Key, SSL) end,
|
||||
case lists:filter(Filter, RequiredKeys) of
|
||||
ensure_ssl_file_key(SSL, RequiredKeyPaths) ->
|
||||
NotFoundRef = make_ref(),
|
||||
Filter = fun(KeyPath) -> NotFoundRef =:= emqx_map_lib:deep_get(KeyPath, SSL, NotFoundRef) end,
|
||||
case lists:filter(Filter, RequiredKeyPaths) of
|
||||
[] -> ok;
|
||||
Miss -> {error, #{reason => ssl_file_option_not_found, which_options => Miss}}
|
||||
end.
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
all() -> emqx_common_test_helpers:all(?MODULE).
|
||||
|
||||
init_per_suite(Config) ->
|
||||
emqx_common_test_helpers:boot_modules(all),
|
||||
emqx_common_test_helpers:start_apps([]),
|
||||
Config.
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
-module(emqx_common_test_helpers).
|
||||
|
||||
-include("emqx_authentication.hrl").
|
||||
|
||||
-type special_config_handler() :: fun().
|
||||
|
||||
-type apps() :: list(atom()).
|
||||
|
@ -27,6 +29,7 @@
|
|||
boot_modules/1,
|
||||
start_apps/1,
|
||||
start_apps/2,
|
||||
start_apps/3,
|
||||
stop_apps/1,
|
||||
reload/2,
|
||||
app_path/2,
|
||||
|
@ -34,7 +37,8 @@
|
|||
deps_path/2,
|
||||
flush/0,
|
||||
flush/1,
|
||||
render_and_load_app_config/1
|
||||
render_and_load_app_config/1,
|
||||
render_and_load_app_config/2
|
||||
]).
|
||||
|
||||
-export([
|
||||
|
@ -183,17 +187,21 @@ start_apps(Apps) ->
|
|||
application:set_env(system_monitor, db_hostname, ""),
|
||||
ok
|
||||
end,
|
||||
start_apps(Apps, DefaultHandler).
|
||||
start_apps(Apps, DefaultHandler, #{}).
|
||||
|
||||
-spec start_apps(Apps :: apps(), Handler :: special_config_handler()) -> ok.
|
||||
start_apps(Apps, SpecAppConfig) when is_function(SpecAppConfig) ->
|
||||
start_apps(Apps, SpecAppConfig, #{}).
|
||||
|
||||
-spec start_apps(Apps :: apps(), Handler :: special_config_handler(), map()) -> ok.
|
||||
start_apps(Apps, SpecAppConfig, Opts) when is_function(SpecAppConfig) ->
|
||||
%% Load all application code to beam vm first
|
||||
%% Because, minirest, ekka etc.. application will scan these modules
|
||||
lists:foreach(fun load/1, [emqx | Apps]),
|
||||
ok = start_ekka(),
|
||||
mnesia:clear_table(emqx_admin),
|
||||
ok = emqx_ratelimiter_SUITE:load_conf(),
|
||||
lists:foreach(fun(App) -> start_app(App, SpecAppConfig) end, [emqx | Apps]).
|
||||
lists:foreach(fun(App) -> start_app(App, SpecAppConfig, Opts) end, [emqx | Apps]).
|
||||
|
||||
load(App) ->
|
||||
case application:load(App) of
|
||||
|
@ -203,27 +211,31 @@ load(App) ->
|
|||
end.
|
||||
|
||||
render_and_load_app_config(App) ->
|
||||
render_and_load_app_config(App, #{}).
|
||||
|
||||
render_and_load_app_config(App, Opts) ->
|
||||
load(App),
|
||||
Schema = app_schema(App),
|
||||
Conf = app_path(App, filename:join(["etc", app_conf_file(App)])),
|
||||
ConfFilePath = maps:get(conf_file_path, Opts, filename:join(["etc", app_conf_file(App)])),
|
||||
Conf = app_path(App, ConfFilePath),
|
||||
try
|
||||
do_render_app_config(App, Schema, Conf)
|
||||
do_render_app_config(App, Schema, Conf, Opts)
|
||||
catch
|
||||
throw:E:St ->
|
||||
%% turn throw into error
|
||||
error({Conf, E, St})
|
||||
end.
|
||||
|
||||
do_render_app_config(App, Schema, ConfigFile) ->
|
||||
Vars = mustache_vars(App),
|
||||
do_render_app_config(App, Schema, ConfigFile, Opts) ->
|
||||
Vars = mustache_vars(App, Opts),
|
||||
RenderedConfigFile = render_config_file(ConfigFile, Vars),
|
||||
read_schema_configs(Schema, RenderedConfigFile),
|
||||
force_set_config_file_paths(App, [RenderedConfigFile]),
|
||||
copy_certs(App, RenderedConfigFile),
|
||||
ok.
|
||||
|
||||
start_app(App, SpecAppConfig) ->
|
||||
render_and_load_app_config(App),
|
||||
start_app(App, SpecAppConfig, Opts) ->
|
||||
render_and_load_app_config(App, Opts),
|
||||
SpecAppConfig(App),
|
||||
case application:ensure_all_started(App) of
|
||||
{ok, _} ->
|
||||
|
@ -246,12 +258,13 @@ app_schema(App) ->
|
|||
no_schema
|
||||
end.
|
||||
|
||||
mustache_vars(App) ->
|
||||
mustache_vars(App, Opts) ->
|
||||
ExtraMustacheVars = maps:get(extra_mustache_vars, Opts, []),
|
||||
[
|
||||
{platform_data_dir, app_path(App, "data")},
|
||||
{platform_etc_dir, app_path(App, "etc")},
|
||||
{platform_log_dir, app_path(App, "log")}
|
||||
].
|
||||
] ++ ExtraMustacheVars.
|
||||
|
||||
render_config_file(ConfigFile, Vars0) ->
|
||||
Temp =
|
||||
|
@ -283,6 +296,14 @@ generate_config(SchemaModule, ConfigFile) when is_atom(SchemaModule) ->
|
|||
-spec stop_apps(list()) -> ok.
|
||||
stop_apps(Apps) ->
|
||||
[application:stop(App) || App <- Apps ++ [emqx, ekka, mria, mnesia]],
|
||||
%% to avoid inter-suite flakiness
|
||||
application:unset_env(emqx, init_config_load_done),
|
||||
persistent_term:erase(?EMQX_AUTHENTICATION_SCHEMA_MODULE_PT_KEY),
|
||||
emqx_config:erase_schema_mod_and_names(),
|
||||
ok = emqx_config:delete_override_conf_files(),
|
||||
application:unset_env(emqx, local_override_conf_file),
|
||||
application:unset_env(emqx, cluster_override_conf_file),
|
||||
application:unset_env(gen_rpc, port_discovery),
|
||||
ok.
|
||||
|
||||
proj_root() ->
|
||||
|
@ -327,7 +348,7 @@ safe_relative_path_2(Path) ->
|
|||
-spec reload(App :: atom(), SpecAppConfig :: special_config_handler()) -> ok.
|
||||
reload(App, SpecAppConfigHandler) ->
|
||||
application:stop(App),
|
||||
start_app(App, SpecAppConfigHandler),
|
||||
start_app(App, SpecAppConfigHandler, #{}),
|
||||
application:start(App).
|
||||
|
||||
ensure_mnesia_stopped() ->
|
||||
|
@ -469,7 +490,7 @@ is_all_tcp_servers_available(Servers) ->
|
|||
{_, []} ->
|
||||
true;
|
||||
{_, Unavail} ->
|
||||
ct:print("Unavailable servers: ~p", [Unavail]),
|
||||
ct:pal("Unavailable servers: ~p", [Unavail]),
|
||||
false
|
||||
end.
|
||||
|
||||
|
|
|
@ -0,0 +1,944 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_ocsp_cache_SUITE).
|
||||
|
||||
-compile(export_all).
|
||||
-compile(nowarn_export_all).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||
|
||||
-include_lib("ssl/src/ssl_handshake.hrl").
|
||||
|
||||
-define(CACHE_TAB, emqx_ocsp_cache).
|
||||
|
||||
all() ->
|
||||
[{group, openssl}] ++ tests().
|
||||
|
||||
tests() ->
|
||||
emqx_common_test_helpers:all(?MODULE) -- openssl_tests().
|
||||
|
||||
openssl_tests() ->
|
||||
[t_openssl_client].
|
||||
|
||||
groups() ->
|
||||
OpensslTests = openssl_tests(),
|
||||
[
|
||||
{openssl, [
|
||||
{group, tls12},
|
||||
{group, tls13}
|
||||
]},
|
||||
{tls12, [
|
||||
{group, with_status_request},
|
||||
{group, without_status_request}
|
||||
]},
|
||||
{tls13, [
|
||||
{group, with_status_request},
|
||||
{group, without_status_request}
|
||||
]},
|
||||
{with_status_request, [], OpensslTests},
|
||||
{without_status_request, [], OpensslTests}
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
application:load(emqx),
|
||||
emqx_config:save_schema_mod_and_names(emqx_schema),
|
||||
emqx_common_test_helpers:boot_modules(all),
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
ok.
|
||||
|
||||
init_per_group(tls12, Config) ->
|
||||
[{tls_vsn, "-tls1_2"} | Config];
|
||||
init_per_group(tls13, Config) ->
|
||||
[{tls_vsn, "-tls1_3"} | Config];
|
||||
init_per_group(with_status_request, Config) ->
|
||||
[{status_request, true} | Config];
|
||||
init_per_group(without_status_request, Config) ->
|
||||
[{status_request, false} | Config];
|
||||
init_per_group(_Group, Config) ->
|
||||
Config.
|
||||
|
||||
end_per_group(_Group, _Config) ->
|
||||
ok.
|
||||
|
||||
init_per_testcase(t_openssl_client, Config) ->
|
||||
ct:timetrap({seconds, 30}),
|
||||
DataDir = ?config(data_dir, Config),
|
||||
Handler = fun(_) -> ok end,
|
||||
{OCSPResponderPort, OCSPOSPid} = setup_openssl_ocsp(Config),
|
||||
ConfFilePath = filename:join([DataDir, "openssl_listeners.conf"]),
|
||||
emqx_common_test_helpers:start_apps(
|
||||
[],
|
||||
Handler,
|
||||
#{
|
||||
extra_mustache_vars => [{test_data_dir, DataDir}],
|
||||
conf_file_path => ConfFilePath
|
||||
}
|
||||
),
|
||||
ct:sleep(1_000),
|
||||
[
|
||||
{ocsp_responder_port, OCSPResponderPort},
|
||||
{ocsp_responder_os_pid, OCSPOSPid}
|
||||
| Config
|
||||
];
|
||||
init_per_testcase(TestCase, Config) when
|
||||
TestCase =:= t_update_listener;
|
||||
TestCase =:= t_validations
|
||||
->
|
||||
%% when running emqx standalone tests, we can't use those
|
||||
%% features.
|
||||
case does_module_exist(emqx_mgmt_api_test_util) of
|
||||
true ->
|
||||
ct:timetrap({seconds, 30}),
|
||||
%% start the listener with the default (non-ocsp) config
|
||||
TestPid = self(),
|
||||
ok = meck:new(emqx_ocsp_cache, [non_strict, passthrough, no_history, no_link]),
|
||||
meck:expect(
|
||||
emqx_ocsp_cache,
|
||||
http_get,
|
||||
fun(URL, _HTTPTimeout) ->
|
||||
ct:pal("ocsp http request ~p", [URL]),
|
||||
TestPid ! {http_get, URL},
|
||||
{ok, {{"HTTP/1.0", 200, 'OK'}, [], <<"ocsp response">>}}
|
||||
end
|
||||
),
|
||||
emqx_mgmt_api_test_util:init_suite([emqx_conf]),
|
||||
snabbkaffe:start_trace(),
|
||||
Config;
|
||||
false ->
|
||||
[{skip_does_not_apply, true} | Config]
|
||||
end;
|
||||
init_per_testcase(t_ocsp_responder_error_responses, Config) ->
|
||||
ct:timetrap({seconds, 30}),
|
||||
TestPid = self(),
|
||||
ok = meck:new(emqx_ocsp_cache, [non_strict, passthrough, no_history, no_link]),
|
||||
meck:expect(
|
||||
emqx_ocsp_cache,
|
||||
http_get,
|
||||
fun(URL, _HTTPTimeout) ->
|
||||
ct:pal("ocsp http request ~p", [URL]),
|
||||
TestPid ! {http_get, URL},
|
||||
persistent_term:get({?MODULE, http_response})
|
||||
end
|
||||
),
|
||||
DataDir = ?config(data_dir, Config),
|
||||
Type = ssl,
|
||||
Name = test_ocsp,
|
||||
ListenerOpts = #{
|
||||
ssl_options =>
|
||||
#{
|
||||
certfile => filename:join(DataDir, "server.pem"),
|
||||
ocsp => #{
|
||||
enable_ocsp_stapling => true,
|
||||
responder_url => <<"http://localhost:9877/">>,
|
||||
issuer_pem => filename:join(DataDir, "ocsp-issuer.pem"),
|
||||
refresh_http_timeout => 15_000,
|
||||
refresh_interval => 1_000
|
||||
}
|
||||
}
|
||||
},
|
||||
Conf = #{listeners => #{Type => #{Name => ListenerOpts}}},
|
||||
ConfBin = emqx_map_lib:binary_key_map(Conf),
|
||||
hocon_tconf:check_plain(emqx_schema, ConfBin, #{required => false, atom_keys => false}),
|
||||
emqx_config:put_listener_conf(Type, Name, [], ListenerOpts),
|
||||
snabbkaffe:start_trace(),
|
||||
_Heir = spawn_dummy_heir(),
|
||||
{ok, CachePid} = emqx_ocsp_cache:start_link(),
|
||||
[
|
||||
{cache_pid, CachePid}
|
||||
| Config
|
||||
];
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
ct:timetrap({seconds, 10}),
|
||||
TestPid = self(),
|
||||
ok = meck:new(emqx_ocsp_cache, [non_strict, passthrough, no_history, no_link]),
|
||||
meck:expect(
|
||||
emqx_ocsp_cache,
|
||||
http_get,
|
||||
fun(URL, _HTTPTimeout) ->
|
||||
TestPid ! {http_get, URL},
|
||||
{ok, {{"HTTP/1.0", 200, 'OK'}, [], <<"ocsp response">>}}
|
||||
end
|
||||
),
|
||||
_Heir = spawn_dummy_heir(),
|
||||
{ok, CachePid} = emqx_ocsp_cache:start_link(),
|
||||
DataDir = ?config(data_dir, Config),
|
||||
Type = ssl,
|
||||
Name = test_ocsp,
|
||||
ListenerOpts = #{
|
||||
ssl_options =>
|
||||
#{
|
||||
certfile => filename:join(DataDir, "server.pem"),
|
||||
ocsp => #{
|
||||
enable_ocsp_stapling => true,
|
||||
responder_url => <<"http://localhost:9877/">>,
|
||||
issuer_pem => filename:join(DataDir, "ocsp-issuer.pem"),
|
||||
refresh_http_timeout => 15_000,
|
||||
refresh_interval => 1_000
|
||||
}
|
||||
}
|
||||
},
|
||||
Conf = #{listeners => #{Type => #{Name => ListenerOpts}}},
|
||||
ConfBin = emqx_map_lib:binary_key_map(Conf),
|
||||
hocon_tconf:check_plain(emqx_schema, ConfBin, #{required => false, atom_keys => false}),
|
||||
emqx_config:put_listener_conf(Type, Name, [], ListenerOpts),
|
||||
snabbkaffe:start_trace(),
|
||||
[
|
||||
{cache_pid, CachePid}
|
||||
| Config
|
||||
].
|
||||
|
||||
end_per_testcase(t_openssl_client, Config) ->
|
||||
OCSPResponderOSPid = ?config(ocsp_responder_os_pid, Config),
|
||||
catch kill_pid(OCSPResponderOSPid),
|
||||
emqx_common_test_helpers:stop_apps([]),
|
||||
ok;
|
||||
end_per_testcase(TestCase, Config) when
|
||||
TestCase =:= t_update_listener;
|
||||
TestCase =:= t_validations
|
||||
->
|
||||
Skip = proplists:get_bool(skip_does_not_apply, Config),
|
||||
case Skip of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
emqx_mgmt_api_test_util:end_suite([emqx_conf]),
|
||||
meck:unload([emqx_ocsp_cache]),
|
||||
ok
|
||||
end;
|
||||
end_per_testcase(t_ocsp_responder_error_responses, Config) ->
|
||||
CachePid = ?config(cache_pid, Config),
|
||||
catch gen_server:stop(CachePid),
|
||||
meck:unload([emqx_ocsp_cache]),
|
||||
persistent_term:erase({?MODULE, http_response}),
|
||||
ok;
|
||||
end_per_testcase(_TestCase, Config) ->
|
||||
CachePid = ?config(cache_pid, Config),
|
||||
catch gen_server:stop(CachePid),
|
||||
meck:unload([emqx_ocsp_cache]),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Helper functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% The real cache makes `emqx_kernel_sup' the heir to its ETS table.
|
||||
%% In some tests, we don't start the full supervision tree, so we need
|
||||
%% this dummy process.
|
||||
spawn_dummy_heir() ->
|
||||
spawn_link(fun() ->
|
||||
true = register(emqx_kernel_sup, self()),
|
||||
receive
|
||||
stop -> ok
|
||||
end
|
||||
end).
|
||||
|
||||
does_module_exist(Mod) ->
|
||||
case erlang:module_loaded(Mod) of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
case code:ensure_loaded(Mod) of
|
||||
ok ->
|
||||
true;
|
||||
{module, Mod} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
assert_no_http_get() ->
|
||||
receive
|
||||
{http_get, _URL} ->
|
||||
error(should_be_cached)
|
||||
after 0 ->
|
||||
ok
|
||||
end.
|
||||
|
||||
assert_http_get(N) ->
|
||||
assert_http_get(N, 0).
|
||||
|
||||
assert_http_get(0, _Timeout) ->
|
||||
ok;
|
||||
assert_http_get(N, Timeout) when N > 0 ->
|
||||
receive
|
||||
{http_get, URL} ->
|
||||
?assertMatch(<<"http://localhost:9877/", _Request64/binary>>, URL),
|
||||
ok
|
||||
after Timeout ->
|
||||
error({no_http_get, #{mailbox => process_info(self(), messages)}})
|
||||
end,
|
||||
assert_http_get(N - 1, Timeout).
|
||||
|
||||
openssl_client_command(TLSVsn, RequestStatus, Config) ->
|
||||
DataDir = ?config(data_dir, Config),
|
||||
ClientCert = filename:join([DataDir, "client.pem"]),
|
||||
ClientKey = filename:join([DataDir, "client.key"]),
|
||||
Cacert = filename:join([DataDir, "ca.pem"]),
|
||||
Openssl = os:find_executable("openssl"),
|
||||
StatusOpt =
|
||||
case RequestStatus of
|
||||
true -> ["-status"];
|
||||
false -> []
|
||||
end,
|
||||
[
|
||||
Openssl,
|
||||
"s_client",
|
||||
"-connect",
|
||||
"localhost:8883",
|
||||
%% needed to trigger `sni_fun'
|
||||
"-servername",
|
||||
"localhost",
|
||||
TLSVsn,
|
||||
"-CAfile",
|
||||
Cacert,
|
||||
"-cert",
|
||||
ClientCert,
|
||||
"-key",
|
||||
ClientKey
|
||||
] ++ StatusOpt.
|
||||
|
||||
run_openssl_client(TLSVsn, RequestStatus, Config) ->
|
||||
Command0 = openssl_client_command(TLSVsn, RequestStatus, Config),
|
||||
Command = lists:flatten(lists:join(" ", Command0)),
|
||||
os:cmd(Command).
|
||||
|
||||
%% fixme: for some reason, the port program doesn't return any output
|
||||
%% when running in OTP 25 using `open_port`, but the `os:cmd` version
|
||||
%% works fine.
|
||||
%% the `open_port' version works fine in OTP 24 for some reason.
|
||||
spawn_openssl_client(TLSVsn, RequestStatus, Config) ->
|
||||
[Openssl | Args] = openssl_client_command(TLSVsn, RequestStatus, Config),
|
||||
open_port(
|
||||
{spawn_executable, Openssl},
|
||||
[
|
||||
{args, Args},
|
||||
binary,
|
||||
stderr_to_stdout
|
||||
]
|
||||
).
|
||||
|
||||
spawn_openssl_ocsp_responder(Config) ->
|
||||
DataDir = ?config(data_dir, Config),
|
||||
IssuerCert = filename:join([DataDir, "ocsp-issuer.pem"]),
|
||||
IssuerKey = filename:join([DataDir, "ocsp-issuer.key"]),
|
||||
Cacert = filename:join([DataDir, "ca.pem"]),
|
||||
Index = filename:join([DataDir, "index.txt"]),
|
||||
Openssl = os:find_executable("openssl"),
|
||||
open_port(
|
||||
{spawn_executable, Openssl},
|
||||
[
|
||||
{args, [
|
||||
"ocsp",
|
||||
"-ignore_err",
|
||||
"-port",
|
||||
"9877",
|
||||
"-CA",
|
||||
Cacert,
|
||||
"-rkey",
|
||||
IssuerKey,
|
||||
"-rsigner",
|
||||
IssuerCert,
|
||||
"-index",
|
||||
Index
|
||||
]},
|
||||
binary,
|
||||
stderr_to_stdout
|
||||
]
|
||||
).
|
||||
|
||||
kill_pid(OSPid) ->
|
||||
os:cmd("kill -9 " ++ integer_to_list(OSPid)).
|
||||
|
||||
test_ocsp_connection(TLSVsn, WithRequestStatus = true, Config) ->
|
||||
OCSPOutput = run_openssl_client(TLSVsn, WithRequestStatus, Config),
|
||||
?assertMatch(
|
||||
{match, _},
|
||||
re:run(OCSPOutput, "OCSP Response Status: successful"),
|
||||
#{mailbox => process_info(self(), messages)}
|
||||
),
|
||||
?assertMatch(
|
||||
{match, _},
|
||||
re:run(OCSPOutput, "Cert Status: good"),
|
||||
#{mailbox => process_info(self(), messages)}
|
||||
),
|
||||
ok;
|
||||
test_ocsp_connection(TLSVsn, WithRequestStatus = false, Config) ->
|
||||
OCSPOutput = run_openssl_client(TLSVsn, WithRequestStatus, Config),
|
||||
?assertMatch(
|
||||
nomatch,
|
||||
re:run(OCSPOutput, "Cert Status: good", [{capture, none}]),
|
||||
#{mailbox => process_info(self(), messages)}
|
||||
),
|
||||
ok.
|
||||
|
||||
ensure_port_open(Port) ->
|
||||
do_ensure_port_open(Port, 10).
|
||||
|
||||
do_ensure_port_open(Port, 0) ->
|
||||
error({port_not_open, Port});
|
||||
do_ensure_port_open(Port, N) when N > 0 ->
|
||||
Timeout = 1_000,
|
||||
case gen_tcp:connect("localhost", Port, [], Timeout) of
|
||||
{ok, Sock} ->
|
||||
gen_tcp:close(Sock),
|
||||
ok;
|
||||
{error, _} ->
|
||||
ct:sleep(500),
|
||||
do_ensure_port_open(Port, N - 1)
|
||||
end.
|
||||
|
||||
get_sni_fun(ListenerID) ->
|
||||
#{opts := Opts} = emqx_listeners:find_by_id(ListenerID),
|
||||
SSLOpts = proplists:get_value(ssl_options, Opts),
|
||||
proplists:get_value(sni_fun, SSLOpts).
|
||||
|
||||
openssl_version() ->
|
||||
Res0 = string:trim(os:cmd("openssl version"), trailing),
|
||||
[_, Res] = string:split(Res0, " "),
|
||||
{match, [Version]} = re:run(Res, "^([^ ]+)", [{capture, first, list}]),
|
||||
Version.
|
||||
|
||||
setup_openssl_ocsp(Config) ->
|
||||
OCSPResponderPort = spawn_openssl_ocsp_responder(Config),
|
||||
{os_pid, OCSPOSPid} = erlang:port_info(OCSPResponderPort, os_pid),
|
||||
%%%%%%%% Warning!!!
|
||||
%% Apparently, openssl 3.0.7 introduced a bug in the responder
|
||||
%% that makes it hang forever if one probes the port with
|
||||
%% `gen_tcp:open' / `gen_tcp:close'... Comment this out if
|
||||
%% openssl gets updated in CI or in your local machine.
|
||||
OpenSSLVersion = openssl_version(),
|
||||
ct:pal("openssl version: ~p", [OpenSSLVersion]),
|
||||
case OpenSSLVersion of
|
||||
"3." ++ _ ->
|
||||
%% hope that the responder has started...
|
||||
ok;
|
||||
_ ->
|
||||
ensure_port_open(9877)
|
||||
end,
|
||||
ct:sleep(1_000),
|
||||
{OCSPResponderPort, OCSPOSPid}.
|
||||
|
||||
request(Method, Url, QueryParams, Body) ->
|
||||
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
|
||||
Opts = #{return_all => true},
|
||||
case emqx_mgmt_api_test_util:request_api(Method, Url, QueryParams, AuthHeader, Body, Opts) of
|
||||
{ok, {Reason, Headers, BodyR}} ->
|
||||
{ok, {Reason, Headers, emqx_json:decode(BodyR, [return_maps])}};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
get_listener_via_api(ListenerId) ->
|
||||
Path = emqx_mgmt_api_test_util:api_path(["listeners", ListenerId]),
|
||||
request(get, Path, [], []).
|
||||
|
||||
update_listener_via_api(ListenerId, NewConfig) ->
|
||||
Path = emqx_mgmt_api_test_util:api_path(["listeners", ListenerId]),
|
||||
request(put, Path, [], NewConfig).
|
||||
|
||||
put_http_response(Response) ->
|
||||
persistent_term:put({?MODULE, http_response}, Response).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Test cases
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_request_ocsp_response(_Config) ->
|
||||
?check_trace(
|
||||
begin
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
%% not yet cached.
|
||||
?assertEqual([], ets:tab2list(?CACHE_TAB)),
|
||||
?assertEqual(
|
||||
{ok, <<"ocsp response">>},
|
||||
emqx_ocsp_cache:fetch_response(ListenerID)
|
||||
),
|
||||
assert_http_get(1),
|
||||
?assertMatch([{_, <<"ocsp response">>}], ets:tab2list(?CACHE_TAB)),
|
||||
%% already cached; should not perform request again.
|
||||
?assertEqual(
|
||||
{ok, <<"ocsp response">>},
|
||||
emqx_ocsp_cache:fetch_response(ListenerID)
|
||||
),
|
||||
assert_no_http_get(),
|
||||
ok
|
||||
end,
|
||||
fun(Trace) ->
|
||||
?assert(
|
||||
?strict_causality(
|
||||
#{?snk_kind := ocsp_cache_miss, listener_id := _ListenerID},
|
||||
#{?snk_kind := ocsp_http_fetch_and_cache, listener_id := _ListenerID},
|
||||
Trace
|
||||
)
|
||||
),
|
||||
?assertMatch(
|
||||
[_],
|
||||
?of_kind(ocsp_cache_miss, Trace)
|
||||
),
|
||||
?assertMatch(
|
||||
[_],
|
||||
?of_kind(ocsp_http_fetch_and_cache, Trace)
|
||||
),
|
||||
?assertMatch(
|
||||
[_],
|
||||
?of_kind(ocsp_cache_hit, Trace)
|
||||
),
|
||||
ok
|
||||
end
|
||||
).
|
||||
|
||||
t_request_ocsp_response_restart_cache(Config) ->
|
||||
process_flag(trap_exit, true),
|
||||
CachePid = ?config(cache_pid, Config),
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
?check_trace(
|
||||
begin
|
||||
[] = ets:tab2list(?CACHE_TAB),
|
||||
{ok, _} = emqx_ocsp_cache:fetch_response(ListenerID),
|
||||
?wait_async_action(
|
||||
begin
|
||||
Ref = monitor(process, CachePid),
|
||||
exit(CachePid, kill),
|
||||
receive
|
||||
{'DOWN', Ref, process, CachePid, killed} ->
|
||||
ok
|
||||
after 1_000 ->
|
||||
error(cache_not_killed)
|
||||
end,
|
||||
{ok, _} = emqx_ocsp_cache:start_link(),
|
||||
ok
|
||||
end,
|
||||
#{?snk_kind := ocsp_cache_init}
|
||||
),
|
||||
{ok, _} = emqx_ocsp_cache:fetch_response(ListenerID),
|
||||
ok
|
||||
end,
|
||||
fun(Trace) ->
|
||||
%% Only one fetch because the cache table was preserved by
|
||||
%% its heir ("emqx_kernel_sup").
|
||||
?assertMatch(
|
||||
[_],
|
||||
?of_kind(ocsp_http_fetch_and_cache, Trace)
|
||||
),
|
||||
assert_http_get(1),
|
||||
ok
|
||||
end
|
||||
).
|
||||
|
||||
t_request_ocsp_response_bad_http_status(_Config) ->
|
||||
TestPid = self(),
|
||||
meck:expect(
|
||||
emqx_ocsp_cache,
|
||||
http_get,
|
||||
fun(URL, _HTTPTimeout) ->
|
||||
TestPid ! {http_get, URL},
|
||||
{ok, {{"HTTP/1.0", 404, 'Not Found'}, [], <<"not found">>}}
|
||||
end
|
||||
),
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
%% not yet cached.
|
||||
?assertEqual([], ets:tab2list(?CACHE_TAB)),
|
||||
?assertEqual(
|
||||
error,
|
||||
emqx_ocsp_cache:fetch_response(ListenerID)
|
||||
),
|
||||
assert_http_get(1),
|
||||
?assertEqual([], ets:tab2list(?CACHE_TAB)),
|
||||
ok.
|
||||
|
||||
t_request_ocsp_response_timeout(_Config) ->
|
||||
TestPid = self(),
|
||||
meck:expect(
|
||||
emqx_ocsp_cache,
|
||||
http_get,
|
||||
fun(URL, _HTTPTimeout) ->
|
||||
TestPid ! {http_get, URL},
|
||||
{error, timeout}
|
||||
end
|
||||
),
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
%% not yet cached.
|
||||
?assertEqual([], ets:tab2list(?CACHE_TAB)),
|
||||
?assertEqual(
|
||||
error,
|
||||
emqx_ocsp_cache:fetch_response(ListenerID)
|
||||
),
|
||||
assert_http_get(1),
|
||||
?assertEqual([], ets:tab2list(?CACHE_TAB)),
|
||||
ok.
|
||||
|
||||
t_register_listener(_Config) ->
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
Conf = emqx_config:get_listener_conf(ssl, test_ocsp, []),
|
||||
%% should fetch and cache immediately
|
||||
{ok, {ok, _}} =
|
||||
?wait_async_action(
|
||||
emqx_ocsp_cache:register_listener(ListenerID, Conf),
|
||||
#{?snk_kind := ocsp_http_fetch_and_cache, listener_id := ListenerID}
|
||||
),
|
||||
assert_http_get(1),
|
||||
?assertMatch([{_, <<"ocsp response">>}], ets:tab2list(?CACHE_TAB)),
|
||||
ok.
|
||||
|
||||
t_register_twice(_Config) ->
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
Conf = emqx_config:get_listener_conf(ssl, test_ocsp, []),
|
||||
{ok, {ok, _}} =
|
||||
?wait_async_action(
|
||||
emqx_ocsp_cache:register_listener(ListenerID, Conf),
|
||||
#{?snk_kind := ocsp_http_fetch_and_cache, listener_id := ListenerID}
|
||||
),
|
||||
assert_http_get(1),
|
||||
?assertMatch([{_, <<"ocsp response">>}], ets:tab2list(?CACHE_TAB)),
|
||||
%% should have no problem in registering the same listener again.
|
||||
%% this prompts an immediate refresh.
|
||||
{ok, {ok, _}} =
|
||||
?wait_async_action(
|
||||
emqx_ocsp_cache:register_listener(ListenerID, Conf),
|
||||
#{?snk_kind := ocsp_http_fetch_and_cache, listener_id := ListenerID}
|
||||
),
|
||||
ok.
|
||||
|
||||
t_refresh_periodically(_Config) ->
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
Conf = emqx_config:get_listener_conf(ssl, test_ocsp, []),
|
||||
%% should refresh periodically
|
||||
{ok, SubRef} =
|
||||
snabbkaffe:subscribe(
|
||||
fun
|
||||
(#{?snk_kind := ocsp_http_fetch_and_cache, listener_id := ListenerID0}) ->
|
||||
ListenerID0 =:= ListenerID;
|
||||
(_) ->
|
||||
false
|
||||
end,
|
||||
_NEvents = 2,
|
||||
_Timeout = 10_000
|
||||
),
|
||||
ok = emqx_ocsp_cache:register_listener(ListenerID, Conf),
|
||||
?assertMatch({ok, [_, _]}, snabbkaffe:receive_events(SubRef)),
|
||||
assert_http_get(2),
|
||||
ok.
|
||||
|
||||
t_sni_fun_success(_Config) ->
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
ServerName = "localhost",
|
||||
?assertEqual(
|
||||
[
|
||||
{certificate_status, #certificate_status{
|
||||
status_type = ?CERTIFICATE_STATUS_TYPE_OCSP,
|
||||
response = <<"ocsp response">>
|
||||
}}
|
||||
],
|
||||
emqx_ocsp_cache:sni_fun(ServerName, ListenerID)
|
||||
),
|
||||
ok.
|
||||
|
||||
t_sni_fun_http_error(_Config) ->
|
||||
meck:expect(
|
||||
emqx_ocsp_cache,
|
||||
http_get,
|
||||
fun(_URL, _HTTPTimeout) ->
|
||||
{error, timeout}
|
||||
end
|
||||
),
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
ServerName = "localhost",
|
||||
?assertEqual(
|
||||
[],
|
||||
emqx_ocsp_cache:sni_fun(ServerName, ListenerID)
|
||||
),
|
||||
ok.
|
||||
|
||||
%% check that we can start with a non-ocsp stapling listener and
|
||||
%% restart it with the new ocsp config.
|
||||
t_update_listener(Config) ->
|
||||
case proplists:get_bool(skip_does_not_apply, Config) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
do_t_update_listener(Config)
|
||||
end.
|
||||
|
||||
do_t_update_listener(Config) ->
|
||||
DataDir = ?config(data_dir, Config),
|
||||
Keyfile = filename:join([DataDir, "server.key"]),
|
||||
Certfile = filename:join([DataDir, "server.pem"]),
|
||||
Cacertfile = filename:join([DataDir, "ca.pem"]),
|
||||
IssuerPemPath = filename:join([DataDir, "ocsp-issuer.pem"]),
|
||||
{ok, IssuerPem} = file:read_file(IssuerPemPath),
|
||||
|
||||
%% no ocsp at first
|
||||
ListenerId = "ssl:default",
|
||||
{ok, {{_, 200, _}, _, ListenerData0}} = get_listener_via_api(ListenerId),
|
||||
?assertMatch(
|
||||
#{
|
||||
<<"ssl_options">> :=
|
||||
#{
|
||||
<<"ocsp">> :=
|
||||
#{<<"enable_ocsp_stapling">> := false}
|
||||
}
|
||||
},
|
||||
ListenerData0
|
||||
),
|
||||
assert_no_http_get(),
|
||||
|
||||
%% configure ocsp
|
||||
OCSPConfig =
|
||||
#{
|
||||
<<"ssl_options">> =>
|
||||
#{
|
||||
<<"keyfile">> => Keyfile,
|
||||
<<"certfile">> => Certfile,
|
||||
<<"cacertfile">> => Cacertfile,
|
||||
<<"ocsp">> =>
|
||||
#{
|
||||
<<"enable_ocsp_stapling">> => true,
|
||||
%% we use the file contents to check that
|
||||
%% the API converts that to an internally
|
||||
%% managed file
|
||||
<<"issuer_pem">> => IssuerPem,
|
||||
<<"responder_url">> => <<"http://localhost:9877">>
|
||||
}
|
||||
}
|
||||
},
|
||||
ListenerData1 = emqx_map_lib:deep_merge(ListenerData0, OCSPConfig),
|
||||
{ok, {_, _, ListenerData2}} = update_listener_via_api(ListenerId, ListenerData1),
|
||||
?assertMatch(
|
||||
#{
|
||||
<<"ssl_options">> :=
|
||||
#{
|
||||
<<"ocsp">> :=
|
||||
#{
|
||||
<<"enable_ocsp_stapling">> := true,
|
||||
<<"issuer_pem">> := _,
|
||||
<<"responder_url">> := _
|
||||
}
|
||||
}
|
||||
},
|
||||
ListenerData2
|
||||
),
|
||||
%% issuer pem should have been uploaded and saved to a new
|
||||
%% location
|
||||
?assertNotEqual(
|
||||
IssuerPemPath,
|
||||
emqx_map_lib:deep_get(
|
||||
[<<"ssl_options">>, <<"ocsp">>, <<"issuer_pem">>],
|
||||
ListenerData2
|
||||
)
|
||||
),
|
||||
?assertNotEqual(
|
||||
IssuerPem,
|
||||
emqx_map_lib:deep_get(
|
||||
[<<"ssl_options">>, <<"ocsp">>, <<"issuer_pem">>],
|
||||
ListenerData2
|
||||
)
|
||||
),
|
||||
assert_http_get(1, 5_000),
|
||||
ok.
|
||||
|
||||
t_ocsp_responder_error_responses(_Config) ->
|
||||
ListenerId = <<"ssl:test_ocsp">>,
|
||||
Conf = emqx_config:get_listener_conf(ssl, test_ocsp, []),
|
||||
?check_trace(
|
||||
begin
|
||||
%% successful response without headers
|
||||
put_http_response({ok, {200, <<"ocsp_response">>}}),
|
||||
{ok, {ok, _}} =
|
||||
?wait_async_action(
|
||||
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||
#{?snk_kind := ocsp_http_fetch_and_cache, headers := false},
|
||||
1_000
|
||||
),
|
||||
|
||||
%% error response with headers
|
||||
put_http_response({ok, {{"HTTP/1.0", 500, "Internal Server Error"}, [], <<"error">>}}),
|
||||
{ok, {ok, _}} =
|
||||
?wait_async_action(
|
||||
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||
#{?snk_kind := ocsp_http_fetch_bad_code, code := 500, headers := true},
|
||||
1_000
|
||||
),
|
||||
|
||||
%% error response without headers
|
||||
put_http_response({ok, {500, <<"error">>}}),
|
||||
{ok, {ok, _}} =
|
||||
?wait_async_action(
|
||||
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||
#{?snk_kind := ocsp_http_fetch_bad_code, code := 500, headers := false},
|
||||
1_000
|
||||
),
|
||||
|
||||
%% econnrefused
|
||||
put_http_response(
|
||||
{error,
|
||||
{failed_connect, [
|
||||
{to_address, {"localhost", 9877}},
|
||||
{inet, [inet], econnrefused}
|
||||
]}}
|
||||
),
|
||||
{ok, {ok, _}} =
|
||||
?wait_async_action(
|
||||
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||
#{?snk_kind := ocsp_http_fetch_error, error := {failed_connect, _}},
|
||||
1_000
|
||||
),
|
||||
|
||||
%% timeout
|
||||
put_http_response({error, timeout}),
|
||||
{ok, {ok, _}} =
|
||||
?wait_async_action(
|
||||
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||
#{?snk_kind := ocsp_http_fetch_error, error := timeout},
|
||||
1_000
|
||||
),
|
||||
|
||||
ok
|
||||
end,
|
||||
[]
|
||||
),
|
||||
ok.
|
||||
|
||||
t_unknown_requests(_Config) ->
|
||||
emqx_ocsp_cache ! unknown,
|
||||
?assertEqual(ok, gen_server:cast(emqx_ocsp_cache, unknown)),
|
||||
?assertEqual({error, {unknown_call, unknown}}, gen_server:call(emqx_ocsp_cache, unknown)),
|
||||
ok.
|
||||
|
||||
t_validations(Config) ->
|
||||
case proplists:get_bool(skip_does_not_apply, Config) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
do_t_validations(Config)
|
||||
end.
|
||||
|
||||
do_t_validations(_Config) ->
|
||||
ListenerId = <<"ssl:default">>,
|
||||
{ok, {{_, 200, _}, _, ListenerData0}} = get_listener_via_api(ListenerId),
|
||||
|
||||
ListenerData1 =
|
||||
emqx_map_lib:deep_merge(
|
||||
ListenerData0,
|
||||
#{
|
||||
<<"ssl_options">> =>
|
||||
#{<<"ocsp">> => #{<<"enable_ocsp_stapling">> => true}}
|
||||
}
|
||||
),
|
||||
{error, {_, _, ResRaw1}} = update_listener_via_api(ListenerId, ListenerData1),
|
||||
#{<<"code">> := <<"BAD_REQUEST">>, <<"message">> := MsgRaw1} =
|
||||
emqx_json:decode(ResRaw1, [return_maps]),
|
||||
?assertMatch(
|
||||
#{
|
||||
<<"mismatches">> :=
|
||||
#{
|
||||
<<"listeners:ssl_not_required_bind">> :=
|
||||
#{
|
||||
<<"reason">> :=
|
||||
<<"The responder URL is required for OCSP stapling">>
|
||||
}
|
||||
}
|
||||
},
|
||||
emqx_json:decode(MsgRaw1, [return_maps])
|
||||
),
|
||||
|
||||
ListenerData2 =
|
||||
emqx_map_lib:deep_merge(
|
||||
ListenerData0,
|
||||
#{
|
||||
<<"ssl_options">> =>
|
||||
#{
|
||||
<<"ocsp">> => #{
|
||||
<<"enable_ocsp_stapling">> => true,
|
||||
<<"responder_url">> => <<"http://localhost:9877">>
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
{error, {_, _, ResRaw2}} = update_listener_via_api(ListenerId, ListenerData2),
|
||||
#{<<"code">> := <<"BAD_REQUEST">>, <<"message">> := MsgRaw2} =
|
||||
emqx_json:decode(ResRaw2, [return_maps]),
|
||||
?assertMatch(
|
||||
#{
|
||||
<<"mismatches">> :=
|
||||
#{
|
||||
<<"listeners:ssl_not_required_bind">> :=
|
||||
#{
|
||||
<<"reason">> :=
|
||||
<<"The issuer PEM path is required for OCSP stapling">>
|
||||
}
|
||||
}
|
||||
},
|
||||
emqx_json:decode(MsgRaw2, [return_maps])
|
||||
),
|
||||
|
||||
ListenerData3a =
|
||||
emqx_map_lib:deep_merge(
|
||||
ListenerData0,
|
||||
#{
|
||||
<<"ssl_options">> =>
|
||||
#{
|
||||
<<"ocsp">> => #{
|
||||
<<"enable_ocsp_stapling">> => true,
|
||||
<<"responder_url">> => <<"http://localhost:9877">>,
|
||||
<<"issuer_pem">> => <<"some_file">>
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
ListenerData3 = emqx_map_lib:deep_remove([<<"ssl_options">>, <<"certfile">>], ListenerData3a),
|
||||
{error, {_, _, ResRaw3}} = update_listener_via_api(ListenerId, ListenerData3),
|
||||
#{<<"code">> := <<"BAD_REQUEST">>, <<"message">> := MsgRaw3} =
|
||||
emqx_json:decode(ResRaw3, [return_maps]),
|
||||
?assertMatch(
|
||||
#{
|
||||
<<"mismatches">> :=
|
||||
#{
|
||||
<<"listeners:ssl_not_required_bind">> :=
|
||||
#{
|
||||
<<"reason">> :=
|
||||
<<"Server certificate must be defined when using OCSP stapling">>
|
||||
}
|
||||
}
|
||||
},
|
||||
emqx_json:decode(MsgRaw3, [return_maps])
|
||||
),
|
||||
|
||||
ok.
|
||||
|
||||
t_unknown_error_fetching_ocsp_response(_Config) ->
|
||||
ListenerID = <<"ssl:test_ocsp">>,
|
||||
TestPid = self(),
|
||||
ok = meck:expect(
|
||||
emqx_ocsp_cache,
|
||||
http_get,
|
||||
fun(_RequestURI, _HTTPTimeout) ->
|
||||
TestPid ! error_raised,
|
||||
meck:exception(error, something_went_wrong)
|
||||
end
|
||||
),
|
||||
?assertEqual(error, emqx_ocsp_cache:fetch_response(ListenerID)),
|
||||
receive
|
||||
error_raised -> ok
|
||||
after 200 -> ct:fail("should have tried to fetch ocsp response")
|
||||
end,
|
||||
ok.
|
||||
|
||||
t_openssl_client(Config) ->
|
||||
TLSVsn = ?config(tls_vsn, Config),
|
||||
WithStatusRequest = ?config(status_request, Config),
|
||||
%% ensure ocsp response is already cached.
|
||||
ListenerID = <<"ssl:default">>,
|
||||
?assertMatch(
|
||||
{ok, _},
|
||||
emqx_ocsp_cache:fetch_response(ListenerID),
|
||||
#{msgs => process_info(self(), messages)}
|
||||
),
|
||||
timer:sleep(500),
|
||||
test_ocsp_connection(TLSVsn, WithStatusRequest, Config).
|
|
@ -0,0 +1,68 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIF+zCCA+OgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UEBhMCU0Ux
|
||||
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQK
|
||||
DAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENBMREwDwYDVQQDDAhNeVJvb3RD
|
||||
QTAeFw0yMzAxMTIxMzA4MTZaFw0zMzAxMDkxMzA4MTZaMGsxCzAJBgNVBAYTAlNF
|
||||
MRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAoMCU15T3JnTmFtZTEZMBcGA1UE
|
||||
CwwQTXlJbnRlcm1lZGlhdGVDQTEZMBcGA1UEAwwQTXlJbnRlcm1lZGlhdGVDQTCC
|
||||
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQG7dMeU/y9HDNHzhydR0bm
|
||||
wN9UGplqJOJPwqJRaZZcrn9umgJ9SU2il2ceEVxMDwzBWCRKJO5/H9A9k13SqsXM
|
||||
2c2c9xXfIF1kb820lCm1Uow5hZ/auDjxliNk9kNJDigCRi3QoIs/dVeWzFsgEC2l
|
||||
gxRqauN2eNFb6/yXY788YALHBsCRV2NFOFXxtPsvLXpD9Q/8EqYsSMuLARRdHVNU
|
||||
ryaEF5lhShpcuz0TlIuTy2TiuXJUtJ+p7a4Z7friZ6JsrmQWsVQBj44F8TJRHWzW
|
||||
C7vm9c+dzEX9eqbr5iPL+L4ctMW9Lz6ePcYfIXne6CElusRUf8G+xM1uwovF9bpV
|
||||
+9IqY7tAu9G1iY9iNtJgNNDKOCcOGKcZCx6Cg1XYOEKReNnUMazvYeqRrrjV5WQ0
|
||||
vOcD5zcBRNTXCddCLa7U0guXP9mQrfuk4NTH1Bt77JieTJ8cfDXHwtaKf6aGbmZP
|
||||
wl1Xi/GuXNUP/xeog78RKyFwBmjt2JKwvWzMpfmH4mEkG9moh2alva+aEz6LIJuP
|
||||
16g6s0Q6c793/OvUtpNcewHw4Vjn39LD9o6VLp854G4n8dVpUWSbWS+sXD1ZE69H
|
||||
g/sMNMyq+09ufkbewY8xoCm/rQ1pqDZAVMWsstJEaYu7b/eb7R+RGOj1YECCV/Yp
|
||||
EZPdDotbSNRkIi2d/a1NAgMBAAGjgaQwgaEwHQYDVR0OBBYEFExwhjsVUom6tQ+S
|
||||
qq6xMUETvnPzMB8GA1UdIwQYMBaAFD90kfU5pc5l48THu0Ayj9SNpHuhMBIGA1Ud
|
||||
EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDsGA1UdHwQ0MDIwMKAuoCyG
|
||||
Kmh0dHA6Ly9sb2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUuY3JsLnBlbTANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAK6NgdWQYtPNKQNBGjsgtgqTRh+k30iqSO6Y3yE1KGABO
|
||||
EuQdVqkC2qUIbCB0M0qoV0ab50KNLfU6cbshggW4LDpcMpoQpI05fukNh1jm3ZuZ
|
||||
0xsB7vlmlsv00tpqmfIl/zykPDynHKOmFh/hJP/KetMy4+wDv4/+xP31UdEj5XvG
|
||||
HvMtuqOS23A+H6WPU7ol7KzKBnU2zz/xekvPbUD3JqV+ynP5bgbIZHAndd0o9T8e
|
||||
NFX23Us4cTenU2/ZlOq694bRzGaK+n3Ksz995Nbtzv5fbUgqmf7Mcq4iHGRVtV11
|
||||
MRyBrsXZp2vbF63c4hrf2Zd6SWRoaDKRhP2DMhajpH9zZASSTlfejg/ZRO2s+Clh
|
||||
YrSTkeMAdnRt6i/q4QRcOTCfsX75RFM5v67njvTXsSaSTnAwaPi78tRtf+WSh0EP
|
||||
VVPzy++BszBVlJ1VAf7soWZHCjZxZ8ZPqVTy5okoHwWQ09WmYe8GfulDh1oj0wbK
|
||||
3FjN7bODWHJN+bFf5aQfK+tumYKoPG8RXL6QxpEzjFWjxhIMJHHMKfDWnAV1o1+7
|
||||
/1/aDzq7MzEYBbrgQR7oE5ZHtyqhCf9LUgw0Kr7/8QWuNAdeDCJzjXRROU0hJczp
|
||||
dOyfRlLbHmLLmGOnROlx6LsGNQ17zuz6SPi7ei8/ylhykawDOAGkM1+xFakmQhM=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFzzCCA7egAwIBAgIUYjc7hD7/UJ0/VPADfNfp/WpOwRowDQYJKoZIhvcNAQEL
|
||||
BQAwbzELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJ
|
||||
U3RvY2tob2xtMRIwEAYDVQQKDAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENB
|
||||
MREwDwYDVQQDDAhNeVJvb3RDQTAeFw0yMzAxMTIxMzA4MTRaFw00MzAxMDcxMzA4
|
||||
MTRaMG8xCzAJBgNVBAYTAlNFMRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAcM
|
||||
CVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMREwDwYDVQQLDAhNeVJvb3RD
|
||||
QTERMA8GA1UEAwwITXlSb290Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
|
||||
AoICAQCnBwSOYVJw47IoMHMXTVDtOYvUt3rqsurEhFcB4O8xmf2mmwr6m7s8A5Ft
|
||||
AvAehg1GvnXT3t/KiyU7BK+acTwcErGyZwS2wvdB0lpHWSpOn/u5y+4ZETvQefcj
|
||||
ZTdDOM9VN5nutpitgNb+1yL8sqSexfVbY7DnYYvFjOVBYoP/SGvM9jVjCad+0WL3
|
||||
FhuD+L8QAxzCieX3n9UMymlFwINQuEc+TDjuNcEqt+0J5EgS1fwzxb2RCVL0TNv4
|
||||
9a71hFGCNRj20AeZm99hbdufm7+0AFO7ocV5q43rLrWFUoBzqKPYIjga/cv/UdWZ
|
||||
c5RLRXw3JDSrCqkf/mOlaEhNPlmWRF9MSus5Da3wuwgGCaVzmrf30rWR5aHHcscG
|
||||
e+AOgJ4HayvBUQeb6ZlRXc0YlACiLToMKxuyxDyUcDfVEXpUIsDILF8dkiVQxEU3
|
||||
j9g6qjXiqPVdNiwpqXfBKObj8vNCzORnoHYs8cCgib3RgDVWeqkDmlSwlZE7CvQh
|
||||
U4Loj4l7813xxzYEKkVaT1JdXPWu42CG/b4Y/+f4V+3rkJkYzUwndX6kZNksIBai
|
||||
phmtvKt+CTdP1eAbT+C9AWWF3PT31+BIhuT0u9tR8BVSkXdQB8dG4M/AAJcTo640
|
||||
0mdYYOXT153gEKHJuUBm750ZTy+r6NjNvpw8VrMAakJwHqnIdQIDAQABo2MwYTAd
|
||||
BgNVHQ4EFgQUP3SR9TmlzmXjxMe7QDKP1I2ke6EwHwYDVR0jBBgwFoAUP3SR9Tml
|
||||
zmXjxMe7QDKP1I2ke6EwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw
|
||||
DQYJKoZIhvcNAQELBQADggIBAFMFv4C+I0+xOAb9v6G/IOpfPBZ1ez31EXKJJBra
|
||||
lulP4nRHQMeb310JS8BIeQ3dl+7+PkSxPABZSwc3jkxdSMvhc+Z4MQtTgos+Qsjs
|
||||
gH7sTqwWeeQ0lHYxWmkXijrh5OPRZwTKzYQlkcn85BCUXl2KDuNEdiqPbDTao+lc
|
||||
lA0/UAvC6NCyFKq/jqf4CmW5Kx6yG1v1LaE+IXn7cbIXj+DaehocVXi0wsXqj03Q
|
||||
DDUHuLHZP+LBsg4e91/0Jy2ekNRTYJifSqr+9ufHl0ZX1pFDZyf396IgZ5CQZ0PJ
|
||||
nRxZHlCfsxWxmxxdy3FQSE6YwXhdTjjoAa1ApZcKkkt1beJa6/oRLze/ux5x+5q+
|
||||
4QczufHd6rjoKBi6BM3FgFQ8As5iNohHXlMHd/xITo1Go3CWw2j9TGH5vzksOElK
|
||||
B0mcwwt2zwNEjvfytc+tI5jcfGN3tiT5fVHS8hw9dWKevypLL+55Ua9G8ZgDHasT
|
||||
XFRJHgmnbyFcaAe26D2dSKmhC9u2mHBH+MaI8dj3e7wNBfpxNgp41aFIk+QTmiFW
|
||||
VXFED6DHQ/Mxq93ACalHdYg18PlIYClbT6Pf2xXBnn33YPhn5xzoTZ+cDH/RpaQp
|
||||
s0UUTSJT1UTXgtXPnZWQfvKlMjJEIiVFiLEC0sgZRlWuZDRAY0CdZJJxvQp59lqu
|
||||
cbTm
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
rkE8Bz5a/iwO+QwdO5Q2cp5+0nm41A==
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,38 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIGmjCCBIKgAwIBAgICEAYwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
|
||||
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
|
||||
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
|
||||
DTIzMDMwNjE5NTA0N1oXDTMzMDYxMTE5NTA0N1owezELMAkGA1UEBhMCU0UxEjAQ
|
||||
BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN
|
||||
eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExFTATBgNVBAMMDG9j
|
||||
c3AuY2xpZW50MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKZ9mYEA
|
||||
5knzGM9iSnJDFMZnjS8jA67KXBKjDXaJ1Y3qNxb2DloiKn4Hagqt/NHs9izFsOh1
|
||||
yP8o6lAbAIUC6NJiBi+KI6yYBNDWZ42hyedaMCzJvD51Jcr9mN+tWAhokXtEdouI
|
||||
O2rlMjhvgqNQXKHk0gshz7N9mqg1TqSFQXRtw4EAbYEVoHNF/5r+GBlcQ/ISeioU
|
||||
rknmT4MNKUpcNs5MQeFcbhAVPWFlq1rGm0PWdMQVFi7N+aH+DhEO9a1rCb1hTeP9
|
||||
XRSfu39NqPR8Bc2LfRPMo5VpVwOuRW0MUd7aFfeU4l3ncjk2lLtqxqr5pM3K3P4j
|
||||
cF/9ri+SESCsqmMFJdeb/5XDk3Oxc3Ap11JQbLCYhtXZgFicr7QPCE5cuxM+Hem/
|
||||
r3fU1XGmJ9bSczhSvpcIMc49Vf+MhlnzX1vMCVlncGAxPPumpP33TqWY1FAGBKCv
|
||||
5lrD2CNWEuDVUN/AXzl5qeFvOTkhOd99H6zZyidmvaD4sQOyrZ0m/+wRti4cE0GQ
|
||||
O2Gfjf0yA2rWevc9XSGfyk1yBLlkVTD583zjTdFj1NguywH8UVhEQMJrHSPvmovc
|
||||
Tsttkrwl+w/y6HV7c91HrhOC2hihMq7C0ip6WaH6vIBYWNpcs+g/8CpiTVVxEJ68
|
||||
5dQrDouKyR26SmWT1zLZw8tyb4Q/7YmzcHbxAgMBAAGjggE2MIIBMjAJBgNVHRME
|
||||
AjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBH
|
||||
ZW5lcmF0ZWQgQ2xpZW50IENlcnRpZmljYXRlMB0GA1UdDgQWBBSJ/yia067wCafe
|
||||
kDCgk+e8PJTCUDAfBgNVHSMEGDAWgBRMcIY7FVKJurUPkqqusTFBE75z8zAOBgNV
|
||||
HQ8BAf8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMDsGA1Ud
|
||||
HwQ0MDIwMKAuoCyGKmh0dHA6Ly9sb2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUu
|
||||
Y3JsLnBlbTAxBggrBgEFBQcBAQQlMCMwIQYIKwYBBQUHMAGGFWh0dHA6Ly9sb2Nh
|
||||
bGhvc3Q6OTg3NzANBgkqhkiG9w0BAQsFAAOCAgEAN2XfYgbrjxC6OWh9UoMLQaDD
|
||||
59JPxAUBxlRtWzTWqxY2jfT+OwJfDP4e+ef2G1YEG+qyt57ddlm/EwX9IvAvG0D4
|
||||
wd4tfItG88IJWKDM3wpT5KYrUsu+PlQTFmGmaWlORK/mRKlmfjbP5CIAcUedvCS9
|
||||
j9PkCrbbkklAmp0ULLSLUkYajmfFOkQ+VdGhQ6nAamTeyh2Z2S4dVjsKc8yBViMo
|
||||
/V6HP56rOvUqiVTcvhZtH7QDptMSTzuJ+AsmreYjwIiTGzYS/i8QVAFuPfXJKEOB
|
||||
jD5WhUaP/8Snbuft4MxssPAph8okcmxLfb55nw+soNc2oS1wWwKMe7igRelq8vtg
|
||||
bu00QSEGiY1eq/vFgZh0+Wohy/YeYzhO4Jq40FFpKiVbkLzexpNH/Afj2QrHuZ7y
|
||||
259uGGfv5tGA+TW6PsckCQknEb5V4V35ZZlbWVRKpuADeNPoDuoYPtc5eOomIkmw
|
||||
rFz/gPZWSA+4pYEgXgqcaM8+KP0i53eTbWqwy5DVgXiuaTYWU4m1FTsIZ+/nGIqW
|
||||
Dsgqd/D6jivf9Yvm+VFYTZsxIfq5sMdjxSuMBo0nZrzFDpqc6m6fVVoHv5R9Yliw
|
||||
MbxgmFQ84CKLy7iNKGSGVN2SIr1obMQ0e/t3NiCHib3WKzmZFoNoFCtVzAgsxGmF
|
||||
Q6rY83JdIPPW4LqZNcE=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,6 @@
|
|||
V 330419130816Z 1000 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=localhost
|
||||
V 330419130816Z 1001 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=MyClient
|
||||
R 330419130816Z 230112130816Z 1002 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=client-revoked
|
||||
V 330419130816Z 1003 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=ocsp.server
|
||||
V 330419130816Z 1004 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=ocsp.client
|
||||
V 330425123656Z 1005 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=client-no-dist-points
|
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
xBO4CmF79pAcAXvDoRB619Flx5/G
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,34 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIF+zCCA+OgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UEBhMCU0Ux
|
||||
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQK
|
||||
DAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENBMREwDwYDVQQDDAhNeVJvb3RD
|
||||
QTAeFw0yMzAxMTIxMzA4MTZaFw0zMzAxMDkxMzA4MTZaMGsxCzAJBgNVBAYTAlNF
|
||||
MRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAoMCU15T3JnTmFtZTEZMBcGA1UE
|
||||
CwwQTXlJbnRlcm1lZGlhdGVDQTEZMBcGA1UEAwwQTXlJbnRlcm1lZGlhdGVDQTCC
|
||||
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQG7dMeU/y9HDNHzhydR0bm
|
||||
wN9UGplqJOJPwqJRaZZcrn9umgJ9SU2il2ceEVxMDwzBWCRKJO5/H9A9k13SqsXM
|
||||
2c2c9xXfIF1kb820lCm1Uow5hZ/auDjxliNk9kNJDigCRi3QoIs/dVeWzFsgEC2l
|
||||
gxRqauN2eNFb6/yXY788YALHBsCRV2NFOFXxtPsvLXpD9Q/8EqYsSMuLARRdHVNU
|
||||
ryaEF5lhShpcuz0TlIuTy2TiuXJUtJ+p7a4Z7friZ6JsrmQWsVQBj44F8TJRHWzW
|
||||
C7vm9c+dzEX9eqbr5iPL+L4ctMW9Lz6ePcYfIXne6CElusRUf8G+xM1uwovF9bpV
|
||||
+9IqY7tAu9G1iY9iNtJgNNDKOCcOGKcZCx6Cg1XYOEKReNnUMazvYeqRrrjV5WQ0
|
||||
vOcD5zcBRNTXCddCLa7U0guXP9mQrfuk4NTH1Bt77JieTJ8cfDXHwtaKf6aGbmZP
|
||||
wl1Xi/GuXNUP/xeog78RKyFwBmjt2JKwvWzMpfmH4mEkG9moh2alva+aEz6LIJuP
|
||||
16g6s0Q6c793/OvUtpNcewHw4Vjn39LD9o6VLp854G4n8dVpUWSbWS+sXD1ZE69H
|
||||
g/sMNMyq+09ufkbewY8xoCm/rQ1pqDZAVMWsstJEaYu7b/eb7R+RGOj1YECCV/Yp
|
||||
EZPdDotbSNRkIi2d/a1NAgMBAAGjgaQwgaEwHQYDVR0OBBYEFExwhjsVUom6tQ+S
|
||||
qq6xMUETvnPzMB8GA1UdIwQYMBaAFD90kfU5pc5l48THu0Ayj9SNpHuhMBIGA1Ud
|
||||
EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDsGA1UdHwQ0MDIwMKAuoCyG
|
||||
Kmh0dHA6Ly9sb2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUuY3JsLnBlbTANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAK6NgdWQYtPNKQNBGjsgtgqTRh+k30iqSO6Y3yE1KGABO
|
||||
EuQdVqkC2qUIbCB0M0qoV0ab50KNLfU6cbshggW4LDpcMpoQpI05fukNh1jm3ZuZ
|
||||
0xsB7vlmlsv00tpqmfIl/zykPDynHKOmFh/hJP/KetMy4+wDv4/+xP31UdEj5XvG
|
||||
HvMtuqOS23A+H6WPU7ol7KzKBnU2zz/xekvPbUD3JqV+ynP5bgbIZHAndd0o9T8e
|
||||
NFX23Us4cTenU2/ZlOq694bRzGaK+n3Ksz995Nbtzv5fbUgqmf7Mcq4iHGRVtV11
|
||||
MRyBrsXZp2vbF63c4hrf2Zd6SWRoaDKRhP2DMhajpH9zZASSTlfejg/ZRO2s+Clh
|
||||
YrSTkeMAdnRt6i/q4QRcOTCfsX75RFM5v67njvTXsSaSTnAwaPi78tRtf+WSh0EP
|
||||
VVPzy++BszBVlJ1VAf7soWZHCjZxZ8ZPqVTy5okoHwWQ09WmYe8GfulDh1oj0wbK
|
||||
3FjN7bODWHJN+bFf5aQfK+tumYKoPG8RXL6QxpEzjFWjxhIMJHHMKfDWnAV1o1+7
|
||||
/1/aDzq7MzEYBbrgQR7oE5ZHtyqhCf9LUgw0Kr7/8QWuNAdeDCJzjXRROU0hJczp
|
||||
dOyfRlLbHmLLmGOnROlx6LsGNQ17zuz6SPi7ei8/ylhykawDOAGkM1+xFakmQhM=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,14 @@
|
|||
listeners.ssl.default {
|
||||
bind = "0.0.0.0:8883"
|
||||
max_connections = 512000
|
||||
ssl_options {
|
||||
keyfile = "{{ test_data_dir }}/server.key"
|
||||
certfile = "{{ test_data_dir }}/server.pem"
|
||||
cacertfile = "{{ test_data_dir }}/ca.pem"
|
||||
ocsp {
|
||||
enable_ocsp_stapling = true
|
||||
issuer_pem = "{{ test_data_dir }}/ocsp-issuer.pem"
|
||||
responder_url = "http://127.0.0.1:9877"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCnVPRWgP59GU15
|
||||
HddFwPZflFfcSkeuWU8tgKQhZcNoBli4lIfemuoV/hkGRVFexAiAw3/u5wvOaMaN
|
||||
V8n9KxxgAUNLh5YaknpnNdhfQDyM0S5UJIbVeLzAQWxkBXpI3uBfW4WPSULRnVyR
|
||||
psLEfl1qOklGOyuZfRbkkkkVwtJEmGEH0kz0fy6xenn3R3/mTeIbj+5TNqiBXWn1
|
||||
/qgTiNf2Ni7SE6Nk2lP4V8iofcBIrsp6KtEWdipGEJZeXCg/X0g/qVt15tF1l00M
|
||||
uEWRHt1qGBELJJTcNzQvdqHAPz0AfQRjTtXyocw5+pFth8Q8a7gyjrjv5nhnpAKQ
|
||||
msrt3vyNAgMBAAECggEABnWvIQ/Fw0qQxRYz00uJt1LguW5cqgxklBsdOvTUwFVO
|
||||
Y4HIZP2R/9tZV/ahF4l10pK5g52DxSoiUB6Ne6qIY+RolqfbUZdKBmX7vmGadM02
|
||||
fqUSV3dbwghEiO/1Mo74FnZQB6IKZFEw26aWakN+k7VAUufB3SEJGzXSgHaO63ru
|
||||
dFGSiYI8U+q+YnhUJjCnmI12fycNfy451TdUQtGZb6pNmm5HRUF6hpAV8Le9LojP
|
||||
Ql9eacPpsrzU15X5ElCQZ/f9iNh1bplcISuhrULgKUKOvAVrBlEK67uRVy6g98xA
|
||||
c/rgNLkbL/jZEsAc3/vHAyFgd3lABfwpBGLHej3QgQKBgQDFNYmfBNQr89HC5Zc+
|
||||
M6jXcAT/R+0GNczBTfC4iyNemwqsumSSRelNZ748UefKuS3F6Mvb2CBqE2LbB61G
|
||||
hrnCffG2pARjZ491SefRwghhWWVGLP1p8KliLgOGBehA1REgJb+XULncjuHZuh4O
|
||||
LVn3HVnWGxeBGg+yKa6Z4YQi3QKBgQDZN0O8ZcZY74lRJ0UjscD9mJ1yHlsssZag
|
||||
njkX/f0GR/iVpfaIxQNC3gvWUy2LsU0He9sidcB0cfej0j/qZObQyFsCB0+utOgy
|
||||
+hX7gokV2pes27WICbNWE2lJL4QZRJgvf82OaEy57kfDrm+eK1XaSZTZ10P82C9u
|
||||
gAmMnontcQKBgGu29lhY9tqa7jOZ26Yp6Uri8JfO3XPK5u+edqEVvlfqL0Zw+IW8
|
||||
kdWpmIqx4f0kcA/tO4v03J+TvycLZmVjKQtGZ0PvCkaRRhY2K9yyMomZnmtaH4BB
|
||||
5wKtR1do2pauyg/ZDnDDswD5OfsGYWw08TK8YVlEqu3lIjWZ9rguKVIxAoGAZYUk
|
||||
zVqr10ks3pcCA2rCjkPT4lA5wKvHgI4ylPoKVfMxRY/pp4acvZXV5ne9o7pcDBFh
|
||||
G7v5FPNnEFPlt4EtN4tMragJH9hBZgHoYEJkG6islweg0lHmVWaBIMlqbfzXO+v5
|
||||
gINSyNuLAvP2CvCqEXmubhnkFrpbgMOqsuQuBqECgYB3ss2PDhBF+5qoWgqymFof
|
||||
1ovRPuQ9sPjWBn5IrCdoYITDnbBzBZERx7GLs6A/PUlWgST7jkb1PY/TxYSUfXzJ
|
||||
SNd47q0mCQ+IUdqUbHgpK9b1ncwLMsnexpYZdHJWRLgnUhOx7OMjJc/4iLCAFCoN
|
||||
3KJ7/V1keo7GBHOwnsFcCA==
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,35 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIGCTCCA/GgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
|
||||
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
|
||||
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
|
||||
DTIzMDExMjEzMDgxNloXDTMzMDQxOTEzMDgxNloweDELMAkGA1UEBhMCU0UxEjAQ
|
||||
BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN
|
||||
eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExEjAQBgNVBAMMCWxv
|
||||
Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdU9FaA/n0Z
|
||||
TXkd10XA9l+UV9xKR65ZTy2ApCFlw2gGWLiUh96a6hX+GQZFUV7ECIDDf+7nC85o
|
||||
xo1Xyf0rHGABQ0uHlhqSemc12F9APIzRLlQkhtV4vMBBbGQFekje4F9bhY9JQtGd
|
||||
XJGmwsR+XWo6SUY7K5l9FuSSSRXC0kSYYQfSTPR/LrF6efdHf+ZN4huP7lM2qIFd
|
||||
afX+qBOI1/Y2LtITo2TaU/hXyKh9wEiuynoq0RZ2KkYQll5cKD9fSD+pW3Xm0XWX
|
||||
TQy4RZEe3WoYEQsklNw3NC92ocA/PQB9BGNO1fKhzDn6kW2HxDxruDKOuO/meGek
|
||||
ApCayu3e/I0CAwEAAaOCAagwggGkMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQD
|
||||
AgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2Vy
|
||||
dGlmaWNhdGUwHQYDVR0OBBYEFGy5LQPzIelruJl7mL0mtUXM57XhMIGaBgNVHSME
|
||||
gZIwgY+AFExwhjsVUom6tQ+Sqq6xMUETvnPzoXOkcTBvMQswCQYDVQQGEwJTRTES
|
||||
MBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xEjAQBgNVBAoM
|
||||
CU15T3JnTmFtZTERMA8GA1UECwwITXlSb290Q0ExETAPBgNVBAMMCE15Um9vdENB
|
||||
ggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwOwYDVR0f
|
||||
BDQwMjAwoC6gLIYqaHR0cDovL2xvY2FsaG9zdDo5ODc4L2ludGVybWVkaWF0ZS5j
|
||||
cmwucGVtMDEGCCsGAQUFBwEBBCUwIzAhBggrBgEFBQcwAYYVaHR0cDovL2xvY2Fs
|
||||
aG9zdDo5ODc3MA0GCSqGSIb3DQEBCwUAA4ICAQCX3EQgiCVqLhnCNd0pmptxXPxo
|
||||
l1KyZkpdrFa/NgSqRhkuZSAkszwBDDS/gzkHFKEUhmqs6/UZwN4+Rr3LzrHonBiN
|
||||
aQ6GeNNXZ/3xAQfUCwjjGmz9Sgw6kaX19Gnk2CjI6xP7T+O5UmsMI9hHUepC9nWa
|
||||
XX2a0hsO/KOVu5ZZckI16Ek/jxs2/HEN0epYdvjKFAaVmzZZ5PATNjrPQXvPmq2r
|
||||
x++La+3bXZsrH8P2FhPpM5t/IxKKW/Tlpgz92c2jVSIHF5khSA/MFDC+dk80OFmm
|
||||
v4ZTPIMuZ//Q+wo0f9P48rsL9D27qS7CA+8pn9wu+cfnBDSt7JD5Yipa1gHz71fy
|
||||
YTa9qRxIAPpzW2v7TFZE8eSKFUY9ipCeM2BbdmCQGmq4+v36b5TZoyjH4k0UVWGo
|
||||
Gclos2cic5Vxi8E6hb7b7yZpjEfn/5lbCiGMfAnI6aoOyrWg6keaRA33kaLUEZiK
|
||||
OgFNbPkjiTV0ZQyLXf7uK9YFhpVzJ0dv0CFNse8rZb7A7PLn8VrV/ZFnJ9rPoawn
|
||||
t7ZGxC0d5BRSEyEeEgsQdxuY4m8OkE18zwhCkt2Qs3uosOWlIrYmqSEa0i/sPSQP
|
||||
jiwB4nEdBrf8ZygzuYjT5T9YRSwhVox4spS/Av8Ells5JnkuKAhCVv9gHxYwbj0c
|
||||
CzyLJgE1z9Tq63m+gQ==
|
||||
-----END CERTIFICATE-----
|
|
@ -473,3 +473,43 @@ password_converter_test() ->
|
|||
?assertEqual(<<"123">>, emqx_schema:password_converter(<<"123">>, #{})),
|
||||
?assertThrow("must_quote", emqx_schema:password_converter(foobar, #{})),
|
||||
ok.
|
||||
|
||||
url_type_test_() ->
|
||||
[
|
||||
?_assertEqual(
|
||||
{ok, <<"http://some.server/">>},
|
||||
typerefl:from_string(emqx_schema:url(), <<"http://some.server/">>)
|
||||
),
|
||||
?_assertEqual(
|
||||
{ok, <<"http://192.168.0.1/">>},
|
||||
typerefl:from_string(emqx_schema:url(), <<"http://192.168.0.1">>)
|
||||
),
|
||||
?_assertEqual(
|
||||
{ok, <<"http://some.server/">>},
|
||||
typerefl:from_string(emqx_schema:url(), "http://some.server/")
|
||||
),
|
||||
?_assertEqual(
|
||||
{ok, <<"http://some.server/">>},
|
||||
typerefl:from_string(emqx_schema:url(), <<"http://some.server">>)
|
||||
),
|
||||
?_assertEqual(
|
||||
{ok, <<"http://some.server:9090/">>},
|
||||
typerefl:from_string(emqx_schema:url(), <<"http://some.server:9090">>)
|
||||
),
|
||||
?_assertEqual(
|
||||
{ok, <<"https://some.server:9090/">>},
|
||||
typerefl:from_string(emqx_schema:url(), <<"https://some.server:9090">>)
|
||||
),
|
||||
?_assertEqual(
|
||||
{ok, <<"https://some.server:9090/path?q=uery">>},
|
||||
typerefl:from_string(emqx_schema:url(), <<"https://some.server:9090/path?q=uery">>)
|
||||
),
|
||||
?_assertEqual(
|
||||
{error, {unsupported_scheme, <<"postgres">>}},
|
||||
typerefl:from_string(emqx_schema:url(), <<"postgres://some.server:9090">>)
|
||||
),
|
||||
?_assertEqual(
|
||||
{error, empty_host_not_allowed},
|
||||
typerefl:from_string(emqx_schema:url(), <<"">>)
|
||||
)
|
||||
].
|
||||
|
|
|
@ -117,7 +117,7 @@ ssl_files_failure_test_() ->
|
|||
%% empty string
|
||||
?assertMatch(
|
||||
{error, #{
|
||||
reason := invalid_file_path_or_pem_string, which_options := [<<"keyfile">>]
|
||||
reason := invalid_file_path_or_pem_string, which_options := [[<<"keyfile">>]]
|
||||
}},
|
||||
emqx_tls_lib:ensure_ssl_files("/tmp", #{
|
||||
<<"keyfile">> => <<>>,
|
||||
|
@ -128,7 +128,7 @@ ssl_files_failure_test_() ->
|
|||
%% not valid unicode
|
||||
?assertMatch(
|
||||
{error, #{
|
||||
reason := invalid_file_path_or_pem_string, which_options := [<<"keyfile">>]
|
||||
reason := invalid_file_path_or_pem_string, which_options := [[<<"keyfile">>]]
|
||||
}},
|
||||
emqx_tls_lib:ensure_ssl_files("/tmp", #{
|
||||
<<"keyfile">> => <<255, 255>>,
|
||||
|
@ -136,6 +136,18 @@ ssl_files_failure_test_() ->
|
|||
<<"cacertfile">> => bin(test_key())
|
||||
})
|
||||
),
|
||||
?assertMatch(
|
||||
{error, #{
|
||||
reason := invalid_file_path_or_pem_string,
|
||||
which_options := [[<<"ocsp">>, <<"issuer_pem">>]]
|
||||
}},
|
||||
emqx_tls_lib:ensure_ssl_files("/tmp", #{
|
||||
<<"keyfile">> => bin(test_key()),
|
||||
<<"certfile">> => bin(test_key()),
|
||||
<<"cacertfile">> => bin(test_key()),
|
||||
<<"ocsp">> => #{<<"issuer_pem">> => <<255, 255>>}
|
||||
})
|
||||
),
|
||||
%% not printable
|
||||
?assertMatch(
|
||||
{error, #{reason := invalid_file_path_or_pem_string}},
|
||||
|
@ -155,7 +167,8 @@ ssl_files_failure_test_() ->
|
|||
#{
|
||||
<<"cacertfile">> => bin(TmpFile),
|
||||
<<"keyfile">> => bin(TmpFile),
|
||||
<<"certfile">> => bin(TmpFile)
|
||||
<<"certfile">> => bin(TmpFile),
|
||||
<<"ocsp">> => #{<<"issuer_pem">> => bin(TmpFile)}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -170,22 +183,29 @@ ssl_files_save_delete_test() ->
|
|||
SSL0 = #{
|
||||
<<"keyfile">> => Key,
|
||||
<<"certfile">> => Key,
|
||||
<<"cacertfile">> => Key
|
||||
<<"cacertfile">> => Key,
|
||||
<<"ocsp">> => #{<<"issuer_pem">> => Key}
|
||||
},
|
||||
Dir = filename:join(["/tmp", "ssl-test-dir"]),
|
||||
{ok, SSL} = emqx_tls_lib:ensure_ssl_files(Dir, SSL0),
|
||||
File = maps:get(<<"keyfile">>, SSL),
|
||||
?assertMatch(<<"/tmp/ssl-test-dir/key-", _:16/binary>>, File),
|
||||
?assertEqual({ok, bin(test_key())}, file:read_file(File)),
|
||||
FileKey = maps:get(<<"keyfile">>, SSL),
|
||||
?assertMatch(<<"/tmp/ssl-test-dir/key-", _:16/binary>>, FileKey),
|
||||
?assertEqual({ok, bin(test_key())}, file:read_file(FileKey)),
|
||||
FileIssuerPem = emqx_map_lib:deep_get([<<"ocsp">>, <<"issuer_pem">>], SSL),
|
||||
?assertMatch(<<"/tmp/ssl-test-dir/ocsp_issuer_pem-", _:16/binary>>, FileIssuerPem),
|
||||
?assertEqual({ok, bin(test_key())}, file:read_file(FileIssuerPem)),
|
||||
%% no old file to delete
|
||||
ok = emqx_tls_lib:delete_ssl_files(Dir, SSL, undefined),
|
||||
?assertEqual({ok, bin(test_key())}, file:read_file(File)),
|
||||
?assertEqual({ok, bin(test_key())}, file:read_file(FileKey)),
|
||||
?assertEqual({ok, bin(test_key())}, file:read_file(FileIssuerPem)),
|
||||
%% old and new identical, no delete
|
||||
ok = emqx_tls_lib:delete_ssl_files(Dir, SSL, SSL),
|
||||
?assertEqual({ok, bin(test_key())}, file:read_file(File)),
|
||||
?assertEqual({ok, bin(test_key())}, file:read_file(FileKey)),
|
||||
?assertEqual({ok, bin(test_key())}, file:read_file(FileIssuerPem)),
|
||||
%% new is gone, delete old
|
||||
ok = emqx_tls_lib:delete_ssl_files(Dir, undefined, SSL),
|
||||
?assertEqual({error, enoent}, file:read_file(File)),
|
||||
?assertEqual({error, enoent}, file:read_file(FileKey)),
|
||||
?assertEqual({error, enoent}, file:read_file(FileIssuerPem)),
|
||||
%% test idempotence
|
||||
ok = emqx_tls_lib:delete_ssl_files(Dir, undefined, SSL),
|
||||
ok.
|
||||
|
@ -198,7 +218,8 @@ ssl_files_handle_non_generated_file_test() ->
|
|||
SSL0 = #{
|
||||
<<"keyfile">> => TmpKeyFile,
|
||||
<<"certfile">> => TmpKeyFile,
|
||||
<<"cacertfile">> => TmpKeyFile
|
||||
<<"cacertfile">> => TmpKeyFile,
|
||||
<<"ocsp">> => #{<<"issuer_pem">> => TmpKeyFile}
|
||||
},
|
||||
Dir = filename:join(["/tmp", "ssl-test-dir-00"]),
|
||||
{ok, SSL2} = emqx_tls_lib:ensure_ssl_files(Dir, SSL0),
|
||||
|
@ -216,24 +237,32 @@ ssl_file_replace_test() ->
|
|||
SSL0 = #{
|
||||
<<"keyfile">> => Key1,
|
||||
<<"certfile">> => Key1,
|
||||
<<"cacertfile">> => Key1
|
||||
<<"cacertfile">> => Key1,
|
||||
<<"ocsp">> => #{<<"issuer_pem">> => Key1}
|
||||
},
|
||||
SSL1 = #{
|
||||
<<"keyfile">> => Key2,
|
||||
<<"certfile">> => Key2,
|
||||
<<"cacertfile">> => Key2
|
||||
<<"cacertfile">> => Key2,
|
||||
<<"ocsp">> => #{<<"issuer_pem">> => Key2}
|
||||
},
|
||||
Dir = filename:join(["/tmp", "ssl-test-dir2"]),
|
||||
{ok, SSL2} = emqx_tls_lib:ensure_ssl_files(Dir, SSL0),
|
||||
{ok, SSL3} = emqx_tls_lib:ensure_ssl_files(Dir, SSL1),
|
||||
File1 = maps:get(<<"keyfile">>, SSL2),
|
||||
File2 = maps:get(<<"keyfile">>, SSL3),
|
||||
IssuerPem1 = emqx_map_lib:deep_get([<<"ocsp">>, <<"issuer_pem">>], SSL2),
|
||||
IssuerPem2 = emqx_map_lib:deep_get([<<"ocsp">>, <<"issuer_pem">>], SSL3),
|
||||
?assert(filelib:is_regular(File1)),
|
||||
?assert(filelib:is_regular(File2)),
|
||||
?assert(filelib:is_regular(IssuerPem1)),
|
||||
?assert(filelib:is_regular(IssuerPem2)),
|
||||
%% delete old file (File1, in SSL2)
|
||||
ok = emqx_tls_lib:delete_ssl_files(Dir, SSL3, SSL2),
|
||||
?assertNot(filelib:is_regular(File1)),
|
||||
?assert(filelib:is_regular(File2)),
|
||||
?assertNot(filelib:is_regular(IssuerPem1)),
|
||||
?assert(filelib:is_regular(IssuerPem2)),
|
||||
ok.
|
||||
|
||||
bin(X) -> iolist_to_binary(X).
|
||||
|
|
|
@ -55,7 +55,7 @@ init_per_suite(Config) ->
|
|||
|
||||
end_per_suite(_Config) ->
|
||||
ok = emqx_authz_test_lib:restore_authorizers(),
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_authz]).
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]).
|
||||
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
ok = emqx_authz_test_lib:reset_authorizers(),
|
||||
|
|
|
@ -52,7 +52,7 @@ init_per_suite(Config) ->
|
|||
end_per_suite(_Config) ->
|
||||
ok = emqx_authz_test_lib:restore_authorizers(),
|
||||
ok = stop_apps([emqx_resource, cowboy]),
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_authz]).
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]).
|
||||
|
||||
set_special_configs(emqx_authz) ->
|
||||
ok = emqx_authz_test_lib:reset_authorizers();
|
||||
|
|
|
@ -36,7 +36,7 @@ init_per_suite(Config) ->
|
|||
|
||||
end_per_suite(_Config) ->
|
||||
ok = emqx_authz_test_lib:restore_authorizers(),
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_authz]).
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]).
|
||||
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
ok = emqx_authz_test_lib:reset_authorizers(),
|
||||
|
|
|
@ -50,7 +50,7 @@ init_per_suite(Config) ->
|
|||
end_per_suite(_Config) ->
|
||||
ok = emqx_authz_test_lib:restore_authorizers(),
|
||||
ok = stop_apps([emqx_resource]),
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_authz]).
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]).
|
||||
|
||||
set_special_configs(emqx_authz) ->
|
||||
ok = emqx_authz_test_lib:reset_authorizers();
|
||||
|
|
|
@ -57,7 +57,7 @@ end_per_suite(_Config) ->
|
|||
ok = emqx_authz_test_lib:restore_authorizers(),
|
||||
ok = emqx_resource:remove_local(?MYSQL_RESOURCE),
|
||||
ok = stop_apps([emqx_resource]),
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_authz]).
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]).
|
||||
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
ok = emqx_authz_test_lib:reset_authorizers(),
|
||||
|
|
|
@ -57,7 +57,7 @@ end_per_suite(_Config) ->
|
|||
ok = emqx_authz_test_lib:restore_authorizers(),
|
||||
ok = emqx_resource:remove_local(?PGSQL_RESOURCE),
|
||||
ok = stop_apps([emqx_resource]),
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_authz]).
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]).
|
||||
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
ok = emqx_authz_test_lib:reset_authorizers(),
|
||||
|
|
|
@ -58,7 +58,7 @@ end_per_suite(_Config) ->
|
|||
ok = emqx_authz_test_lib:restore_authorizers(),
|
||||
ok = emqx_resource:remove_local(?REDIS_RESOURCE),
|
||||
ok = stop_apps([emqx_resource]),
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_authz]).
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]).
|
||||
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
ok = emqx_authz_test_lib:reset_authorizers(),
|
||||
|
|
|
@ -42,6 +42,8 @@ init_per_suite(_Config) ->
|
|||
[].
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
ok = emqx_config:put([bridges], #{}),
|
||||
ok = emqx_config:put_raw([bridges], #{}),
|
||||
ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_bridge]),
|
||||
ok = emqx_connector_test_helpers:stop_apps([emqx_resource]),
|
||||
_ = application:stop(emqx_connector),
|
||||
|
|
|
@ -163,7 +163,7 @@ diff_listeners(Type, Stop, Start) -> {#{Type => Stop}, #{Type => Start}}.
|
|||
|
||||
ensure_ssl_cert(#{<<"listeners">> := #{<<"https">> := #{<<"enable">> := true}}} = Conf) ->
|
||||
Https = emqx_map_lib:deep_get([<<"listeners">>, <<"https">>], Conf, undefined),
|
||||
Opts = #{required_keys => [<<"keyfile">>, <<"certfile">>, <<"cacertfile">>]},
|
||||
Opts = #{required_keys => [[<<"keyfile">>], [<<"certfile">>], [<<"cacertfile">>]]},
|
||||
case emqx_tls_lib:ensure_ssl_files(?DIR, Https, Opts) of
|
||||
{ok, undefined} ->
|
||||
{error, <<"ssl_cert_not_found">>};
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("emqx/include/emqx_hooks.hrl").
|
||||
-include_lib("emqx_conf/include/emqx_conf.hrl").
|
||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||
|
||||
-define(DEFAULT_CLUSTER_NAME_ATOM, emqxcl).
|
||||
|
||||
-define(OTHER_CLUSTER_NAME_ATOM, test_emqx_cluster).
|
||||
-define(OTHER_CLUSTER_NAME_STRING, "test_emqx_cluster").
|
||||
-define(CLUSTER_RPC_SHARD, emqx_cluster_rpc_shard).
|
||||
|
||||
-define(CONF_DEFAULT, <<
|
||||
"\n"
|
||||
|
@ -54,6 +54,8 @@
|
|||
"}\n"
|
||||
>>).
|
||||
|
||||
-import(emqx_common_test_helpers, [on_exit/1]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Setups
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -89,7 +91,7 @@ init_per_testcase(_, Config) ->
|
|||
timer:sleep(200),
|
||||
Config.
|
||||
|
||||
end_per_testcase(_, Config) ->
|
||||
end_per_testcase(_, _Config) ->
|
||||
case erlang:whereis(node()) of
|
||||
undefined ->
|
||||
ok;
|
||||
|
@ -97,7 +99,8 @@ end_per_testcase(_, Config) ->
|
|||
erlang:unlink(P),
|
||||
erlang:exit(P, kill)
|
||||
end,
|
||||
Config.
|
||||
emqx_common_test_helpers:call_janitor(),
|
||||
ok.
|
||||
|
||||
load_cfg(Cfg) ->
|
||||
ok = emqx_common_test_helpers:load_config(emqx_exhook_schema, Cfg).
|
||||
|
@ -300,6 +303,12 @@ t_cluster_name(_) ->
|
|||
|
||||
emqx_common_test_helpers:stop_apps([emqx, emqx_exhook]),
|
||||
emqx_common_test_helpers:start_apps([emqx, emqx_exhook], SetEnvFun),
|
||||
on_exit(fun() ->
|
||||
emqx_common_test_helpers:stop_apps([emqx, emqx_exhook]),
|
||||
load_cfg(?CONF_DEFAULT),
|
||||
emqx_common_test_helpers:start_apps([emqx_exhook]),
|
||||
mria:wait_for_tables([?CLUSTER_MFA, ?CLUSTER_COMMIT])
|
||||
end),
|
||||
|
||||
?assertEqual(?OTHER_CLUSTER_NAME_STRING, emqx_sys:cluster_name()),
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ init_per_suite(Config) ->
|
|||
end_per_suite(Config) ->
|
||||
emqx_gateway_auth_ct:stop(),
|
||||
emqx_config:erase(gateway),
|
||||
emqx_mgmt_api_test_util:end_suite([cowboy, emqx_authn, emqx_gateway]),
|
||||
emqx_mgmt_api_test_util:end_suite([cowboy, emqx_conf, emqx_authn, emqx_gateway]),
|
||||
Config.
|
||||
|
||||
init_per_testcase(_Case, Config) ->
|
||||
|
|
|
@ -24,12 +24,15 @@ init_suite() ->
|
|||
init_suite([]).
|
||||
|
||||
init_suite(Apps) ->
|
||||
init_suite(Apps, fun set_special_configs/1).
|
||||
init_suite(Apps, fun set_special_configs/1, #{}).
|
||||
|
||||
init_suite(Apps, SetConfigs) ->
|
||||
init_suite(Apps, SetConfigs) when is_function(SetConfigs) ->
|
||||
init_suite(Apps, SetConfigs, #{}).
|
||||
|
||||
init_suite(Apps, SetConfigs, Opts) ->
|
||||
mria:start(),
|
||||
application:load(emqx_management),
|
||||
emqx_common_test_helpers:start_apps(Apps ++ [emqx_dashboard], SetConfigs),
|
||||
emqx_common_test_helpers:start_apps(Apps ++ [emqx_dashboard], SetConfigs, Opts),
|
||||
emqx_common_test_http:create_default_app().
|
||||
|
||||
end_suite() ->
|
||||
|
|
|
@ -295,8 +295,15 @@ t_batch_query_counter(_) ->
|
|||
ok
|
||||
end,
|
||||
fun(Trace) ->
|
||||
QueryTrace = ?of_kind(call_batch_query, Trace),
|
||||
?assertMatch([#{batch := BatchReq} | _] when length(BatchReq) > 1, QueryTrace)
|
||||
QueryTrace = [
|
||||
Event
|
||||
|| Event = #{
|
||||
?snk_kind := call_batch_query,
|
||||
batch := BatchReq
|
||||
} <- Trace,
|
||||
length(BatchReq) > 1
|
||||
],
|
||||
?assertMatch([_ | _], QueryTrace)
|
||||
end
|
||||
),
|
||||
{ok, NMsgs} = emqx_resource:query(?ID, get_counter),
|
||||
|
@ -648,19 +655,18 @@ t_query_counter_async_inflight_batch(_) ->
|
|||
5_000
|
||||
),
|
||||
fun(Trace) ->
|
||||
QueryTrace = ?of_kind(call_batch_query_async, Trace),
|
||||
?assertMatch(
|
||||
[
|
||||
#{
|
||||
QueryTrace = [
|
||||
Event
|
||||
|| Event = #{
|
||||
?snk_kind := call_batch_query_async,
|
||||
batch := [
|
||||
{query, _, {inc_counter, 1}, _, _},
|
||||
{query, _, {inc_counter, 1}, _, _}
|
||||
]
|
||||
}
|
||||
| _
|
||||
} <-
|
||||
Trace
|
||||
],
|
||||
QueryTrace
|
||||
)
|
||||
?assertMatch([_ | _], QueryTrace)
|
||||
end
|
||||
),
|
||||
tap_metrics(?LINE),
|
||||
|
@ -1275,10 +1281,11 @@ t_retry_batch(_Config) ->
|
|||
%% each time should be the original batch (no duplicate
|
||||
%% elements or reordering).
|
||||
ExpectedSeenPayloads = lists:flatten(lists:duplicate(4, Payloads)),
|
||||
?assertEqual(
|
||||
ExpectedSeenPayloads,
|
||||
?projection(n, ?of_kind(connector_demo_batch_inc_individual, Trace))
|
||||
Trace1 = lists:sublist(
|
||||
?projection(n, ?of_kind(connector_demo_batch_inc_individual, Trace)),
|
||||
length(ExpectedSeenPayloads)
|
||||
),
|
||||
?assertEqual(ExpectedSeenPayloads, Trace1),
|
||||
?assertMatch(
|
||||
[#{n := ExpectedCount}],
|
||||
?of_kind(connector_demo_inc_counter, Trace)
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
{profiles, [
|
||||
{test, [
|
||||
{deps, [
|
||||
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.5.0"}}}
|
||||
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.8.5"}}}
|
||||
]}
|
||||
]}
|
||||
]}.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Add support for OCSP stapling and CRL check for SSL MQTT listeners.
|
|
@ -0,0 +1 @@
|
|||
为 SSL MQTT 监听器增加对 OCSP Stapling 的支持。
|
2
mix.exs
2
mix.exs
|
@ -61,7 +61,7 @@ defmodule EMQXUmbrella.MixProject do
|
|||
{:ecpool, github: "emqx/ecpool", tag: "0.5.3", override: true},
|
||||
{:replayq, github: "emqx/replayq", tag: "0.3.7", override: true},
|
||||
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},
|
||||
{:emqtt, github: "emqx/emqtt", tag: "1.8.2", override: true},
|
||||
{:emqtt, github: "emqx/emqtt", tag: "1.8.5", override: true},
|
||||
{:rulesql, github: "emqx/rulesql", tag: "0.1.4"},
|
||||
{:observer_cli, "1.7.1"},
|
||||
{:system_monitor, github: "ieQu1/system_monitor", tag: "3.0.3"},
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
, {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.3"}}}
|
||||
, {replayq, {git, "https://github.com/emqx/replayq.git", {tag, "0.3.7"}}}
|
||||
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
|
||||
, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.8.2"}}}
|
||||
, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.8.5"}}}
|
||||
, {rulesql, {git, "https://github.com/emqx/rulesql", {tag, "0.1.4"}}}
|
||||
, {observer_cli, "1.7.1"} % NOTE: depends on recon 2.5.x
|
||||
, {system_monitor, {git, "https://github.com/ieQu1/system_monitor", {tag, "3.0.3"}}}
|
||||
|
|
|
@ -12,6 +12,7 @@ CMD
|
|||
CN
|
||||
CONNACK
|
||||
CoAP
|
||||
CRLs
|
||||
Cygwin
|
||||
DES
|
||||
DN
|
||||
|
@ -41,6 +42,7 @@ Makefile
|
|||
MitM
|
||||
Multicast
|
||||
NIF
|
||||
OCSP
|
||||
OTP
|
||||
PEM
|
||||
PINGREQ
|
||||
|
|
Loading…
Reference in New Issue