Merge remote-tracking branch 'ce/master' into cassa

This commit is contained in:
JianBo He 2023-03-22 09:30:50 +08:00
commit 65c2da7ef5
197 changed files with 3654 additions and 601 deletions

26
.github/workflows/geen_master.yaml vendored Normal file
View File

@ -0,0 +1,26 @@
---
name: Keep master green
on:
schedule:
# run hourly
- cron: "0 * * * *"
workflow_dispatch:
jobs:
rerun-failed-jobs:
runs-on: ubuntu-22.04
if: github.repository_owner == 'emqx'
permissions:
checks: read
actions: write
steps:
- uses: actions/checkout@v3
- name: run script
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 scripts/rerun-failed-checks.py

View File

@ -152,6 +152,7 @@ $(PROFILES:%=clean-%):
.PHONY: clean-all .PHONY: clean-all
clean-all: clean-all:
@rm -f rebar.lock @rm -f rebar.lock
@rm -rf deps
@rm -rf _build @rm -rf _build
.PHONY: deps-all .PHONY: deps-all

View File

@ -1810,6 +1810,56 @@ server_ssl_opts_schema_ocsp_refresh_http_timeout {
} }
} }
server_ssl_opts_schema_enable_crl_check {
desc {
en: "Whether to enable CRL verification for this listener."
zh: "是否为该监听器启用 CRL 检查。"
}
label: {
en: "Enable CRL Check"
zh: "启用 CRL 检查"
}
}
crl_cache_refresh_http_timeout {
desc {
en: "The timeout for the HTTP request when fetching CRLs. This is"
" a global setting for all listeners."
zh: "获取 CRLs 时 HTTP 请求的超时。 该配置对所有启用 CRL 检查的监听器监听器有效。"
}
label: {
en: "CRL Cache Refresh HTTP Timeout"
zh: "CRL 缓存刷新 HTTP 超时"
}
}
crl_cache_refresh_interval {
desc {
en: "The period to refresh the CRLs from the servers. This is a global setting"
" for all URLs and listeners."
zh: "从服务器刷新CRL的周期。 该配置对所有 URL 和监听器有效。"
}
label: {
en: "CRL Cache Refresh Interval"
zh: "CRL 缓存刷新间隔"
}
}
crl_cache_capacity {
desc {
en: "The maximum number of CRL URLs that can be held in cache. If the cache is at"
" full capacity and a new URL must be fetched, then it'll evict the oldest"
" inserted URL in the cache."
zh: "缓存中可容纳的 CRL URL 的最大数量。"
" 如果缓存的容量已满,并且必须获取一个新的 URL"
"那么它将驱逐缓存中插入的最老的 URL。"
}
label: {
en: "CRL Cache Capacity"
zh: "CRL 缓存容量"
}
}
fields_listeners_tcp { fields_listeners_tcp {
desc { desc {
en: """TCP listeners.""" en: """TCP listeners."""

View File

@ -32,10 +32,10 @@
%% `apps/emqx/src/bpapi/README.md' %% `apps/emqx/src/bpapi/README.md'
%% Community edition %% Community edition
-define(EMQX_RELEASE_CE, "5.0.20"). -define(EMQX_RELEASE_CE, "5.0.21").
%% Enterprise edition %% Enterprise edition
-define(EMQX_RELEASE_EE, "5.0.2-alpha.1"). -define(EMQX_RELEASE_EE, "5.0.2-alpha.2").
%% the HTTP API version %% the HTTP API version
-define(EMQX_API_VERSION, "5.0"). -define(EMQX_API_VERSION, "5.0").

View File

@ -59,4 +59,12 @@
{statistics, true} {statistics, true}
]}. ]}.
{project_plugins, [erlfmt]}. {project_plugins, [
{erlfmt, [
{files, [
"{src,include,test}/*.{hrl,erl,app.src}",
"rebar.config",
"rebar.config.script"
]}
]}
]}.

View File

@ -24,20 +24,20 @@ IsQuicSupp = fun() ->
end, end,
Bcrypt = {bcrypt, {git, "https://github.com/emqx/erlang-bcrypt.git", {tag, "0.6.0"}}}, Bcrypt = {bcrypt, {git, "https://github.com/emqx/erlang-bcrypt.git", {tag, "0.6.0"}}},
Quicer = {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.113"}}}. Quicer = {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.114"}}}.
Dialyzer = fun(Config) -> Dialyzer = fun(Config) ->
{dialyzer, OldDialyzerConfig} = lists:keyfind(dialyzer, 1, Config), {dialyzer, OldDialyzerConfig} = lists:keyfind(dialyzer, 1, Config),
{plt_extra_apps, OldExtra} = lists:keyfind(plt_extra_apps, 1, OldDialyzerConfig), {plt_extra_apps, OldExtra} = lists:keyfind(plt_extra_apps, 1, OldDialyzerConfig),
Extra = OldExtra ++ [quicer || IsQuicSupp()], Extra = OldExtra ++ [quicer || IsQuicSupp()],
NewDialyzerConfig = [{plt_extra_apps, Extra} | OldDialyzerConfig], NewDialyzerConfig = [{plt_extra_apps, Extra} | OldDialyzerConfig],
lists:keystore( lists:keystore(
dialyzer, dialyzer,
1, 1,
Config, Config,
{dialyzer, NewDialyzerConfig} {dialyzer, NewDialyzerConfig}
) )
end. end.
ExtraDeps = fun(C) -> ExtraDeps = fun(C) ->
{deps, Deps0} = lists:keyfind(deps, 1, C), {deps, Deps0} = lists:keyfind(deps, 1, C),

View File

@ -0,0 +1,314 @@
%%--------------------------------------------------------------------
%% 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 CRL cache.
%%--------------------------------------------------------------------
-module(emqx_crl_cache).
%% API
-export([
start_link/0,
start_link/1,
register_der_crls/2,
refresh/1,
evict/1
]).
%% gen_server callbacks
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2
]).
%% internal exports
-export([http_get/2]).
-behaviour(gen_server).
-include("logger.hrl").
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
-define(HTTP_TIMEOUT, timer:seconds(15)).
-define(RETRY_TIMEOUT, 5_000).
-ifdef(TEST).
-define(MIN_REFRESH_PERIOD, timer:seconds(5)).
-else.
-define(MIN_REFRESH_PERIOD, timer:minutes(1)).
-endif.
-define(DEFAULT_REFRESH_INTERVAL, timer:minutes(15)).
-define(DEFAULT_CACHE_CAPACITY, 100).
-record(state, {
refresh_timers = #{} :: #{binary() => timer:tref()},
refresh_interval = timer:minutes(15) :: timer:time(),
http_timeout = ?HTTP_TIMEOUT :: timer:time(),
%% keeps track of URLs by insertion time
insertion_times = gb_trees:empty() :: gb_trees:tree(timer:time(), url()),
%% the set of cached URLs, for testing if an URL is already
%% registered.
cached_urls = sets:new([{version, 2}]) :: sets:set(url()),
cache_capacity = 100 :: pos_integer(),
%% for future use
extra = #{} :: map()
}).
-type url() :: uri_string:uri_string().
-type state() :: #state{}.
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
start_link() ->
Config = gather_config(),
start_link(Config).
start_link(Config = #{cache_capacity := _, refresh_interval := _, http_timeout := _}) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Config, []).
-spec refresh(url()) -> ok.
refresh(URL) ->
gen_server:cast(?MODULE, {refresh, URL}).
-spec evict(url()) -> ok.
evict(URL) ->
gen_server:cast(?MODULE, {evict, URL}).
%% Adds CRLs in DER format to the cache and register them for periodic
%% refresh.
-spec register_der_crls(url(), [public_key:der_encoded()]) -> ok.
register_der_crls(URL, CRLs) when is_list(CRLs) ->
gen_server:cast(?MODULE, {register_der_crls, URL, CRLs}).
%%--------------------------------------------------------------------
%% gen_server behaviour
%%--------------------------------------------------------------------
init(Config) ->
#{
cache_capacity := CacheCapacity,
refresh_interval := RefreshIntervalMS,
http_timeout := HTTPTimeoutMS
} = Config,
State = #state{
cache_capacity = CacheCapacity,
refresh_interval = RefreshIntervalMS,
http_timeout = HTTPTimeoutMS
},
{ok, State}.
handle_call(Call, _From, State) ->
{reply, {error, {bad_call, Call}}, State}.
handle_cast({evict, URL}, State0 = #state{refresh_timers = RefreshTimers0}) ->
emqx_ssl_crl_cache:delete(URL),
MTimer = maps:get(URL, RefreshTimers0, undefined),
emqx_misc:cancel_timer(MTimer),
RefreshTimers = maps:without([URL], RefreshTimers0),
State = State0#state{refresh_timers = RefreshTimers},
?tp(
crl_cache_evict,
#{url => URL}
),
{noreply, State};
handle_cast({register_der_crls, URL, CRLs}, State0) ->
handle_register_der_crls(State0, URL, CRLs);
handle_cast({refresh, URL}, State0) ->
case do_http_fetch_and_cache(URL, State0#state.http_timeout) of
{error, Error} ->
?tp(crl_refresh_failure, #{error => Error, url => URL}),
?SLOG(error, #{
msg => "failed_to_fetch_crl_response",
url => URL,
error => Error
}),
{noreply, ensure_timer(URL, State0, ?RETRY_TIMEOUT)};
{ok, _CRLs} ->
?SLOG(debug, #{
msg => "fetched_crl_response",
url => URL
}),
{noreply, ensure_timer(URL, State0)}
end;
handle_cast(_Cast, State) ->
{noreply, State}.
handle_info(
{timeout, TRef, {refresh, URL}},
State = #state{
refresh_timers = RefreshTimers,
http_timeout = HTTPTimeoutMS
}
) ->
case maps:get(URL, RefreshTimers, undefined) of
TRef ->
?tp(debug, crl_refresh_timer, #{url => URL}),
case do_http_fetch_and_cache(URL, HTTPTimeoutMS) of
{error, Error} ->
?SLOG(error, #{
msg => "failed_to_fetch_crl_response",
url => URL,
error => Error
}),
{noreply, ensure_timer(URL, State, ?RETRY_TIMEOUT)};
{ok, _CRLs} ->
?tp(debug, crl_refresh_timer_done, #{url => URL}),
{noreply, ensure_timer(URL, State)}
end;
_ ->
{noreply, State}
end;
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% internal functions
%%--------------------------------------------------------------------
http_get(URL, HTTPTimeout) ->
httpc:request(
get,
{URL, [{"connection", "close"}]},
[{timeout, HTTPTimeout}],
[{body_format, binary}]
).
do_http_fetch_and_cache(URL, HTTPTimeoutMS) ->
?tp(crl_http_fetch, #{crl_url => URL}),
Resp = ?MODULE:http_get(URL, HTTPTimeoutMS),
case Resp of
{ok, {{_, 200, _}, _, Body}} ->
case parse_crls(Body) of
error ->
{error, invalid_crl};
CRLs ->
%% Note: must ensure it's a string and not a
%% binary because that's what the ssl manager uses
%% when doing lookups.
emqx_ssl_crl_cache:insert(to_string(URL), {der, CRLs}),
?tp(crl_cache_insert, #{url => URL, crls => CRLs}),
{ok, CRLs}
end;
{ok, {{_, Code, _}, _, Body}} ->
{error, {bad_response, #{code => Code, body => Body}}};
{error, Error} ->
{error, {http_error, Error}}
end.
parse_crls(Bin) ->
try
[CRL || {'CertificateList', CRL, not_encrypted} <- public_key:pem_decode(Bin)]
catch
_:_ ->
error
end.
ensure_timer(URL, State = #state{refresh_interval = Timeout}) ->
ensure_timer(URL, State, Timeout).
ensure_timer(URL, State = #state{refresh_timers = RefreshTimers0}, Timeout) ->
?tp(crl_cache_ensure_timer, #{url => URL, timeout => Timeout}),
MTimer = maps:get(URL, RefreshTimers0, undefined),
emqx_misc:cancel_timer(MTimer),
RefreshTimers = RefreshTimers0#{
URL => emqx_misc:start_timer(
Timeout,
{refresh, URL}
)
},
State#state{refresh_timers = RefreshTimers}.
-spec gather_config() ->
#{
cache_capacity := pos_integer(),
refresh_interval := timer:time(),
http_timeout := timer:time()
}.
gather_config() ->
%% TODO: add a config handler to refresh the config when those
%% globals change?
CacheCapacity = emqx_config:get([crl_cache, capacity], ?DEFAULT_CACHE_CAPACITY),
RefreshIntervalMS0 = emqx_config:get([crl_cache, refresh_interval], ?DEFAULT_REFRESH_INTERVAL),
MinimumRefreshInverval = ?MIN_REFRESH_PERIOD,
RefreshIntervalMS = max(RefreshIntervalMS0, MinimumRefreshInverval),
HTTPTimeoutMS = emqx_config:get([crl_cache, http_timeout], ?HTTP_TIMEOUT),
#{
cache_capacity => CacheCapacity,
refresh_interval => RefreshIntervalMS,
http_timeout => HTTPTimeoutMS
}.
-spec handle_register_der_crls(state(), url(), [public_key:der_encoded()]) -> {noreply, state()}.
handle_register_der_crls(State0, URL0, CRLs) ->
#state{cached_urls = CachedURLs0} = State0,
URL = to_string(URL0),
case sets:is_element(URL, CachedURLs0) of
true ->
{noreply, State0};
false ->
emqx_ssl_crl_cache:insert(URL, {der, CRLs}),
?tp(debug, new_crl_url_inserted, #{url => URL}),
State1 = do_register_url(State0, URL),
State2 = handle_cache_overflow(State1),
State = ensure_timer(URL, State2),
{noreply, State}
end.
-spec do_register_url(state(), url()) -> state().
do_register_url(State0, URL) ->
#state{
cached_urls = CachedURLs0,
insertion_times = InsertionTimes0
} = State0,
Now = erlang:monotonic_time(),
CachedURLs = sets:add_element(URL, CachedURLs0),
InsertionTimes = gb_trees:enter(Now, URL, InsertionTimes0),
State0#state{
cached_urls = CachedURLs,
insertion_times = InsertionTimes
}.
-spec handle_cache_overflow(state()) -> state().
handle_cache_overflow(State0) ->
#state{
cached_urls = CachedURLs0,
insertion_times = InsertionTimes0,
cache_capacity = CacheCapacity,
refresh_timers = RefreshTimers0
} = State0,
case sets:size(CachedURLs0) > CacheCapacity of
false ->
State0;
true ->
{_Time, OldestURL, InsertionTimes} = gb_trees:take_smallest(InsertionTimes0),
emqx_ssl_crl_cache:delete(OldestURL),
MTimer = maps:get(OldestURL, RefreshTimers0, undefined),
emqx_misc:cancel_timer(MTimer),
RefreshTimers = maps:remove(OldestURL, RefreshTimers0),
CachedURLs = sets:del_element(OldestURL, CachedURLs0),
?tp(debug, crl_cache_overflow, #{oldest_url => OldestURL}),
State0#state{
insertion_times = InsertionTimes,
cached_urls = CachedURLs,
refresh_timers = RefreshTimers
}
end.
to_string(B) when is_binary(B) ->
binary_to_list(B);
to_string(L) when is_list(L) ->
L.

View File

@ -36,7 +36,8 @@ init([]) ->
child_spec(emqx_stats, worker), child_spec(emqx_stats, worker),
child_spec(emqx_metrics, 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) child_spec(emqx_ocsp_cache, worker),
child_spec(emqx_crl_cache, worker)
] ]
}}. }}.

View File

