Merge remote-tracking branch 'origin/release-v44' into main-v4.4
This commit is contained in:
commit
772ea4988b
|
@ -29,6 +29,7 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: source
|
path: source
|
||||||
|
fetch-depth: 0 # clone full git history
|
||||||
- name: detect-profiles
|
- name: detect-profiles
|
||||||
id: detect-profiles
|
id: detect-profiles
|
||||||
uses: ./source/.github/actions/detect-profiles
|
uses: ./source/.github/actions/detect-profiles
|
||||||
|
|
|
@ -39,6 +39,8 @@ jobs:
|
||||||
# keep using v1 for now as the otp-23 image has an old version git
|
# keep using v1 for now as the otp-23 image has an old version git
|
||||||
# TODO: change to v3 after OTP is upgraded to 23.3.4.18-1
|
# TODO: change to v3 after OTP is upgraded to 23.3.4.18-1
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # clone full git history
|
||||||
- name: fix-git-unsafe-repository
|
- name: fix-git-unsafe-repository
|
||||||
run: git config --global --add safe.directory /__w/emqx/emqx
|
run: git config --global --add safe.directory /__w/emqx/emqx
|
||||||
- uses: ./.github/actions/detect-profiles
|
- uses: ./.github/actions/detect-profiles
|
||||||
|
@ -123,6 +125,8 @@ jobs:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # clone full git history
|
||||||
- name: ensure access to github
|
- name: ensure access to github
|
||||||
if: endsWith(github.repository, 'enterprise')
|
if: endsWith(github.repository, 'enterprise')
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -14,6 +14,8 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # clone full git history
|
||||||
- id: detect-profiles
|
- id: detect-profiles
|
||||||
uses: ./.github/actions/detect-profiles
|
uses: ./.github/actions/detect-profiles
|
||||||
|
|
||||||
|
@ -55,6 +57,8 @@ jobs:
|
||||||
-d "{\"repo\":\"emqx/emqx\", \"tag\": \"${{ github.ref_name }}\" }" \
|
-d "{\"repo\":\"emqx/emqx\", \"tag\": \"${{ github.ref_name }}\" }" \
|
||||||
${{ secrets.EMQX_IO_RELEASE_API }}
|
${{ secrets.EMQX_IO_RELEASE_API }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # clone full git history
|
||||||
- name: get version
|
- name: get version
|
||||||
id: version
|
id: version
|
||||||
run: echo "version=$(./pkg-vsn.sh)" >> $GITHUB_OUTPUT
|
run: echo "version=$(./pkg-vsn.sh)" >> $GITHUB_OUTPUT
|
||||||
|
|
|
@ -11,12 +11,13 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prepare:
|
prepare:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: aws-amd64
|
||||||
container: ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-ubuntu20.04
|
container: ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-ubuntu20.04
|
||||||
outputs:
|
outputs:
|
||||||
fast_ct_apps: ${{ steps.run_find_apps.outputs.fast_ct_apps }}
|
fast_ct_apps: ${{ steps.run_find_apps.outputs.fast_ct_apps }}
|
||||||
docker_ct_apps: ${{ steps.run_find_apps.outputs.docker_ct_apps }}
|
docker_ct_apps: ${{ steps.run_find_apps.outputs.docker_ct_apps }}
|
||||||
steps:
|
steps:
|
||||||
|
- uses: AutoModality/action-clean@v1
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: source
|
path: source
|
||||||
|
@ -40,6 +41,7 @@ jobs:
|
||||||
- name: get_all_deps
|
- name: get_all_deps
|
||||||
working-directory: source
|
working-directory: source
|
||||||
run: |
|
run: |
|
||||||
|
git config --global --add safe.directory $(pwd)
|
||||||
# build the default profile for two purposes
|
# build the default profile for two purposes
|
||||||
# 1. download all dependencies (so the individual app runs do not depend on github credentials)
|
# 1. download all dependencies (so the individual app runs do not depend on github credentials)
|
||||||
# 2. some of the files such as segmented config files are not created when compiling only the test profile
|
# 2. some of the files such as segmented config files are not created when compiling only the test profile
|
||||||
|
@ -55,7 +57,7 @@ jobs:
|
||||||
|
|
||||||
eunit_and_proper:
|
eunit_and_proper:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
runs-on: ubuntu-20.04
|
runs-on: aws-amd64
|
||||||
container: ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-ubuntu20.04
|
container: ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-ubuntu20.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
@ -83,7 +85,7 @@ jobs:
|
||||||
|
|
||||||
fast_ct:
|
fast_ct:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ${{ matrix.runs-on }}
|
||||||
container: ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-ubuntu20.04
|
container: ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-ubuntu20.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
@ -246,7 +248,7 @@ jobs:
|
||||||
- eunit_and_proper
|
- eunit_and_proper
|
||||||
- fast_ct
|
- fast_ct
|
||||||
- docker_ct
|
- docker_ct
|
||||||
runs-on: ubuntu-20.04
|
runs-on: aws-amd64
|
||||||
container: ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-ubuntu20.04
|
container: ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-ubuntu20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: AutoModality/action-clean@v1
|
- uses: AutoModality/action-clean@v1
|
||||||
|
@ -276,7 +278,7 @@ jobs:
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
needs: make_cover
|
needs: make_cover
|
||||||
runs-on: ubuntu-20.04
|
runs-on: aws-amd64
|
||||||
steps:
|
steps:
|
||||||
- name: Coveralls Finished
|
- name: Coveralls Finished
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -125,7 +125,10 @@ handle_request(_Method, _Path, Req) ->
|
||||||
cowboy_req:reply(400, #{<<"content-type">> => <<"text/plain">>}, <<"Not found.">>, Req).
|
cowboy_req:reply(400, #{<<"content-type">> => <<"text/plain">>}, <<"Not found.">>, Req).
|
||||||
|
|
||||||
authorize_appid(Req) ->
|
authorize_appid(Req) ->
|
||||||
authorize_appid(cowboy_req:method(Req), cowboy_req:path(Req), Req).
|
authorize_appid(
|
||||||
|
iolist_to_binary(string:uppercase(cowboy_req:method(Req))),
|
||||||
|
iolist_to_binary(cowboy_req:path(Req)),
|
||||||
|
Req).
|
||||||
|
|
||||||
authorize_appid(<<"GET">>, <<"/api/v4/emqx_prometheus">>, _Req) ->
|
authorize_appid(<<"GET">>, <<"/api/v4/emqx_prometheus">>, _Req) ->
|
||||||
true;
|
true;
|
||||||
|
|
|
@ -95,6 +95,9 @@
|
||||||
end
|
end
|
||||||
end()).
|
end()).
|
||||||
|
|
||||||
|
-define(GET_RES_ALIVE_TIMEOUT, 60000).
|
||||||
|
-define(PROBE_RES_PREFIX, "__probe__:").
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Load resource/action providers from all available applications
|
%% Load resource/action providers from all available applications
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -363,7 +366,7 @@ test_resource(#{type := Type} = Params) ->
|
||||||
{ok, #resource_type{}} ->
|
{ok, #resource_type{}} ->
|
||||||
%% Resource will be deleted after test.
|
%% Resource will be deleted after test.
|
||||||
%% Use random resource id, ensure test func will not delete the resource in used.
|
%% Use random resource id, ensure test func will not delete the resource in used.
|
||||||
ResId = resource_id(),
|
ResId = probe_resource_id(),
|
||||||
try
|
try
|
||||||
case create_resource(maps:put(id, ResId, Params), no_retry) of
|
case create_resource(maps:put(id, ResId, Params), no_retry) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
|
@ -405,7 +408,7 @@ is_resource_alive(Nodes, ResId, _Opts = #{fetch := true}) ->
|
||||||
{ok, #resource_type{on_status = {Mod, OnStatus}}}
|
{ok, #resource_type{on_status = {Mod, OnStatus}}}
|
||||||
= emqx_rule_registry:find_resource_type(ResType),
|
= emqx_rule_registry:find_resource_type(ResType),
|
||||||
case rpc:multicall(Nodes,
|
case rpc:multicall(Nodes,
|
||||||
?MODULE, fetch_resource_status, [Mod, OnStatus, ResId], 5000) of
|
?MODULE, fetch_resource_status, [Mod, OnStatus, ResId], ?GET_RES_ALIVE_TIMEOUT) of
|
||||||
{ResL, []} ->
|
{ResL, []} ->
|
||||||
is_resource_alive_(ResL);
|
is_resource_alive_(ResL);
|
||||||
{_, _Error} ->
|
{_, _Error} ->
|
||||||
|
@ -420,7 +423,7 @@ is_resource_alive(Nodes, ResId, _Opts = #{fetch := true}) ->
|
||||||
end;
|
end;
|
||||||
is_resource_alive(Nodes, ResId, _Opts = #{fetch := false}) ->
|
is_resource_alive(Nodes, ResId, _Opts = #{fetch := false}) ->
|
||||||
try
|
try
|
||||||
case rpc:multicall(Nodes, ?MODULE, get_resource_status, [ResId], 5000) of
|
case rpc:multicall(Nodes, ?MODULE, get_resource_status, [ResId], ?GET_RES_ALIVE_TIMEOUT) of
|
||||||
{ResL, []} ->
|
{ResL, []} ->
|
||||||
is_resource_alive_(ResL);
|
is_resource_alive_(ResL);
|
||||||
{_, _Errors} ->
|
{_, _Errors} ->
|
||||||
|
@ -532,10 +535,15 @@ refresh_rule(#rule{id = RuleId, for = Topics, actions = Actions}) ->
|
||||||
refresh_resource_status() ->
|
refresh_resource_status() ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(#resource{id = ResId, type = ResType}) ->
|
fun(#resource{id = ResId, type = ResType}) ->
|
||||||
case emqx_rule_registry:find_resource_type(ResType) of
|
case is_prober(ResId) of
|
||||||
{ok, #resource_type{on_status = {Mod, OnStatus}}} ->
|
false ->
|
||||||
fetch_resource_status(Mod, OnStatus, ResId);
|
case emqx_rule_registry:find_resource_type(ResType) of
|
||||||
_ -> ok
|
{ok, #resource_type{on_status = {Mod, OnStatus}}} ->
|
||||||
|
fetch_resource_status(Mod, OnStatus, ResId);
|
||||||
|
_ -> ok
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
ok
|
||||||
end
|
end
|
||||||
end, emqx_rule_registry:get_resources()).
|
end, emqx_rule_registry:get_resources()).
|
||||||
|
|
||||||
|
@ -662,6 +670,9 @@ ignore_lib_apps(Apps) ->
|
||||||
resource_id() ->
|
resource_id() ->
|
||||||
gen_id("resource:", fun emqx_rule_registry:find_resource/1).
|
gen_id("resource:", fun emqx_rule_registry:find_resource/1).
|
||||||
|
|
||||||
|
probe_resource_id() ->
|
||||||
|
gen_id(?PROBE_RES_PREFIX, fun emqx_rule_registry:find_resource/1).
|
||||||
|
|
||||||
rule_id() ->
|
rule_id() ->
|
||||||
gen_id("rule:", fun emqx_rule_registry:get_rule/1).
|
gen_id("rule:", fun emqx_rule_registry:get_rule/1).
|
||||||
|
|
||||||
|
@ -811,4 +822,9 @@ find_type(ResId) ->
|
||||||
{ok, Type}.
|
{ok, Type}.
|
||||||
|
|
||||||
alarm_name_of_resource_down(Type, ResId) ->
|
alarm_name_of_resource_down(Type, ResId) ->
|
||||||
list_to_binary(io_lib:format("resource/~s/~s/down", [Type, ResId])).
|
unicode:characters_to_binary(io_lib:format("resource/~ts/~ts/down", [Type, ResId])).
|
||||||
|
|
||||||
|
is_prober(<<?PROBE_RES_PREFIX, _/binary>>) ->
|
||||||
|
true;
|
||||||
|
is_prober(_ResId) ->
|
||||||
|
false.
|
||||||
|
|
|
@ -62,7 +62,8 @@ groups() ->
|
||||||
t_create_rule,
|
t_create_rule,
|
||||||
t_reset_metrics,
|
t_reset_metrics,
|
||||||
t_reset_metrics_fallbacks,
|
t_reset_metrics_fallbacks,
|
||||||
t_create_resource
|
t_create_resource,
|
||||||
|
t_clean_resource_alarms
|
||||||
]},
|
]},
|
||||||
{actions, [],
|
{actions, [],
|
||||||
[t_inspect_action
|
[t_inspect_action
|
||||||
|
@ -309,21 +310,29 @@ t_create_resource(_Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_clean_resource_alarms(_Config) ->
|
t_clean_resource_alarms(_Config) ->
|
||||||
|
lists:foreach(fun(ResId) ->
|
||||||
|
clean_resource_alarms(ResId)
|
||||||
|
end, [<<"abc">>, <<"哈喽"/utf8>>]).
|
||||||
|
|
||||||
|
clean_resource_alarms(ResId) ->
|
||||||
|
emqx_rule_registry:register_resource_types(
|
||||||
|
[make_simple_debug_resource_type()]),
|
||||||
ok = emqx_rule_engine:load_providers(),
|
ok = emqx_rule_engine:load_providers(),
|
||||||
{ok, #resource{id = ResId}} = emqx_rule_engine:create_resource(
|
{ok, #resource{id = ResId}} = emqx_rule_engine:create_resource(
|
||||||
#{type => built_in,
|
#{id => ResId,
|
||||||
|
type => built_in,
|
||||||
config => #{},
|
config => #{},
|
||||||
description => <<"debug resource">>}),
|
description => <<"debug resource">>}),
|
||||||
?assert(true, is_binary(ResId)),
|
|
||||||
Name = emqx_rule_engine:alarm_name_of_resource_down(ResId, built_in),
|
Name = emqx_rule_engine:alarm_name_of_resource_down(ResId, built_in),
|
||||||
_ = emqx_alarm:activate(Name, #{id => ResId, type => built_in}),
|
_ = emqx_alarm:activate(Name, #{id => ResId, type => built_in}),
|
||||||
AlarmExist = fun(#{name := AName}) -> AName == Name end,
|
AlarmExist = fun(#{name := AName}) -> AName == Name end,
|
||||||
Len = length(lists:filter(AlarmExist, emqx_alarm:get_alarms())),
|
Len = length(lists:filter(AlarmExist, emqx_alarm:get_alarms(activated))),
|
||||||
?assert(Len == 1),
|
?assertEqual(1, Len),
|
||||||
|
emqx_rule_engine:ensure_resource_deleted(ResId),
|
||||||
|
emqx_alarm:deactivate(Name),
|
||||||
|
LenAfterRemove = length(lists:filter(AlarmExist, emqx_alarm:get_alarms(activated))),
|
||||||
|
?assertEqual(0, LenAfterRemove),
|
||||||
ok = emqx_rule_engine:unload_providers(),
|
ok = emqx_rule_engine:unload_providers(),
|
||||||
emqx_rule_registry:remove_resource(ResId),
|
|
||||||
LenAfterRemove = length(lists:filter(AlarmExist, emqx_alarm:get_alarms())),
|
|
||||||
?assert(LenAfterRemove == 0),
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
15
build
15
build
|
@ -62,20 +62,9 @@ log() {
|
||||||
echo "===< $msg"
|
echo "===< $msg"
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_unwanted_file() {
|
|
||||||
if [ -e "${1}" ]; then
|
|
||||||
log "Deleting file: ${1}"
|
|
||||||
rm -f "${1}"
|
|
||||||
else
|
|
||||||
log "Cannot delete file: ${1} -- file not found"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
make_rel() {
|
make_rel() {
|
||||||
./rebar3 as "$PROFILE" release
|
# shellcheck disable=SC1010
|
||||||
# delete outdated cert store
|
./rebar3 as "$PROFILE" do release,tar
|
||||||
delete_unwanted_file _build/"${PROFILE}"/rel/emqx/lib/certifi*/priv/cacerts.pem
|
|
||||||
./rebar3 as "$PROFILE" tar
|
|
||||||
}
|
}
|
||||||
|
|
||||||
relup_db() {
|
relup_db() {
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
# v4.3.22
|
# v4.3.22
|
||||||
|
|
||||||
|
This marks the last release of EMQX v4.3 Opensource Edition.
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
|
- Make sure listener's `tls_versions` config value is one or more of `tlsv1`, `tlsv1.1`, `tlsv1.2`, `tlsv1.3` [#9260](https://github.com/emqx/emqx/pull/9260).
|
||||||
|
|
||||||
- Remove useless information from the dashboard listener failure log [#9260](https://github.com/emqx/emqx/pull/9260).
|
- Remove useless information from the dashboard listener failure log [#9260](https://github.com/emqx/emqx/pull/9260).
|
||||||
|
|
||||||
|
@ -39,8 +42,8 @@
|
||||||
- Added configurations to enable more `client.disconnected` events (and counter bumps) [#9267](https://github.com/emqx/emqx/pull/9267).
|
- Added configurations to enable more `client.disconnected` events (and counter bumps) [#9267](https://github.com/emqx/emqx/pull/9267).
|
||||||
Prior to this change, the `client.disconnected` event (and counter bump) is triggered when a client
|
Prior to this change, the `client.disconnected` event (and counter bump) is triggered when a client
|
||||||
performs a 'normal' disconnect, or is 'kicked' by system admin, but NOT triggered when a
|
performs a 'normal' disconnect, or is 'kicked' by system admin, but NOT triggered when a
|
||||||
stale connection had to be 'discarded' (for clean session) or 'takenover' (for non-clean session).
|
stale connection had to be 'discarded' (for clean session) or 'takeovered' (for non-clean session) by new connection.
|
||||||
Now it is possible to set configs `broker.client_disconnect_discarded` and `broker.client_disconnect_takenover` to `on` to enable the event in these scenarios.
|
Now it is possible to set configs `broker.client_disconnect_discarded` and `broker.client_disconnect_takeovered` to `on` to enable the event in these scenarios.
|
||||||
|
|
||||||
- For Rule-Engine resource creation failure, delay before the first retry [#9313](https://github.com/emqx/emqx/pull/9313).
|
- For Rule-Engine resource creation failure, delay before the first retry [#9313](https://github.com/emqx/emqx/pull/9313).
|
||||||
Prior to this change, the retry delay was added *after* the retry failure.
|
Prior to this change, the retry delay was added *after* the retry failure.
|
||||||
|
@ -70,7 +73,7 @@
|
||||||
|
|
||||||
- Make sure Rule-Engine API supports Percent-encoding `rule_id` and `resource_id` in HTTP request path [#9190](https://github.com/emqx/emqx/pull/9190).
|
- Make sure Rule-Engine API supports Percent-encoding `rule_id` and `resource_id` in HTTP request path [#9190](https://github.com/emqx/emqx/pull/9190).
|
||||||
Note that the `id` in `POST /api/v4/rules` should be literals (not encoded) when creating a `rule` or `resource`.
|
Note that the `id` in `POST /api/v4/rules` should be literals (not encoded) when creating a `rule` or `resource`.
|
||||||
See docs [Create Rule](https://www.emqx.io/docs/zh/v4.3/advanced/http-api.html#post-api-v4-rules) [Create Resource](https://www.emqx.io/docs/zh/v4.3/advanced/http-api.html#post-api-v4-resources).
|
See docs [Create Rule](https://docs.emqx.com/en/enterprise/v4.4/advanced/http-api.html#post-api-v4-rules) [Create Resource](https://docs.emqx.com/en/enterprise/v4.4/advanced/http-api.html#post-api-v4-resources).
|
||||||
|
|
||||||
- Calling 'DELETE /alarms/deactivated' now deletes deactived alarms on all nodes, including remote nodes, not just the local node [#9280](https://github.com/emqx/emqx/pull/9280).
|
- Calling 'DELETE /alarms/deactivated' now deletes deactived alarms on all nodes, including remote nodes, not just the local node [#9280](https://github.com/emqx/emqx/pull/9280).
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
# v4.3.22
|
# v4.3.22
|
||||||
|
|
||||||
|
这是 EMQX 开原版 v4.3 系列的最后一个版本。
|
||||||
|
|
||||||
## 增强
|
## 增强
|
||||||
|
|
||||||
|
- 检查监听器的 `tls_versions` 配置值是 `tlsv1`,`tlsv1.1`,`tlsv1.2`,`tlsv1.3` 中的一个或多个组合 [#9260](https://github.com/emqx/emqx/pull/9260)。
|
||||||
|
|
||||||
- 删除 Dashboard 监听器失败时日志中的无用信息 [#9260](https://github.com/emqx/emqx/pull/9260).
|
- 删除 Dashboard 监听器失败时日志中的无用信息 [#9260](https://github.com/emqx/emqx/pull/9260).
|
||||||
|
|
||||||
- 当 CoAP 网关给设备投递消息并收到设备发来的确认之后,回调 `'message.acked'` 钩子 [#9264](https://github.com/emqx/emqx/pull/9264)。
|
- 当 CoAP 网关给设备投递消息并收到设备发来的确认之后,回调 `'message.acked'` 钩子 [#9264](https://github.com/emqx/emqx/pull/9264)。
|
||||||
|
@ -34,7 +38,7 @@
|
||||||
|
|
||||||
- 为更多类型的 `client.disconnected` 事件(计数器触发)提供可配置项 [#9267](https://github.com/emqx/emqx/pull/9267)。
|
- 为更多类型的 `client.disconnected` 事件(计数器触发)提供可配置项 [#9267](https://github.com/emqx/emqx/pull/9267)。
|
||||||
此前,`client.disconnected` 事件及计数器仅会在客户端正常断开连接或客户端被系统管理员踢出时触发,
|
此前,`client.disconnected` 事件及计数器仅会在客户端正常断开连接或客户端被系统管理员踢出时触发,
|
||||||
但不会在旧 session 被废弃 (clean_session = true) 或旧 session 被接管 (clean_session = false) 时被触发。
|
但不会在旧 session 被新连接废弃时 (clean_session = true) ,或旧 session 被新连接接管时 (clean_session = false) 被触发。
|
||||||
可将 `broker.client_disconnect_discarded` 和 `broker.client_disconnect_takovered` 选项设置为 `on` 来启用此场景下的客户端断连事件。
|
可将 `broker.client_disconnect_discarded` 和 `broker.client_disconnect_takovered` 选项设置为 `on` 来启用此场景下的客户端断连事件。
|
||||||
|
|
||||||
- 规则引擎资源创建失败后,第一次重试前增加一个延迟 [#9313](https://github.com/emqx/emqx/pull/9313)。
|
- 规则引擎资源创建失败后,第一次重试前增加一个延迟 [#9313](https://github.com/emqx/emqx/pull/9313)。
|
||||||
|
@ -65,7 +69,7 @@
|
||||||
|
|
||||||
- 使规则引擎 API 在 HTTP 请求路径中支持百分号编码的 `rule_id` 及 `resource_id` [#9190](https://github.com/emqx/emqx/pull/9190)。
|
- 使规则引擎 API 在 HTTP 请求路径中支持百分号编码的 `rule_id` 及 `resource_id` [#9190](https://github.com/emqx/emqx/pull/9190)。
|
||||||
注意在创建规则或资源时,HTTP body 中的 `id` 字段仍为字面值,而不是编码之后的值。
|
注意在创建规则或资源时,HTTP body 中的 `id` 字段仍为字面值,而不是编码之后的值。
|
||||||
详情请参考 [创建规则](https://www.emqx.io/docs/zh/v4.3/advanced/http-api.html#post-api-v4-rules) 和 [创建资源](https://www.emqx.io/docs/zh/v4.3/advanced/http-api.html#post-api-v4-resources)。
|
详情请参考 [创建规则](https://docs.emqx.com/zh/enterprise/v4.4/advanced/http-api.html#post-api-v4-rules) 和 [创建资源](https://docs.emqx.com/zh/enterprise/v4.4/advanced/http-api.html#post-api-v4-resources)。
|
||||||
|
|
||||||
- 修复调用 'DELETE /alarms/deactivated' 只在单个节点上生效的问题,现在将会删除所有节点上的非活跃警告 [#9280](https://github.com/emqx/emqx/pull/9280)。
|
- 修复调用 'DELETE /alarms/deactivated' 只在单个节点上生效的问题,现在将会删除所有节点上的非活跃警告 [#9280](https://github.com/emqx/emqx/pull/9280)。
|
||||||
|
|
||||||
|
|
|
@ -1550,11 +1550,10 @@ listener.ssl.external.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem
|
||||||
## listener.ssl.external.ocsp_refresh_http_timeout = 15s
|
## listener.ssl.external.ocsp_refresh_http_timeout = 15s
|
||||||
|
|
||||||
## Whether to enable CRL verification and caching for this listener.
|
## Whether to enable CRL verification and caching for this listener.
|
||||||
## If set to true, requires specifying the CRL server URLs.
|
|
||||||
##
|
##
|
||||||
## Value: boolean
|
## Value: boolean
|
||||||
## Default: false
|
## Default: false
|
||||||
## listener.ssl.external.enable_crl_cache = true
|
## listener.ssl.external.enable_crl_check = true
|
||||||
|
|
||||||
## Comma-separated URL list for CRL servers to fetch and cache CRLs
|
## Comma-separated URL list for CRL servers to fetch and cache CRLs
|
||||||
## from. Must include the path to the CRL file(s).
|
## from. Must include the path to the CRL file(s).
|
||||||
|
@ -1562,18 +1561,19 @@ listener.ssl.external.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem
|
||||||
## Value: String
|
## Value: String
|
||||||
## listener.ssl.external.crl_cache_urls = http://my.crl.server/intermediate.crl.pem, http://my.other.crl.server/another.crl.pem
|
## listener.ssl.external.crl_cache_urls = http://my.crl.server/intermediate.crl.pem, http://my.other.crl.server/another.crl.pem
|
||||||
|
|
||||||
## The timeout for the HTTP request when fetching CRLs.
|
## The timeout for the HTTP request when fetching CRLs. This is
|
||||||
|
## global for all listeners.
|
||||||
##
|
##
|
||||||
## Value: Duration
|
## Value: Duration
|
||||||
## Default: 15 s
|
## Default: 15 s
|
||||||
## listener.ssl.external.crl_cache_http_timeout = 15s
|
crl_cache_http_timeout = 15s
|
||||||
|
|
||||||
## The period to refresh the CRLs from the servers. This is global
|
## The period to refresh the CRLs from the servers. This is global
|
||||||
## for all URLs and listeners.
|
## for all URLs and listeners.
|
||||||
##
|
##
|
||||||
## Value: Duration
|
## Value: Duration
|
||||||
## Default: 15 m
|
## Default: 15 m
|
||||||
## crl_cache.refresh_interval = 15m
|
crl_cache_refresh_interval = 15m
|
||||||
|
|
||||||
## The Ephemeral Diffie-Helman key exchange is a very effective way of
|
## The Ephemeral Diffie-Helman key exchange is a very effective way of
|
||||||
## ensuring Forward Secrecy by exchanging a set of keys that never hit
|
## ensuring Forward Secrecy by exchanging a set of keys that never hit
|
||||||
|
@ -2539,9 +2539,9 @@ broker.route_batch_clean = off
|
||||||
|
|
||||||
## Enable client disconnect event will be triggered by which reasons.
|
## Enable client disconnect event will be triggered by which reasons.
|
||||||
## Value: on | off
|
## Value: on | off
|
||||||
## `takeover`: session was takenover by another client with same client ID. (clean_session = false)
|
## `discarded`: session was discarded by another client with same client ID when new connection use `clean_session = true`.
|
||||||
## Default: off
|
## Default: off
|
||||||
## `discard`: session was takeover by another client with same client ID. (clean_session = true)
|
## `takeover`: session was takeovered by another client with same client ID when new connection use `clean_session = false`.
|
||||||
## Default: off
|
## Default: off
|
||||||
##
|
##
|
||||||
# broker.client_disconnect_discarded = off
|
# broker.client_disconnect_discarded = off
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
-ifndef(EMQX_ENTERPRISE).
|
-ifndef(EMQX_ENTERPRISE).
|
||||||
|
|
||||||
-define(EMQX_RELEASE, {opensource, "4.4.11-alpha.2"}).
|
-define(EMQX_RELEASE, {opensource, "4.4.11"}).
|
||||||
|
|
||||||
-else.
|
-else.
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,10 @@ http_handlers() ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
is_authorized(Req) ->
|
is_authorized(Req) ->
|
||||||
is_authorized(cowboy_req:method(Req), cowboy_req:path(Req), Req).
|
is_authorized(
|
||||||
|
iolist_to_binary(string:uppercase(cowboy_req:method(Req))),
|
||||||
|
iolist_to_binary(cowboy_req:path(Req)),
|
||||||
|
Req).
|
||||||
|
|
||||||
is_authorized(<<"GET">>, <<"/api/v4/emqx_prometheus">>, _Req) ->
|
is_authorized(<<"GET">>, <<"/api/v4/emqx_prometheus">>, _Req) ->
|
||||||
true;
|
true;
|
||||||
|
|
|
@ -42,7 +42,9 @@ end_per_suite(_) ->
|
||||||
emqx_ct_helpers:stop_apps([emqx_modules]).
|
emqx_ct_helpers:stop_apps([emqx_modules]).
|
||||||
|
|
||||||
set_special_configs(emqx) ->
|
set_special_configs(emqx) ->
|
||||||
application:set_env(emqx, modules, [{emqx_mod_delayed, []}]),
|
AclFilePath = filename:join(["test", "emqx_SUITE_data", "acl.conf"]),
|
||||||
|
application:set_env(emqx, modules, [{emqx_mod_delayed, []},
|
||||||
|
{emqx_mod_acl_internal, [{acl_file, AclFilePath}]}]),
|
||||||
application:set_env(emqx, allow_anonymous, false),
|
application:set_env(emqx, allow_anonymous, false),
|
||||||
application:set_env(emqx, enable_acl_cache, false);
|
application:set_env(emqx, enable_acl_cache, false);
|
||||||
set_special_configs(_App) ->
|
set_special_configs(_App) ->
|
||||||
|
|
|
@ -842,21 +842,21 @@ end}.
|
||||||
%% @doc Define a determined authentication plugin/module check order.
|
%% @doc Define a determined authentication plugin/module check order.
|
||||||
%% see detailed doc in emqx.conf
|
%% see detailed doc in emqx.conf
|
||||||
{mapping, "auth_order", "emqx.auth_order", [
|
{mapping, "auth_order", "emqx.auth_order", [
|
||||||
{default, "none"},
|
{default, "none"}, % keep default value in sync with emqx_conf.erl
|
||||||
{datatype, string}
|
{datatype, string}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
%% @doc Same as auth_order, but for ACL.
|
%% @doc Same as auth_order, but for ACL.
|
||||||
{mapping, "acl_order", "emqx.acl_order", [
|
{mapping, "acl_order", "emqx.acl_order", [
|
||||||
{default, "none"},
|
{default, "none"}, % keep default value in sync with emqx_conf.erl
|
||||||
{datatype, string}
|
{datatype, string}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
%% @doc Specify a module that defines the `enrich_clientid_alias/2'
|
%% @doc Specify a module that defines the `enrich_with_aliases/2'
|
||||||
%% function. This function will be used to enrich the client/channel
|
%% function. This function will be used to enrich the client/channel
|
||||||
%% information with clientid and/or common name aliases (or other
|
%% information with clientid and/or common name aliases (or other
|
||||||
%% enrichments the module may implement).
|
%% enrichments the module may implement).
|
||||||
{mapping, "clientid_enrichment_module", "emqx.clientid_enrichment_module", [
|
{mapping, "alias_enrichment_module", "emqx.alias_enrichment_module", [
|
||||||
{datatype, atom}
|
{datatype, atom}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
@ -1698,11 +1698,11 @@ end}.
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{mapping, "listener.ssl.$name.ocsp_refresh_http_timeout", "emqx.listeners", [
|
{mapping, "listener.ssl.$name.ocsp_refresh_http_timeout", "emqx.listeners", [
|
||||||
{default, "15000ms"},
|
{default, "15s"},
|
||||||
{datatype, {duration, ms}}
|
{datatype, {duration, ms}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{mapping, "listener.ssl.$name.enable_crl_cache", "emqx.listeners", [
|
{mapping, "listener.ssl.$name.enable_crl_check", "emqx.listeners", [
|
||||||
{default, false},
|
{default, false},
|
||||||
{datatype, {enum, [true, false]}}
|
{datatype, {enum, [true, false]}}
|
||||||
]}.
|
]}.
|
||||||
|
@ -1712,12 +1712,12 @@ end}.
|
||||||
{datatype, string}
|
{datatype, string}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{mapping, "listener.ssl.$name.crl_cache_http_timeout", "emqx.listeners", [
|
{mapping, "crl_cache_http_timeout", "emqx.crl_cache_http_timeout", [
|
||||||
{default, "15s"},
|
{default, "15s"},
|
||||||
{datatype, {duration, ms}}
|
{datatype, {duration, ms}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{mapping, "crl_cache.refresh_interval", "emqx.crl_cache_refresh_interval", [
|
{mapping, "crl_cache_refresh_interval", "emqx.crl_cache_refresh_interval", [
|
||||||
{default, "15m"},
|
{default, "15m"},
|
||||||
{datatype, {duration, ms}}
|
{datatype, {duration, ms}}
|
||||||
]}.
|
]}.
|
||||||
|
@ -2310,7 +2310,16 @@ end}.
|
||||||
SslOpts = fun(Prefix) ->
|
SslOpts = fun(Prefix) ->
|
||||||
Versions = case SplitFun(cuttlefish:conf_get(Prefix ++ ".tls_versions", Conf, undefined)) of
|
Versions = case SplitFun(cuttlefish:conf_get(Prefix ++ ".tls_versions", Conf, undefined)) of
|
||||||
undefined -> undefined;
|
undefined -> undefined;
|
||||||
L -> [list_to_atom(V) || V <- L]
|
L ->
|
||||||
|
Versions0 = [list_to_atom(V) || V <- L],
|
||||||
|
SupportVersions = ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'],
|
||||||
|
case lists:all(fun(V) -> lists:member(V, SupportVersions) end, Versions0) of
|
||||||
|
false ->
|
||||||
|
cuttlefish:invalid(
|
||||||
|
lists:flatten(io_lib:format("tls_versions: only support ~p", [SupportVersions])));
|
||||||
|
true ->
|
||||||
|
Versions0
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
TLSCiphers = cuttlefish:conf_get(Prefix++".ciphers", Conf, undefined),
|
TLSCiphers = cuttlefish:conf_get(Prefix++".ciphers", Conf, undefined),
|
||||||
PSKCiphers = cuttlefish:conf_get(Prefix++".psk_ciphers", Conf, undefined),
|
PSKCiphers = cuttlefish:conf_get(Prefix++".psk_ciphers", Conf, undefined),
|
||||||
|
@ -2337,9 +2346,9 @@ end}.
|
||||||
undefined -> undefined;
|
undefined -> undefined;
|
||||||
_ -> {fun emqx_psk:lookup/3, <<>>}
|
_ -> {fun emqx_psk:lookup/3, <<>>}
|
||||||
end,
|
end,
|
||||||
CRLCheck = case cuttlefish:conf_get(Prefix ++ ".enable_crl_cache", Conf, false) of
|
CRLCheck = case cuttlefish:conf_get(Prefix ++ ".enable_crl_check", Conf, false) of
|
||||||
true ->
|
true ->
|
||||||
HTTPTimeout = cuttlefish:conf_get(Prefix ++ ".crl_cache_http_timeout", Conf, timer:seconds(15)),
|
HTTPTimeout = cuttlefish:conf_get("crl_cache_http_timeout", Conf, timer:seconds(15)),
|
||||||
%% {crl_check, true} doesn't work
|
%% {crl_check, true} doesn't work
|
||||||
[ {crl_check, peer}
|
[ {crl_check, peer}
|
||||||
, {crl_cache, {ssl_crl_cache, {internal, [{http, HTTPTimeout}]}}}
|
, {crl_cache, {ssl_crl_cache, {internal, [{http, HTTPTimeout}]}}}
|
||||||
|
@ -2374,7 +2383,7 @@ end}.
|
||||||
undefined -> undefined;
|
undefined -> undefined;
|
||||||
URLs -> string:tokens(URLs, ", ")
|
URLs -> string:tokens(URLs, ", ")
|
||||||
end,
|
end,
|
||||||
Filter([ {crl_cache_enabled, cuttlefish:conf_get(Prefix ++ ".enable_crl_cache", Conf, false)}
|
Filter([ {crl_check_enabled, cuttlefish:conf_get(Prefix ++ ".enable_crl_check", Conf, false)}
|
||||||
, {crl_cache_urls, CRLURLs}
|
, {crl_cache_urls, CRLURLs}
|
||||||
])
|
])
|
||||||
end,
|
end,
|
||||||
|
@ -2685,13 +2694,13 @@ end}.
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
%% @doc Configuration of disconnected event reason.
|
%% @doc Configuration of disconnected event reason.
|
||||||
%% `takeover`: session was takenover by another client with same client ID. (clean_session = false)
|
%% `discarded`: session was discarded by another client with same client ID when new connection use `clean_session = true`.
|
||||||
%% `discard`: session was takeover by another client with same client ID. (clean_session = true)
|
|
||||||
{mapping, "broker.client_disconnect_discarded", "emqx.client_disconnect_discarded", [
|
{mapping, "broker.client_disconnect_discarded", "emqx.client_disconnect_discarded", [
|
||||||
{default, off},
|
{default, off},
|
||||||
{datatype, flag}
|
{datatype, flag}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
%% `takeovered`: session was takeovered by another client with same client ID when new connection use `clean_session = false`.
|
||||||
{mapping, "broker.client_disconnect_takeovered", "emqx.client_disconnect_takeovered", [
|
{mapping, "broker.client_disconnect_takeovered", "emqx.client_disconnect_takeovered", [
|
||||||
{default, off},
|
{default, off},
|
||||||
{datatype, flag}
|
{datatype, flag}
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
, {getopt, "1.0.1"}
|
, {getopt, "1.0.1"}
|
||||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "1.0.1"}}}
|
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "1.0.1"}}}
|
||||||
, {lc, {git, "https://github.com/emqx/lc.git", {tag, "0.3.2"}}}
|
, {lc, {git, "https://github.com/emqx/lc.git", {tag, "0.3.2"}}}
|
||||||
, {mongodb, {git,"https://github.com/emqx/mongodb-erlang", {tag, "v3.0.14"}}}
|
, {mongodb, {git,"https://github.com/emqx/mongodb-erlang", {tag, "v3.0.15"}}}
|
||||||
, {epgsql, {git, "https://github.com/emqx/epgsql.git", {tag, "4.6.0"}}}
|
, {epgsql, {git, "https://github.com/emqx/epgsql.git", {tag, "4.6.0"}}}
|
||||||
, {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.7"}}}
|
, {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.7"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
|
@ -42,11 +42,11 @@ for keychain in ${keychains}; do
|
||||||
done
|
done
|
||||||
security -v list-keychains -s "${keychain_names[@]}" "${KEYCHAIN}"
|
security -v list-keychains -s "${keychain_names[@]}" "${KEYCHAIN}"
|
||||||
|
|
||||||
# sign
|
# known runtime executables and binaries
|
||||||
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/erts-*/bin/{beam.smp,dyn_erl,epmd,erl,erl_call,erl_child_setup,erlexec,escript,heart,inet_gethost,run_erl,to_erl}
|
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/erts-*/bin/{beam.smp,dyn_erl,epmd,erl,erl_call,erl_child_setup,erlexec,escript,heart,inet_gethost,run_erl,to_erl}
|
||||||
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/lib/asn1-*/priv/lib/asn1rt_nif.so
|
|
||||||
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/lib/bcrypt-*/priv/bcrypt_nif.so
|
|
||||||
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/lib/crypto-*/priv/lib/{crypto.so,otp_test_engine.so}
|
|
||||||
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/lib/jiffy-*/priv/jiffy.so
|
|
||||||
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/lib/os_mon-*/priv/bin/{cpu_sup,memsup}
|
|
||||||
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/lib/runtime_tools-*/priv/lib/{dyntrace.so,trace_ip_drv.so,trace_file_drv.so}
|
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/lib/runtime_tools-*/priv/lib/{dyntrace.so,trace_ip_drv.so,trace_file_drv.so}
|
||||||
|
codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime "${REL_DIR}"/lib/os_mon-*/priv/bin/{cpu_sup,memsup}
|
||||||
|
# other files from runtime and dependencies
|
||||||
|
for f in asn1rt_nif.so bcrypt_nif.so crypto.so otp_test_engine.so crypto_callback.so jiffy.so crc32cer_nif.so sasl_auth.so snappyer.so odbcserver; do
|
||||||
|
find "${REL_DIR}"/lib/ -name "$f" -exec codesign -s "${APPLE_DEVELOPER_IDENTITY}" -f --verbose=4 --timestamp --options=runtime {} \;
|
||||||
|
done
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% internal exports for ad-hoc debugging.
|
%% internal exports for ad-hoc debugging.
|
||||||
-export([ set_clientid_enrichment_module/0
|
-export([ set_alias_enrichment_module/0
|
||||||
, set_special_auth_module/0
|
, set_special_auth_module/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ start(_Type, _Args) ->
|
||||||
ok = emqx_plugins:init(),
|
ok = emqx_plugins:init(),
|
||||||
_ = emqx_plugins:load(),
|
_ = emqx_plugins:load(),
|
||||||
_ = start_ce_modules(),
|
_ = start_ce_modules(),
|
||||||
set_clientid_enrichment_module(),
|
set_alias_enrichment_module(),
|
||||||
_ = set_special_auth_module(),
|
_ = set_special_auth_module(),
|
||||||
register(emqx, self()),
|
register(emqx, self()),
|
||||||
print_vsn(),
|
print_vsn(),
|
||||||
|
@ -85,14 +85,14 @@ start_ce_modules() ->
|
||||||
ok.
|
ok.
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
set_clientid_enrichment_module() ->
|
set_alias_enrichment_module() ->
|
||||||
case emqx:get_env(clientid_enrichment_module) of
|
case emqx:get_env(alias_enrichment_module) of
|
||||||
undefined ->
|
undefined ->
|
||||||
ok;
|
ok;
|
||||||
Mod ->
|
Mod ->
|
||||||
case erlang:function_exported(Mod, enrich_clientid_alias, 2) of
|
case erlang:function_exported(Mod, enrich_with_aliases, 2) of
|
||||||
true ->
|
true ->
|
||||||
persistent_term:put(clientid_enrichment_module, Mod);
|
persistent_term:put(alias_enrichment_module, Mod);
|
||||||
false ->
|
false ->
|
||||||
ok
|
ok
|
||||||
end
|
end
|
||||||
|
|
|
@ -313,7 +313,7 @@ handle_in(?CONNECT_PACKET(ConnPkt) = Packet, Channel) ->
|
||||||
fun set_log_meta/2,
|
fun set_log_meta/2,
|
||||||
fun check_banned/2,
|
fun check_banned/2,
|
||||||
fun count_flapping_event/2,
|
fun count_flapping_event/2,
|
||||||
fun enrich_clientid_alias/2,
|
fun enrich_with_aliases/2,
|
||||||
fun auth_connect/2
|
fun auth_connect/2
|
||||||
], ConnPkt, Channel#channel{conn_state = connecting}) of
|
], ConnPkt, Channel#channel{conn_state = connecting}) of
|
||||||
{ok, NConnPkt, NChannel = #channel{clientinfo = ClientInfo}} ->
|
{ok, NConnPkt, NChannel = #channel{clientinfo = ClientInfo}} ->
|
||||||
|
@ -1363,12 +1363,12 @@ check_banned(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Enrich ClientID Alias
|
%% Enrich ClientID Alias
|
||||||
|
|
||||||
enrich_clientid_alias(Packet, Channel) ->
|
enrich_with_aliases(Packet, Channel) ->
|
||||||
case persistent_term:get(clientid_enrichment_module, undefined) of
|
case persistent_term:get(alias_enrichment_module, undefined) of
|
||||||
undefined ->
|
undefined ->
|
||||||
{ok, Channel};
|
{ok, Channel};
|
||||||
Mod ->
|
Mod ->
|
||||||
Mod:enrich_clientid_alias(Packet, Channel)
|
Mod:enrich_with_aliases(Packet, Channel)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
, start_link/1
|
, start_link/1
|
||||||
, refresh/1
|
, refresh/1
|
||||||
, evict/1
|
, evict/1
|
||||||
|
, refresh_config/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
|
@ -41,12 +42,14 @@
|
||||||
|
|
||||||
-define(LOG(Level, Format, Args),
|
-define(LOG(Level, Format, Args),
|
||||||
logger:log(Level, "[~p] " ++ Format, [?MODULE | Args])).
|
logger:log(Level, "[~p] " ++ Format, [?MODULE | Args])).
|
||||||
-define(HTTP_TIMEOUT, timer:seconds(10)).
|
-define(HTTP_TIMEOUT, timer:seconds(15)).
|
||||||
-define(RETRY_TIMEOUT, 5_000).
|
-define(RETRY_TIMEOUT, 5_000).
|
||||||
|
|
||||||
-record(state,
|
-record(state,
|
||||||
{ refresh_timers = #{} :: #{binary() => timer:tref()}
|
{ refresh_timers = #{} :: #{binary() => timer:tref()}
|
||||||
, refresh_interval = timer:minutes(15) :: timer:time()
|
, refresh_interval = timer:minutes(15) :: timer:time()
|
||||||
|
, http_timeout = ?HTTP_TIMEOUT :: timer:time()
|
||||||
|
, extra = #{} :: map() %% for future use
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -54,16 +57,11 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
Listeners = emqx:get_env(listeners, []),
|
Config = gather_config(),
|
||||||
URLs = collect_urls(Listeners),
|
start_link(Config).
|
||||||
RefreshIntervalMS0 = emqx:get_env(crl_cache_refresh_interval,
|
|
||||||
timer:minutes(15)),
|
|
||||||
MinimumRefreshInverval = timer:minutes(1),
|
|
||||||
RefreshIntervalMS = max(RefreshIntervalMS0, MinimumRefreshInverval),
|
|
||||||
start_link(#{urls => URLs, refresh_interval => RefreshIntervalMS}).
|
|
||||||
|
|
||||||
start_link(Opts = #{urls := _, refresh_interval := _}) ->
|
start_link(Config = #{urls := _, refresh_interval := _, http_timeout := _}) ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, Config, []).
|
||||||
|
|
||||||
refresh(URL) ->
|
refresh(URL) ->
|
||||||
gen_server:cast(?MODULE, {refresh, URL}).
|
gen_server:cast(?MODULE, {refresh, URL}).
|
||||||
|
@ -71,13 +69,24 @@ refresh(URL) ->
|
||||||
evict(URL) ->
|
evict(URL) ->
|
||||||
gen_server:cast(?MODULE, {evict, URL}).
|
gen_server:cast(?MODULE, {evict, URL}).
|
||||||
|
|
||||||
|
%% to pick up changes from the config
|
||||||
|
-spec refresh_config() -> ok.
|
||||||
|
refresh_config() ->
|
||||||
|
gen_server:cast(?MODULE, refresh_config).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server behaviour
|
%% gen_server behaviour
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init(#{urls := URLs, refresh_interval := RefreshIntervalMS}) ->
|
init(Config) ->
|
||||||
|
#{ urls := URLs
|
||||||
|
, refresh_interval := RefreshIntervalMS
|
||||||
|
, http_timeout := HTTPTimeoutMS
|
||||||
|
} = Config,
|
||||||
State = lists:foldl(fun(URL, Acc) -> ensure_timer(URL, Acc, 0) end,
|
State = lists:foldl(fun(URL, Acc) -> ensure_timer(URL, Acc, 0) end,
|
||||||
#state{refresh_interval = RefreshIntervalMS},
|
#state{ refresh_interval = RefreshIntervalMS
|
||||||
|
, http_timeout = HTTPTimeoutMS
|
||||||
|
},
|
||||||
URLs),
|
URLs),
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
|
@ -95,7 +104,7 @@ handle_cast({evict, URL}, State0 = #state{refresh_timers = RefreshTimers0}) ->
|
||||||
}),
|
}),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_cast({refresh, URL}, State0) ->
|
handle_cast({refresh, URL}, State0) ->
|
||||||
case do_http_fetch_and_cache(URL) of
|
case do_http_fetch_and_cache(URL, State0#state.http_timeout) of
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
?tp(crl_refresh_failure, #{error => Error, url => URL}),
|
?tp(crl_refresh_failure, #{error => Error, url => URL}),
|
||||||
?LOG(error, "failed to fetch crl response for ~p; error: ~p",
|
?LOG(error, "failed to fetch crl response for ~p; error: ~p",
|
||||||
|
@ -105,16 +114,33 @@ handle_cast({refresh, URL}, State0) ->
|
||||||
?LOG(debug, "fetched crl response for ~p", [URL]),
|
?LOG(debug, "fetched crl response for ~p", [URL]),
|
||||||
{noreply, ensure_timer(URL, State0)}
|
{noreply, ensure_timer(URL, State0)}
|
||||||
end;
|
end;
|
||||||
|
handle_cast(refresh_config, State0) ->
|
||||||
|
#{ urls := URLs
|
||||||
|
, http_timeout := HTTPTimeoutMS
|
||||||
|
, refresh_interval := RefreshIntervalMS
|
||||||
|
} = gather_config(),
|
||||||
|
State = lists:foldl(fun(URL, Acc) -> ensure_timer(URL, Acc, 0) end,
|
||||||
|
State0#state{ refresh_interval = RefreshIntervalMS
|
||||||
|
, http_timeout = HTTPTimeoutMS
|
||||||
|
},
|
||||||
|
URLs),
|
||||||
|
?tp(crl_cache_refresh_config, #{ refresh_interval => RefreshIntervalMS
|
||||||
|
, http_timeout => HTTPTimeoutMS
|
||||||
|
, urls => URLs
|
||||||
|
}),
|
||||||
|
{noreply, State};
|
||||||
handle_cast(_Cast, State) ->
|
handle_cast(_Cast, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({timeout, TRef, {refresh, URL}},
|
handle_info({timeout, TRef, {refresh, URL}},
|
||||||
State = #state{refresh_timers = RefreshTimers}) ->
|
State = #state{ refresh_timers = RefreshTimers
|
||||||
|
, http_timeout = HTTPTimeoutMS
|
||||||
|
}) ->
|
||||||
case maps:get(URL, RefreshTimers, undefined) of
|
case maps:get(URL, RefreshTimers, undefined) of
|
||||||
TRef ->
|
TRef ->
|
||||||
?tp(crl_refresh_timer, #{url => URL}),
|
?tp(crl_refresh_timer, #{url => URL}),
|
||||||
?LOG(debug, "refreshing crl response for ~p", [URL]),
|
?LOG(debug, "refreshing crl response for ~p", [URL]),
|
||||||
case do_http_fetch_and_cache(URL) of
|
case do_http_fetch_and_cache(URL, HTTPTimeoutMS) of
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
?LOG(error, "failed to fetch crl response for ~p; error: ~p",
|
?LOG(error, "failed to fetch crl response for ~p; error: ~p",
|
||||||
[URL, Error]),
|
[URL, Error]),
|
||||||
|
@ -142,10 +168,9 @@ http_get(URL, HTTPTimeout) ->
|
||||||
[{body_format, binary}]
|
[{body_format, binary}]
|
||||||
).
|
).
|
||||||
|
|
||||||
do_http_fetch_and_cache(URL) ->
|
do_http_fetch_and_cache(URL, HTTPTimeoutMS) ->
|
||||||
?tp(crl_http_fetch, #{crl_url => URL}),
|
?tp(crl_http_fetch, #{crl_url => URL}),
|
||||||
%% FIXME: read from config
|
Resp = ?MODULE:http_get(URL, HTTPTimeoutMS),
|
||||||
Resp = ?MODULE:http_get(URL, ?HTTP_TIMEOUT),
|
|
||||||
case Resp of
|
case Resp of
|
||||||
{ok, {{_, 200, _}, _, Body}} ->
|
{ok, {{_, 200, _}, _, Body}} ->
|
||||||
case parse_crls(Body) of
|
case parse_crls(Body) of
|
||||||
|
@ -174,6 +199,7 @@ ensure_timer(URL, State = #state{refresh_interval = Timeout}) ->
|
||||||
ensure_timer(URL, State, Timeout).
|
ensure_timer(URL, State, Timeout).
|
||||||
|
|
||||||
ensure_timer(URL, State = #state{refresh_timers = RefreshTimers0}, Timeout) ->
|
ensure_timer(URL, State = #state{refresh_timers = RefreshTimers0}, Timeout) ->
|
||||||
|
?tp(crl_cache_ensure_timer, #{url => URL, timeout => Timeout}),
|
||||||
MTimer = maps:get(URL, RefreshTimers0, undefined),
|
MTimer = maps:get(URL, RefreshTimers0, undefined),
|
||||||
emqx_misc:cancel_timer(MTimer),
|
emqx_misc:cancel_timer(MTimer),
|
||||||
RefreshTimers = RefreshTimers0#{URL => emqx_misc:start_timer(
|
RefreshTimers = RefreshTimers0#{URL => emqx_misc:start_timer(
|
||||||
|
@ -187,7 +213,7 @@ collect_urls(Listeners) ->
|
||||||
CRLOpts1 =
|
CRLOpts1 =
|
||||||
lists:filter(
|
lists:filter(
|
||||||
fun(CRLOpts) ->
|
fun(CRLOpts) ->
|
||||||
proplists:get_bool(crl_cache_enabled, CRLOpts)
|
proplists:get_bool(crl_check_enabled, CRLOpts)
|
||||||
end,
|
end,
|
||||||
CRLOpts0),
|
CRLOpts0),
|
||||||
CRLURLs =
|
CRLURLs =
|
||||||
|
@ -197,3 +223,20 @@ collect_urls(Listeners) ->
|
||||||
end,
|
end,
|
||||||
CRLOpts1),
|
CRLOpts1),
|
||||||
lists:usort(CRLURLs).
|
lists:usort(CRLURLs).
|
||||||
|
|
||||||
|
-spec gather_config() -> #{ urls := [string()]
|
||||||
|
, refresh_interval := timer:time()
|
||||||
|
, http_timeout := timer:time()
|
||||||
|
}.
|
||||||
|
gather_config() ->
|
||||||
|
Listeners = emqx:get_env(listeners, []),
|
||||||
|
URLs = collect_urls(Listeners),
|
||||||
|
RefreshIntervalMS0 = emqx:get_env(crl_cache_refresh_interval,
|
||||||
|
timer:minutes(15)),
|
||||||
|
MinimumRefreshInverval = timer:minutes(1),
|
||||||
|
RefreshIntervalMS = max(RefreshIntervalMS0, MinimumRefreshInverval),
|
||||||
|
HTTPTimeoutMS = emqx:get_env(crl_cache_http_timeout, ?HTTP_TIMEOUT),
|
||||||
|
#{ urls => URLs
|
||||||
|
, refresh_interval => RefreshIntervalMS
|
||||||
|
, http_timeout => HTTPTimeoutMS
|
||||||
|
}.
|
||||||
|
|
|
@ -306,7 +306,7 @@ find_by_id(Id, [L | Rest]) ->
|
||||||
-spec maybe_register_crl_urls([esockd:option()]) -> ok.
|
-spec maybe_register_crl_urls([esockd:option()]) -> ok.
|
||||||
maybe_register_crl_urls(Options) ->
|
maybe_register_crl_urls(Options) ->
|
||||||
CRLOptions = proplists:get_value(crl_options, Options, []),
|
CRLOptions = proplists:get_value(crl_options, Options, []),
|
||||||
case proplists:get_bool(crl_cache_enabled, CRLOptions) of
|
case proplists:get_bool(crl_check_enabled, CRLOptions) of
|
||||||
false ->
|
false ->
|
||||||
ok;
|
ok;
|
||||||
true ->
|
true ->
|
||||||
|
|
|
@ -56,6 +56,29 @@ init_per_testcase(t_not_cached_and_unreachable, Config) ->
|
||||||
[ {crl_pem, CRLPem}
|
[ {crl_pem, CRLPem}
|
||||||
, {crl_der, CRLDer}
|
, {crl_der, CRLDer}
|
||||||
| Config];
|
| Config];
|
||||||
|
init_per_testcase(t_refresh_config, Config) ->
|
||||||
|
DataDir = ?config(data_dir, Config),
|
||||||
|
CRLFile = filename:join([DataDir, "crl.pem"]),
|
||||||
|
{ok, CRLPem} = file:read_file(CRLFile),
|
||||||
|
[{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem),
|
||||||
|
TestPid = self(),
|
||||||
|
ok = meck:new(emqx_crl_cache, [non_strict, passthrough, no_history, no_link]),
|
||||||
|
meck:expect(emqx_crl_cache, http_get,
|
||||||
|
fun(URL, _HTTPTimeout) ->
|
||||||
|
TestPid ! {http_get, URL},
|
||||||
|
{ok, {{"HTTP/1.0", 200, 'OK'}, [], CRLPem}}
|
||||||
|
end),
|
||||||
|
OldListeners = emqx:get_env(listeners),
|
||||||
|
OldRefreshInterval = emqx:get_env(crl_cache_refresh_interval),
|
||||||
|
OldHTTPTimeout = emqx:get_env(crl_cache_http_timeout),
|
||||||
|
ok = setup_crl_options(Config, #{is_cached => false}),
|
||||||
|
[ {crl_pem, CRLPem}
|
||||||
|
, {crl_der, CRLDer}
|
||||||
|
, {old_configs, [ {listeners, OldListeners}
|
||||||
|
, {crl_cache_refresh_interval, OldRefreshInterval}
|
||||||
|
, {crl_cache_http_timeout, OldHTTPTimeout}
|
||||||
|
]}
|
||||||
|
| Config];
|
||||||
init_per_testcase(_TestCase, Config) ->
|
init_per_testcase(_TestCase, Config) ->
|
||||||
DataDir = ?config(data_dir, Config),
|
DataDir = ?config(data_dir, Config),
|
||||||
CRLFile = filename:join([DataDir, "crl.pem"]),
|
CRLFile = filename:join([DataDir, "crl.pem"]),
|
||||||
|
@ -80,25 +103,50 @@ end_per_testcase(TestCase, Config)
|
||||||
emqx_crl_cache_http_server:stop(ServerPid),
|
emqx_crl_cache_http_server:stop(ServerPid),
|
||||||
emqx_ct_helpers:stop_apps([]),
|
emqx_ct_helpers:stop_apps([]),
|
||||||
emqx_ct_helpers:change_emqx_opts(
|
emqx_ct_helpers:change_emqx_opts(
|
||||||
ssl_twoway, [ {crl_options, [ {crl_cache_enabled, false}
|
ssl_twoway, [ {crl_options, [ {crl_check_enabled, false}
|
||||||
, {crl_cache_urls, []}
|
, {crl_cache_urls, []}
|
||||||
]}
|
]}
|
||||||
]),
|
]),
|
||||||
application:stop(cowboy),
|
application:stop(cowboy),
|
||||||
clear_crl_cache(),
|
clear_crl_cache(),
|
||||||
|
ok = snabbkaffe:stop(),
|
||||||
ok;
|
ok;
|
||||||
end_per_testcase(t_not_cached_and_unreachable, _Config) ->
|
end_per_testcase(t_not_cached_and_unreachable, _Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]),
|
emqx_ct_helpers:stop_apps([]),
|
||||||
emqx_ct_helpers:change_emqx_opts(
|
emqx_ct_helpers:change_emqx_opts(
|
||||||
ssl_twoway, [ {crl_options, [ {crl_cache_enabled, false}
|
ssl_twoway, [ {crl_options, [ {crl_check_enabled, false}
|
||||||
, {crl_cache_urls, []}
|
, {crl_cache_urls, []}
|
||||||
]}
|
]}
|
||||||
]),
|
]),
|
||||||
clear_crl_cache(),
|
clear_crl_cache(),
|
||||||
|
ok = snabbkaffe:stop(),
|
||||||
|
ok;
|
||||||
|
end_per_testcase(t_refresh_config, Config) ->
|
||||||
|
OldConfigs = ?config(old_configs, Config),
|
||||||
|
meck:unload([emqx_crl_cache]),
|
||||||
|
clear_crl_cache(),
|
||||||
|
emqx_ct_helpers:stop_apps([]),
|
||||||
|
emqx_ct_helpers:change_emqx_opts(
|
||||||
|
ssl_twoway, [ {crl_options, [ {crl_check_enabled, false}
|
||||||
|
, {crl_cache_urls, []}
|
||||||
|
]}
|
||||||
|
]),
|
||||||
|
clear_crl_cache(),
|
||||||
|
lists:foreach(
|
||||||
|
fun({Key, MValue}) ->
|
||||||
|
case MValue of
|
||||||
|
undefined -> ok;
|
||||||
|
Value -> application:set_env(emqx, Key, Value)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
OldConfigs),
|
||||||
|
application:stop(cowboy),
|
||||||
|
ok = snabbkaffe:stop(),
|
||||||
ok;
|
ok;
|
||||||
end_per_testcase(_TestCase, _Config) ->
|
end_per_testcase(_TestCase, _Config) ->
|
||||||
meck:unload([emqx_crl_cache]),
|
meck:unload([emqx_crl_cache]),
|
||||||
clear_crl_cache(),
|
clear_crl_cache(),
|
||||||
|
ok = snabbkaffe:stop(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -194,7 +242,7 @@ setup_crl_options(Config, #{is_cached := IsCached}) ->
|
||||||
, {crl_cache,
|
, {crl_cache,
|
||||||
{ssl_crl_cache, {internal, [{http, timer:seconds(15)}]}}}
|
{ssl_crl_cache, {internal, [{http, timer:seconds(15)}]}}}
|
||||||
]}
|
]}
|
||||||
, {crl_options, [ {crl_cache_enabled, true}
|
, {crl_options, [ {crl_check_enabled, true}
|
||||||
, {crl_cache_urls, URLs}
|
, {crl_cache_urls, URLs}
|
||||||
]}
|
]}
|
||||||
]),
|
]),
|
||||||
|
@ -257,6 +305,7 @@ t_init_refresh(Config) ->
|
||||||
URL2 = "http://localhost/crl2.pem",
|
URL2 = "http://localhost/crl2.pem",
|
||||||
Opts = #{ urls => [URL1, URL2]
|
Opts = #{ urls => [URL1, URL2]
|
||||||
, refresh_interval => timer:minutes(15)
|
, refresh_interval => timer:minutes(15)
|
||||||
|
, http_timeout => timer:seconds(15)
|
||||||
},
|
},
|
||||||
ok = snabbkaffe:start_trace(),
|
ok = snabbkaffe:start_trace(),
|
||||||
{ok, SubRef} = snabbkaffe:subscribe(
|
{ok, SubRef} = snabbkaffe:subscribe(
|
||||||
|
@ -421,6 +470,44 @@ t_filled_cache(Config) ->
|
||||||
emqtt:disconnect(C),
|
emqtt:disconnect(C),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_refresh_config(_Config) ->
|
||||||
|
URLs = [ "http://localhost:9878/some.crl.pem"
|
||||||
|
, "http://localhost:9878/another.crl.pem"
|
||||||
|
],
|
||||||
|
SortedURLs = lists:sort(URLs),
|
||||||
|
emqx_ct_helpers:change_emqx_opts(
|
||||||
|
ssl_twoway, [ {crl_options, [ {crl_check_enabled, true}
|
||||||
|
, {crl_cache_urls, URLs}
|
||||||
|
]}
|
||||||
|
]),
|
||||||
|
%% has to be more than 1 minute
|
||||||
|
NewRefreshInterval = timer:seconds(64),
|
||||||
|
NewHTTPTimeout = timer:seconds(7),
|
||||||
|
application:set_env(emqx, crl_cache_refresh_interval, NewRefreshInterval),
|
||||||
|
application:set_env(emqx, crl_cache_http_timeout, NewHTTPTimeout),
|
||||||
|
?check_trace(
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_crl_cache:refresh_config(),
|
||||||
|
#{?snk_kind := crl_cache_refresh_config},
|
||||||
|
_Timeout = 10_000),
|
||||||
|
fun(Res, Trace) ->
|
||||||
|
?assertMatch({ok, {ok, _}}, Res),
|
||||||
|
?assertMatch(
|
||||||
|
[#{ urls := SortedURLs
|
||||||
|
, refresh_interval := NewRefreshInterval
|
||||||
|
, http_timeout := NewHTTPTimeout
|
||||||
|
}],
|
||||||
|
?of_kind(crl_cache_refresh_config, Trace),
|
||||||
|
#{ expected => #{ urls => SortedURLs
|
||||||
|
, refresh_interval => NewRefreshInterval
|
||||||
|
, http_timeout => NewHTTPTimeout
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
?assertEqual(SortedURLs, ?projection(url, ?of_kind(crl_cache_ensure_timer, Trace))),
|
||||||
|
ok
|
||||||
|
end),
|
||||||
|
ok.
|
||||||
|
|
||||||
%% If the CRL is not cached when the client tries to connect and the
|
%% If the CRL is not cached when the client tries to connect and the
|
||||||
%% CRL server is unreachable, the client will be denied connection.
|
%% CRL server is unreachable, the client will be denied connection.
|
||||||
t_not_cached_and_unreachable(Config) ->
|
t_not_cached_and_unreachable(Config) ->
|
||||||
|
|
Loading…
Reference in New Issue