Merge remote-tracking branch 'origin/release-v44' into main-v4.4

This commit is contained in:
Zaiming (Stone) Shi 2022-11-28 09:48:01 +01:00
commit 772ea4988b
22 changed files with 283 additions and 104 deletions

View File

@ -29,6 +29,7 @@ jobs:
- uses: actions/checkout@v3
with:
path: source
fetch-depth: 0 # clone full git history
- name: detect-profiles
id: detect-profiles
uses: ./source/.github/actions/detect-profiles

View File

@ -39,6 +39,8 @@ jobs:
# 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
- uses: actions/checkout@v1
with:
fetch-depth: 0 # clone full git history
- name: fix-git-unsafe-repository
run: git config --global --add safe.directory /__w/emqx/emqx
- uses: ./.github/actions/detect-profiles
@ -123,6 +125,8 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # clone full git history
- name: ensure access to github
if: endsWith(github.repository, 'enterprise')
run: |

View File

@ -14,6 +14,8 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # clone full git history
- id: detect-profiles
uses: ./.github/actions/detect-profiles
@ -55,6 +57,8 @@ jobs:
-d "{\"repo\":\"emqx/emqx\", \"tag\": \"${{ github.ref_name }}\" }" \
${{ secrets.EMQX_IO_RELEASE_API }}
- uses: actions/checkout@v3
with:
fetch-depth: 0 # clone full git history
- name: get version
id: version
run: echo "version=$(./pkg-vsn.sh)" >> $GITHUB_OUTPUT

View File

@ -11,12 +11,13 @@ on:
jobs:
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
outputs:
fast_ct_apps: ${{ steps.run_find_apps.outputs.fast_ct_apps }}
docker_ct_apps: ${{ steps.run_find_apps.outputs.docker_ct_apps }}
steps:
- uses: AutoModality/action-clean@v1
- uses: actions/checkout@v3
with:
path: source
@ -40,6 +41,7 @@ jobs:
- name: get_all_deps
working-directory: source
run: |
git config --global --add safe.directory $(pwd)
# build the default profile for two purposes
# 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
@ -55,7 +57,7 @@ jobs:
eunit_and_proper:
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
strategy:
fail-fast: false
@ -83,7 +85,7 @@ jobs:
fast_ct:
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
strategy:
fail-fast: false
@ -246,7 +248,7 @@ jobs:
- eunit_and_proper
- fast_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
steps:
- uses: AutoModality/action-clean@v1
@ -276,7 +278,7 @@ jobs:
finish:
needs: make_cover
runs-on: ubuntu-20.04
runs-on: aws-amd64
steps:
- name: Coveralls Finished
env:

View File