@ -487,7 +487,8 @@ esockd_opts(ListenerId, Type, Opts0) ->
tcp -> tcp ->
Opts3#{tcp_options => tcp_opts(Opts0)}; Opts3#{tcp_options => tcp_opts(Opts0)};
ssl -> ssl ->
OptsWithSNI = inject_sni_fun(ListenerId, Opts0), OptsWithCRL = inject_crl_config(Opts0),
OptsWithSNI = inject_sni_fun(ListenerId, OptsWithCRL),
SSLOpts = ssl_opts(OptsWithSNI), SSLOpts = ssl_opts(OptsWithSNI),
Opts3#{ssl_options => SSLOpts, tcp_options => tcp_opts(Opts0)} Opts3#{ssl_options => SSLOpts, tcp_options => tcp_opts(Opts0)}
end end
@ -794,3 +795,17 @@ inject_sni_fun(ListenerId, Conf = #{ssl_options := #{ocsp := #{enable_ocsp_stapl
emqx_ocsp_cache:inject_sni_fun(ListenerId, Conf); emqx_ocsp_cache:inject_sni_fun(ListenerId, Conf);
inject_sni_fun(_ListenerId, Conf) -> inject_sni_fun(_ListenerId, Conf) ->
Conf. Conf.
inject_crl_config(
Conf = #{ssl_options := #{enable_crl_check := true} = SSLOpts}
) ->
HTTPTimeout = emqx_config:get([crl_cache, http_timeout], timer:seconds(15)),
Conf#{
ssl_options := SSLOpts#{
%% `crl_check => true' doesn't work
crl_check => peer,
crl_cache => {emqx_ssl_crl_cache, {internal, [{http, HTTPTimeout}]}}
}
};
inject_crl_config(Conf) ->
Conf.

View File

@ -545,10 +545,23 @@ readable_error_msg(Error) ->
{ok, Msg} -> {ok, Msg} ->
Msg; Msg;
false -> false ->
iolist_to_binary(io_lib:format("~0p", [Error])) to_hr_error(Error)
end end
end. end.
to_hr_error(nxdomain) ->
<<"Could not resolve host">>;
to_hr_error(econnrefused) ->
<<"Connection refused">>;
to_hr_error({unauthorized_client, _}) ->
<<"Unauthorized client">>;
to_hr_error({not_authorized, _}) ->
<<"Not authorized">>;
to_hr_error({malformed_username_or_password, _}) ->
<<"Bad username or password">>;
to_hr_error(Error) ->
iolist_to_binary(io_lib:format("~0p", [Error])).
try_to_existing_atom(Convert, Data, Encoding) -> try_to_existing_atom(Convert, Data, Encoding) ->
try Convert(Data, Encoding) of try Convert(Data, Encoding) of
Atom -> Atom ->

View File

@ -226,6 +226,11 @@ roots(low) ->
sc( sc(
ref("trace"), ref("trace"),
#{} #{}
)},
{"crl_cache",
sc(
ref("crl_cache"),
#{hidden => true}
)} )}
]. ].
@ -794,6 +799,37 @@ fields("listeners") ->
} }
)} )}
]; ];
fields("crl_cache") ->
%% Note: we make the refresh interval and HTTP timeout global (not
%% per-listener) because multiple SSL listeners might point to the
%% same URL. If they had diverging timeout options, it would be
%% confusing.
[
{"refresh_interval",
sc(
duration(),
#{
default => <<"15m">>,
desc => ?DESC("crl_cache_refresh_interval")
}
)},
{"http_timeout",
sc(
duration(),
#{
default => <<"15s">>,
desc => ?DESC("crl_cache_refresh_http_timeout")
}
)},
{"capacity",
sc(
pos_integer(),
#{
default => 100,
desc => ?DESC("crl_cache_capacity")
}
)}
];
fields("mqtt_tcp_listener") -> fields("mqtt_tcp_listener") ->
mqtt_listener(1883) ++ mqtt_listener(1883) ++
[ [
@ -2063,6 +2099,8 @@ desc("shared_subscription_group") ->
"Per group dispatch strategy for shared subscription"; "Per group dispatch strategy for shared subscription";
desc("ocsp") -> desc("ocsp") ->
"Per listener OCSP Stapling configuration."; "Per listener OCSP Stapling configuration.";
desc("crl_cache") ->
"Global CRL cache options.";
desc(_) -> desc(_) ->
undefined. undefined.
@ -2260,13 +2298,22 @@ server_ssl_opts_schema(Defaults, IsRanchListener) ->
required => false, required => false,
validator => fun ocsp_inner_validator/1 validator => fun ocsp_inner_validator/1
} }
)},
{"enable_crl_check",
sc(
boolean(),
#{
default => false,
desc => ?DESC("server_ssl_opts_schema_enable_crl_check")
}
)} )}
] ]
]. ].
mqtt_ssl_listener_ssl_options_validator(Conf) -> mqtt_ssl_listener_ssl_options_validator(Conf) ->
Checks = [ Checks = [
fun ocsp_outer_validator/1 fun ocsp_outer_validator/1,
fun crl_outer_validator/1
], ],
case emqx_misc:pipeline(Checks, Conf, not_used) of case emqx_misc:pipeline(Checks, Conf, not_used) of
{ok, _, _} -> {ok, _, _} ->
@ -2301,6 +2348,18 @@ ocsp_inner_validator(#{<<"enable_ocsp_stapling">> := true} = Conf) ->
), ),
ok. ok.
crl_outer_validator(
#{<<"enable_crl_check">> := true} = SSLOpts
) ->
case maps:get(<<"verify">>, SSLOpts) of
verify_peer ->
ok;
_ ->
{error, "verify must be verify_peer when CRL check is enabled"}
end;
crl_outer_validator(_SSLOpts) ->
ok.
%% @doc Make schema for SSL client. %% @doc Make schema for SSL client.
-spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema(). -spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema().
client_ssl_opts_schema(Defaults) -> client_ssl_opts_schema(Defaults) ->

View File

@ -0,0 +1,237 @@
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2015-2022. 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.
%%
%% %CopyrightEnd%
%%--------------------------------------------------------------------
%% Copyright (c) 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.
%%--------------------------------------------------------------------
%----------------------------------------------------------------------
% Based on `otp/lib/ssl/src/ssl_crl_cache.erl'
%----------------------------------------------------------------------
%----------------------------------------------------------------------
%% Purpose: Simple default CRL cache
%%----------------------------------------------------------------------
-module(emqx_ssl_crl_cache).
-include_lib("ssl/src/ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
-behaviour(ssl_crl_cache_api).
-export_type([crl_src/0, uri/0]).
-type crl_src() :: {file, file:filename()} | {der, public_key:der_encoded()}.
-type uri() :: uri_string:uri_string().
-export([lookup/3, select/2, fresh_crl/2]).
-export([insert/1, insert/2, delete/1]).
%% 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 => ".*"
}}
]).
%%====================================================================
%% Cache callback API
%%====================================================================
lookup(
#'DistributionPoint'{distributionPoint = {fullName, Names}},
_Issuer,
CRLDbInfo
) ->
get_crls(Names, CRLDbInfo);
lookup(_, _, _) ->
not_available.
select(GenNames, CRLDbHandle) when is_list(GenNames) ->
lists:flatmap(
fun
({directoryName, Issuer}) ->
select(Issuer, CRLDbHandle);
(_) ->
[]
end,
GenNames
);
select(Issuer, {{_Cache, Mapping}, _}) ->
case ssl_pkix_db:lookup(Issuer, Mapping) of
undefined ->
[];
CRLs ->
CRLs
end.
fresh_crl(#'DistributionPoint'{distributionPoint = {fullName, Names}}, CRL) ->
case get_crls(Names, undefined) of
not_available ->
CRL;
NewCRL ->
NewCRL
end.
%%====================================================================
%% API
%%====================================================================
insert(CRLs) ->
insert(?NO_DIST_POINT, CRLs).
insert(URI, {file, File}) when is_list(URI) ->
case file:read_file(File) of
{ok, PemBin} ->
PemEntries = public_key:pem_decode(PemBin),
CRLs = [
CRL
|| {'CertificateList', CRL, not_encrypted} <-
PemEntries
],
do_insert(URI, CRLs);
Error ->
Error
end;
insert(URI, {der, CRLs}) ->
do_insert(URI, CRLs).
delete({file, File}) ->
case file:read_file(File) of
{ok, PemBin} ->
PemEntries = public_key:pem_decode(PemBin),
CRLs = [
CRL
|| {'CertificateList', CRL, not_encrypted} <-
PemEntries
],
ssl_manager:delete_crls({?NO_DIST_POINT, CRLs});
Error ->
Error
end;
delete({der, CRLs}) ->
ssl_manager:delete_crls({?NO_DIST_POINT, CRLs});
delete(URI) ->
case uri_string:normalize(URI, [return_map]) of
#{scheme := "http", path := Path} ->
ssl_manager:delete_crls(string:trim(Path, leading, "/"));
_ ->
{error, {only_http_distribution_points_supported, URI}}
end.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_insert(URI, CRLs) ->
case uri_string:normalize(URI, [return_map]) of
#{scheme := "http", path := Path} ->
ssl_manager:insert_crls(string:trim(Path, leading, "/"), CRLs);
_ ->
{error, {only_http_distribution_points_supported, URI}}
end.
get_crls([], _) ->
not_available;
get_crls(
[{uniformResourceIdentifier, "http" ++ _ = URL} | Rest],
CRLDbInfo
) ->
case cache_lookup(URL, CRLDbInfo) of
[] ->
handle_http(URL, Rest, CRLDbInfo);
CRLs ->
CRLs
end;
get_crls([_ | Rest], CRLDbInfo) ->
%% unsupported CRL location
get_crls(Rest, CRLDbInfo).
http_lookup(URL, Rest, CRLDbInfo, Timeout) ->
case application:ensure_started(inets) of
ok ->
http_get(URL, Rest, CRLDbInfo, Timeout);
_ ->
get_crls(Rest, CRLDbInfo)
end.
http_get(URL, Rest, CRLDbInfo, Timeout) ->
case emqx_crl_cache:http_get(URL, Timeout) of
{ok, {_Status, _Headers, Body}} ->
case Body of
<<"-----BEGIN", _/binary>> ->
Pem = public_key:pem_decode(Body),
CRLs = lists:filtermap(
fun
({'CertificateList', CRL, not_encrypted}) ->
{true, CRL};
(_) ->
false
end,
Pem
),
emqx_crl_cache:register_der_crls(URL, CRLs),
CRLs;
_ ->
try public_key:der_decode('CertificateList', Body) of
_ ->
CRLs = [Body],
emqx_crl_cache:register_der_crls(URL, CRLs),
CRLs
catch
_:_ ->
get_crls(Rest, CRLDbInfo)
end
end;
{error, _Reason} ->
get_crls(Rest, CRLDbInfo)
end.
cache_lookup(_, undefined) ->
[];
cache_lookup(URL, {{Cache, _}, _}) ->
#{path := Path} = uri_string:normalize(URL, [return_map]),
case ssl_pkix_db:lookup(string:trim(Path, leading, "/"), Cache) of
undefined ->
[];
[CRLs] ->
CRLs
end.
handle_http(URI, Rest, {_, [{http, Timeout}]} = CRLDbInfo) ->
CRLs = http_lookup(URI, Rest, CRLDbInfo, Timeout),
%% Uncomment to improve performance, but need to
%% implement cache limit and or cleaning to prevent
%% DoS attack possibilities
%%insert(URI, {der, CRLs}),
CRLs;
handle_http(_, Rest, CRLDbInfo) ->
get_crls(Rest, CRLDbInfo).

View File

@ -16,7 +16,7 @@
-module(emqx_common_test_helpers). -module(emqx_common_test_helpers).
-include("emqx_authentication.hrl"). -include_lib("emqx/include/emqx_authentication.hrl").
-type special_config_handler() :: fun(). -type special_config_handler() :: fun().
@ -262,12 +262,13 @@ app_schema(App) ->
end. end.
mustache_vars(App, Opts) -> mustache_vars(App, Opts) ->
ExtraMustacheVars = maps:get(extra_mustache_vars, Opts, []), ExtraMustacheVars = maps:get(extra_mustache_vars, Opts, #{}),
[ Defaults = #{
{platform_data_dir, app_path(App, "data")}, platform_data_dir => app_path(App, "data"),
{platform_etc_dir, app_path(App, "etc")}, platform_etc_dir => app_path(App, "etc"),
{platform_log_dir, app_path(App, "log")} platform_log_dir => app_path(App, "log")
] ++ ExtraMustacheVars. },
maps:merge(Defaults, ExtraMustacheVars).
render_config_file(ConfigFile, Vars0) -> render_config_file(ConfigFile, Vars0) ->
Temp = Temp =
@ -275,7 +276,7 @@ render_config_file(ConfigFile, Vars0) ->
{ok, T} -> T; {ok, T} -> T;
{error, Reason} -> error({failed_to_read_config_template, ConfigFile, Reason}) {error, Reason} -> error({failed_to_read_config_template, ConfigFile, Reason})
end, end,
Vars = [{atom_to_list(N), iolist_to_binary(V)} || {N, V} <- Vars0], Vars = [{atom_to_list(N), iolist_to_binary(V)} || {N, V} <- maps:to_list(Vars0)],
Targ = bbmustache:render(Temp, Vars), Targ = bbmustache:render(Temp, Vars),
NewName = ConfigFile ++ ".rendered", NewName = ConfigFile ++ ".rendered",
ok = file:write_file(NewName, Targ), ok = file:write_file(NewName, Targ),

File diff suppressed because it is too large Load Diff

View File

@ -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-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFdTCCA12gAwIBAgICEAUwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
DTIzMDExODEyMzY1NloXDTMzMDQyNTEyMzY1NlowgYQxCzAJBgNVBAYTAlNFMRIw
EAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAcMCVN0b2NraG9sbTESMBAGA1UECgwJ
TXlPcmdOYW1lMRkwFwYDVQQLDBBNeUludGVybWVkaWF0ZUNBMR4wHAYDVQQDDBVj
bGllbnQtbm8tZGlzdC1wb2ludHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCYQqNF7o20tEwyXphDgtwkZ628baYzQoCmmaufR+5SPQWdTN+GFeApv0dP
4y/ncZV24rgButMo73e4+wPsILwSGhaVIU0mMaCmexyC4W6INBkQsVB5FAd/YM0O
gdxS6A42h9HZTaAJ+4ftgFdOOHiP3lwicXeIYykAE7Y5ikxlnHgi8p1PTLowN4Q+
AjuXChRzmU16cUEAevZKkTVf7VCcK66aJsxBsxfykkGHhc6qLqmlMt6Te6DPCi/R
KP/kARTDWNEkp6qtpvzByYFYAKPSZxPuryajAC3RLuGNkVSB+PZ6NnZW6ASeTdra
Lwuiwsi5XPBeFb0147naQOBzSGG/AgMBAAGjggEHMIIBAzAJBgNVHRMEAjAAMBEG
CWCGSAGG+EIBAQQEAwIFoDBBBglghkgBhvhCAQ0ENBYyT3BlblNTTCBHZW5lcmF0
ZWQgQ2xpZW50IENlcnRpZmljYXRlIChubyBDUkwgaW5mbykwHQYDVR0OBBYEFBiV
sjDe46MixvftT/wej1mxGuN7MB8GA1UdIwQYMBaAFExwhjsVUom6tQ+Sqq6xMUET
vnPzMA4GA1UdDwEB/wQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
AwQwMQYIKwYBBQUHAQEEJTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0
Ojk4NzcwDQYJKoZIhvcNAQELBQADggIBAKBEnKYVLFtZb3MI0oMJkrWBssVCq5ja
OYomZ61I13QLEeyPevTSWAcWFQ4zQDF/SWBsXjsrC+JIEjx2xac6XCpxcx3jDUgo
46u/hx2rT8tMKa60hW0V1Dk6w8ZHiCe94BlFLsWFKnn6dVzoJd2u3vgUaleh3uxF
hug8XY+wmHd36rO0kVe3DrsqdIdOfhMiJLDxU0cBA79vI5kCvqB8DIwCWtOzkA82
EPl3Iws5NPhuFAR9u0xOQu0akzmSJFcEGLZ4qfatHD/tZGRduyFvMKy5iIeMzuEs
2etm01tfLHqgKGOKp5LjPm7Aoac/GeVoTvctGF+wayvOuYE7inlGZToz3kQMMzHZ
ZGBBgOhXbR2y74QoFv6DUqmmTRbGfiLYyErA5r881ntgciQi02xrGjoAFntvKb+H
HNB22Qprz16OmdC9dJKF2RhO6Cketdhv65wFWw6xlhRMCWYPY3CI8tWkxS4A4yit
RZQZg3yaeHXMaCAu5HxuqAQXKGjz+7w7N6diwbT7o7CfKk8iHUrGfkQ5nCS0GZ1r
lU1vgKtdzVvJ6HmBrCRcdNqh/L/wdIltwI/52j+TKRtELM1qHuLAYmhcRBW+2wuH
ewaNA9KEgEk6JC+iR8uOBi0ZLkMIm47j+ZLJRJVUfgkVEEFjyiYSFfpwwcgT+/Aw
EczVZOdUEbDM
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCYQqNF7o20tEwy
XphDgtwkZ628baYzQoCmmaufR+5SPQWdTN+GFeApv0dP4y/ncZV24rgButMo73e4
+wPsILwSGhaVIU0mMaCmexyC4W6INBkQsVB5FAd/YM0OgdxS6A42h9HZTaAJ+4ft
gFdOOHiP3lwicXeIYykAE7Y5ikxlnHgi8p1PTLowN4Q+AjuXChRzmU16cUEAevZK
kTVf7VCcK66aJsxBsxfykkGHhc6qLqmlMt6Te6DPCi/RKP/kARTDWNEkp6qtpvzB
yYFYAKPSZxPuryajAC3RLuGNkVSB+PZ6NnZW6ASeTdraLwuiwsi5XPBeFb0147na
QOBzSGG/AgMBAAECggEACSMuozq+vFJ5pCgzIRIQXgruzTkTWU4rZFQijYuGjN7m
oFsFqwlTC45UHEI5FL2nR5wxiMEKfRFp8Or3gEsyni98nXSDKcCesH8A5gXbWUcv
HeZWOv3tuUI47B709vDAMZuTB2R2L0MuFB24n5QaACBLDTIcB05UHpIQRIG9NffH
MhxqFB2kuakp67VekYGZkBCNkqfL3VQZIGRpQC8SvpnRXELqZgI4MyJgvkK6myWj
Vtpwm8YiOQoJHJx4raoVfS2NWTsCwL0M0aXMMtmM2QfMP/xB9OifxnmDDBs7Tie8
0Wri845xLTCYthaU8B06rhoQdKXoqKmQMoF2doPm8QKBgQDN+0E0PtPkyxIho8pV
CsQnmif91EQQqWxOdkHbE96lT0UKu6ziBSbB4ClRHYil5c8p7INxRpj7pruOY3Kw
MAcacIMMBNhLBJL4R0hr/pwr18WOZxCIMcLHTaCfbVqL71TKp4/6C+GexZfaYJ46
IZEpLU5RPmD4f9MPIDDm6KcPxwKBgQC9O9TOor93g+A4sU54CGOqvVDrdi5TnGF8
YdimvUsT20gl2WGX5vq3OohzZi7U8FuxKHWpbgh2efqGLcFsRNFZ/T0ZXX4DDafN
Gzyu/DMVuFO4ccgFJNnl45w3/yFG40kL6yS8kss/iEYu550/uOZ1FjH+kJ0vjV6G
JD8q0PgOSQKBgG2i9cLcSia2nBEBwFlhoKS/ndeyWwRPWZGtykHUoqZ0ufgLiurG
+SkqqnM9eBVta8YR2Ki7fgQ8bApPDqWO+sjs6CPGlGXhqmSydG7fF7sSX1n7q8YC
Tn2M6RjSuOZQ3l37sFvUZSQAYmJfGPkyErTLI6uEu1KpnuqnJMBTR1DTAoGAIGQn
bx9oirqmHM4s0lsNRGKXgVZ/Y4x3G2VcQl5QhZuZY/ErxWaiL87zIF2zUnu6Fj8I
tPHCvRTwDxux6ih1dWPlm3vnX/psaK1q28ELtYIRwpanWEoQiktFqEghmBK7pDCh
3y15YOygptK6lfe+avhboml6nnMiZO+7aEbQzxECgYALuUM4fo1dQYmYuZIqZoFJ
TXGyzMkNGs61SMiD6mW6XgXj5h5T8Q0MdpmHkwsm+z9A/1of5cxkE6d8HCCz+dt5
tnY7OC0gYB1+gDld8MZgFgP6k0qklreLVhzEz11TbMldifa1EE4VjUDG/NeAEtbq
GbLaw0NhGJtRCgL9Bc7i7g==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFnDCCA4SgAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
DTIzMDExMjEzMDgxNloXDTMzMDQxOTEzMDgxNlowfTELMAkGA1UEBhMCU0UxEjAQ
BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN
eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExFzAVBgNVBAMMDmNs
aWVudC1yZXZva2VkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs+R6
PDtIxVlUoLYbDBbaVcxgoLjnWcvqL8wSqyWuqi/Y3cjuNYCziR9nR5dWajtkBjzJ
HyhgAr6gBVSRt4RRmDXoOcprK3GcpowAr65UAmC4hdH0af6FdKjKCnFw67byUg52
f7ueXZ6t/XuuKxlU/f2rjXVwmmnlhBi5EHDkXxvfgWXJekDfsPbW9j0kaCUWCpfj
rzGbfkXqrPkslO41PYlCbPxoiRItJjindFjcQySYvRq7A2uYMGsrxv4n3rzo5NGt
goBmnGj61ii9WOdopcFxKirhIB9zrxC4x0opRfIaF/n1ZXk6NOnaDxu1LTZ18wfC
ZB979ge6pleeKoPf7QIDAQABo4IBNjCCATIwCQYDVR0TBAIwADARBglghkgBhvhC
AQEEBAMCBaAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIENsaWVu
dCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUQeItXr3nc6CZ++G9UCoq1YlQ9oowHwYD
VR0jBBgwFoAUTHCGOxVSibq1D5KqrrExQRO+c/MwDgYDVR0PAQH/BAQDAgXgMB0G
A1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDA7BgNVHR8ENDAyMDCgLqAshipo
dHRwOi8vbG9jYWxob3N0Ojk4NzgvaW50ZXJtZWRpYXRlLmNybC5wZW0wMQYIKwYB
BQUHAQEEJTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0Ojk4NzcwDQYJ
KoZIhvcNAQELBQADggIBAIFuhokODd54/1B2JiNyG6FMq/2z8B+UquC2iw3p2pyM
g/Jz4Ouvg6gGwUwmykEua06FRCxx5vJ5ahdhXvKst/zH/0qmYTFNMhNsDy76J/Ot
Ss+VwQ8ddpEG3EIUI9BQxB3xL7z7kRQzploQjakNcDWtDt1BmN05Iy2vz4lnYJky
Kss6ya9jEkNibHekhxJuchJ0fVGlVe74MO7RNDFG7+O3tMlxu0zH/LpW093V7BI2
snXNAwQBizvWTrDKWLDu5JsX8KKkrmDtFTs9gegnxDCOYdtG5GbbMq+H1SjWUJPV
wiXTF8/eE02s4Jzm7ZAxre4bRt/hAg7xTGmDQ1Hn+LzLn18I9LaW5ZWqSwwpgv+g
Z/jiLO9DJ/y525Cl7DLCpSFoDTWlQXouKhcgALcVay/cXCsZ3oFZCustburLiJi/
zgBeEk1gVpwljriJLeZifyfWtJx6yfgB/h6fid8XLsGRD+Yc8Tzs8J1LIgi+j4ZT
UzKX3B85Kht/dr43UDMtWOF3edkOMaJu7rcg5tTsK+LIyHtXvebKPVvvA9f27Dz/
4gmhAwwqS87Xv3FMVhZ03DNOJ6XAF+T6OTEqwYs+iK56IMSl1Jy+bCzo0j5jZVbl
XFwGxUHzM7pfM6PDx657oUxG1QwM/fIWA18F+kY/yigXxq6pYMeAiQsPanOThgHp
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCz5Ho8O0jFWVSg
thsMFtpVzGCguOdZy+ovzBKrJa6qL9jdyO41gLOJH2dHl1ZqO2QGPMkfKGACvqAF
VJG3hFGYNeg5ymsrcZymjACvrlQCYLiF0fRp/oV0qMoKcXDrtvJSDnZ/u55dnq39
e64rGVT9/auNdXCaaeWEGLkQcORfG9+BZcl6QN+w9tb2PSRoJRYKl+OvMZt+Reqs
+SyU7jU9iUJs/GiJEi0mOKd0WNxDJJi9GrsDa5gwayvG/ifevOjk0a2CgGacaPrW
KL1Y52ilwXEqKuEgH3OvELjHSilF8hoX+fVleTo06doPG7UtNnXzB8JkH3v2B7qm
V54qg9/tAgMBAAECggEAAml+HRgjZ+gEezot3yngSBW7NvR7v6e9DmKDXpGdB7Go
DANBdGyzG5PU9/AGy9pbgzzl6nnJXcgOD7w8TvRifrK8WCgHa1f05IPMj458GGMR
HlQ8HX647eFEgkLWo4Z6tdB1VM2geDtkNFmn8nJ+wgAYgIdSWPOyDOUi+B43ZbIN
eaLWkP2fiX9tcJp41cytW+ng2YIm4s90Nt4FJPNBNzOrhVm35jciId02MmEjCEnr
0YbK9uoMDC2YLg8vhRcjtsUHV2rREkwEAQj8nCWvWWheIwk943d6OicGAD/yebpV
PTjtlZlpIbrovfvuMcoTxJg3WS8LTg/+cNWAX5a3eQKBgQDcRY7nVSJusYyN0Bij
YWc9H47wU+YucaGT25xKe26w1pl6s4fmr1Sc3NcaN2iyUv4BuAvaQzymHe4g9deU
D9Ws/NCQ9EjHJJsklNyn2KCgkSp7oPKhPwyl64XfPdV2gr5AD6MILf7Rkyib5sSf
1WK8i25KatT7M4mCtrBVJYHNpQKBgQDREjwPIaQBPXouVpnHhSwRHfKD0B1a2koq
4VE6Fnf3ogkiGfV9kqXwIfPHL0tfotFraM3FFmld8RcxhKUPr4oj+K9KTxmMD9lm
9Hal0ANXYmHs5a1iHyoNmTpBGHALWLT9fCoeg+EIYabi2+P1c7cDIdUPkEzo4GmI
nCIpv7hGqQKBgEFUC+8GK+EinWoN1tDV+ZWCP5V9fJ43q1E7592bQBgIfZqLlnnP
dEvVn6Ix3sZMoPMHj9Ra7qjh5Zc28ooCLEBS9tSW7uLJM44k7FCHihQ1GaFy+aLj
HTA0aw7rutycKCq9uH+bjKDBgWDDj3tMAS2kOMCvcJ1UCquO3TtTlWzVAoGBAIDN
8yJ/X0NEVNnnkKZTbWq+QILk3LD0e20fk6Nt5Es0ENxpkczjZEglIsM8Z/trnAnI
b71UqWWu+tMPHYIka77tn1DwmpSnzxCW2+Ib3XMgsaP5fHBPMuFd3X3tSFo1NIxW
yrwyE5nOT7rELhUyTTYoydLk2/09BMedKY7/BtDBAoGAXeX1pX74K1i/uWyYKwYZ
sskRueSo9whDJuZWgNiUovArr57eA+oA+bKdFpiE419348bkFF8jNoGFQ6MXMedD
LqHAYIj+ZPIC4+rObHqO5EaIyblgutwx3citkQp7HXDBxojnOKA9mKQXj1vxCaL1
/1fFNJQCzEqwnKwnhI2MJ28=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFljCCA36gAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
DTIzMDExMjEzMDgxNloXDTMzMDQxOTEzMDgxNlowdzELMAkGA1UEBhMCU0UxEjAQ
BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN
eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExETAPBgNVBAMMCE15
Q2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvGuAShewEo8V
/+aWVO/MuUt92m8K0Ut4nC2gOvpjMjf8mhSSf6KfnxPklsFwP4fdyPOjOiXwCsf3
1QO5fjVr8to3iGTHhEyZpzRcRqmw1eYJC7iDh3BqtYLAT30R+Kq6Mk+f4tXB5Lp/
2jXgdi0wshWagCPgJO3CtiwGyE8XSa+Q6EBYwzgh3NFbgYdJma4x+S86Y/5WfmXP
zF//UipsFp4gFUqwGuj6kJrN9NnA1xCiuOxCyN4JuFNMfM/tkeh26jAp0OHhJGsT
s3YiUm9Dpt7Rs7o0so9ov9K+hgDFuQw9HZW3WIJI99M5a9QZ4ZEQqKpABtYBl/Nb
VPXcr+T3fQIDAQABo4IBNjCCATIwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMC
BaAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIENsaWVudCBDZXJ0
aWZpY2F0ZTAdBgNVHQ4EFgQUOIChBA5aZB0dPWEtALfMIfSopIIwHwYDVR0jBBgw
FoAUTHCGOxVSibq1D5KqrrExQRO+c/MwDgYDVR0PAQH/BAQDAgXgMB0GA1UdJQQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDBDA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8v
bG9jYWxob3N0Ojk4NzgvaW50ZXJtZWRpYXRlLmNybC5wZW0wMQYIKwYBBQUHAQEE
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0Ojk4NzcwDQYJKoZIhvcN
AQELBQADggIBAE0qTL5WIWcxRPU9oTrzJ+oxMTp1JZ7oQdS+ZekLkQ8mP7T6C/Ew
6YftjvkopnHUvn842+PTRXSoEtlFiTccmA60eMAai2tn5asxWBsLIRC9FH3LzOgV
/jgyY7HXuh8XyDBCDD+Sj9QityO+accTHijYAbHPAVBwmZU8nO5D/HsxLjRrCfQf
qf4OQpX3l1ryOi19lqoRXRGwcoZ95dqq3YgTMlLiEqmerQZSR6iSPELw3bcwnAV1
hoYYzeKps3xhwszCTz2+WaSsUO2sQlcFEsZ9oHex/02UiM4a8W6hGFJl5eojErxH
7MqaSyhwwyX6yt8c75RlNcUThv+4+TLkUTbTnWgC9sFjYfd5KSfAdIMp3jYzw3zw
XEMTX5FaLaOCAfUDttPzn+oNezWZ2UyFTQXQE2CazpRdJoDd04qVg9WLpQxLYRP7
xSFEHulOPccdAYF2C45yNtJAZyWKfGaAZIxrgEXbMkcdDMlYphpRwpjS8SIBNZ31
KFE8BczKrg2qO0ywIjanPaRgrFVmeSvBKeU/YLQVx6fZMgOk6vtidLGZLyDXy0Ff
yaZSoj+on++RDz1IXb96Y8scuNlfcYI8QeoNjwiLtf80BV8SRJiG4e/jTvMf/z9L
kWrnDWvx4xkUmxFg4TK42dkNp7sEYBTlVVq9fjKE92ha7FGZRqsxOLNQ
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC8a4BKF7ASjxX/
5pZU78y5S33abwrRS3icLaA6+mMyN/yaFJJ/op+fE+SWwXA/h93I86M6JfAKx/fV
A7l+NWvy2jeIZMeETJmnNFxGqbDV5gkLuIOHcGq1gsBPfRH4qroyT5/i1cHkun/a
NeB2LTCyFZqAI+Ak7cK2LAbITxdJr5DoQFjDOCHc0VuBh0mZrjH5Lzpj/lZ+Zc/M
X/9SKmwWniAVSrAa6PqQms302cDXEKK47ELI3gm4U0x8z+2R6HbqMCnQ4eEkaxOz
diJSb0Om3tGzujSyj2i/0r6GAMW5DD0dlbdYgkj30zlr1BnhkRCoqkAG1gGX81tU
9dyv5Pd9AgMBAAECggEAAifx6dZKIeNkQ8OaNp5V2IKIPSqBOV4/h/xKMkUZXisV
eDmTCf8du0PR7hfLqrt9xYsGDv+6FQ1/8K231l8qR0tP/6CTl/0ynM4qqEAGeFXN
3h2LvM4liFbdjImechrcwcnVaNKg/DogT5zHUYSMtB/rokaG0VBO3IX/+SGz0aXi
LOLAx6SPaLOVX9GYUCiigTSEDwaQA+F3F6J2fR4u8PrXo+OQUqxjQ/fGXWp+4IfA
6djlpvzO2849/WPB1tL20iLXJlL2OL0UgQNtbKWTjexMe+wgCR5BzCwTyPsQvMwX
YOQrTOwgF3b6O+gLks5wSRT0ivq1sKgzA534+X4M+wKBgQDirPTLlrYobOO8KUpV
LOJU8x9leiRNU9CZWrW/mOw/BXGXikqNWvgL595vvADsjYciuRxSqEE7lClB8Pp9
20TMlES9orx7gdoQJCodpNV1BuBJhE9YtUiXzWAj+7m3D9LsXM1ewW/2A7Vvopj3
4zKY7uHAFlo3nXwLOfChG5/i9wKBgQDUy5fPFa58xmn7Elb6x4vmUDHg6P4pf75E
XHRQvNA8I7DTrpqfcsF1N4WuJ3Lm//RSpw7bnyqP20GoEfGHu/iCUPf29B7CuXhO
vvD+I8uPdn8EcKUBWV+V0xNQN/gCe0TzrEjAkZcO2Lq0j93R8HVl3BbowxgRvQV9
GmxQG/boKwKBgFeV8uSzsGEAaiKrZbBxrmaappgEUQCcES8gULfes/JJ/TFL2zCx
ZMTc7CMKZuUAbqXpFtuNbd9CiYqUPYXh8ryF0eXgeqnSa9ruzmMz7NLSPFnLyQkC
yzD0x2BABOuKLrrrxOMHJWbO2g1vq2GlJUjYjNw3BtcUf/iqg6MM1IPTAoGAWYWJ
SSqS7JVAcsrFYt1eIrdsNHVwr565OeM3X9v/Mr3FH1jeXeQWNSz1hU29Ticx7y+u
1YBBlKGmHoHl/bd7lb9ggjkzU7JZRa+YjSIb+i/cwc5t7IJf7xUMk/vnz4tyd5zs
Qm89gJZ2/Y1kwXSKvx53WNbyokvGKlpaZN1O418CgYACliGux77pe4bWeXSFFd9N
50ipxDLVghw1c5AiZn25GR5YHJZaV4R0wmFcHdZvogLKi0jDMPvU69PaiT8eX/A1
COkxv7jY1vtKlEtb+gugMjMN8wvb2va4kyFamjqnleiZlBSqIF/Y17wBoMvaWgZ0
bEPCN//ts5hBwgb1TwGrrg==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFljCCA36gAwIBAgICEAowDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
DTIzMDMxNjIwMjAzMloXDTMzMDYyMTIwMjAzMlowdjELMAkGA1UEBhMCU0UxEjAQ
BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN
eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExEDAOBgNVBAMMB0Ns
aWVudDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDcDhlEvUIYc9uA
ocOBXt5thKrovs+8V0Eus/WrHMTKBk0Kw4X+7HBaRBoZj2sZpYfN63lVaO75kW4I
uJuorGj5PAXYWJj+4uAsCc95xAN/liCuHJnxE5togWVt8W+z0Zll98RIpiCohqiE
FLDL4X6FREL07GLgQZ/BFORvAwU+Gog05AFh43iZDnJl8MmrG2HBSRXtSZ6vQj9A
NrOSqz5eK4YIHEEsgwTWQmhtNwu3Y+GzrAPWCA4TeYrSRwIrnGh20fOWXkAMldS4
eRXmBztEsXMGqbe6oYO1QPYOlmoGO8EaaDPJ2sFIuM0zn98Alq3kCnRhM5Bi9RpJ
7IpudIopAgMBAAGjggE3MIIBMzAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIF
oDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRp
ZmljYXRlMB0GA1UdDgQWBBQoIuXq3wG6JEzAEj9wPe7am0OVgjAfBgNVHSMEGDAW
gBRMcIY7FVKJurUPkqqusTFBE75z8zAOBgNVHQ8BAf8EBAMCBeAwHQYDVR0lBBYw
FAYIKwYBBQUHAwIGCCsGAQUFBwMEMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9s
b2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUxLmNybC5wZW0wMQYIKwYBBQUHAQEE
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0Ojk4NzcwDQYJKoZIhvcN
AQELBQADggIBAHqKYcwkm3ODPD7Mqxq3bsswSXregWfc8tqfIBc5FZg2F+IzhxcJ
kINB0lmcNdLALK6ka0sDs1Nrj1KB96NcHUqE+WY/qPS1Yksr34yFatb1ddlKQ9HK
VRrIsi0ZfjBpHpvoQ0GsLeyRKm7iN/Fm5H9u8rw6RBu0Oe/l20FVSQIDzldYw51L
uV/E9No8ZhdQ2Dffujs8madI7b7I1NMXS+Z1pZ+gYrz6O60tDEprE+rYuYWypURr
fK+DnLLl+KQ+eekTPynw7LRpFzI/1cOMmd4BRnsBHCbCObfNp7WPasemZOEXGIlZ
CQwZS62DYOJE4u4Nz5pSF+JgXfr6X/Im6Y1SV900xVHfoL0GpFDI9k+0Y5ncHfSH
+V9HlRWB3zqQF+yla32XOpBbER0vFDH52gp8/o1ZGg7rr6KrP4QKxnqywNLiAPDX
txaAykZhON7uG8j+Lbjx5Ik91NRn9Fd5NH/vtT33a4uig2TP9EWd7EPcD2z8ONuD
yiK3S37XAnmSKKX4HcCpEb+LedtqQo/+sqWyWXkpKdpkUSozvcYS4J/ob3z9N2IE
qIH5I+Mty1I4EB4W89Pem8DHNq86Lt0Ea6TBtPTV8NwR5aG2vvLzb5lNdpANXYcp
nGr57mTWaHnQh+yqgy66J++k+WokWkAkwE989AvUfNoQ+Jr6cTH8nKo2
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcDhlEvUIYc9uA
ocOBXt5thKrovs+8V0Eus/WrHMTKBk0Kw4X+7HBaRBoZj2sZpYfN63lVaO75kW4I
uJuorGj5PAXYWJj+4uAsCc95xAN/liCuHJnxE5togWVt8W+z0Zll98RIpiCohqiE
FLDL4X6FREL07GLgQZ/BFORvAwU+Gog05AFh43iZDnJl8MmrG2HBSRXtSZ6vQj9A
NrOSqz5eK4YIHEEsgwTWQmhtNwu3Y+GzrAPWCA4TeYrSRwIrnGh20fOWXkAMldS4
eRXmBztEsXMGqbe6oYO1QPYOlmoGO8EaaDPJ2sFIuM0zn98Alq3kCnRhM5Bi9RpJ
7IpudIopAgMBAAECggEARcly2gnrXDXh9vlWN0EO6UyZpxZcay6AzX7k+k81WZyF
8lPvutjhCL9wR4rkPE3ys6tp31xX7W3hp4JkWynSYLhYYjQ20R7CWTUDR2qScXP7
CTyo1XuSXaIruKJI+o4OR/g7l46X7NpHtxuYtg/dQAZV9bbB5LzrHSCzEUGz9+17
jV//cBgLBiMdlbdLuQoGt4NQpBkNrauBVFq7Nq648uKkICmUo3Bzn/dfn3ehB+Zc
+580S+tawYd224j19tFQmd5oK8tfjqKuHenNGjp/gsRoY86N7qAtc3VIQ0yjE6ez
tgREo/ftCb8kGfwRJOAQIeeDamBv+FWNT6QzcOtbwQKBgQDzWhY9BUgI8JVzjYg0
oWfU90On81BtckKsEo//8MTlgwOD2PnUF0hTZF3RcSPYT+HybouTqHT8EOLBAzqy
1+koH06MnAc/Y2ipaAe2fGaVH3SuXAsV/b8VcWWl4Qx7tYJDhE7sKmdl3/+jHZ7A
hZQzgOQnxxCANBo3pwF9KboDbwKBgQDnfglSpgYdGzFpWp1hZnPl2RKIfX/4M2z2
s+hVN1tp+1VySPrBRUC3J6hKPQUzzeUzPICclHOnO+kP7jAos/rlJ9VcNdAQTbTL
7Ds9Em1KJTBphE038YbW3e93rydQpukXh50wRD9RI/3F3A/1rKhab92DXZZr6Wqu
JouhNV8f5wKBgQCLQ3XQi/Iyc4QDse5NuETUgoCsX7kaOTZghOr1nFMByV08mfI2
5vAUES8DigzqYKS8eXjVEqWIDx3FOVThPmCG/ouUOkKHixs9P3SSgVSvaGX81l3d
wu4UlmWGbWkYbsJSYyhLTOUJTwxby7qrEIbEhrGK9gfCZo7OZHucpkF2bwKBgFhl
1qWK5JbExY+XnLWO6/7/b4ZTdkSPTrK+bJ/t7aiA41Yq7CZVjarjJ+6BcrUfkMCK
AArK3Yck55C/wgApCkvrdBwsKHGxWrLsWIqvuLAxl1UTwnD0eCsgwMsRRZAUzLnB
fZLq3MrdVZDywd1suzUdtpbta/11OtmZuoQq31JNAoGAIzmevuPaUZRqnjDpLXAm
Bo11q6CunhG5qZX4wifeZ9Fp5AaQu97F36igZ5/eDxFkDCrFRq6teMiNjRQZSLA3
5tMBkq6BtN2Ozzm/6D135c4BF14ODHqAMPUy4sXbw5PS/kRFs4fKAH/+LcAOPgyI
N/jJIY1LfM7PzrG2NdyscMU=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFljCCA36gAwIBAgICEAswDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
DTIzMDMxNjIwMjAzMloXDTMzMDYyMTIwMjAzMlowdjELMAkGA1UEBhMCU0UxEjAQ
BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN
eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExEDAOBgNVBAMMB0Ns
aWVudDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFLcCjzNhfY6Sk
2nSdrB/6UPPeTCCH5NBBVLvu1hdlqLS4qEdq8+EjyMDZTtspRtYPkBfjpOrlBWUO
lKyxw2mZOjZ8iWvd4sJaAI/6KZl5X0Rdsg1RjzW03kUdLx9XJCyrYY0YFrT1dgJo
Ih56jk2SJX7wrz0NCJ05VPIdpaOF6CcziA+YhdVHcE6xyHagsYI0JdDWxFZrl9zT
LyhaDgBUN/yUQBnxKzxs8TMT4YVSi73ouh5IP9Xvs52hd6HO8ZGVr+YthQZKo95p
OlwFF+AQWxdDIKoPYUPFo8XMOXvOeQ9iUJarxrYSrelLXtGkaGLBolAvqo/YKE7j
rcJWjRGHAgMBAAGjggE3MIIBMzAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIF
oDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRp
ZmljYXRlMB0GA1UdDgQWBBTOo9YSgx1h5k/imP7nOfRfzQrRxjAfBgNVHSMEGDAW
gBRMcIY7FVKJurUPkqqusTFBE75z8zAOBgNVHQ8BAf8EBAMCBeAwHQYDVR0lBBYw
FAYIKwYBBQUHAwIGCCsGAQUFBwMEMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9s
b2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUyLmNybC5wZW0wMQYIKwYBBQUHAQEE
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0Ojk4NzcwDQYJKoZIhvcN
AQELBQADggIBAFo91lLqjPY67Wmj2yWxZuTTuUwXdXXUQxL6sEUUnfkECvRhNyBA
eCHkfVopNbXZ5tdLfsUvXF0ulaC76GCK/P7gHOG9D/RJX/85VzhuJcqa4dsEEifg
IiKIG7viYxSA6HFXuyzHvwNco3FqTBHbY46lKf1lWRVLhiAtcwcyPP34/RWcPfQi
6NZfLyitu5U7Z9XVN5wCp8sg0ayaO5Ib2ejIYuBCUddV1gV//tSDf+rKCgtAbm/X
K64Bf3GdaX3h6EhoqMZ+Z2f4XpKSXTabsWAU44xdVxisI82eo+NwT8KleE65GpOv
nPvr/dLq5fQ6VtHbRL3wWqhzB1VKVCtd8a6RE2k8HVWflU3qgwJ+woF19ed921eq
OZxc+KzjsGFyW1D2fPdgoZFmePadSstIME7qtCNEi7D3im01/1KKzE2m/nosrHeW
ePjY2YrXu0w47re/N2kBJL2xRbj+fAjBsfNn9RhvQsWheXG6mgg8w1ac6y72ZA2W
72pWoDkgXQMX5XBBj/zMnmwtrX9zTILFjNGFuWMPYgBRI0xOf2FoqqZ67cQ2yTW/
1T/6Mp0FSh4cIo/ENiNSdvlt3BIo84EyOm3iHHy28Iv5SiFjF0pkwtXlYYvjM3+R
BeWqlPsVCZXcVC1rPVDzfWZE219yghldY4I3QPJ7dlmszi8eI0HtzhTK
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFLcCjzNhfY6Sk
2nSdrB/6UPPeTCCH5NBBVLvu1hdlqLS4qEdq8+EjyMDZTtspRtYPkBfjpOrlBWUO
lKyxw2mZOjZ8iWvd4sJaAI/6KZl5X0Rdsg1RjzW03kUdLx9XJCyrYY0YFrT1dgJo
Ih56jk2SJX7wrz0NCJ05VPIdpaOF6CcziA+YhdVHcE6xyHagsYI0JdDWxFZrl9zT
LyhaDgBUN/yUQBnxKzxs8TMT4YVSi73ouh5IP9Xvs52hd6HO8ZGVr+YthQZKo95p
OlwFF+AQWxdDIKoPYUPFo8XMOXvOeQ9iUJarxrYSrelLXtGkaGLBolAvqo/YKE7j
rcJWjRGHAgMBAAECggEABJYUCcyJcnbagytBxfnaNQUuAp8AIypFG3kipq0l5Stk
gGaTJq5F4OTGS4ofRsqeu07IgBSAfqJcJH8toPkDQqfvs6ftO1Mso2UzakMOcP51
Ywxd91Kjm+LKOyHkHGDirPGnutUg/YpLLrrMvTk/bJHDZCM4i/WP1WTREVFjUgl7
4L6Y53x2Lk5shJJhv0MzTGaoZzQcW0EbhNH1AI6MBv5/CN5m/7/+HCPlHSNKnozl
o3PXD6l0XNfOY2Hi6MgS/Vd70s3VmDT9UCJNsDjdFpKNHmI7vr9FScOLN8EwbqWe
maFa0TPknmPDmVjEGMtgGlJWL7Sm0MpNW+WsEXcDPQKBgQDv3sp0nVML9pxdzX/w
rGebFaZaMYDWmV9w0V1uXYh4ZkpFmqrWkq/QSTGpwIP/x8WH9FBDUZqspLpPBNgG
ft1XhuY34y3hoCxOyRhQcR/1dY+lgCzuN4G4MG3seq/cAXhrmkPljma/iO8KzoRK
Pa+uaKFGHy1vWY2AmOhT20zr4wKBgQDScA3478TFHg9THlSFzqpzvVn5eAvmmrCQ
RMYIZKFWPortqzeJHdA5ShVF1XBBar1yNMid7E7FXqi/P8Oh+E6Nuc7JxyVIJWlV
mcBE1ceTKdZn7A0nuQIaU6hcn7xz/UHmxGur1ZcNQm3diFJ2CPn11lzZlkSZLSCN
V86nndA9DQKBgQCWsUxXPo7xsRhDBdseg/ECyPMdLoRWTTxcT+t2bmRR31FBsQ0q
iDTTkWgV0NAcXJCH/MB/ykB1vXceNVjRm9nKJwFyktI8MLglNsiDoM4HErgPrRqM
/WoNIL+uFNVuTa4tS1jkWjXKlmg2Tc9mJKK92xWWS/frQENZSraKF/eXKQKBgGR9
ni6CUTTQZgELOtGrHzql8ZFwAj7dH/PE48yeQW0t8KoOWTbhRc4V0pLGmhSjJFSl
YCgJ8JPP4EVz7bgrG1gSou04bFVHiEWYZnh4nhVopTp7Psz5TEfGK2AP5658Ajxx
D/m+xaNPVae0sawsHTGIbE57s8ZyBll41Pa2JfsBAoGBANtS7SOehkflSdry0eAZ
50Ec3CmY+fArC044hQCmXxol5SiTUnXf/OIQH8y+RZUjF4F3PbbrFNLm/J6wuUPw
XUIb4gAL75srakL8DXqyTYfO02aNrFEHhXzMs+GoAnRkFy16IAAFwpjbYSPanfed
PfHCTWz6Y0pGdh1hUJAFV/3v
-----END PRIVATE KEY-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFljCCA36gAwIBAgICEAwwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
DTIzMDMxNjIwMjAzMloXDTMzMDYyMTIwMjAzMlowdjELMAkGA1UEBhMCU0UxEjAQ
BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN
eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExEDAOBgNVBAMMB0Ns
aWVudDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEOZ6fYNjZDNXX
eOyapHMOMeNeYM3b7vsWXAbiJIt4utVrTS0A+/G640t/U0g8F9jbKgbjEEPtgPJ7
GltjLWObfqDWKSO2D9/ei2+NauqgiN/HX+dQnSKHob0McXBXvLfrA4tn4braKrbg
p1fZB8bAECuT/bUhVBqWlzrUwDMpqjMJWDab48ixezb2gnc/ePE6wq/d3ecDb0/k
cYWQ0LX4JiQBgaTGhwczyoGfL1z2vx5kJqptK+r0Hc2jNCn6kFvoZUCYjCWgWNxZ
sQk7fObQQkUb/XQyqRaKJBWDyqsNcuK2gOg3LGeolAlgtMiEqGhHv77XdJnJug/w
3OiHpP/7AgMBAAGjggE3MIIBMzAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIF
oDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRp
ZmljYXRlMB0GA1UdDgQWBBRxZFdIkSg6zDZCakXmIest5a6dBzAfBgNVHSMEGDAW
gBRMcIY7FVKJurUPkqqusTFBE75z8zAOBgNVHQ8BAf8EBAMCBeAwHQYDVR0lBBYw
FAYIKwYBBQUHAwIGCCsGAQUFBwMEMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9s
b2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUzLmNybC5wZW0wMQYIKwYBBQUHAQEE
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0Ojk4NzcwDQYJKoZIhvcN
AQELBQADggIBAEntkhiPpQtModUF/ffnxruq+cqopPhIdMXhMD8gtU5e4e7o3EHX
lfZKIbxyw56v6dFPrl4TuHBiBudqIvBCsPtllWKixWvg6FV3CrEeTcg4shUIaJcD
pqv1qHLwS4pue6oau/lb8jv1GuzuBXoMFQwlmiOXO7xXqXjV2GdmkFJCDdB/0BW1
VHvh0DXgotaxITWKhCpSNB7F7LSvegRwZIAN6JXrLDpue7tgqLqBB1EzpmS6ALbn
uZDdikOs/tGAFB3un/3Gl7jEPL8UGOoSj/H9PUT5AFHrHJDH72+QSXu09agz8RWJ
V939njYFCAxQ8Jt2mOK8BJQDJgPtLfIIb1iYicQV13Eypt8uIUYvp0i0Wq8WxPbq
rOEvQYpcGUsreS5XqZ7y68hgq6ePiR18Fnc3GyTV5o6qT3W7IOvPArTzNV5fFCwM
lx8xSEm+ebJrJPphp6Uc/h8evohvAN8R/Z7FSo9OL6V+F3ywPqWTXaqiIiRc9PS0
0vxsYZ96EeZY5HzjN6LzHxmkv4KYM5I1qmXlviQlaU+sotp3tzegADlM4K78nUFh
HuXamecEcS73eAgjk+FGqJ9E25B0TLlQMcP6tCKdaUIGn6ZsF5wT87GzqT99wL/5
foHCYIkyG7ZmAQmoaKBd4q6xqVOUHovmsPza69FuSrsBxoRR39PtAnrY
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEOZ6fYNjZDNXX
eOyapHMOMeNeYM3b7vsWXAbiJIt4utVrTS0A+/G640t/U0g8F9jbKgbjEEPtgPJ7
GltjLWObfqDWKSO2D9/ei2+NauqgiN/HX+dQnSKHob0McXBXvLfrA4tn4braKrbg
p1fZB8bAECuT/bUhVBqWlzrUwDMpqjMJWDab48ixezb2gnc/ePE6wq/d3ecDb0/k
cYWQ0LX4JiQBgaTGhwczyoGfL1z2vx5kJqptK+r0Hc2jNCn6kFvoZUCYjCWgWNxZ
sQk7fObQQkUb/XQyqRaKJBWDyqsNcuK2gOg3LGeolAlgtMiEqGhHv77XdJnJug/w
3OiHpP/7AgMBAAECggEADSe89sig5E63SKAlFXcGw0H2XgqIzDP/TGMnqPvNoYhX
eSXUgxDhBptpB9e9a4RaKwaFxxPjlSXEdYFX9O22YSN1RMMl6Q8Zl9g3edhcDR6W
b7Qbx2x8qj6Rjibnlh8JiFPiaDjN2wUeSDBss/9D98NkKiJ9Ue2YCYmJAOA3B3w9
2t4Co5+3YrxkdzkvibTQCUSEwHFeB1Nim21126fknMPxyrf+AezRBRc8JNAHqzWb
4QEeMnmIJDOzc3Oh7+P85tNyejOeRm9T7X3EQ0jKXgLYe+HUzXclBQ66b9x9Nc9b
tNn6XkMlLlsQ3f149Th6PtHksH3hM+GF8bMuCp9yxQKBgQDGk0PYPkLqTD8jHjJW
s8wBNhozigZPGaynxdTsD7L6UtDdOl1sSW/jFOj9UIs1duBce9dP1IjFc0jY+Kin
lMLv3qCtk5ZjxRglOoLipR9hdClcM69rDoRZdoQK8KYa+QHcOTSazIp3fnw4gWSX
nscelMfd1rtVP0dOGTuqE/73/QKBgQD8+F5WAi2IOVPHnBxAAOP+6XTs9Ntn1sDi
L5wNgm+QA28aJJ4KRAwdXIc3IFPlHxZI77c2K1L9dKDu9X4UcyZIZYDvGVLuOOt5
twaRaGuJW03cjbgOWC7rGyfzfZ49YlCZi2YuxERclBkbqgWD9hfa8twUfKNguF2Y
AyiOhohtVwKBgQCJB8zUp7pzhqQ3LrpcHHzWBSi1kjTiVvxPVnSlZfwDRCz/zSv0
8wRz9tUFIZS/E0ama4tcenTblL+bgpSX+E9BSiclQOiR9su/vQ3fK0Vpccis6LnP
rdflCKT8C68Eg/slppBHloijBzTfpWLuQlJ0JwV5b5ocrKsfGMiUiHH1XQKBgQDg
RnakfEPP7TtY0g+9ssxwOJxAZImM0zmojpsk4wpzvIeovuQap9+xvFHoztFyZhBE
07oz3U8zhE4V7TI9gSVktBEOaf47U914yIqbKd+FJJywODkBBq96I1ZVKn67X0mk
B5GtTrZo+agU/bTsHKdjp0L1KtdSLcJUviAb1Cxp+wKBgDrGqS01CCgxSUwMaZe4
8HFWp/oMSyVDG9lTSC3uP/VL76zNFI55T3X06Q87hDN3gCJGUOmHzDZ/oCOgM4/S
SU55M4lXeSEdFe84tMXJKOv5JXTkulzBYzATJ5J8DeS/4YZxMKyPDLXX8wgwmU+l
i6Imd3qCPhh5eI3z9eSNDX+6
-----END PRIVATE KEY-----

View File

@ -0,0 +1,20 @@
-----BEGIN X509 CRL-----
MIIDPDCCASQCAQEwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0UxEjAQBgNV
BAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQLDBBNeUlu
dGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBFw0yMjA3MjAy
MDIzNTNaFw0zMjEwMjUyMDIzNTNaMBUwEwICEAIXDTIyMDYxMzEyNDIwNVqgbjBs
MB8GA1UdIwQYMBaAFCuv1TkzC1fSgTfzE1m1u5pRCJsVMDwGA1UdHAQ1MDOgLqAs
hipodHRwOi8vbG9jYWxob3N0Ojk4NzgvaW50ZXJtZWRpYXRlLmNybC5wZW2EAf8w
CwYDVR0UBAQCAhADMA0GCSqGSIb3DQEBCwUAA4ICAQBbWdqRFsIrG6coL6ln1RL+
uhgW9l3XMmjNlyiYHHNzOgnlBok9xu9UdaVCOKC6GEthWSzSlBY1AZugje57DQQd
RkIJol9am94lKMTjF/qhzFLiSjho8fwZGDGyES5YeZXkLqNMOf6m/drKaI3iofWf
l63qU9jY8dnSrVDkwgCguUL2FTx60v5H9NPxSctQ3VDxDvDj0sTAcHFknQcZbfvY
ZWpOYNS0FAJlQPVK9wUoDxI0LhrWDq5h/T1jcGO34fPT8RUA5HRtFVUevqSuOLWx
WTfTx5oDeMZPJTvHWUcg4yMElHty4tEvtkFxLSYbZqj7qTU+mi/LAN3UKBH/gBEN
y2OsJvFhVRgHf+zPYegf3WzBSoeaXNAJZ4UnRo34P9AL3Mrh+4OOUP++oYRKjWno
pYtAmTrIwEYoLyisEhhZ6aD92f/Op3dIYsxwhHt0n0lKrbTmUfiJUAe7kUZ4PMn4
Gg/OHlbEDaDxW1dCymjyRGl+3/8kjy7bkYUXCf7w6JBeL2Hw2dFp1Gh13NRjre93
PYlSOvI6QNisYGscfuYPwefXogVrNjf/ttCksMa51tUk+ylw7ZMZqQjcPPSzmwKc
5CqpnQHfolvRuN0xIVZiAn5V6/MdHm7ocrXxOkzWQyaoNODTq4js8h8eYXgAkt1w
p1PTEFBucGud7uBDE6Ub6A==
-----END X509 CRL-----

View File

@ -0,0 +1,12 @@
crl_cache.refresh_interval = {{ refresh_interval }}
crl_cache.http_timeout = 17s
crl_cache.capacity = {{ cache_capacity }}
listeners.ssl.default {
ssl_options {
keyfile = "{{ test_data_dir }}/server.key.pem"
certfile = "{{ test_data_dir }}/server.cert.pem"
cacertfile = "{{ test_data_dir }}/ca-chain.cert.pem"
verify = verify_peer
enable_crl_check = true
}
}

View File

@ -0,0 +1,67 @@
-module(emqx_crl_cache_http_server).
-behaviour(gen_server).
-compile([nowarn_export_all, export_all]).
set_crl(CRLPem) ->
ets:insert(?MODULE, {crl, CRLPem}).
%%--------------------------------------------------------------------
%% `gen_server' APIs
%%--------------------------------------------------------------------
start_link(Parent, BasePort, CRLPem, Opts) ->
process_flag(trap_exit, true),
stop_http(),
timer:sleep(100),
gen_server:start_link(?MODULE, {Parent, BasePort, CRLPem, Opts}, []).
init({Parent, BasePort, CRLPem, Opts}) ->
Tab = ets:new(?MODULE, [named_table, ordered_set, public]),
ets:insert(Tab, {crl, CRLPem}),
ok = start_http(Parent, [{port, BasePort} | Opts]),
Parent ! {self(), ready},
{ok, #{parent => Parent}}.
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
stop_http().
stop(Pid) ->
ok = gen_server:stop(Pid).
%%--------------------------------------------------------------------
%% Callbacks
%%--------------------------------------------------------------------
start_http(Parent, Opts) ->
{ok, _Pid1} = cowboy:start_clear(http, Opts, #{
env => #{dispatch => compile_router(Parent)}
}),
ok.
stop_http() ->
cowboy:stop_listener(http),
ok.
compile_router(Parent) ->
{ok, _} = application:ensure_all_started(cowboy),
cowboy_router:compile([
{'_', [{'_', ?MODULE, #{parent => Parent}}]}
]).
init(Req, #{parent := Parent} = State) ->
%% assert
<<"GET">> = cowboy_req:method(Req),
[{crl, CRLPem}] = ets:lookup(?MODULE, crl),
Parent ! {http_get, iolist_to_binary(cowboy_req:uri(Req))},
Reply = reply(Req, CRLPem),
{ok, Reply, State}.
reply(Req, CRLPem) ->
cowboy_req:reply(200, #{<<"content-type">> => <<"text/plain">>}, CRLPem, Req).

View File

@ -0,0 +1,12 @@
node.name = test@127.0.0.1
node.cookie = emqxsecretcookie
node.data_dir = "{{ test_priv_dir }}"
listeners.ssl.default {
ssl_options {
keyfile = "{{ test_data_dir }}/server.key.pem"
certfile = "{{ test_data_dir }}/server.cert.pem"
cacertfile = "{{ test_data_dir }}/ca-chain.cert.pem"
verify = verify_peer
enable_crl_check = false
}
}

View File

@ -0,0 +1,19 @@
-----BEGIN X509 CRL-----
MIIDJTCCAQ0CAQEwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0UxEjAQBgNV
BAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQLDBBNeUlu
dGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBFw0yMzAxMTIx
MzA4MTZaFw0zMzAxMDkxMzA4MTZaoG4wbDAfBgNVHSMEGDAWgBRMcIY7FVKJurUP
kqqusTFBE75z8zA8BgNVHRwENTAzoC6gLIYqaHR0cDovL2xvY2FsaG9zdDo5ODc4
L2ludGVybWVkaWF0ZS5jcmwucGVthAH/MAsGA1UdFAQEAgIQADANBgkqhkiG9w0B
AQsFAAOCAgEAJGOZuqZL4m7zUaRyBrxeT6Tqo+XKz7HeD5zvO4BTNX+0E0CRyki4
HhIGbxjv2NKWoaUv0HYbGAiZdO4TaPu3w3tm4+pGEDBclBj2KTdbB+4Hlzv956gD
KXZ//ziNwx1SCoxxkxB+TALxReN0shE7Mof9GlB5HPskhLorZgg/pmgJtIykEpsq
QAjJo4aq+f2/L+9dzRM205fVFegtsHvgEVNKz6iK6skt+kDhj/ks9BKsnfCDIGr+
XnPYwS9yDnnhFdoJ40AQQDtomxggAjfgcSnqtHCxZwKJohuztbSWUgD/4yxzlrwP
Dk1cT/Ajjjqb2dXVOfTLK1VB2168uuouArxZ7KYbXwBjHduYWGGkA6FfkNJO/jpF
SL9qhX3oxcRF3hDhWigN1ZRD7NpDKwVal3Y9tmvO5bWhb5VF+3qv0HGeSGp6V0dp
sjwhIj+78bkUrcXxrivACLAXgSTGonx1uXD+T4P4NCt148dgRAbgd8sUXK5FcgU2
cdBl8Kv2ZUjEaod5gUzDtf22VGSoO9lHvfHdpG9o2H3wC7s4tyLTidNrduIguJff
IIgc44Y252iV0sOmZ5S0jjTRiF1YUUPy9qA/6bOnr2LohbwbNZv9tDlNj8cdhxUz
cKiS+c7Qsz+YCcrp19QRiJoQae/gUqz7kmUZQgyPmDd+ArE0V+kDZEE=
-----END X509 CRL-----

View File

@ -0,0 +1,19 @@
-----BEGIN X509 CRL-----
MIIC/TCB5gIBATANBgkqhkiG9w0BAQsFADBrMQswCQYDVQQGEwJTRTESMBAGA1UE
CAwJU3RvY2tob2xtMRIwEAYDVQQKDAlNeU9yZ05hbWUxGTAXBgNVBAsMEE15SW50
ZXJtZWRpYXRlQ0ExGTAXBgNVBAMMEE15SW50ZXJtZWRpYXRlQ0EXDTIzMDExODEz
Mjc1M1oXDTMzMDExNTEzMjc1M1owFTATAgIQAhcNMjMwMTEyMTMwODE2WqAwMC4w
HwYDVR0jBBgwFoAUTHCGOxVSibq1D5KqrrExQRO+c/MwCwYDVR0UBAQCAhACMA0G
CSqGSIb3DQEBCwUAA4ICAQCxoRYDc5MaBpDI+HQUX60+obFeZJdBkPO2wMb6HBQq
e0lZM2ukS+4n5oGhRelsvmEz0qKvnYS6ISpuFzv4Qy6Vaun/KwIYAdXsEQVwDHsu
Br4m1V01igjFnujowwR/7F9oPnZOmBaBdiyYbjgGV0YMF7sOfl4UO2MqI2GSGqVk
63wELT1AXjx31JVoyATQOQkq1A5HKFYLEbFmdF/8lNfbxSCBY2tuJ+uWVQtzjM0y
i+/owz5ez1BZ/Swx8akYhuvs8DVVTbjXydidVSrxt/QEf3+oJCzTA9qFqt4MH7gL
6BAglCGtRiYTHqeMHrwddaHF2hzR61lHJlkMCL61yhVuL8WsEJ/AxVX0W3MfQ4Cw
x/A6xIkgqtu+HtQnPyDcJxyaFHtFC+U67nSbEQySFvHfMw42DGdIGojKQCeUer9W
ECFC8OATQwN2h//f8QkY7D0H3k/brrNYDfdFIcCti9iZiFrrPFxO7NbOTfkeKCt3
7IwYduRc8DWKmS8c7j2re1KkdYnfE1sfwbn3trImkcET5tvDlVCZ1glnBQzk82PS
HvKmSjD2pZI7upfLkoMgMhYyYJhYk7Mw2o4JXuddYGKmmw3bJyHkG/Ot5NAKjb7g
k1QCeWzxO1xXm8PNDDFWMn351twUGDQ/cwrUw0ODeUZpfL0BtTn4YnfCLLTvZDxo
Vg==
-----END X509 CRL-----

View File

@ -0,0 +1,20 @@
-----BEGIN X509 CRL-----
MIIDPDCCASQCAQEwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0UxEjAQBgNV
BAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQLDBBNeUlu
dGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBFw0yMzAxMTIx
MzA4MTZaFw0zMzAxMDkxMzA4MTZaMBUwEwICEAIXDTIzMDExMjEzMDgxNlqgbjBs
MB8GA1UdIwQYMBaAFExwhjsVUom6tQ+Sqq6xMUETvnPzMDwGA1UdHAQ1MDOgLqAs
hipodHRwOi8vbG9jYWxob3N0Ojk4NzgvaW50ZXJtZWRpYXRlLmNybC5wZW2EAf8w
CwYDVR0UBAQCAhABMA0GCSqGSIb3DQEBCwUAA4ICAQCPadbaehEqLv4pwqF8em8T
CW8TOQ4Vjz02uiVk9Bo0za1dQqQmwCBA6UE1BcOh+aWzQxBRz56NeUcfhgDxTntG
xLs896N9MHIG6UxpqJH8cH+DXKHsQjvvCjXtiObmBQR1RiG5C1vEMkfzTt/WSrq5
7blowLDs4NP6YbtqXEyyUkF7DQSUEUuIDWPQdx1f++nSpVaHWW4xpoO4umesaJco
FuxaXQnZpTHHQfqUJVIL2Mmzvez9thgfKTV3vgkYrGiSLW2m2+Tfga30pUc0qaVI
RrBVORVbcu9m1sV0aJyk96b2T/+i2FRR/np4TOcLgckBpHKeK2FH69lHFr0W/71w
CErNTxahoh82Yi8POenu+S1m2sDnrF1FMf+ZG/i2wr0nW6/+zVGQsEOw77Spbmei
dbEchu3iWF1XEO/n4zVBzl6a1o2RyVg+1pItYd5C5bPwcrfZnBrm4WECPxO+6rbW
2/wz9Iku4XznTLqLEpXLAtenAdo73mLGC7riviX7mhcxfN2UjNfLuVGHmG8XwIsM
Lgpr6DKaxHwpHgW3wA3SGJrY5dj0TvGWaoInrNt1cOMnIpoxRNy5+ko71Ubx3yrV
RhbUMggd1GG1ct9uZn82v74RYF6J8Xcxn9vDFJu5LLT5kvfy414kdJeTXKqfKXA/
atdUgFa0otoccn5FzyUuzg==
-----END X509 CRL-----

View File

@ -0,0 +1,20 @@
-----BEGIN X509 CRL-----
MIIDPDCCASQCAQEwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0UxEjAQBgNV
BAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQLDBBNeUlu
dGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBFw0yMjA3MjAy
MDIzNTNaFw0zMjEwMjUyMDIzNTNaMBUwEwICEAIXDTIyMDYxMzEyNDIwNVqgbjBs
MB8GA1UdIwQYMBaAFCuv1TkzC1fSgTfzE1m1u5pRCJsVMDwGA1UdHAQ1MDOgLqAs
hipodHRwOi8vbG9jYWxob3N0Ojk4NzgvaW50ZXJtZWRpYXRlLmNybC5wZW2EAf8w
CwYDVR0UBAQCAhADMA0GCSqGSIb3DQEBCwUAA4ICAQBbWdqRFsIrG6coL6ln1RL+
uhgW9l3XMmjNlyiYHHNzOgnlBok9xu9UdaVCOKC6GEthWSzSlBY1AZugje57DQQd
RkIJol9am94lKMTjF/qhzFLiSjho8fwZGDGyES5YeZXkLqNMOf6m/drKaI3iofWf
l63qU9jY8dnSrVDkwgCguUL2FTx60v5H9NPxSctQ3VDxDvDj0sTAcHFknQcZbfvY
ZWpOYNS0FAJlQPVK9wUoDxI0LhrWDq5h/T1jcGO34fPT8RUA5HRtFVUevqSuOLWx
WTfTx5oDeMZPJTvHWUcg4yMElHty4tEvtkFxLSYbZqj7qTU+mi/LAN3UKBH/gBEN
y2OsJvFhVRgHf+zPYegf3WzBSoeaXNAJZ4UnRo34P9AL3Mrh+4OOUP++oYRKjWno
pYtAmTrIwEYoLyisEhhZ6aD92f/Op3dIYsxwhHt0n0lKrbTmUfiJUAe7kUZ4PMn4
Gg/OHlbEDaDxW1dCymjyRGl+3/8kjy7bkYUXCf7w6JBeL2Hw2dFp1Gh13NRjre93
PYlSOvI6QNisYGscfuYPwefXogVrNjf/ttCksMa51tUk+ylw7ZMZqQjcPPSzmwKc
5CqpnQHfolvRuN0xIVZiAn5V6/MdHm7ocrXxOkzWQyaoNODTq4js8h8eYXgAkt1w
p1PTEFBucGud7uBDE6Ub6A==
-----END X509 CRL-----

View File

@ -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-----

View File

@ -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-----

View File

@ -76,7 +76,7 @@ init_per_testcase(t_openssl_client, Config) ->
[], [],
Handler, Handler,
#{ #{
extra_mustache_vars => [{test_data_dir, DataDir}], extra_mustache_vars => #{test_data_dir => DataDir},
conf_file_path => ConfFilePath conf_file_path => ConfFilePath
} }
), ),

View File

@ -54,6 +54,17 @@ emqx_bridge_schema {
} }
} }
desc_status_reason {
desc {
en: "This is the reason given in case a bridge is failing to connect."
zh: "桥接连接失败的原因。"
}
label: {
en: "Failure reason"
zh: "失败原因"
}
}
desc_node_status { desc_node_status {
desc { desc {
en: """The status of the bridge for each node. en: """The status of the bridge for each node.

View File

@ -46,18 +46,33 @@
-export([lookup_from_local_node/2]). -export([lookup_from_local_node/2]).
-define(BAD_REQUEST(Reason), {400, error_msg('BAD_REQUEST', Reason)}). %% [TODO] Move those to a commonly shared header file
-define(ERROR_MSG(CODE, REASON), #{code => CODE, message => emqx_misc:readable_error_msg(REASON)}).
-define(OK(CONTENT), {200, CONTENT}).
-define(NO_CONTENT, 204).
-define(BAD_REQUEST(CODE, REASON), {400, ?ERROR_MSG(CODE, REASON)}).
-define(BAD_REQUEST(REASON), ?BAD_REQUEST('BAD_REQUEST', REASON)).
-define(NOT_FOUND(REASON), {404, ?ERROR_MSG('NOT_FOUND', REASON)}).
-define(INTERNAL_ERROR(REASON), {500, ?ERROR_MSG('INTERNAL_ERROR', REASON)}).
-define(NOT_IMPLEMENTED, 501).
-define(SERVICE_UNAVAILABLE(REASON), {503, ?ERROR_MSG('SERVICE_UNAVAILABLE', REASON)}).
%% End TODO
-define(BRIDGE_NOT_ENABLED, -define(BRIDGE_NOT_ENABLED,
?BAD_REQUEST(<<"Forbidden operation, bridge not enabled">>) ?BAD_REQUEST(<<"Forbidden operation, bridge not enabled">>)
). ).
-define(NOT_FOUND(Reason), {404, error_msg('NOT_FOUND', Reason)}). -define(BRIDGE_NOT_FOUND(BRIDGE_TYPE, BRIDGE_NAME),
-define(BRIDGE_NOT_FOUND(BridgeType, BridgeName),
?NOT_FOUND( ?NOT_FOUND(
<<"Bridge lookup failed: bridge named '", BridgeName/binary, "' of type ", <<"Bridge lookup failed: bridge named '", (BRIDGE_NAME)/binary, "' of type ",
(atom_to_binary(BridgeType))/binary, " does not exist.">> (bin(BRIDGE_TYPE))/binary, " does not exist.">>
) )
). ).
@ -301,7 +316,7 @@ schema("/bridges") ->
'operationId' => '/bridges', 'operationId' => '/bridges',
get => #{ get => #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"List Bridges">>, summary => <<"List bridges">>,
description => ?DESC("desc_api1"), description => ?DESC("desc_api1"),
responses => #{ responses => #{
200 => emqx_dashboard_swagger:schema_with_example( 200 => emqx_dashboard_swagger:schema_with_example(
@ -312,7 +327,7 @@ schema("/bridges") ->
}, },
post => #{ post => #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"Create Bridge">>, summary => <<"Create bridge">>,
description => ?DESC("desc_api2"), description => ?DESC("desc_api2"),
'requestBody' => emqx_dashboard_swagger:schema_with_examples( 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
emqx_bridge_schema:post_request(), emqx_bridge_schema:post_request(),
@ -329,7 +344,7 @@ schema("/bridges/:id") ->
'operationId' => '/bridges/:id', 'operationId' => '/bridges/:id',
get => #{ get => #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"Get Bridge">>, summary => <<"Get bridge">>,
description => ?DESC("desc_api3"), description => ?DESC("desc_api3"),
parameters => [param_path_id()], parameters => [param_path_id()],
responses => #{ responses => #{
@ -339,7 +354,7 @@ schema("/bridges/:id") ->
}, },
put => #{ put => #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"Update Bridge">>, summary => <<"Update bridge">>,
description => ?DESC("desc_api4"), description => ?DESC("desc_api4"),
parameters => [param_path_id()], parameters => [param_path_id()],
'requestBody' => emqx_dashboard_swagger:schema_with_examples( 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
@ -354,7 +369,7 @@ schema("/bridges/:id") ->
}, },
delete => #{ delete => #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"Delete Bridge">>, summary => <<"Delete bridge">>,
description => ?DESC("desc_api5"), description => ?DESC("desc_api5"),
parameters => [param_path_id()], parameters => [param_path_id()],
responses => #{ responses => #{
@ -373,7 +388,7 @@ schema("/bridges/:id/metrics") ->
'operationId' => '/bridges/:id/metrics', 'operationId' => '/bridges/:id/metrics',
get => #{ get => #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"Get Bridge Metrics">>, summary => <<"Get bridge metrics">>,
description => ?DESC("desc_bridge_metrics"), description => ?DESC("desc_bridge_metrics"),
parameters => [param_path_id()], parameters => [param_path_id()],
responses => #{ responses => #{
@ -387,7 +402,7 @@ schema("/bridges/:id/metrics/reset") ->
'operationId' => '/bridges/:id/metrics/reset', 'operationId' => '/bridges/:id/metrics/reset',
put => #{ put => #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"Reset Bridge Metrics">>, summary => <<"Reset bridge metrics">>,
description => ?DESC("desc_api6"), description => ?DESC("desc_api6"),
parameters => [param_path_id()], parameters => [param_path_id()],
responses => #{ responses => #{
@ -402,7 +417,7 @@ schema("/bridges/:id/enable/:enable") ->
put => put =>
#{ #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"Enable or Disable Bridge">>, summary => <<"Enable or disable bridge">>,
desc => ?DESC("desc_enable_bridge"), desc => ?DESC("desc_enable_bridge"),
parameters => [param_path_id(), param_path_enable()], parameters => [param_path_id(), param_path_enable()],
responses => responses =>
@ -418,7 +433,7 @@ schema("/bridges/:id/:operation") ->
'operationId' => '/bridges/:id/:operation', 'operationId' => '/bridges/:id/:operation',
post => #{ post => #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"Stop or Restart Bridge">>, summary => <<"Stop or restart bridge">>,
description => ?DESC("desc_api7"), description => ?DESC("desc_api7"),
parameters => [ parameters => [
param_path_id(), param_path_id(),
@ -440,7 +455,7 @@ schema("/nodes/:node/bridges/:id/:operation") ->
'operationId' => '/nodes/:node/bridges/:id/:operation', 'operationId' => '/nodes/:node/bridges/:id/:operation',
post => #{ post => #{
tags => [<<"bridges">>], tags => [<<"bridges">>],
summary => <<"Stop/Restart Bridge">>, summary => <<"Stop/Restart bridge">>,
description => ?DESC("desc_api8"), description => ?DESC("desc_api8"),
parameters => [ parameters => [
param_path_node(), param_path_node(),
@ -480,7 +495,7 @@ schema("/bridges_probe") ->
'/bridges'(post, #{body := #{<<"type">> := BridgeType, <<"name">> := BridgeName} = Conf0}) -> '/bridges'(post, #{body := #{<<"type">> := BridgeType, <<"name">> := BridgeName} = Conf0}) ->
case emqx_bridge:lookup(BridgeType, BridgeName) of case emqx_bridge:lookup(BridgeType, BridgeName) of
{ok, _} -> {ok, _} ->
{400, error_msg('ALREADY_EXISTS', <<"bridge already exists">>)}; ?BAD_REQUEST('ALREADY_EXISTS', <<"bridge already exists">>);
{error, not_found} -> {error, not_found} ->
Conf = filter_out_request_body(Conf0), Conf = filter_out_request_body(Conf0),
{ok, _} = emqx_bridge:create(BridgeType, BridgeName, Conf), {ok, _} = emqx_bridge:create(BridgeType, BridgeName, Conf),
@ -492,12 +507,12 @@ schema("/bridges_probe") ->
case is_ok(NodeReplies) of case is_ok(NodeReplies) of
{ok, NodeBridges} -> {ok, NodeBridges} ->
AllBridges = [ AllBridges = [
format_resource(Data, Node) [format_resource(Data, Node) || Data <- Bridges]
|| {Node, Bridges} <- lists:zip(Nodes, NodeBridges), Data <- Bridges || {Node, Bridges} <- lists:zip(Nodes, NodeBridges)
], ],
{200, zip_bridges([AllBridges])}; ?OK(zip_bridges(AllBridges));
{error, Reason} -> {error, Reason} ->
{500, error_msg('INTERNAL_ERROR', Reason)} ?INTERNAL_ERROR(Reason)
end. end.
'/bridges/:id'(get, #{bindings := #{id := Id}}) -> '/bridges/:id'(get, #{bindings := #{id := Id}}) ->
@ -529,16 +544,16 @@ schema("/bridges_probe") ->
end, end,
case emqx_bridge:check_deps_and_remove(BridgeType, BridgeName, AlsoDeleteActs) of case emqx_bridge:check_deps_and_remove(BridgeType, BridgeName, AlsoDeleteActs) of
{ok, _} -> {ok, _} ->
204; ?NO_CONTENT;
{error, {rules_deps_on_this_bridge, RuleIds}} -> {error, {rules_deps_on_this_bridge, RuleIds}} ->
?BAD_REQUEST( ?BAD_REQUEST(
{<<"Cannot delete bridge while active rules are defined for this bridge">>, {<<"Cannot delete bridge while active rules are defined for this bridge">>,
RuleIds} RuleIds}
); );
{error, timeout} -> {error, timeout} ->
{503, error_msg('SERVICE_UNAVAILABLE', <<"request timeout">>)}; ?SERVICE_UNAVAILABLE(<<"request timeout">>);
{error, Reason} -> {error, Reason} ->
{500, error_msg('INTERNAL_ERROR', Reason)} ?INTERNAL_ERROR(Reason)
end; end;
{error, not_found} -> {error, not_found} ->
?BRIDGE_NOT_FOUND(BridgeType, BridgeName) ?BRIDGE_NOT_FOUND(BridgeType, BridgeName)
@ -555,7 +570,7 @@ schema("/bridges_probe") ->
ok = emqx_bridge_resource:reset_metrics( ok = emqx_bridge_resource:reset_metrics(
emqx_bridge_resource:resource_id(BridgeType, BridgeName) emqx_bridge_resource:resource_id(BridgeType, BridgeName)
), ),
{204} ?NO_CONTENT
end end
). ).
@ -566,9 +581,9 @@ schema("/bridges_probe") ->
Params1 = maybe_deobfuscate_bridge_probe(Params), Params1 = maybe_deobfuscate_bridge_probe(Params),
case emqx_bridge_resource:create_dry_run(ConnType, maps:remove(<<"type">>, Params1)) of case emqx_bridge_resource:create_dry_run(ConnType, maps:remove(<<"type">>, Params1)) of
ok -> ok ->
204; ?NO_CONTENT;
{error, Reason} when not is_tuple(Reason); element(1, Reason) =/= 'exit' -> {error, Reason} when not is_tuple(Reason); element(1, Reason) =/= 'exit' ->
{400, error_msg('TEST_FAILED', to_hr_reason(Reason))} ?BAD_REQUEST('TEST_FAILED', Reason)
end; end;
BadRequest -> BadRequest ->
BadRequest BadRequest
@ -602,7 +617,7 @@ do_lookup_from_all_nodes(BridgeType, BridgeName, SuccCode, FormatFun) ->
{ok, [{error, not_found} | _]} -> {ok, [{error, not_found} | _]} ->
?BRIDGE_NOT_FOUND(BridgeType, BridgeName); ?BRIDGE_NOT_FOUND(BridgeType, BridgeName);
{error, Reason} -> {error, Reason} ->
{500, error_msg('INTERNAL_ERROR', Reason)} ?INTERNAL_ERROR(Reason)
end. end.
lookup_from_local_node(BridgeType, BridgeName) -> lookup_from_local_node(BridgeType, BridgeName) ->
@ -620,15 +635,15 @@ lookup_from_local_node(BridgeType, BridgeName) ->
OperFunc -> OperFunc ->
case emqx_bridge:disable_enable(OperFunc, BridgeType, BridgeName) of case emqx_bridge:disable_enable(OperFunc, BridgeType, BridgeName) of
{ok, _} -> {ok, _} ->
204; ?NO_CONTENT;
{error, {pre_config_update, _, bridge_not_found}} -> {error, {pre_config_update, _, bridge_not_found}} ->
?BRIDGE_NOT_FOUND(BridgeType, BridgeName); ?BRIDGE_NOT_FOUND(BridgeType, BridgeName);
{error, {_, _, timeout}} -> {error, {_, _, timeout}} ->
{503, error_msg('SERVICE_UNAVAILABLE', <<"request timeout">>)}; ?SERVICE_UNAVAILABLE(<<"request timeout">>);
{error, timeout} -> {error, timeout} ->
{503, error_msg('SERVICE_UNAVAILABLE', <<"request timeout">>)}; ?SERVICE_UNAVAILABLE(<<"request timeout">>);
{error, Reason} -> {error, Reason} ->
{500, error_msg('INTERNAL_ERROR', Reason)} ?INTERNAL_ERROR(Reason)
end end
end end
). ).
@ -748,7 +763,7 @@ pick_bridges_by_id(Type, Name, BridgesAllNodes) ->
format_bridge_info_with_metrics([FirstBridge | _] = Bridges) -> format_bridge_info_with_metrics([FirstBridge | _] = Bridges) ->
Res = maps:remove(node, FirstBridge), Res = maps:remove(node, FirstBridge),
NodeStatus = collect_status(Bridges), NodeStatus = node_status(Bridges),
NodeMetrics = collect_metrics(Bridges), NodeMetrics = collect_metrics(Bridges),
redact(Res#{ redact(Res#{
status => aggregate_status(NodeStatus), status => aggregate_status(NodeStatus),
@ -765,8 +780,8 @@ format_bridge_metrics(Bridges) ->
Res = format_bridge_info_with_metrics(Bridges), Res = format_bridge_info_with_metrics(Bridges),
maps:with([metrics, node_metrics], Res). maps:with([metrics, node_metrics], Res).
collect_status(Bridges) -> node_status(Bridges) ->
[maps:with([node, status], B) || B <- Bridges]. [maps:with([node, status, status_reason], B) || B <- Bridges].
aggregate_status(AllStatus) -> aggregate_status(AllStatus) ->
Head = fun([A | _]) -> A end, Head = fun([A | _]) -> A end,
@ -837,52 +852,63 @@ format_resource(
) )
). ).
format_resource_data(#{status := Status, metrics := Metrics}) -> format_resource_data(ResData) ->
#{status => Status, metrics => format_metrics(Metrics)}; maps:fold(fun format_resource_data/3, #{}, maps:with([status, metrics, error], ResData)).
format_resource_data(#{status := Status}) ->
#{status => Status}.
format_metrics(#{ format_resource_data(error, undefined, Result) ->
counters := #{ Result;
'dropped' := Dropped, format_resource_data(error, Error, Result) ->
'dropped.other' := DroppedOther, Result#{status_reason => emqx_misc:readable_error_msg(Error)};
'dropped.expired' := DroppedExpired, format_resource_data(
'dropped.queue_full' := DroppedQueueFull, metrics,
'dropped.resource_not_found' := DroppedResourceNotFound, #{
'dropped.resource_stopped' := DroppedResourceStopped, counters := #{
'matched' := Matched, 'dropped' := Dropped,
'retried' := Retried, 'dropped.other' := DroppedOther,
'late_reply' := LateReply, 'dropped.expired' := DroppedExpired,
'failed' := SentFailed, 'dropped.queue_full' := DroppedQueueFull,
'success' := SentSucc, 'dropped.resource_not_found' := DroppedResourceNotFound,
'received' := Rcvd 'dropped.resource_stopped' := DroppedResourceStopped,
'matched' := Matched,
'retried' := Retried,
'late_reply' := LateReply,
'failed' := SentFailed,
'success' := SentSucc,
'received' := Rcvd
},
gauges := Gauges,
rate := #{
matched := #{current := Rate, last5m := Rate5m, max := RateMax}
}
}, },
gauges := Gauges, Result
rate := #{ ) ->
matched := #{current := Rate, last5m := Rate5m, max := RateMax}
}
}) ->
Queued = maps:get('queuing', Gauges, 0), Queued = maps:get('queuing', Gauges, 0),
SentInflight = maps:get('inflight', Gauges, 0), SentInflight = maps:get('inflight', Gauges, 0),
?METRICS( Result#{
Dropped, metrics =>
DroppedOther, ?METRICS(
DroppedExpired, Dropped,
DroppedQueueFull, DroppedOther,
DroppedResourceNotFound, DroppedExpired,
DroppedResourceStopped, DroppedQueueFull,
Matched, DroppedResourceNotFound,
Queued, DroppedResourceStopped,
Retried, Matched,
LateReply, Queued,
SentFailed, Retried,
SentInflight, LateReply,
SentSucc, SentFailed,
Rate, SentInflight,
Rate5m, SentSucc,
RateMax, Rate,
Rcvd Rate5m,
). RateMax,
Rcvd
)
};
format_resource_data(K, V, Result) ->
Result#{K => V}.
fill_defaults(Type, RawConf) -> fill_defaults(Type, RawConf) ->
PackedConf = pack_bridge_conf(Type, RawConf), PackedConf = pack_bridge_conf(Type, RawConf),
@ -924,6 +950,7 @@ filter_out_request_body(Conf) ->
<<"type">>, <<"type">>,
<<"name">>, <<"name">>,
<<"status">>, <<"status">>,
<<"error">>,
<<"node_status">>, <<"node_status">>,
<<"node_metrics">>, <<"node_metrics">>,
<<"metrics">>, <<"metrics">>,
@ -931,9 +958,6 @@ filter_out_request_body(Conf) ->
], ],
maps:without(ExtraConfs, Conf). maps:without(ExtraConfs, Conf).
error_msg(Code, Msg) ->
#{code => Code, message => emqx_misc:readable_error_msg(Msg)}.
bin(S) when is_list(S) -> bin(S) when is_list(S) ->
list_to_binary(S); list_to_binary(S);
bin(S) when is_atom(S) -> bin(S) when is_atom(S) ->
@ -944,30 +968,31 @@ bin(S) when is_binary(S) ->
call_operation(NodeOrAll, OperFunc, Args = [_Nodes, BridgeType, BridgeName]) -> call_operation(NodeOrAll, OperFunc, Args = [_Nodes, BridgeType, BridgeName]) ->
case is_ok(do_bpapi_call(NodeOrAll, OperFunc, Args)) of case is_ok(do_bpapi_call(NodeOrAll, OperFunc, Args)) of
Ok when Ok =:= ok; is_tuple(Ok), element(1, Ok) =:= ok -> Ok when Ok =:= ok; is_tuple(Ok), element(1, Ok) =:= ok ->
204; ?NO_CONTENT;
{error, not_implemented} -> {error, not_implemented} ->
%% Should only happen if we call `start` on a node that is %% Should only happen if we call `start` on a node that is
%% still on an older bpapi version that doesn't support it. %% still on an older bpapi version that doesn't support it.
maybe_try_restart(NodeOrAll, OperFunc, Args); maybe_try_restart(NodeOrAll, OperFunc, Args);
{error, timeout} -> {error, timeout} ->
{503, error_msg('SERVICE_UNAVAILABLE', <<"request timeout">>)}; ?SERVICE_UNAVAILABLE(<<"Request timeout">>);
{error, {start_pool_failed, Name, Reason}} -> {error, {start_pool_failed, Name, Reason}} ->
{503, ?SERVICE_UNAVAILABLE(
error_msg( bin(io_lib:format("Failed to start ~p pool for reason ~p", [Name, Reason]))
'SERVICE_UNAVAILABLE', );
bin(
io_lib:format(
"failed to start ~p pool for reason ~p",
[Name, Reason]
)
)
)};
{error, not_found} -> {error, not_found} ->
?BRIDGE_NOT_FOUND(BridgeType, BridgeName); BridgeId = emqx_bridge_resource:bridge_id(BridgeType, BridgeName),
?SLOG(warning, #{
msg => "bridge_inconsistent_in_cluster_for_call_operation",
reason => not_found,
type => BridgeType,
name => BridgeName,
bridge => BridgeId
}),
?SERVICE_UNAVAILABLE(<<"Bridge not found on remote node: ", BridgeId/binary>>);
{error, {node_not_found, Node}} -> {error, {node_not_found, Node}} ->
?NOT_FOUND(<<"Node not found: ", (atom_to_binary(Node))/binary>>); ?NOT_FOUND(<<"Node not found: ", (atom_to_binary(Node))/binary>>);
{error, Reason} when not is_tuple(Reason); element(1, Reason) =/= 'exit' -> {error, Reason} when not is_tuple(Reason); element(1, Reason) =/= 'exit' ->
?BAD_REQUEST(to_hr_reason(Reason)) ?BAD_REQUEST(Reason)
end. end.
maybe_try_restart(all, start_bridges_to_all_nodes, Args) -> maybe_try_restart(all, start_bridges_to_all_nodes, Args) ->
@ -975,7 +1000,7 @@ maybe_try_restart(all, start_bridges_to_all_nodes, Args) ->
maybe_try_restart(Node, start_bridge_to_node, Args) -> maybe_try_restart(Node, start_bridge_to_node, Args) ->
call_operation(Node, restart_bridge_to_node, Args); call_operation(Node, restart_bridge_to_node, Args);
maybe_try_restart(_, _, _) -> maybe_try_restart(_, _, _) ->
501. ?NOT_IMPLEMENTED.
do_bpapi_call(all, Call, Args) -> do_bpapi_call(all, Call, Args) ->
maybe_unwrap( maybe_unwrap(
@ -1006,19 +1031,6 @@ supported_versions(start_bridge_to_node) -> [2, 3];
supported_versions(start_bridges_to_all_nodes) -> [2, 3]; supported_versions(start_bridges_to_all_nodes) -> [2, 3];
supported_versions(_Call) -> [1, 2, 3]. supported_versions(_Call) -> [1, 2, 3].
to_hr_reason(nxdomain) ->
<<"Host not found">>;
to_hr_reason(econnrefused) ->
<<"Connection refused">>;
to_hr_reason({unauthorized_client, _}) ->
<<"Unauthorized client">>;
to_hr_reason({not_authorized, _}) ->
<<"Not authorized">>;
to_hr_reason({malformed_username_or_password, _}) ->
<<"Malformed username or password">>;
to_hr_reason(Reason) ->
Reason.
redact(Term) -> redact(Term) ->
emqx_misc:redact(Term). emqx_misc:redact(Term).

View File

@ -106,6 +106,12 @@ common_bridge_fields() ->
status_fields() -> status_fields() ->
[ [
{"status", mk(status(), #{desc => ?DESC("desc_status")})}, {"status", mk(status(), #{desc => ?DESC("desc_status")})},
{"status_reason",
mk(binary(), #{
required => false,
desc => ?DESC("desc_status_reason"),
example => <<"Connection refused">>
})},
{"node_status", {"node_status",
mk( mk(
hoconsc:array(ref(?MODULE, "node_status")), hoconsc:array(ref(?MODULE, "node_status")),
@ -190,7 +196,13 @@ fields("node_metrics") ->
fields("node_status") -> fields("node_status") ->
[ [
node_name(), node_name(),
{"status", mk(status(), #{})} {"status", mk(status(), #{})},
{"status_reason",
mk(binary(), #{
required => false,
desc => ?DESC("desc_status_reason"),
example => <<"Connection refused">>
})}
]. ].
desc(bridges) -> desc(bridges) ->

View File

@ -23,7 +23,7 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-define(CONF_DEFAULT, <<"bridges: {}">>). -define(CONF_DEFAULT, <<"bridges: {}">>).
-define(BRIDGE_TYPE, <<"webhook">>). -define(BRIDGE_TYPE_HTTP, <<"webhook">>).
-define(BRIDGE_NAME, (atom_to_binary(?FUNCTION_NAME))). -define(BRIDGE_NAME, (atom_to_binary(?FUNCTION_NAME))).
-define(URL(PORT, PATH), -define(URL(PORT, PATH),
list_to_binary( list_to_binary(
@ -48,7 +48,7 @@
}). }).
-define(MQTT_BRIDGE(SERVER), ?MQTT_BRIDGE(SERVER, <<"mqtt_egress_test_bridge">>)). -define(MQTT_BRIDGE(SERVER), ?MQTT_BRIDGE(SERVER, <<"mqtt_egress_test_bridge">>)).
-define(HTTP_BRIDGE(URL, TYPE, NAME), ?BRIDGE(NAME, TYPE)#{ -define(HTTP_BRIDGE(URL, NAME), ?BRIDGE(NAME, ?BRIDGE_TYPE_HTTP)#{
<<"url">> => URL, <<"url">> => URL,
<<"local_topic">> => <<"emqx_webhook/#">>, <<"local_topic">> => <<"emqx_webhook/#">>,
<<"method">> => <<"post">>, <<"method">> => <<"post">>,
@ -57,6 +57,7 @@
<<"content-type">> => <<"application/json">> <<"content-type">> => <<"application/json">>
} }
}). }).
-define(HTTP_BRIDGE(URL), ?HTTP_BRIDGE(URL, ?BRIDGE_NAME)).
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -97,6 +98,20 @@ init_per_testcase(t_old_bpapi_vsn, Config) ->
meck:expect(emqx_bpapi, supported_version, 1, 1), meck:expect(emqx_bpapi, supported_version, 1, 1),
meck:expect(emqx_bpapi, supported_version, 2, 1), meck:expect(emqx_bpapi, supported_version, 2, 1),
init_per_testcase(common, Config); init_per_testcase(common, Config);
init_per_testcase(StartStop, Config) when
StartStop == t_start_stop_bridges_cluster;
StartStop == t_start_stop_bridges_node
->
meck:new(emqx_bridge_resource, [passthrough]),
meck:expect(
emqx_bridge_resource,
stop,
fun
(_, <<"bridge_not_found">>) -> {error, not_found};
(Type, Name) -> meck:passthrough([Type, Name])
end
),
init_per_testcase(common, Config);
init_per_testcase(_, Config) -> init_per_testcase(_, Config) ->
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
{Port, Sock, Acceptor} = start_http_server(fun handle_fun_200_ok/2), {Port, Sock, Acceptor} = start_http_server(fun handle_fun_200_ok/2),
@ -108,6 +123,12 @@ end_per_testcase(t_broken_bpapi_vsn, Config) ->
end_per_testcase(t_old_bpapi_vsn, Config) -> end_per_testcase(t_old_bpapi_vsn, Config) ->
meck:unload([emqx_bpapi]), meck:unload([emqx_bpapi]),
end_per_testcase(common, Config); end_per_testcase(common, Config);
end_per_testcase(StartStop, Config) when
StartStop == t_start_stop_bridges_cluster;
StartStop == t_start_stop_bridges_node
->
meck:unload([emqx_bridge_resource]),
end_per_testcase(common, Config);
end_per_testcase(_, Config) -> end_per_testcase(_, Config) ->
Sock = ?config(sock, Config), Sock = ?config(sock, Config),
Acceptor = ?config(acceptor, Config), Acceptor = ?config(acceptor, Config),
@ -206,12 +227,12 @@ t_http_crud_apis(Config) ->
{ok, 201, Bridge} = request( {ok, 201, Bridge} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
%ct:pal("---bridge: ~p", [Bridge]), %ct:pal("---bridge: ~p", [Bridge]),
#{ #{
<<"type">> := ?BRIDGE_TYPE, <<"type">> := ?BRIDGE_TYPE_HTTP,
<<"name">> := Name, <<"name">> := Name,
<<"enable">> := true, <<"enable">> := true,
<<"status">> := _, <<"status">> := _,
@ -219,7 +240,7 @@ t_http_crud_apis(Config) ->
<<"url">> := URL1 <<"url">> := URL1
} = emqx_json:decode(Bridge, [return_maps]), } = emqx_json:decode(Bridge, [return_maps]),
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name), BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name),
%% send an message to emqx and the message should be forwarded to the HTTP server %% send an message to emqx and the message should be forwarded to the HTTP server
Body = <<"my msg">>, Body = <<"my msg">>,
emqx:publish(emqx_message:make(<<"emqx_webhook/1">>, Body)), emqx:publish(emqx_message:make(<<"emqx_webhook/1">>, Body)),
@ -243,11 +264,11 @@ t_http_crud_apis(Config) ->
{ok, 200, Bridge2} = request( {ok, 200, Bridge2} = request(
put, put,
uri(["bridges", BridgeID]), uri(["bridges", BridgeID]),
?HTTP_BRIDGE(URL2, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL2, Name)
), ),
?assertMatch( ?assertMatch(
#{ #{
<<"type">> := ?BRIDGE_TYPE, <<"type">> := ?BRIDGE_TYPE_HTTP,
<<"name">> := Name, <<"name">> := Name,
<<"enable">> := true, <<"enable">> := true,
<<"status">> := _, <<"status">> := _,
@ -262,7 +283,7 @@ t_http_crud_apis(Config) ->
?assertMatch( ?assertMatch(
[ [
#{ #{
<<"type">> := ?BRIDGE_TYPE, <<"type">> := ?BRIDGE_TYPE_HTTP,
<<"name">> := Name, <<"name">> := Name,
<<"enable">> := true, <<"enable">> := true,
<<"status">> := _, <<"status">> := _,
@ -279,7 +300,7 @@ t_http_crud_apis(Config) ->
{ok, 200, Bridge3Str} = request(get, uri(["bridges", BridgeID]), []), {ok, 200, Bridge3Str} = request(get, uri(["bridges", BridgeID]), []),
?assertMatch( ?assertMatch(
#{ #{
<<"type">> := ?BRIDGE_TYPE, <<"type">> := ?BRIDGE_TYPE_HTTP,
<<"name">> := Name, <<"name">> := Name,
<<"enable">> := true, <<"enable">> := true,
<<"status">> := _, <<"status">> := _,
@ -311,7 +332,7 @@ t_http_crud_apis(Config) ->
{ok, 404, ErrMsg2} = request( {ok, 404, ErrMsg2} = request(
put, put,
uri(["bridges", BridgeID]), uri(["bridges", BridgeID]),
?HTTP_BRIDGE(URL2, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL2, Name)
), ),
?assertMatch( ?assertMatch(
#{ #{
@ -340,6 +361,34 @@ t_http_crud_apis(Config) ->
}, },
emqx_json:decode(ErrMsg3, [return_maps]) emqx_json:decode(ErrMsg3, [return_maps])
), ),
%% Create non working bridge
BrokenURL = ?URL(Port + 1, "/foo"),
{ok, 201, BrokenBridge} = request(
post,
uri(["bridges"]),
?HTTP_BRIDGE(BrokenURL, Name)
),
#{
<<"type">> := ?BRIDGE_TYPE_HTTP,
<<"name">> := Name,
<<"enable">> := true,
<<"status">> := <<"disconnected">>,
<<"status_reason">> := <<"Connection refused">>,
<<"node_status">> := [
#{<<"status">> := <<"disconnected">>, <<"status_reason">> := <<"Connection refused">>}
| _
],
<<"url">> := BrokenURL
} = emqx_json:decode(BrokenBridge, [return_maps]),
{ok, 200, FixedBridgeResponse} = request(put, uri(["bridges", BridgeID]), ?HTTP_BRIDGE(URL1)),
#{
<<"status">> := <<"connected">>,
<<"node_status">> := [FixedNodeStatus = #{<<"status">> := <<"connected">>} | _]
} = FixedBridge = emqx_json:decode(FixedBridgeResponse, [return_maps]),
?assert(not maps:is_key(<<"status_reason">>, FixedBridge)),
?assert(not maps:is_key(<<"status_reason">>, FixedNodeStatus)),
{ok, 204, <<>>} = request(delete, uri(["bridges", BridgeID]), []),
ok. ok.
t_http_bridges_local_topic(Config) -> t_http_bridges_local_topic(Config) ->
@ -356,16 +405,16 @@ t_http_bridges_local_topic(Config) ->
{ok, 201, _} = request( {ok, 201, _} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name1) ?HTTP_BRIDGE(URL1, Name1)
), ),
%% and we create another one without local_topic %% and we create another one without local_topic
{ok, 201, _} = request( {ok, 201, _} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
maps:remove(<<"local_topic">>, ?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name2)) maps:remove(<<"local_topic">>, ?HTTP_BRIDGE(URL1, Name2))
), ),
BridgeID1 = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name1), BridgeID1 = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name1),
BridgeID2 = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name2), BridgeID2 = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name2),
%% Send an message to emqx and the message should be forwarded to the HTTP server. %% Send an message to emqx and the message should be forwarded to the HTTP server.
%% This is to verify we can have 2 bridges with and without local_topic fields %% This is to verify we can have 2 bridges with and without local_topic fields
%% at the same time. %% at the same time.
@ -400,11 +449,11 @@ t_check_dependent_actions_on_delete(Config) ->
%% POST /bridges/ will create a bridge %% POST /bridges/ will create a bridge
URL1 = ?URL(Port, "path1"), URL1 = ?URL(Port, "path1"),
Name = <<"t_http_crud_apis">>, Name = <<"t_http_crud_apis">>,
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name), BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name),
{ok, 201, _} = request( {ok, 201, _} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
{ok, 201, Rule} = request( {ok, 201, Rule} = request(
post, post,
@ -438,11 +487,11 @@ t_cascade_delete_actions(Config) ->
%% POST /bridges/ will create a bridge %% POST /bridges/ will create a bridge
URL1 = ?URL(Port, "path1"), URL1 = ?URL(Port, "path1"),
Name = <<"t_http_crud_apis">>, Name = <<"t_http_crud_apis">>,
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name), BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name),
{ok, 201, _} = request( {ok, 201, _} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
{ok, 201, Rule} = request( {ok, 201, Rule} = request(
post, post,
@ -472,7 +521,7 @@ t_cascade_delete_actions(Config) ->
{ok, 201, _} = request( {ok, 201, _} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
{ok, 201, _} = request( {ok, 201, _} = request(
post, post,
@ -496,9 +545,9 @@ t_broken_bpapi_vsn(Config) ->
{ok, 201, _Bridge} = request( {ok, 201, _Bridge} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name), BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name),
%% still works since we redirect to 'restart' %% still works since we redirect to 'restart'
{ok, 501, <<>>} = request(post, operation_path(cluster, start, BridgeID), <<"">>), {ok, 501, <<>>} = request(post, operation_path(cluster, start, BridgeID), <<"">>),
{ok, 501, <<>>} = request(post, operation_path(node, start, BridgeID), <<"">>), {ok, 501, <<>>} = request(post, operation_path(node, start, BridgeID), <<"">>),
@ -511,9 +560,9 @@ t_old_bpapi_vsn(Config) ->
{ok, 201, _Bridge} = request( {ok, 201, _Bridge} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name), BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name),
{ok, 204, <<>>} = request(post, operation_path(cluster, stop, BridgeID), <<"">>), {ok, 204, <<>>} = request(post, operation_path(cluster, stop, BridgeID), <<"">>),
{ok, 204, <<>>} = request(post, operation_path(node, stop, BridgeID), <<"">>), {ok, 204, <<>>} = request(post, operation_path(node, stop, BridgeID), <<"">>),
%% still works since we redirect to 'restart' %% still works since we redirect to 'restart'
@ -551,18 +600,18 @@ do_start_stop_bridges(Type, Config) ->
{ok, 201, Bridge} = request( {ok, 201, Bridge} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
%ct:pal("the bridge ==== ~p", [Bridge]), %ct:pal("the bridge ==== ~p", [Bridge]),
#{ #{
<<"type">> := ?BRIDGE_TYPE, <<"type">> := ?BRIDGE_TYPE_HTTP,
<<"name">> := Name, <<"name">> := Name,
<<"enable">> := true, <<"enable">> := true,
<<"status">> := <<"connected">>, <<"status">> := <<"connected">>,
<<"node_status">> := [_ | _], <<"node_status">> := [_ | _],
<<"url">> := URL1 <<"url">> := URL1
} = emqx_json:decode(Bridge, [return_maps]), } = emqx_json:decode(Bridge, [return_maps]),
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name), BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name),
%% stop it %% stop it
{ok, 204, <<>>} = request(post, operation_path(Type, stop, BridgeID), <<"">>), {ok, 204, <<>>} = request(post, operation_path(Type, stop, BridgeID), <<"">>),
{ok, 200, Bridge2} = request(get, uri(["bridges", BridgeID]), []), {ok, 200, Bridge2} = request(get, uri(["bridges", BridgeID]), []),
@ -597,6 +646,16 @@ do_start_stop_bridges(Type, Config) ->
%% Looks ok but doesn't exist %% Looks ok but doesn't exist
{ok, 404, _} = request(post, operation_path(Type, start, <<"webhook:cptn_hook">>), <<"">>), {ok, 404, _} = request(post, operation_path(Type, start, <<"webhook:cptn_hook">>), <<"">>),
%%
{ok, 201, _Bridge} = request(
post,
uri(["bridges"]),
?HTTP_BRIDGE(URL1, <<"bridge_not_found">>)
),
{ok, 503, _} = request(
post, operation_path(Type, stop, <<"webhook:bridge_not_found">>), <<"">>
),
%% Create broken bridge %% Create broken bridge
{ListenPort, Sock} = listen_on_random_port(), {ListenPort, Sock} = listen_on_random_port(),
%% Connecting to this endpoint should always timeout %% Connecting to this endpoint should always timeout
@ -633,18 +692,18 @@ t_enable_disable_bridges(Config) ->
{ok, 201, Bridge} = request( {ok, 201, Bridge} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
%ct:pal("the bridge ==== ~p", [Bridge]), %ct:pal("the bridge ==== ~p", [Bridge]),
#{ #{
<<"type">> := ?BRIDGE_TYPE, <<"type">> := ?BRIDGE_TYPE_HTTP,
<<"name">> := Name, <<"name">> := Name,
<<"enable">> := true, <<"enable">> := true,
<<"status">> := <<"connected">>, <<"status">> := <<"connected">>,
<<"node_status">> := [_ | _], <<"node_status">> := [_ | _],
<<"url">> := URL1 <<"url">> := URL1
} = emqx_json:decode(Bridge, [return_maps]), } = emqx_json:decode(Bridge, [return_maps]),
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name), BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name),
%% disable it %% disable it
{ok, 204, <<>>} = request(put, enable_path(false, BridgeID), <<"">>), {ok, 204, <<>>} = request(put, enable_path(false, BridgeID), <<"">>),
{ok, 200, Bridge2} = request(get, uri(["bridges", BridgeID]), []), {ok, 200, Bridge2} = request(get, uri(["bridges", BridgeID]), []),
@ -690,18 +749,18 @@ t_reset_bridges(Config) ->
{ok, 201, Bridge} = request( {ok, 201, Bridge} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
%ct:pal("the bridge ==== ~p", [Bridge]), %ct:pal("the bridge ==== ~p", [Bridge]),
#{ #{
<<"type">> := ?BRIDGE_TYPE, <<"type">> := ?BRIDGE_TYPE_HTTP,
<<"name">> := Name, <<"name">> := Name,
<<"enable">> := true, <<"enable">> := true,
<<"status">> := <<"connected">>, <<"status">> := <<"connected">>,
<<"node_status">> := [_ | _], <<"node_status">> := [_ | _],
<<"url">> := URL1 <<"url">> := URL1
} = emqx_json:decode(Bridge, [return_maps]), } = emqx_json:decode(Bridge, [return_maps]),
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name), BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name),
{ok, 204, <<>>} = request(put, uri(["bridges", BridgeID, "metrics/reset"]), []), {ok, 204, <<>>} = request(put, uri(["bridges", BridgeID, "metrics/reset"]), []),
%% delete the bridge %% delete the bridge
@ -748,20 +807,20 @@ t_bridges_probe(Config) ->
{ok, 204, <<>>} = request( {ok, 204, <<>>} = request(
post, post,
uri(["bridges_probe"]), uri(["bridges_probe"]),
?HTTP_BRIDGE(URL, ?BRIDGE_TYPE, ?BRIDGE_NAME) ?HTTP_BRIDGE(URL)
), ),
%% second time with same name is ok since no real bridge created %% second time with same name is ok since no real bridge created
{ok, 204, <<>>} = request( {ok, 204, <<>>} = request(
post, post,
uri(["bridges_probe"]), uri(["bridges_probe"]),
?HTTP_BRIDGE(URL, ?BRIDGE_TYPE, ?BRIDGE_NAME) ?HTTP_BRIDGE(URL)
), ),
{ok, 400, NxDomain} = request( {ok, 400, NxDomain} = request(
post, post,
uri(["bridges_probe"]), uri(["bridges_probe"]),
?HTTP_BRIDGE(<<"http://203.0.113.3:1234/foo">>, ?BRIDGE_TYPE, ?BRIDGE_NAME) ?HTTP_BRIDGE(<<"http://203.0.113.3:1234/foo">>)
), ),
?assertMatch( ?assertMatch(
#{ #{
@ -790,7 +849,7 @@ t_bridges_probe(Config) ->
emqx_json:decode(ConnRefused, [return_maps]) emqx_json:decode(ConnRefused, [return_maps])
), ),
{ok, 400, HostNotFound} = request( {ok, 400, CouldNotResolveHost} = request(
post, post,
uri(["bridges_probe"]), uri(["bridges_probe"]),
?MQTT_BRIDGE(<<"nohost:2883">>) ?MQTT_BRIDGE(<<"nohost:2883">>)
@ -798,9 +857,9 @@ t_bridges_probe(Config) ->
?assertMatch( ?assertMatch(
#{ #{
<<"code">> := <<"TEST_FAILED">>, <<"code">> := <<"TEST_FAILED">>,
<<"message">> := <<"Host not found">> <<"message">> := <<"Could not resolve host">>
}, },
emqx_json:decode(HostNotFound, [return_maps]) emqx_json:decode(CouldNotResolveHost, [return_maps])
), ),
AuthnConfig = #{ AuthnConfig = #{
@ -844,7 +903,7 @@ t_bridges_probe(Config) ->
?assertMatch( ?assertMatch(
#{ #{
<<"code">> := <<"TEST_FAILED">>, <<"code">> := <<"TEST_FAILED">>,
<<"message">> := <<"Malformed username or password">> <<"message">> := <<"Bad username or password">>
}, },
emqx_json:decode(Malformed, [return_maps]) emqx_json:decode(Malformed, [return_maps])
), ),
@ -882,12 +941,12 @@ t_metrics(Config) ->
{ok, 201, Bridge} = request( {ok, 201, Bridge} = request(
post, post,
uri(["bridges"]), uri(["bridges"]),
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name) ?HTTP_BRIDGE(URL1, Name)
), ),
%ct:pal("---bridge: ~p", [Bridge]), %ct:pal("---bridge: ~p", [Bridge]),
#{ #{
<<"type">> := ?BRIDGE_TYPE, <<"type">> := ?BRIDGE_TYPE_HTTP,
<<"name">> := Name, <<"name">> := Name,
<<"enable">> := true, <<"enable">> := true,
<<"status">> := _, <<"status">> := _,
@ -895,7 +954,7 @@ t_metrics(Config) ->
<<"url">> := URL1 <<"url">> := URL1
} = emqx_json:decode(Bridge, [return_maps]), } = emqx_json:decode(Bridge, [return_maps]),
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, Name), BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE_HTTP, Name),
%% check for empty bridge metrics %% check for empty bridge metrics
{ok, 200, Bridge1Str} = request(get, uri(["bridges", BridgeID, "metrics"]), []), {ok, 200, Bridge1Str} = request(get, uri(["bridges", BridgeID, "metrics"]), []),
@ -963,7 +1022,7 @@ t_inconsistent_webhook_request_timeouts(Config) ->
Name = ?BRIDGE_NAME, Name = ?BRIDGE_NAME,
BadBridgeParams = BadBridgeParams =
emqx_map_lib:deep_merge( emqx_map_lib:deep_merge(
?HTTP_BRIDGE(URL1, ?BRIDGE_TYPE, Name), ?HTTP_BRIDGE(URL1, Name),
#{ #{
<<"request_timeout">> => <<"1s">>, <<"request_timeout">> => <<"1s">>,
<<"resource_opts">> => #{<<"request_timeout">> => <<"2s">>} <<"resource_opts">> => #{<<"request_timeout">> => <<"2s">>}

View File

@ -162,7 +162,7 @@ gen_schema_json(Dir, I18nFile, SchemaModule, Lang) ->
ok = file:write_file(SchemaJsonFile, IoData). ok = file:write_file(SchemaJsonFile, IoData).
gen_api_schema_json(Dir, I18nFile, Lang) -> gen_api_schema_json(Dir, I18nFile, Lang) ->
emqx_dashboard:init_i18n(I18nFile, Lang), emqx_dashboard:init_i18n(I18nFile, list_to_binary(Lang)),
gen_api_schema_json_hotconf(Dir, Lang), gen_api_schema_json_hotconf(Dir, Lang),
gen_api_schema_json_bridge(Dir, Lang), gen_api_schema_json_bridge(Dir, Lang),
emqx_dashboard:clear_i18n(). emqx_dashboard:clear_i18n().

View File

@ -1,4 +1,41 @@
emqx_ctl # emqx_ctl
=====
Backend module for `emqx_ctl` command. This application accepts dynamic `emqx ctl` command registrations so plugins can add their own commands.
Please note that the 'proxy' command `emqx_ctl` is considered deprecated, going forward, please use `emqx ctl` instead.
## Add a new command
To add a new command, the application must implement a callback function to handle the command, and register the command with `emqx_ctl:register_command/2` API.
### Register
To add a new command which can be executed from `emqx ctl`, the application must call `emqx_ctl:register_command/2` API to register the command.
For example, to add a new command `myplugin` which is to be executed as `emqx ctl myplugin`, the application must call `emqx_ctl:register_command/2` API as follows:
```erlang
emqx_ctl:register_command(mypluin, {myplugin_cli, cmd}).
```
### Callback
The callback function must be exported by the application and must have the following signature:
```erlang
cmd([Arg1, Arg2, ...]) -> ok.
```
It must also implement a special clause to handle the `usage` argument:
```erlang
cmd([usage]) -> "myplugin [arg1] [arg2] ...";
```
### Utility
The `emqx_ctl` application provides some utility functions which help to format the output of the command.
For example `emqx_ctl:print/2` and `emqx_ctl:usage/1`.
## Reference
[emqx_management_cli](../emqx_management/src/emqx_mgmt_cli.erl) can be taken as a reference for how to implement a command.

View File

@ -133,8 +133,8 @@ get_i18n() ->
application:get_env(emqx_dashboard, i18n). application:get_env(emqx_dashboard, i18n).
init_i18n(File, Lang) when is_atom(Lang) -> init_i18n(File, Lang) when is_atom(Lang) ->
init_i18n(File, atom_to_list(Lang)); init_i18n(File, atom_to_binary(Lang));
init_i18n(File, Lang) when is_list(Lang) -> init_i18n(File, Lang) when is_binary(Lang) ->
Cache = hocon_schema:new_desc_cache(File), Cache = hocon_schema:new_desc_cache(File),
application:set_env(emqx_dashboard, i18n, #{lang => Lang, cache => Cache}). application:set_env(emqx_dashboard, i18n, #{lang => Lang, cache => Cache}).

View File

@ -74,7 +74,7 @@ schema("/login") ->
post => #{ post => #{
tags => [<<"dashboard">>], tags => [<<"dashboard">>],
desc => ?DESC(login_api), desc => ?DESC(login_api),
summary => <<"Dashboard Auth">>, summary => <<"Dashboard authentication">>,
'requestBody' => fields([username, password]), 'requestBody' => fields([username, password]),
responses => #{ responses => #{
200 => fields([token, version, license]), 200 => fields([token, version, license]),

View File

@ -155,6 +155,18 @@ t_rest_api(_Config) ->
emqx_dashboard_admin:add_user(<<"admin">>, Password, <<"administrator">>), emqx_dashboard_admin:add_user(<<"admin">>, Password, <<"administrator">>),
ok. ok.
t_swagger_json(_Config) ->
Url = ?HOST ++ "/api-docs/swagger.json",
%% with auth
Auth = auth_header_(<<"admin">>, <<"public_www1">>),
{ok, 200, Body1} = request_api(get, Url, Auth),
?assert(jsx:is_json(Body1)),
%% without auth
{ok, {{"HTTP/1.1", 200, "OK"}, _Headers, Body2}} =
httpc:request(get, {Url, []}, [], [{body_format, binary}]),
?assertEqual(Body1, Body2),
ok.
t_cli(_Config) -> t_cli(_Config) ->
[mria:dirty_delete(?ADMIN, Admin) || Admin <- mnesia:dirty_all_keys(?ADMIN)], [mria:dirty_delete(?ADMIN, Admin) || Admin <- mnesia:dirty_all_keys(?ADMIN)],
emqx_dashboard_cli:admins(["add", "username", "password_ww2"]), emqx_dashboard_cli:admins(["add", "username", "password_ww2"]),

View File

@ -180,7 +180,7 @@ schema("/gateways") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(list_gateway), desc => ?DESC(list_gateway),
summary => <<"List All Gateways">>, summary => <<"List all gateways">>,
parameters => params_gateway_status_in_qs(), parameters => params_gateway_status_in_qs(),
responses => responses =>
#{ #{
@ -201,7 +201,7 @@ schema("/gateways/:name") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(get_gateway), desc => ?DESC(get_gateway),
summary => <<"Get the Gateway">>, summary => <<"Get gateway">>,
parameters => params_gateway_name_in_path(), parameters => params_gateway_name_in_path(),
responses => responses =>
#{ #{
@ -608,7 +608,7 @@ examples_gateway_confs() ->
#{ #{
stomp_gateway => stomp_gateway =>
#{ #{
summary => <<"A simple STOMP gateway configs">>, summary => <<"A simple STOMP gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,
@ -636,7 +636,7 @@ examples_gateway_confs() ->
}, },
mqttsn_gateway => mqttsn_gateway =>
#{ #{
summary => <<"A simple MQTT-SN gateway configs">>, summary => <<"A simple MQTT-SN gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,
@ -672,7 +672,7 @@ examples_gateway_confs() ->
}, },
coap_gateway => coap_gateway =>
#{ #{
summary => <<"A simple CoAP gateway configs">>, summary => <<"A simple CoAP gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,
@ -699,7 +699,7 @@ examples_gateway_confs() ->
}, },
lwm2m_gateway => lwm2m_gateway =>
#{ #{
summary => <<"A simple LwM2M gateway configs">>, summary => <<"A simple LwM2M gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,
@ -735,7 +735,7 @@ examples_gateway_confs() ->
}, },
exproto_gateway => exproto_gateway =>
#{ #{
summary => <<"A simple ExProto gateway configs">>, summary => <<"A simple ExProto gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,
@ -765,7 +765,7 @@ examples_update_gateway_confs() ->
#{ #{
stomp_gateway => stomp_gateway =>
#{ #{
summary => <<"A simple STOMP gateway configs">>, summary => <<"A simple STOMP gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,
@ -782,7 +782,7 @@ examples_update_gateway_confs() ->
}, },
mqttsn_gateway => mqttsn_gateway =>
#{ #{
summary => <<"A simple MQTT-SN gateway configs">>, summary => <<"A simple MQTT-SN gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,
@ -803,7 +803,7 @@ examples_update_gateway_confs() ->
}, },
coap_gateway => coap_gateway =>
#{ #{
summary => <<"A simple CoAP gateway configs">>, summary => <<"A simple CoAP gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,
@ -819,7 +819,7 @@ examples_update_gateway_confs() ->
}, },
lwm2m_gateway => lwm2m_gateway =>
#{ #{
summary => <<"A simple LwM2M gateway configs">>, summary => <<"A simple LwM2M gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,
@ -844,7 +844,7 @@ examples_update_gateway_confs() ->
}, },
exproto_gateway => exproto_gateway =>
#{ #{
summary => <<"A simple ExProto gateway configs">>, summary => <<"A simple ExProto gateway config">>,
value => value =>
#{ #{
enable => true, enable => true,

View File

@ -185,13 +185,13 @@ schema("/gateways/:name/authentication") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(get_authn), desc => ?DESC(get_authn),
summary => <<"Get Authenticator Configuration">>, summary => <<"Get authenticator configuration">>,
parameters => params_gateway_name_in_path(), parameters => params_gateway_name_in_path(),
responses => responses =>
?STANDARD_RESP( ?STANDARD_RESP(
#{ #{
200 => schema_authn(), 200 => schema_authn(),
204 => <<"Authenticator doesn't initiated">> 204 => <<"Authenticator not initialized">>
} }
) )
}, },
@ -199,7 +199,7 @@ schema("/gateways/:name/authentication") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(update_authn), desc => ?DESC(update_authn),
summary => <<"Update Authenticator Configuration">>, summary => <<"Update authenticator configuration">>,
parameters => params_gateway_name_in_path(), parameters => params_gateway_name_in_path(),
'requestBody' => schema_authn(), 'requestBody' => schema_authn(),
responses => responses =>
@ -209,7 +209,7 @@ schema("/gateways/:name/authentication") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(add_authn), desc => ?DESC(add_authn),
summary => <<"Create an Authenticator for a Gateway">>, summary => <<"Create authenticator for gateway">>,
parameters => params_gateway_name_in_path(), parameters => params_gateway_name_in_path(),
'requestBody' => schema_authn(), 'requestBody' => schema_authn(),
responses => responses =>
@ -219,7 +219,7 @@ schema("/gateways/:name/authentication") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(delete_authn), desc => ?DESC(delete_authn),
summary => <<"Delete the Gateway Authenticator">>, summary => <<"Delete gateway authenticator">>,
parameters => params_gateway_name_in_path(), parameters => params_gateway_name_in_path(),
responses => responses =>
?STANDARD_RESP(#{204 => <<"Deleted">>}) ?STANDARD_RESP(#{204 => <<"Deleted">>})
@ -232,7 +232,7 @@ schema("/gateways/:name/authentication/users") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(list_users), desc => ?DESC(list_users),
summary => <<"List users for a Gateway Authenticator">>, summary => <<"List users for gateway authenticator">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_paging_in_qs() ++ params_paging_in_qs() ++
params_fuzzy_in_qs(), params_fuzzy_in_qs(),
@ -250,7 +250,7 @@ schema("/gateways/:name/authentication/users") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(add_user), desc => ?DESC(add_user),
summary => <<"Add User for a Gateway Authenticator">>, summary => <<"Add user for gateway authenticator">>,
parameters => params_gateway_name_in_path(), parameters => params_gateway_name_in_path(),
'requestBody' => emqx_dashboard_swagger:schema_with_examples( 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
ref(emqx_authn_api, request_user_create), ref(emqx_authn_api, request_user_create),
@ -274,7 +274,7 @@ schema("/gateways/:name/authentication/users/:uid") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(get_user), desc => ?DESC(get_user),
summary => <<"Get User Info for a Gateway Authenticator">>, summary => <<"Get user info for gateway authenticator">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_userid_in_path(), params_userid_in_path(),
responses => responses =>
@ -291,7 +291,7 @@ schema("/gateways/:name/authentication/users/:uid") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(update_user), desc => ?DESC(update_user),
summary => <<"Update User Info for a Gateway Authenticator">>, summary => <<"Update user info for gateway authenticator">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_userid_in_path(), params_userid_in_path(),
'requestBody' => emqx_dashboard_swagger:schema_with_examples( 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
@ -312,7 +312,7 @@ schema("/gateways/:name/authentication/users/:uid") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(delete_user), desc => ?DESC(delete_user),
summary => <<"Delete User for a Gateway Authenticator">>, summary => <<"Delete user for gateway authenticator">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_userid_in_path(), params_userid_in_path(),
responses => responses =>

View File

@ -126,7 +126,7 @@ schema("/gateways/:name/authentication/import_users") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(emqx_gateway_api_authn, import_users), desc => ?DESC(emqx_gateway_api_authn, import_users),
summary => <<"Import Users">>, summary => <<"Import users">>,
parameters => params_gateway_name_in_path(), parameters => params_gateway_name_in_path(),
'requestBody' => emqx_dashboard_swagger:file_schema(filename), 'requestBody' => emqx_dashboard_swagger:file_schema(filename),
responses => responses =>
@ -140,7 +140,7 @@ schema("/gateways/:name/listeners/:id/authentication/import_users") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(emqx_gateway_api_listeners, import_users), desc => ?DESC(emqx_gateway_api_listeners, import_users),
summary => <<"Import Users">>, summary => <<"Import users">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path(), params_listener_id_in_path(),
'requestBody' => emqx_dashboard_swagger:file_schema(filename), 'requestBody' => emqx_dashboard_swagger:file_schema(filename),

View File

@ -460,7 +460,7 @@ schema("/gateways/:name/clients") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(list_clients), desc => ?DESC(list_clients),
summary => <<"List Gateway's Clients">>, summary => <<"List gateway's clients">>,
parameters => params_client_query(), parameters => params_client_query(),
responses => responses =>
?STANDARD_RESP(#{ ?STANDARD_RESP(#{
@ -478,7 +478,7 @@ schema("/gateways/:name/clients/:clientid") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(get_client), desc => ?DESC(get_client),
summary => <<"Get Client Info">>, summary => <<"Get client info">>,
parameters => params_client_insta(), parameters => params_client_insta(),
responses => responses =>
?STANDARD_RESP(#{200 => schema_client()}) ?STANDARD_RESP(#{200 => schema_client()})
@ -487,7 +487,7 @@ schema("/gateways/:name/clients/:clientid") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(kick_client), desc => ?DESC(kick_client),
summary => <<"Kick out Client">>, summary => <<"Kick out client">>,
parameters => params_client_insta(), parameters => params_client_insta(),
responses => responses =>
?STANDARD_RESP(#{204 => <<"Kicked">>}) ?STANDARD_RESP(#{204 => <<"Kicked">>})
@ -500,7 +500,7 @@ schema("/gateways/:name/clients/:clientid/subscriptions") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(list_subscriptions), desc => ?DESC(list_subscriptions),
summary => <<"List Client's Subscription">>, summary => <<"List client's subscription">>,
parameters => params_client_insta(), parameters => params_client_insta(),
responses => responses =>
?STANDARD_RESP( ?STANDARD_RESP(
@ -516,7 +516,7 @@ schema("/gateways/:name/clients/:clientid/subscriptions") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(add_subscription), desc => ?DESC(add_subscription),
summary => <<"Add Subscription for Client">>, summary => <<"Add subscription for client">>,
parameters => params_client_insta(), parameters => params_client_insta(),
'requestBody' => emqx_dashboard_swagger:schema_with_examples( 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
ref(subscription), ref(subscription),
@ -540,7 +540,7 @@ schema("/gateways/:name/clients/:clientid/subscriptions/:topic") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(delete_subscription), desc => ?DESC(delete_subscription),
summary => <<"Delete Client's Subscription">>, summary => <<"Delete client's subscription">>,
parameters => params_topic_name_in_path() ++ params_client_insta(), parameters => params_topic_name_in_path() ++ params_client_insta(),
responses => responses =>
?STANDARD_RESP(#{204 => <<"Unsubscribed">>}) ?STANDARD_RESP(#{204 => <<"Unsubscribed">>})
@ -1020,12 +1020,12 @@ examples_client_list() ->
#{ #{
general_client_list => general_client_list =>
#{ #{
summary => <<"General Client List">>, summary => <<"General client list">>,
value => [example_general_client()] value => [example_general_client()]
}, },
lwm2m_client_list => lwm2m_client_list =>
#{ #{
summary => <<"LwM2M Client List">>, summary => <<"LwM2M client list">>,
value => [example_lwm2m_client()] value => [example_lwm2m_client()]
} }
}. }.
@ -1034,12 +1034,12 @@ examples_client() ->
#{ #{
general_client => general_client =>
#{ #{
summary => <<"General Client Info">>, summary => <<"General client info">>,
value => example_general_client() value => example_general_client()
}, },
lwm2m_client => lwm2m_client =>
#{ #{
summary => <<"LwM2M Client Info">>, summary => <<"LwM2M client info">>,
value => example_lwm2m_client() value => example_lwm2m_client()
} }
}. }.
@ -1048,12 +1048,12 @@ examples_subscription_list() ->
#{ #{
general_subscription_list => general_subscription_list =>
#{ #{
summary => <<"A General Subscription List">>, summary => <<"A general subscription list">>,
value => [example_general_subscription()] value => [example_general_subscription()]
}, },
stomp_subscription_list => stomp_subscription_list =>
#{ #{
summary => <<"The Stomp Subscription List">>, summary => <<"The STOMP subscription list">>,
value => [example_stomp_subscription] value => [example_stomp_subscription]
} }
}. }.
@ -1062,12 +1062,12 @@ examples_subscription() ->
#{ #{
general_subscription => general_subscription =>
#{ #{
summary => <<"A General Subscription">>, summary => <<"A general subscription">>,
value => example_general_subscription() value => example_general_subscription()
}, },
stomp_subscription => stomp_subscription =>
#{ #{
summary => <<"A Stomp Subscription">>, summary => <<"A STOMP subscription">>,
value => example_stomp_subscription() value => example_stomp_subscription()
} }
}. }.

View File

@ -362,7 +362,7 @@ schema("/gateways/:name/listeners") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(list_listeners), desc => ?DESC(list_listeners),
summary => <<"List All Listeners">>, summary => <<"List all listeners">>,
parameters => params_gateway_name_in_path(), parameters => params_gateway_name_in_path(),
responses => responses =>
?STANDARD_RESP( ?STANDARD_RESP(
@ -378,7 +378,7 @@ schema("/gateways/:name/listeners") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(add_listener), desc => ?DESC(add_listener),
summary => <<"Add a Listener">>, summary => <<"Add listener">>,
parameters => params_gateway_name_in_path(), parameters => params_gateway_name_in_path(),
%% XXX: How to distinguish the different listener supported by %% XXX: How to distinguish the different listener supported by
%% different types of gateways? %% different types of gateways?
@ -404,7 +404,7 @@ schema("/gateways/:name/listeners/:id") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(get_listener), desc => ?DESC(get_listener),
summary => <<"Get the Listener Configs">>, summary => <<"Get listener config">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path(), params_listener_id_in_path(),
responses => responses =>
@ -421,7 +421,7 @@ schema("/gateways/:name/listeners/:id") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(delete_listener), desc => ?DESC(delete_listener),
summary => <<"Delete the Listener">>, summary => <<"Delete listener">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path(), params_listener_id_in_path(),
responses => responses =>
@ -431,7 +431,7 @@ schema("/gateways/:name/listeners/:id") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(update_listener), desc => ?DESC(update_listener),
summary => <<"Update the Listener Configs">>, summary => <<"Update listener config">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path(), params_listener_id_in_path(),
'requestBody' => emqx_dashboard_swagger:schema_with_examples( 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
@ -456,7 +456,7 @@ schema("/gateways/:name/listeners/:id/authentication") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(get_listener_authn), desc => ?DESC(get_listener_authn),
summary => <<"Get the Listener's Authenticator">>, summary => <<"Get the listener's authenticator">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path(), params_listener_id_in_path(),
responses => responses =>
@ -471,7 +471,7 @@ schema("/gateways/:name/listeners/:id/authentication") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(add_listener_authn), desc => ?DESC(add_listener_authn),
summary => <<"Create an Authenticator for a Listener">>, summary => <<"Create authenticator for listener">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path(), params_listener_id_in_path(),
'requestBody' => schema_authn(), 'requestBody' => schema_authn(),
@ -482,7 +482,7 @@ schema("/gateways/:name/listeners/:id/authentication") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(update_listener_authn), desc => ?DESC(update_listener_authn),
summary => <<"Update the Listener Authenticator configs">>, summary => <<"Update config of authenticator for listener">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path(), params_listener_id_in_path(),
'requestBody' => schema_authn(), 'requestBody' => schema_authn(),
@ -493,7 +493,7 @@ schema("/gateways/:name/listeners/:id/authentication") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(delete_listener_authn), desc => ?DESC(delete_listener_authn),
summary => <<"Delete the Listener's Authenticator">>, summary => <<"Delete the listener's authenticator">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path(), params_listener_id_in_path(),
responses => responses =>
@ -507,7 +507,7 @@ schema("/gateways/:name/listeners/:id/authentication/users") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(list_users), desc => ?DESC(list_users),
summary => <<"List Authenticator's Users">>, summary => <<"List authenticator's users">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path() ++ params_listener_id_in_path() ++
params_paging_in_qs(), params_paging_in_qs(),
@ -525,7 +525,7 @@ schema("/gateways/:name/listeners/:id/authentication/users") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(add_user), desc => ?DESC(add_user),
summary => <<"Add User for an Authenticator">>, summary => <<"Add user for an authenticator">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path(), params_listener_id_in_path(),
'requestBody' => emqx_dashboard_swagger:schema_with_examples( 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
@ -550,7 +550,7 @@ schema("/gateways/:name/listeners/:id/authentication/users/:uid") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(get_user), desc => ?DESC(get_user),
summary => <<"Get User Info">>, summary => <<"Get user info">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path() ++ params_listener_id_in_path() ++
params_userid_in_path(), params_userid_in_path(),
@ -568,7 +568,7 @@ schema("/gateways/:name/listeners/:id/authentication/users/:uid") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(update_user), desc => ?DESC(update_user),
summary => <<"Update User Info">>, summary => <<"Update user info">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path() ++ params_listener_id_in_path() ++
params_userid_in_path(), params_userid_in_path(),
@ -590,7 +590,7 @@ schema("/gateways/:name/listeners/:id/authentication/users/:uid") ->
#{ #{
tags => ?TAGS, tags => ?TAGS,
desc => ?DESC(delete_user), desc => ?DESC(delete_user),
summary => <<"Delete User">>, summary => <<"Delete user">>,
parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path() ++
params_listener_id_in_path() ++ params_listener_id_in_path() ++
params_userid_in_path(), params_userid_in_path(),
@ -712,7 +712,7 @@ examples_listener() ->
#{ #{
tcp_listener => tcp_listener =>
#{ #{
summary => <<"A simple tcp listener example">>, summary => <<"A simple TCP listener example">>,
value => value =>
#{ #{
name => <<"tcp-def">>, name => <<"tcp-def">>,
@ -738,7 +738,7 @@ examples_listener() ->
}, },
ssl_listener => ssl_listener =>
#{ #{
summary => <<"A simple ssl listener example">>, summary => <<"A simple SSL listener example">>,
value => value =>
#{ #{
name => <<"ssl-def">>, name => <<"ssl-def">>,
@ -771,7 +771,7 @@ examples_listener() ->
}, },
udp_listener => udp_listener =>
#{ #{
summary => <<"A simple udp listener example">>, summary => <<"A simple UDP listener example">>,
value => value =>
#{ #{
name => <<"udp-def">>, name => <<"udp-def">>,
@ -789,7 +789,7 @@ examples_listener() ->
}, },
dtls_listener => dtls_listener =>
#{ #{
summary => <<"A simple dtls listener example">>, summary => <<"A simple DTLS listener example">>,
value => value =>
#{ #{
name => <<"dtls-def">>, name => <<"dtls-def">>,
@ -817,7 +817,7 @@ examples_listener() ->
}, },
dtls_listener_with_psk_ciphers => dtls_listener_with_psk_ciphers =>
#{ #{
summary => <<"A dtls listener with PSK example">>, summary => <<"A DTLS listener with PSK example">>,
value => value =>
#{ #{
name => <<"dtls-psk">>, name => <<"dtls-psk">>,
@ -845,7 +845,7 @@ examples_listener() ->
}, },
lisetner_with_authn => lisetner_with_authn =>
#{ #{
summary => <<"A tcp listener with authentication example">>, summary => <<"A TCP listener with authentication example">>,
value => value =>
#{ #{
name => <<"tcp-with-authn">>, name => <<"tcp-with-authn">>,

View File

@ -1,7 +1,7 @@
emqx_topic_metrics_api { emqx_topic_metrics_api {
get_topic_metrics_api { get_topic_metrics_api {
desc { desc {
en: """List Topic metrics""" en: """List topic metrics"""
zh: """获取主题监控数据""" zh: """获取主题监控数据"""
} }
} }
@ -15,21 +15,21 @@ emqx_topic_metrics_api {
post_topic_metrics_api { post_topic_metrics_api {
desc { desc {
en: """Create Topic metrics""" en: """Create topic metrics"""
zh: """创建主题监控数据""" zh: """创建主题监控数据"""
} }
} }
gat_topic_metrics_data_api { gat_topic_metrics_data_api {
desc { desc {
en: """Get Topic metrics""" en: """Get topic metrics"""
zh: """获取主题监控数据""" zh: """获取主题监控数据"""
} }
} }
delete_topic_metrics_data_api { delete_topic_metrics_data_api {
desc { desc {
en: """Delete Topic metrics""" en: """Delete topic metrics"""
zh: """删除主题监控数据""" zh: """删除主题监控数据"""
} }
} }
@ -43,7 +43,7 @@ emqx_topic_metrics_api {
topic_metrics_api_response400 { topic_metrics_api_response400 {
desc { desc {
en: """Bad Request. Already exists or bad topic name""" en: """Bad request. Already exists or bad topic name"""
zh: """错误请求。已存在或错误的主题名称""" zh: """错误请求。已存在或错误的主题名称"""
} }
} }

View File

@ -41,6 +41,7 @@
callback_mode := callback_mode(), callback_mode := callback_mode(),
query_mode := query_mode(), query_mode := query_mode(),
config := resource_config(), config := resource_config(),
error := term(),
state := resource_state(), state := resource_state(),
status := resource_status(), status := resource_status(),
metrics => emqx_metrics_worker:metrics() metrics => emqx_metrics_worker:metrics()

View File

@ -522,7 +522,7 @@ start_resource(Data, From) ->
id => Data#data.id, id => Data#data.id,
reason => Reason reason => Reason
}), }),
_ = maybe_alarm(disconnected, Data#data.id), _ = maybe_alarm(disconnected, Data#data.id, Data#data.error),
%% Keep track of the error reason why the connection did not work %% Keep track of the error reason why the connection did not work
%% so that the Reason can be returned when the verification call is made. %% so that the Reason can be returned when the verification call is made.
UpdatedData = Data#data{status = disconnected, error = Reason}, UpdatedData = Data#data{status = disconnected, error = Reason},
@ -597,7 +597,7 @@ with_health_check(Data, Func) ->
ResId = Data#data.id, ResId = Data#data.id,
HCRes = emqx_resource:call_health_check(Data#data.manager_id, Data#data.mod, Data#data.state), HCRes = emqx_resource:call_health_check(Data#data.manager_id, Data#data.mod, Data#data.state),
{Status, NewState, Err} = parse_health_check_result(HCRes, Data), {Status, NewState, Err} = parse_health_check_result(HCRes, Data),
_ = maybe_alarm(Status, ResId), _ = maybe_alarm(Status, ResId, Err),
ok = maybe_resume_resource_workers(ResId, Status), ok = maybe_resume_resource_workers(ResId, Status),
UpdatedData = Data#data{ UpdatedData = Data#data{
state = NewState, status = Status, error = Err state = NewState, status = Status, error = Err
@ -616,15 +616,20 @@ update_state(Data, _DataWas) ->
health_check_interval(Opts) -> health_check_interval(Opts) ->
maps:get(health_check_interval, Opts, ?HEALTHCHECK_INTERVAL). maps:get(health_check_interval, Opts, ?HEALTHCHECK_INTERVAL).
maybe_alarm(connected, _ResId) -> maybe_alarm(connected, _ResId, _Error) ->
ok; ok;
maybe_alarm(_Status, <<?TEST_ID_PREFIX, _/binary>>) -> maybe_alarm(_Status, <<?TEST_ID_PREFIX, _/binary>>, _Error) ->
ok; ok;
maybe_alarm(_Status, ResId) -> maybe_alarm(_Status, ResId, Error) ->
HrError =
case Error of
undefined -> <<"Unknown reason">>;
_Else -> emqx_misc:readable_error_msg(Error)
end,
emqx_alarm:activate( emqx_alarm:activate(
ResId, ResId,
#{resource_id => ResId, reason => resource_down}, #{resource_id => ResId, reason => resource_down},
<<"resource down: ", ResId/binary>> <<"resource down: ", HrError/binary>>
). ).
maybe_resume_resource_workers(ResId, connected) -> maybe_resume_resource_workers(ResId, connected) ->
@ -666,6 +671,7 @@ maybe_reply(Actions, From, Reply) ->
data_record_to_external_map(Data) -> data_record_to_external_map(Data) ->
#{ #{
id => Data#data.id, id => Data#data.id,
error => Data#data.error,
mod => Data#data.mod, mod => Data#data.mod,
callback_mode => Data#data.callback_mode, callback_mode => Data#data.callback_mode,
query_mode => Data#data.query_mode, query_mode => Data#data.query_mode,

View File

@ -180,7 +180,7 @@ schema("/rules") ->
ref(emqx_dashboard_swagger, page), ref(emqx_dashboard_swagger, page),
ref(emqx_dashboard_swagger, limit) ref(emqx_dashboard_swagger, limit)
], ],
summary => <<"List Rules">>, summary => <<"List rules">>,
responses => #{ responses => #{
200 => 200 =>
[ [
@ -193,7 +193,7 @@ schema("/rules") ->
post => #{ post => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => ?DESC("api2"), description => ?DESC("api2"),
summary => <<"Create a Rule">>, summary => <<"Create a rule">>,
'requestBody' => rule_creation_schema(), 'requestBody' => rule_creation_schema(),
responses => #{ responses => #{
400 => error_schema('BAD_REQUEST', "Invalid Parameters"), 400 => error_schema('BAD_REQUEST', "Invalid Parameters"),
@ -207,7 +207,7 @@ schema("/rule_events") ->
get => #{ get => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => ?DESC("api3"), description => ?DESC("api3"),
summary => <<"List Events">>, summary => <<"List rule events">>,
responses => #{ responses => #{
200 => mk(ref(emqx_rule_api_schema, "rule_events"), #{}) 200 => mk(ref(emqx_rule_api_schema, "rule_events"), #{})
} }
@ -219,7 +219,7 @@ schema("/rules/:id") ->
get => #{ get => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => ?DESC("api4"), description => ?DESC("api4"),
summary => <<"Get a Rule">>, summary => <<"Get rule">>,
parameters => param_path_id(), parameters => param_path_id(),
responses => #{ responses => #{
404 => error_schema('NOT_FOUND', "Rule not found"), 404 => error_schema('NOT_FOUND', "Rule not found"),
@ -229,7 +229,7 @@ schema("/rules/:id") ->
put => #{ put => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => ?DESC("api5"), description => ?DESC("api5"),
summary => <<"Update a Rule">>, summary => <<"Update rule">>,
parameters => param_path_id(), parameters => param_path_id(),
'requestBody' => rule_creation_schema(), 'requestBody' => rule_creation_schema(),
responses => #{ responses => #{
@ -240,7 +240,7 @@ schema("/rules/:id") ->
delete => #{ delete => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => ?DESC("api6"), description => ?DESC("api6"),
summary => <<"Delete a Rule">>, summary => <<"Delete rule">>,
parameters => param_path_id(), parameters => param_path_id(),
responses => #{ responses => #{
204 => <<"Delete rule successfully">> 204 => <<"Delete rule successfully">>
@ -253,7 +253,7 @@ schema("/rules/:id/metrics") ->
get => #{ get => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => ?DESC("api4_1"), description => ?DESC("api4_1"),
summary => <<"Get a Rule's Metrics">>, summary => <<"Get rule metrics">>,
parameters => param_path_id(), parameters => param_path_id(),
responses => #{ responses => #{
404 => error_schema('NOT_FOUND', "Rule not found"), 404 => error_schema('NOT_FOUND', "Rule not found"),
@ -267,7 +267,7 @@ schema("/rules/:id/metrics/reset") ->
put => #{ put => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => ?DESC("api7"), description => ?DESC("api7"),
summary => <<"Reset a Rule Metrics">>, summary => <<"Reset rule metrics">>,
parameters => param_path_id(), parameters => param_path_id(),
responses => #{ responses => #{
404 => error_schema('NOT_FOUND', "Rule not found"), 404 => error_schema('NOT_FOUND', "Rule not found"),
@ -281,7 +281,7 @@ schema("/rule_test") ->
post => #{ post => #{
tags => [<<"rules">>], tags => [<<"rules">>],
description => ?DESC("api8"), description => ?DESC("api8"),
summary => <<"Test a Rule">>, summary => <<"Test a rule">>,
'requestBody' => rule_test_schema(), 'requestBody' => rule_test_schema(),
responses => #{ responses => #{
400 => error_schema('BAD_REQUEST', "Invalid Parameters"), 400 => error_schema('BAD_REQUEST', "Invalid Parameters"),

25
build
View File

@ -147,7 +147,7 @@ make_rel() {
make_elixir_rel() { make_elixir_rel() {
./scripts/pre-compile.sh "$PROFILE" ./scripts/pre-compile.sh "$PROFILE"
export_release_vars "$PROFILE" export_elixir_release_vars "$PROFILE"
# for some reason, this has to be run outside "do"... # for some reason, this has to be run outside "do"...
mix local.rebar --if-missing --force mix local.rebar --if-missing --force
# shellcheck disable=SC1010 # shellcheck disable=SC1010
@ -362,7 +362,7 @@ function join {
# used to control the Elixir Mix Release output # used to control the Elixir Mix Release output
# see docstring in `mix.exs` # see docstring in `mix.exs`
export_release_vars() { export_elixir_release_vars() {
local profile="$1" local profile="$1"
case "$profile" in case "$profile" in
emqx|emqx-enterprise) emqx|emqx-enterprise)
@ -376,27 +376,6 @@ export_release_vars() {
exit 1 exit 1
esac esac
export MIX_ENV="$profile" export MIX_ENV="$profile"
local erl_opts=()
case "$(is_enterprise "$profile")" in
'yes')
erl_opts+=( "{d, 'EMQX_RELEASE_EDITION', ee}" )
;;
'no')
erl_opts+=( "{d, 'EMQX_RELEASE_EDITION', ce}" )
;;
esac
# At this time, Mix provides no easy way to pass `erl_opts' to
# dependencies. The workaround is to set this variable before
# compiling the project, so that `emqx_release.erl' picks up
# `emqx_vsn' as if it was compiled by rebar3.
erl_opts+=( "{compile_info,[{emqx_vsn,\"${PKG_VSN}\"}]}" )
erl_opts+=( "{d,snk_kind,msg}" )
ERL_COMPILER_OPTIONS="[$(join , "${erl_opts[@]}")]"
export ERL_COMPILER_OPTIONS
} }
log "building artifact=$ARTIFACT for profile=$PROFILE" log "building artifact=$ARTIFACT for profile=$PROFILE"

