diff --git a/.github/workflows/build_packages.yaml b/.github/workflows/build_packages.yaml index 1165e130a..4e43f663b 100644 --- a/.github/workflows/build_packages.yaml +++ b/.github/workflows/build_packages.yaml @@ -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 diff --git a/.github/workflows/build_slim_packages.yaml b/.github/workflows/build_slim_packages.yaml index bd529a1b1..5a2b4de6c 100644 --- a/.github/workflows/build_slim_packages.yaml +++ b/.github/workflows/build_slim_packages.yaml @@ -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: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a7f3cbe6c..14df0776f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -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 diff --git a/.github/workflows/run_test_cases.yaml b/.github/workflows/run_test_cases.yaml index c9ddfcec2..910c3a77c 100644 --- a/.github/workflows/run_test_cases.yaml +++ b/.github/workflows/run_test_cases.yaml @@ -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: diff --git a/apps/emqx_management/src/emqx_mgmt_http.erl b/apps/emqx_management/src/emqx_mgmt_http.erl index aeceabe32..0d20e5230 100644 --- a/apps/emqx_management/src/emqx_mgmt_http.erl +++ b/apps/emqx_management/src/emqx_mgmt_http.erl @@ -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; diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine.erl b/apps/emqx_rule_engine/src/emqx_rule_engine.erl index 0f62a2ac7..c9f32b962 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_engine.erl @@ -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(<>) -> + true; +is_prober(_ResId) -> + false. diff --git a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl index 105dcc334..2e0015998 100644 --- a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl +++ b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl @@ -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. %%------------------------------------------------------------------------------ diff --git a/build b/build index fa4b7017e..23e5cef49 100755 --- a/build +++ b/build @@ -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() { diff --git a/changes/v4.3.22-en.md b/changes/v4.3.22-en.md index d4116990a..079720c16 100644 --- a/changes/v4.3.22-en.md +++ b/changes/v4.3.22-en.md @@ -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). diff --git a/changes/v4.3.22-zh.md b/changes/v4.3.22-zh.md index 2e02538ca..549089bad 100644 --- a/changes/v4.3.22-zh.md +++ b/changes/v4.3.22-zh.md @@ -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)。 diff --git a/etc/emqx.conf b/etc/emqx.conf index c9bcb7b59..bcd2a67c5 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -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 diff --git a/include/emqx_release.hrl b/include/emqx_release.hrl index 9ee7c67c7..12265aa81 100644 --- a/include/emqx_release.hrl +++ b/include/emqx_release.hrl @@ -29,7 +29,7 @@ -ifndef(EMQX_ENTERPRISE). --define(EMQX_RELEASE, {opensource, "4.4.11-alpha.2"}). +-define(EMQX_RELEASE, {opensource, "4.4.11"}). -else. diff --git a/lib-ce/emqx_dashboard/src/emqx_dashboard.erl b/lib-ce/emqx_dashboard/src/emqx_dashboard.erl index 45b87b38a..dbd441239 100644 --- a/lib-ce/emqx_dashboard/src/emqx_dashboard.erl +++ b/lib-ce/emqx_dashboard/src/emqx_dashboard.erl @@ -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; diff --git a/lib-ce/emqx_modules/test/emqx_mod_delayed_SUITE.erl b/lib-ce/emqx_modules/test/emqx_mod_delayed_SUITE.erl index cf0399210..e2341dddc 100644 --- a/lib-ce/emqx_modules/test/emqx_mod_delayed_SUITE.erl +++ b/lib-ce/emqx_modules/test/emqx_mod_delayed_SUITE.erl @@ -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) -> diff --git a/priv/emqx.schema b/priv/emqx.schema index e2daf197f..3b9d13c9b 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -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} diff --git a/rebar.config b/rebar.config index ae93dbc04..5462565ac 100644 --- a/rebar.config +++ b/rebar.config @@ -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"}}} ]}. diff --git a/scripts/macos-sign-binaries.sh b/scripts/macos-sign-binaries.sh index 7be40f621..384744b2a 100755 --- a/scripts/macos-sign-binaries.sh +++ b/scripts/macos-sign-binaries.sh @@ -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 diff --git a/src/emqx_app.erl b/src/emqx_app.erl index 5a55a1abd..012386144 100644 --- a/src/emqx_app.erl +++ b/src/emqx_app.erl @@ -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 diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index 1d726fa89..4cc30c1ad 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -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. %%-------------------------------------------------------------------- diff --git a/src/emqx_crl_cache.erl b/src/emqx_crl_cache.erl index bca18e7a4..4fabd3c1b 100644 --- a/src/emqx_crl_cache.erl +++ b/src/emqx_crl_cache.erl @@ -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 + }. diff --git a/src/emqx_listeners.erl b/src/emqx_listeners.erl index 68a57142a..c6e70862d 100644 --- a/src/emqx_listeners.erl +++ b/src/emqx_listeners.erl @@ -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 -> diff --git a/test/emqx_crl_cache_SUITE.erl b/test/emqx_crl_cache_SUITE.erl index f9090d85f..db1fc2ff2 100644 --- a/test/emqx_crl_cache_SUITE.erl +++ b/test/emqx_crl_cache_SUITE.erl @@ -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) ->