@ -125,7 +125,10 @@ handle_request(_Method, _Path, Req) ->
cowboy_req:reply(400, #{<<"content-type">> => <<"text/plain">>}, <<"Not found.">>, 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) ->
true;

View File

@ -95,6 +95,9 @@
end
end()).
-define(GET_RES_ALIVE_TIMEOUT, 60000).
-define(PROBE_RES_PREFIX, "__probe__:").
%%------------------------------------------------------------------------------
%% Load resource/action providers from all available applications
%%------------------------------------------------------------------------------
@ -363,7 +366,7 @@ test_resource(#{type := Type} = Params) ->
{ok, #resource_type{}} ->
%% Resource will be deleted after test.
%% Use random resource id, ensure test func will not delete the resource in used.
ResId = resource_id(),
ResId = probe_resource_id(),
try
case create_resource(maps:put(id, ResId, Params), no_retry) of
{ok, _} ->
@ -405,7 +408,7 @@ is_resource_alive(Nodes, ResId, _Opts = #{fetch := true}) ->
{ok, #resource_type{on_status = {Mod, OnStatus}}}
= emqx_rule_registry:find_resource_type(ResType),
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, []} ->
is_resource_alive_(ResL);
{_, _Error} ->
@ -420,7 +423,7 @@ is_resource_alive(Nodes, ResId, _Opts = #{fetch := true}) ->
end;
is_resource_alive(Nodes, ResId, _Opts = #{fetch := false}) ->
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, []} ->
is_resource_alive_(ResL);
{_, _Errors} ->
@ -532,10 +535,15 @@ refresh_rule(#rule{id = RuleId, for = Topics, actions = Actions}) ->
refresh_resource_status() ->
lists:foreach(
fun(#resource{id = ResId, type = ResType}) ->
case emqx_rule_registry:find_resource_type(ResType) of
{ok, #resource_type{on_status = {Mod, OnStatus}}} ->
fetch_resource_status(Mod, OnStatus, ResId);
_ -> ok
case is_prober(ResId) of
false ->
case emqx_rule_registry:find_resource_type(ResType) of
{ok, #resource_type{on_status = {Mod, OnStatus}}} ->
fetch_resource_status(Mod, OnStatus, ResId);
_ -> ok
end;
true ->
ok
end
end, emqx_rule_registry:get_resources()).
@ -662,6 +670,9 @@ ignore_lib_apps(Apps) ->
resource_id() ->
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() ->
gen_id("rule:", fun emqx_rule_registry:get_rule/1).
@ -811,4 +822,9 @@ find_type(ResId) ->
{ok, Type}.
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.

View File

@ -62,7 +62,8 @@ groups() ->
t_create_rule,
t_reset_metrics,
t_reset_metrics_fallbacks,
t_create_resource
t_create_resource,
t_clean_resource_alarms
]},
{actions, [],
[t_inspect_action
@ -309,21 +310,29 @@ t_create_resource(_Config) ->
ok.
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, #resource{id = ResId}} = emqx_rule_engine:create_resource(
#{type => built_in,
#{id => ResId,
type => built_in,
config => #{},
description => <<"debug resource">>}),
?assert(true, is_binary(ResId)),
Name = emqx_rule_engine:alarm_name_of_resource_down(ResId, built_in),
_ = emqx_alarm:activate(Name, #{id => ResId, type => built_in}),
AlarmExist = fun(#{name := AName}) -> AName == Name end,
Len = length(lists:filter(AlarmExist, emqx_alarm:get_alarms())),
?assert(Len == 1),
Len = length(lists:filter(AlarmExist, emqx_alarm:get_alarms(activated))),
?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(),
emqx_rule_registry:remove_resource(ResId),
LenAfterRemove = length(lists:filter(AlarmExist, emqx_alarm:get_alarms())),
?assert(LenAfterRemove == 0),
ok.
%%------------------------------------------------------------------------------

15
build
View File

@ -62,20 +62,9 @@ log() {
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() {
./rebar3 as "$PROFILE" release
# delete outdated cert store
delete_unwanted_file _build/"${PROFILE}"/rel/emqx/lib/certifi*/priv/cacerts.pem
./rebar3 as "$PROFILE" tar
# shellcheck disable=SC1010
./rebar3 as "$PROFILE" do release,tar
}
relup_db() {

View File

@ -1,7 +1,10 @@
# v4.3.22
This marks the last release of EMQX v4.3 Opensource Edition.
## 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).
@ -39,8 +42,8 @@
- 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
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).
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.
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_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).
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).
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).

View File

@ -1,7 +1,11 @@
# 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).
- 当 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` 事件及计数器仅会在客户端正常断开连接或客户端被系统管理员踢出时触发,
但不会在旧 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` 来启用此场景下的客户端断连事件。
- 规则引擎资源创建失败后,第一次重试前增加一个延迟 [#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)。
注意在创建规则或资源时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)。

View File

@ -1550,11 +1550,10 @@ listener.ssl.external.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem
## listener.ssl.external.ocsp_refresh_http_timeout = 15s
## Whether to enable CRL verification and caching for this listener.
## If set to true, requires specifying the CRL server URLs.
##
## Value: boolean
## 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
## 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
## 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
## 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
## for all URLs and listeners.
##
## Value: Duration
## 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
## 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.
## 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
## `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
##
# broker.client_disconnect_discarded = off

View File

@ -29,7 +29,7 @@
-ifndef(EMQX_ENTERPRISE).
-define(EMQX_RELEASE, {opensource, "4.4.11-alpha.2"}).
-define(EMQX_RELEASE, {opensource, "4.4.11"}).
-else.

View File

@ -107,7 +107,10 @@ http_handlers() ->
%%--------------------------------------------------------------------
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) ->
true;

View File

@ -42,7 +42,9 @@ end_per_suite(_) ->
emqx_ct_helpers:stop_apps([emqx_modules]).
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, enable_acl_cache, false);
set_special_configs(_App) ->

View File

@ -842,21 +842,21 @@ end}.
%% @doc Define a determined authentication plugin/module check order.
%% see detailed doc in emqx.conf
{mapping, "auth_order", "emqx.auth_order", [
{default, "none"},
{default, "none"}, % keep default value in sync with emqx_conf.erl
{datatype, string}
]}.
%% @doc Same as auth_order, but for ACL.
{mapping, "acl_order", "emqx.acl_order", [
{default, "none"},
{default, "none"}, % keep default value in sync with emqx_conf.erl
{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
%% information with clientid and/or common name aliases (or other
%% enrichments the module may implement).
{mapping, "clientid_enrichment_module", "emqx.clientid_enrichment_module", [
{mapping, "alias_enrichment_module", "emqx.alias_enrichment_module", [
{datatype, atom}
]}.
@ -1698,11 +1698,11 @@ end}.
]}.
{mapping, "listener.ssl.$name.ocsp_refresh_http_timeout", "emqx.listeners", [
{default, "15000ms"},
{default, "15s"},
{datatype, {duration, ms}}
]}.
{mapping, "listener.ssl.$name.enable_crl_cache", "emqx.listeners", [
{mapping, "listener.ssl.$name.enable_crl_check", "emqx.listeners", [
{default, false},
{datatype, {enum, [true, false]}}
]}.
@ -1712,12 +1712,12 @@ end}.
{datatype, string}
]}.
{mapping, "listener.ssl.$name.crl_cache_http_timeout", "emqx.listeners", [
{mapping, "crl_cache_http_timeout", "emqx.crl_cache_http_timeout", [
{default, "15s"},
{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"},
{datatype, {duration, ms}}
]}.
@ -2310,7 +2310,16 @@ end}.
SslOpts = fun(Prefix) ->
Versions = case SplitFun(cuttlefish:conf_get(Prefix ++ ".tls_versions", Conf, undefined)) of
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,
TLSCiphers = cuttlefish:conf_get(Prefix++".ciphers", Conf, undefined),
PSKCiphers = cuttlefish:conf_get(Prefix++".psk_ciphers", Conf, undefined),
@ -2337,9 +2346,9 @@ end}.
undefined -> undefined;
_ -> {fun emqx_psk:lookup/3, <<>>}
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 ->
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, peer}
, {crl_cache, {ssl_crl_cache, {internal, [{http, HTTPTimeout}]}}}
@ -2374,7 +2383,7 @@ end}.
undefined -> undefined;
URLs -> string:tokens(URLs, ", ")
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}
])
end,
@ -2685,13 +2694,13 @@ end}.
]}.
%% @doc Configuration of disconnected event reason.
%% `takeover`: session was takenover by another client with same client ID. (clean_session = false)
%% `discard`: session was takeover by another client with same client ID. (clean_session = true)
%% `discarded`: session was discarded by another client with same client ID when new connection use `clean_session = true`.
{mapping, "broker.client_disconnect_discarded", "emqx.client_disconnect_discarded", [
{default, off},
{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", [
{default, off},
{datatype, flag}

View File

@ -61,7 +61,7 @@
, {getopt, "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"}}}
, {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"}}}
, {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.7"}}}
]}.

View File

@ -42,11 +42,11 @@ for keychain in ${keychains}; do
done
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}"/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/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