View File

@ -1 +0,0 @@
Add low level tuning settings for QUIC listeners.

View File

@ -1 +0,0 @@
为 QUIC 监听器添加更多底层调优选项。

View File

@ -1 +0,0 @@
Start releasing Rocky Linux 9 (compatible with Enterprise Linux 9) and MacOS 12 packages

View File

@ -1 +0,0 @@
开始发布Rocky Linux 9与Enterprise Linux 9兼容和MacOS 12软件包。

View File

@ -1 +0,0 @@
Errors returned by rule engine API are formatted in a more human readable way rather than dumping the raw error including the stacktrace.

View File

@ -1 +0,0 @@
规则引擎 API 返回用户可读的错误信息而不是原始的栈追踪信息。

View File

@ -1 +0,0 @@
Add deb package support for `raspbian9` and `raspbian10`.

View File

@ -1 +0,0 @@
`raspbian9``raspbian10` 增加 deb 包支持。

View File

@ -1 +1 @@
Add support for OCSP stapling and CRL check for SSL MQTT listeners. Add support for OCSP stapling for SSL MQTT listeners.

View File

@ -1 +0,0 @@
为 SSL MQTT 监听器增加对 OCSP Stapling 的支持。

View File

@ -0,0 +1 @@
Add CRL check support for TLS MQTT listeners.

View File

@ -1 +0,0 @@
Add pod disruption budget to helm chart

View File

@ -1 +0,0 @@
在 Helm chart 中添加干扰预算 (disruption budget)。

View File

@ -1,2 +0,0 @@
When connecting with the flag `clean_start=false`, EMQX will filter out messages that published by banned clients.
Previously, the messages sent by banned clients may still be delivered to subscribers in this scenario.

View File

@ -1,2 +0,0 @@
当使用 `clean_start=false` 标志连接时EMQX 将会从消息队列中过滤出被封禁客户端发出的消息,使它们不能被下发给订阅者。
此前被封禁客户端发出的消息仍可能在这一场景下被下发给订阅者。

View File

@ -1,2 +0,0 @@
QUIC transport Multistreams support and QUIC TLS cacert support.

View File

@ -1 +0,0 @@
QUIC 传输多流支持和 QUIC TLS cacert 支持。

View File

@ -1 +0,0 @@
For helm charts, add MQTT ingress bridge; and removed stale `mgmt` references.

View File

@ -1 +0,0 @@
在 helm chart 中新增了 MQTT 桥接 ingress 的配置参数;并删除了旧版本遗留的 `mgmt` 配置。

View File

@ -1 +0,0 @@
Validate `bytes` param to `GET /trace/:name/log` to not exceed signed 32bit integer.