View File

@ -26,7 +26,7 @@
]).
%% internal exports for ad-hoc debugging.
-export([ set_clientid_enrichment_module/0
-export([ set_alias_enrichment_module/0
, set_special_auth_module/0
]).
@ -54,7 +54,7 @@ start(_Type, _Args) ->
ok = emqx_plugins:init(),
_ = emqx_plugins:load(),
_ = start_ce_modules(),
set_clientid_enrichment_module(),
set_alias_enrichment_module(),
_ = set_special_auth_module(),
register(emqx, self()),
print_vsn(),
@ -85,14 +85,14 @@ start_ce_modules() ->
ok.
-endif.
set_clientid_enrichment_module() ->
case emqx:get_env(clientid_enrichment_module) of
set_alias_enrichment_module() ->
case emqx:get_env(alias_enrichment_module) of
undefined ->
ok;
Mod ->
case erlang:function_exported(Mod, enrich_clientid_alias, 2) of
case erlang:function_exported(Mod, enrich_with_aliases, 2) of
true ->
persistent_term:put(clientid_enrichment_module, Mod);
persistent_term:put(alias_enrichment_module, Mod);
false ->
ok
end

View File

@ -313,7 +313,7 @@ handle_in(?CONNECT_PACKET(ConnPkt) = Packet, Channel) ->
fun set_log_meta/2,
fun check_banned/2,
fun count_flapping_event/2,
fun enrich_clientid_alias/2,
fun enrich_with_aliases/2,
fun auth_connect/2
], ConnPkt, Channel#channel{conn_state = connecting}) of
{ok, NConnPkt, NChannel = #channel{clientinfo = ClientInfo}} ->
@ -1363,12 +1363,12 @@ check_banned(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) ->
%%--------------------------------------------------------------------
%% Enrich ClientID Alias
enrich_clientid_alias(Packet, Channel) ->
case persistent_term:get(clientid_enrichment_module, undefined) of
enrich_with_aliases(Packet, Channel) ->
case persistent_term:get(alias_enrichment_module, undefined) of
undefined ->
{ok, Channel};
Mod ->
Mod:enrich_clientid_alias(Packet, Channel)
Mod:enrich_with_aliases(Packet, Channel)
end.
%%--------------------------------------------------------------------

View File

@ -23,6 +23,7 @@
, start_link/1
, refresh/1
, evict/1
, refresh_config/0
]).
%% gen_server callbacks
@ -41,12 +42,14 @@
-define(LOG(Level, Format, 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).
-record(state,
{ refresh_timers = #{} :: #{binary() => timer:tref()}
, refresh_interval = timer:minutes(15) :: timer:time()
, http_timeout = ?HTTP_TIMEOUT :: timer:time()
, extra = #{} :: map() %% for future use
}).
%%--------------------------------------------------------------------
@ -54,16 +57,11 @@
%%--------------------------------------------------------------------
start_link() ->
Listeners = emqx:get_env(listeners, []),
URLs = collect_urls(Listeners),
RefreshIntervalMS0 = emqx:get_env(crl_cache_refresh_interval,
timer:minutes(15)),
MinimumRefreshInverval = timer:minutes(1),
RefreshIntervalMS = max(RefreshIntervalMS0, MinimumRefreshInverval),
start_link(#{urls => URLs, refresh_interval => RefreshIntervalMS}).
Config = gather_config(),
start_link(Config).
start_link(Opts = #{urls := _, refresh_interval := _}) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).
start_link(Config = #{urls := _, refresh_interval := _, http_timeout := _}) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Config, []).
refresh(URL) ->
gen_server:cast(?MODULE, {refresh, URL}).
@ -71,13 +69,24 @@ refresh(URL) ->
evict(URL) ->
gen_server:cast(?MODULE, {evict, URL}).
%% to pick up changes from the config
-spec refresh_config() -> ok.
refresh_config() ->
gen_server:cast(?MODULE, refresh_config).
%%--------------------------------------------------------------------
%% gen_server behaviour
%%--------------------------------------------------------------------
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{refresh_interval = RefreshIntervalMS},
#state{ refresh_interval = RefreshIntervalMS
, http_timeout = HTTPTimeoutMS
},
URLs),
{ok, State}.
@ -95,7 +104,7 @@ handle_cast({evict, URL}, State0 = #state{refresh_timers = RefreshTimers0}) ->
}),
{noreply, State};
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} ->
?tp(crl_refresh_failure, #{error => Error, url => URL}),
?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]),
{noreply, ensure_timer(URL, State0)}
end;
handle_cast(refresh_config, State0) ->
#{ urls := URLs
, http_timeout := HTTPTimeoutMS
, refresh_interval := RefreshIntervalMS
} = gather_config(),
State = lists:foldl(fun(URL, Acc) -> ensure_timer(URL, Acc, 0) end,
State0#state{ refresh_interval = RefreshIntervalMS
, http_timeout = HTTPTimeoutMS
},
URLs),
?tp(crl_cache_refresh_config, #{ refresh_interval => RefreshIntervalMS
, http_timeout => HTTPTimeoutMS
, urls => URLs
}),
{noreply, State};
handle_cast(_Cast, State) ->
{noreply, State}.
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
TRef ->
?tp(crl_refresh_timer, #{url => 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} ->
?LOG(error, "failed to fetch crl response for ~p; error: ~p",
[URL, Error]),
@ -142,10 +168,9 @@ http_get(URL, HTTPTimeout) ->
[{body_format, binary}]
).
do_http_fetch_and_cache(URL) ->
do_http_fetch_and_cache(URL, HTTPTimeoutMS) ->
?tp(crl_http_fetch, #{crl_url => URL}),
%% FIXME: read from config
Resp = ?MODULE:http_get(URL, ?HTTP_TIMEOUT),
Resp = ?MODULE:http_get(URL, HTTPTimeoutMS),
case Resp of
{ok, {{_, 200, _}, _, Body}} ->
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 = #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(
@ -187,7 +213,7 @@ collect_urls(Listeners) ->
CRLOpts1 =
lists:filter(
fun(CRLOpts) ->
proplists:get_bool(crl_cache_enabled, CRLOpts)
proplists:get_bool(crl_check_enabled, CRLOpts)
end,
CRLOpts0),
CRLURLs =
@ -197,3 +223,20 @@ collect_urls(Listeners) ->
end,
CRLOpts1),
lists:usort(CRLURLs).
-spec gather_config() -> #{ urls := [string()]
, refresh_interval := timer:time()
, http_timeout := timer:time()
}.
gather_config() ->
Listeners = emqx:get_env(listeners, []),
URLs = collect_urls(Listeners),
RefreshIntervalMS0 = emqx:get_env(crl_cache_refresh_interval,
timer:minutes(15)),
MinimumRefreshInverval = timer:minutes(1),
RefreshIntervalMS = max(RefreshIntervalMS0, MinimumRefreshInverval),
HTTPTimeoutMS = emqx:get_env(crl_cache_http_timeout, ?HTTP_TIMEOUT),
#{ urls => URLs
, refresh_interval => RefreshIntervalMS
, http_timeout => HTTPTimeoutMS
}.