View File

@ -1 +0,0 @@
验证 `GET /trace/:name/log``bytes` 参数使其不超过有符号的32位整数。

View File

@ -1 +0,0 @@
Fix return type structure for error case in API schema for `/gateways/:name/clients`.

View File

@ -1 +0,0 @@
修复 API `/gateways/:name/clients` 返回值的类型结构错误。

View File

@ -1 +0,0 @@
In dashboard API for `/monitor(_current)/nodes/:node` return `404` instead of `400` if node does not exist.

View File

@ -1 +0,0 @@
如果 API 查询的节点不存在,将会返回 404 而不再是 400。

View File

@ -1,7 +0,0 @@
To prevent errors caused by an incorrect EMQX node cookie provided from an environment variable,
we have implemented a fail-fast mechanism.
Previously, when an incorrect cookie was provided, the command would still attempt to ping the node,
leading to the error message 'Node xxx not responding to pings'.
With the new implementation, if a mismatched cookie is detected,
a message will be logged to indicate that the cookie is incorrect,
and the command will terminate with an error code of 1 without trying to ping the node.

View File

@ -1,4 +0,0 @@
在 cookie 给错时,快速失败。
在此修复前,即使 cookie 配置错误emqx 命令仍然会尝试去 ping EMQX 节点,
并得到一个 "Node xxx not responding to pings" 的错误。
修复后,如果发现 cookie 不一致,立即打印不一致的错误信息并退出。

View File

@ -1 +0,0 @@
Fix bridge metrics when running in async mode with batching enabled (`batch_size` > 1).

View File

@ -1 +0,0 @@
修复使用异步和批量配置的桥接计数不准确的问题。

View File

@ -1 +0,0 @@
Fix error message when the target node of `emqx_ctl cluster join` command is not running.

View File

@ -1 +0,0 @@
修正当`emqx_ctl cluster join`命令的目标节点未运行时的错误信息。

View File

@ -1,2 +0,0 @@
Allow setting node name from `EMQX_NODE__NAME` when running in docker.
Prior to this fix, only `EMQX_NODE_NAME` is allowed.

View File

@ -1,2 +0,0 @@
在 docker 中启动时,允许使用 `EMQX_NODE__NAME` 环境变量来配置节点名。
在此修复前,只能使 `EMQX_NODE_NAME`

View File

@ -1 +0,0 @@
When resources on some nodes in the cluster are still in the 'initializing/connecting' state, the `bridges/` API will crash due to missing Metrics information for those resources. This fix will ignore resources that do not have Metrics information.

View File

@ -1 +0,0 @@
当集群中某些节点上的资源仍处于 '初始化/连接中' 状态时,`bridges/` API 将由于缺少这些资源的 Metrics 信息而崩溃。此修复后将忽略没有 Metrics 信息的资源。