View File

@ -306,7 +306,7 @@ find_by_id(Id, [L | Rest]) ->
-spec maybe_register_crl_urls([esockd:option()]) -> ok.
maybe_register_crl_urls(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 ->
ok;
true ->

View File

@ -56,6 +56,29 @@ init_per_testcase(t_not_cached_and_unreachable, Config) ->
[ {crl_pem, CRLPem}
, {crl_der, CRLDer}
| Config];
init_per_testcase(t_refresh_config, Config) ->
DataDir = ?config(data_dir, Config),
CRLFile = filename:join([DataDir, "crl.pem"]),
{ok, CRLPem} = file:read_file(CRLFile),
[{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem),
TestPid = self(),
ok = meck:new(emqx_crl_cache, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_crl_cache, http_get,
fun(URL, _HTTPTimeout) ->
TestPid ! {http_get, URL},
{ok, {{"HTTP/1.0", 200, 'OK'}, [], CRLPem}}
end),
OldListeners = emqx:get_env(listeners),
OldRefreshInterval = emqx:get_env(crl_cache_refresh_interval),
OldHTTPTimeout = emqx:get_env(crl_cache_http_timeout),
ok = setup_crl_options(Config, #{is_cached => false}),
[ {crl_pem, CRLPem}
, {crl_der, CRLDer}
, {old_configs, [ {listeners, OldListeners}
, {crl_cache_refresh_interval, OldRefreshInterval}
, {crl_cache_http_timeout, OldHTTPTimeout}
]}
| Config];
init_per_testcase(_TestCase, Config) ->
DataDir = ?config(data_dir, Config),
CRLFile = filename:join([DataDir, "crl.pem"]),
@ -80,25 +103,50 @@ end_per_testcase(TestCase, Config)
emqx_crl_cache_http_server:stop(ServerPid),
emqx_ct_helpers:stop_apps([]),
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, []}
]}
]),
application:stop(cowboy),
clear_crl_cache(),
ok = snabbkaffe:stop(),
ok;
end_per_testcase(t_not_cached_and_unreachable, _Config) ->
emqx_ct_helpers:stop_apps([]),
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, []}
]}
]),
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;
end_per_testcase(_TestCase, _Config) ->
meck:unload([emqx_crl_cache]),
clear_crl_cache(),
ok = snabbkaffe:stop(),
ok.
%%--------------------------------------------------------------------
@ -194,7 +242,7 @@ setup_crl_options(Config, #{is_cached := IsCached}) ->
, {crl_cache,
{ssl_crl_cache, {internal, [{http, timer:seconds(15)}]}}}
]}
, {crl_options, [ {crl_cache_enabled, true}
, {crl_options, [ {crl_check_enabled, true}
, {crl_cache_urls, URLs}
]}
]),
@ -257,6 +305,7 @@ t_init_refresh(Config) ->
URL2 = "http://localhost/crl2.pem",
Opts = #{ urls => [URL1, URL2]
, refresh_interval => timer:minutes(15)
, http_timeout => timer:seconds(15)
},
ok = snabbkaffe:start_trace(),
{ok, SubRef} = snabbkaffe:subscribe(
@ -421,6 +470,44 @@ t_filled_cache(Config) ->
emqtt:disconnect(C),
ok.
t_refresh_config(_Config) ->
URLs = [ "http://localhost:9878/some.crl.pem"
, "http://localhost:9878/another.crl.pem"
],
SortedURLs = lists:sort(URLs),
emqx_ct_helpers:change_emqx_opts(
ssl_twoway, [ {crl_options, [ {crl_check_enabled, true}
, {crl_cache_urls, URLs}
]}
]),
%% has to be more than 1 minute
NewRefreshInterval = timer:seconds(64),
NewHTTPTimeout = timer:seconds(7),
application:set_env(emqx, crl_cache_refresh_interval, NewRefreshInterval),
application:set_env(emqx, crl_cache_http_timeout, NewHTTPTimeout),
?check_trace(
?wait_async_action(
emqx_crl_cache:refresh_config(),
#{?snk_kind := crl_cache_refresh_config},
_Timeout = 10_000),
fun(Res, Trace) ->
?assertMatch({ok, {ok, _}}, Res),
?assertMatch(
[#{ urls := SortedURLs
, refresh_interval := NewRefreshInterval
, http_timeout := NewHTTPTimeout
}],
?of_kind(crl_cache_refresh_config, Trace),
#{ expected => #{ urls => SortedURLs
, refresh_interval => NewRefreshInterval
, http_timeout => NewHTTPTimeout
}
}),
?assertEqual(SortedURLs, ?projection(url, ?of_kind(crl_cache_ensure_timer, Trace))),
ok
end),
ok.
%% If the CRL is not cached when the client tries to connect and the
%% CRL server is unreachable, the client will be denied connection.
t_not_cached_and_unreachable(Config) ->