View File

@ -1,2 +0,0 @@
Fix Swagger API doc rendering crash.
In version 5.0.18, a bug was introduced that resulted in duplicated field names in the configuration schema. This, in turn, caused the Swagger schema generated to become invalid.

View File

@ -1,2 +0,0 @@
修复 Swagger API 文档渲染崩溃。
在版本 5.0.18 中,引入了一个错误,导致配置 schema 中出现了重复的配置名称,进而导致生成了无效的 Swagger spec。

View File

@ -1,2 +0,0 @@
For influxdb bridge, added integer value placeholder annotation hint to `write_syntax` documentation.
Also supported setting a constant value for the `timestamp` field.

View File

@ -1,2 +0,0 @@
为 influxdb 桥接的配置项 `write_syntax` 描述文档增加了类型标识符的提醒。
另外在配置中支持 `timestamp` 使用一个常量。

View File

@ -1,5 +0,0 @@
Improve behavior of the `replicant` nodes when the `core` cluster becomes partitioned (for example when a core node leaves the cluster).
Previously, the replicant nodes were unable to rebalance connections to the core nodes, until the core cluster became whole again.
This was indicated by the error messages: `[error] line: 182, mfa: mria_lb:list_core_nodes/1, msg: mria_lb_core_discovery divergent cluster`.
[Mria PR](https://github.com/emqx/mria/pull/123/files)

View File

@ -1,6 +0,0 @@
改进 `core` 集群被分割时 `replicant`节点的行为。
修复前,如果 `core` 集群分裂成两个小集群(例如一个节点离开集群)时,`replicant` 节点无法重新平衡与核心节点的连接,直到核心集群再次变得完整。
这种个问题会导致 replicant 节点出现如下日志:
`[error] line: 182, mfa: mria_lb:list_core_nodes/1, msg: mria_lb_core_discovery divergent cluster`
[Mria PR](https://github.com/emqx/mria/pull/123/files)

View File

@ -1,3 +0,0 @@
Fixed two bugs introduced in v5.0.18.
* The environment varialbe `SSL_DIST_OPTFILE` was not set correctly for non-boot commands.
* When cookie is overridden from environment variable, EMQX node is unable to start.

View File

@ -1,3 +0,0 @@
修复 v5.0.18 引入的 2 个bug。
* 环境变量 `SSL_DIST_OPTFILE` 的值设置错误导致节点无法为 Erlang distribution 启用 SSL。
* 当节点的 cookie 从环境变量重载 (而不是设置在配置文件中时),节点无法启动的问题。

Some files were not shown because too many files have changed in this diff Show More