Browse Source

feat(tls): ee only: TLS partial_chain and Keyusage

William Yang 1 year atrás
parent
commit
7c37bf9965

+ 12 - 2
apps/emqx/src/emqx_listeners.erl

@@ -75,6 +75,10 @@
 -define(TYPES_STRING, ["tcp", "ssl", "ws", "wss", "quic"]).
 -define(MARK_DEL, ?TOMBSTONE_CONFIG_CHANGE_REQ).
 
+-ifndef(EMQX_RELEASE_EDITION).
+-define(EMQX_RELEASE_EDITION, ce).
+-endif.
+
 -spec id_example() -> atom().
 id_example() -> 'tcp:default'.
 
@@ -974,15 +978,21 @@ quic_listener_optional_settings() ->
         stateless_operation_expiration_ms
     ].
 
+-if(?EMQX_RELEASE_EDITION == ee).
 inject_root_fun(#{ssl_options := SslOpts} = Opts) ->
-    Opts#{ssl_options := emqx_tls_lib:opt_partial_chain(SslOpts)};
+    Opts#{ssl_options := emqx_auth_ext_tls_lib:opt_partial_chain(SslOpts)}.
+-else.
 inject_root_fun(Opts) ->
     Opts.
+-endif.
 
+-if(?EMQX_RELEASE_EDITION == ee).
 inject_verify_fun(#{ssl_options := SslOpts} = Opts) ->
-    Opts#{ssl_options := emqx_tls_lib:opt_verify_fun(SslOpts)};
+    Opts#{ssl_options := emqx_auth_ext_tls_lib:opt_verify_fun(SslOpts)}.
+-else.
 inject_verify_fun(Opts) ->
     Opts.
+-endif.
 
 inject_sni_fun(ListenerId, Conf = #{ssl_options := #{ocsp := #{enable_ocsp_stapling := true}}}) ->
     emqx_ocsp_cache:inject_sni_fun(ListenerId, Conf);

+ 3 - 17
apps/emqx/src/emqx_schema.erl

@@ -191,6 +191,8 @@
 -define(DEFAULT_MULTIPLIER, 1.5).
 -define(DEFAULT_BACKOFF, 0.75).
 
+-define(INJECTING_CONFIGS, [?AUTH_EXT_SCHEMA_MODS]).
+
 namespace() -> emqx.
 
 tags() ->
@@ -2178,22 +2180,6 @@ common_ssl_opts_schema(Defaults, Type) ->
                     desc => ?DESC(common_ssl_opts_schema_verify)
                 }
             )},
-        {"partial_chain",
-            sc(
-                hoconsc:enum([true, false, two_cacerts_from_cacertfile, cacert_from_cacertfile]),
-                #{
-                    default => Df(partial_chain, false),
-                    desc => ?DESC(common_ssl_opts_schema_partial_chain)
-                }
-            )},
-        {"verify_peer_ext_key_usage",
-            sc(
-                string(),
-                #{
-                    required => false,
-                    desc => ?DESC(common_ssl_opts_verify_peer_ext_key_usage)
-                }
-            )},
         {"reuse_sessions",
             sc(
                 boolean(),
@@ -2263,7 +2249,7 @@ common_ssl_opts_schema(Defaults, Type) ->
                     desc => ?DESC(common_ssl_opts_schema_hibernate_after)
                 }
             )}
-    ].
+    ] ++ emqx_schema_hooks:injection_point('common_ssl_opts_schema').
 
 %% @doc Make schema for SSL listener options.
 -spec server_ssl_opts_schema(map(), boolean()) -> hocon_schema:field_schema().

+ 0 - 54
apps/emqx/src/emqx_tls_lib.erl

@@ -24,8 +24,6 @@
     default_ciphers/0,
     selected_ciphers/1,
     integral_ciphers/2,
-    opt_partial_chain/1,
-    opt_verify_fun/1,
     all_ciphers_set_cached/0
 ]).
 
@@ -688,55 +686,3 @@ ensure_ssl_file_key(SSL, RequiredKeyPaths) ->
         [] -> ok;
         Miss -> {error, #{reason => ssl_file_option_not_found, which_options => Miss}}
     end.
-
-%% @doc enable TLS partial_chain validation if set.
--spec opt_partial_chain(SslOpts :: map()) -> NewSslOpts :: map().
-opt_partial_chain(#{partial_chain := false} = SslOpts) ->
-    maps:remove(partial_chain, SslOpts);
-opt_partial_chain(#{partial_chain := true} = SslOpts) ->
-    SslOpts#{partial_chain := rootfun_trusted_ca_from_cacertfile(1, SslOpts)};
-opt_partial_chain(#{partial_chain := cacert_from_cacertfile} = SslOpts) ->
-    SslOpts#{partial_chain := rootfun_trusted_ca_from_cacertfile(1, SslOpts)};
-opt_partial_chain(#{partial_chain := two_cacerts_from_cacertfile} = SslOpts) ->
-    SslOpts#{partial_chain := rootfun_trusted_ca_from_cacertfile(2, SslOpts)};
-opt_partial_chain(SslOpts) ->
-    SslOpts.
-
-%% @doc make verify_fun if set.
--spec opt_verify_fun(SslOpts :: map()) -> NewSslOpts :: map().
-opt_verify_fun(#{verify_peer_ext_key_usage := V} = SslOpts) when V =/= undefined ->
-    SslOpts#{verify_fun => emqx_const_v2:make_tls_verify_fun(verify_cert_extKeyUsage, V)};
-opt_verify_fun(SslOpts) ->
-    SslOpts.
-
-%% @doc Helper, make TLS root_fun
-rootfun_trusted_ca_from_cacertfile(NumOfCerts, #{cacertfile := Cacertfile}) ->
-    case file:read_file(Cacertfile) of
-        {ok, PemBin} ->
-            try
-                do_rootfun_trusted_ca_from_cacertfile(NumOfCerts, PemBin)
-            catch
-                _Error:_Info:ST ->
-                    %% The cacertfile will be checked by OTP SSL as well and OTP choice to be silent on this.
-                    %% We are touching security sutffs, don't leak extra info..
-                    ?SLOG(error, #{
-                        msg => "trusted_cacert_not_found_in_cacertfile", stacktrace => ST
-                    }),
-                    throw({error, ?FUNCTION_NAME})
-            end;
-        {error, Reason} ->
-            throw({error, {read_cacertfile_error, Cacertfile, Reason}})
-    end;
-rootfun_trusted_ca_from_cacertfile(_NumOfCerts, _SslOpts) ->
-    throw({error, cacertfile_unset}).
-
-do_rootfun_trusted_ca_from_cacertfile(NumOfCerts, PemBin) ->
-    %% The last one or two should be the top parent in the chain if it is a chain
-    Certs = public_key:pem_decode(PemBin),
-    Pos = length(Certs) - NumOfCerts + 1,
-    Trusted = [
-        CADer
-     || {'Certificate', CADer, _} <-
-            lists:sublist(public_key:pem_decode(PemBin), Pos, NumOfCerts)
-    ],
-    emqx_const_v2:make_tls_root_fun(cacert_from_cacertfile, Trusted).

+ 20 - 0
apps/emqx_auth_ext/.gitignore

@@ -0,0 +1,20 @@
+.rebar3
+_build
+_checkouts
+_vendor
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+logs
+.idea
+*.iml
+rebar3.crashdump
+*~

+ 94 - 0
apps/emqx_auth_ext/BSL.txt

@@ -0,0 +1,94 @@
+Business Source License 1.1
+
+Licensor:             Hangzhou EMQ Technologies Co., Ltd.
+Licensed Work:        EMQX Enterprise Edition
+                      The Licensed Work is (c) 2023
+                      Hangzhou EMQ Technologies Co., Ltd.
+Additional Use Grant: Students and educators are granted right to copy,
+                      modify, and create derivative work for research
+                      or education.
+Change Date:          2028-01-26
+Change License:       Apache License, Version 2.0
+
+For information about alternative licensing arrangements for the Software,
+please contact Licensor: https://www.emqx.com/en/contact
+
+Notice
+
+The Business Source License (this document, or the “License”) is not an Open
+Source license. However, the Licensed Work will eventually be made available
+under an Open Source License, as stated in this License.
+
+License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
+“Business Source License” is a trademark of MariaDB Corporation Ab.
+
+-----------------------------------------------------------------------------
+
+Business Source License 1.1
+
+Terms
+
+The Licensor hereby grants you the right to copy, modify, create derivative
+works, redistribute, and make non-production use of the Licensed Work. The
+Licensor may make an Additional Use Grant, above, permitting limited
+production use.
+
+Effective on the Change Date, or the fourth anniversary of the first publicly
+available distribution of a specific version of the Licensed Work under this
+License, whichever comes first, the Licensor hereby grants you rights under
+the terms of the Change License, and the rights granted in the paragraph
+above terminate.
+
+If your use of the Licensed Work does not comply with the requirements
+currently in effect as described in this License, you must purchase a
+commercial license from the Licensor, its affiliated entities, or authorized
+resellers, or you must refrain from using the Licensed Work.
+
+All copies of the original and modified Licensed Work, and derivative works
+of the Licensed Work, are subject to this License. This License applies
+separately for each version of the Licensed Work and the Change Date may vary
+for each version of the Licensed Work released by Licensor.
+
+You must conspicuously display this License on each original or modified copy
+of the Licensed Work. If you receive the Licensed Work in original or
+modified form from a third party, the terms and conditions set forth in this
+License apply to your use of that work.
+
+Any use of the Licensed Work in violation of this License will automatically
+terminate your rights under this License for the current and all other
+versions of the Licensed Work.
+
+This License does not grant you any right in any trademark or logo of
+Licensor or its affiliates (provided that you may use a trademark or logo of
+Licensor as expressly required by this License).
+
+TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
+AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
+EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
+TITLE.
+
+MariaDB hereby grants you permission to use this License’s text to license
+your works, and to refer to it using the trademark “Business Source License”,
+as long as you comply with the Covenants of Licensor below.
+
+Covenants of Licensor
+
+In consideration of the right to use this License’s text and the “Business
+Source License” name and trademark, Licensor covenants to MariaDB, and to all
+other recipients of the licensed work to be provided by Licensor:
+
+1. To specify as the Change License the GPL Version 2.0 or any later version,
+   or a license that is compatible with GPL Version 2.0 or a later version,
+   where “compatible” means that software provided under the Change License can
+   be included in a program with software provided under GPL Version 2.0 or a
+   later version. Licensor may specify additional Change Licenses without
+   limitation.
+
+2. To either: (a) specify an additional grant of rights to use that does not
+   impose any additional restriction on the right granted in this License, as
+   the Additional Use Grant; or (b) insert the text “None”.
+
+3. To specify a Change Date.
+
+4. Not to modify this License in any other way.

+ 7 - 0
apps/emqx_auth_ext/README.md

@@ -0,0 +1,7 @@
+# EMQX Extended Auth Library 
+
+Library that extends EMQX authentication capbility for enterprise.
+
+# License
+
+EMQ Business Source License 1.1, refer to [LICENSE](BSL.txt).

+ 2 - 0
apps/emqx_auth_ext/rebar.config

@@ -0,0 +1,2 @@
+{erl_opts, [debug_info]}.
+{deps, [{emqx, {path, "../emqx"}}]}.

+ 21 - 0
apps/emqx_auth_ext/src/emqx_auth_ext.app.src

@@ -0,0 +1,21 @@
+{application, emqx_auth_ext, [
+    {description, "EMQX Extended Auth Library"},
+    {vsn, "0.1.0"},
+    {registered, []},
+    {applications, [
+        kernel,
+        stdlib,
+        ssl,
+        emqx
+    ]},
+    {env, []},
+    {modules, [
+        emqx_auth_ext,
+        emqx_auth_ext_schema,
+        emqx_auth_ext_tls_lib,
+        emqx_auth_ext_tls_const_v1
+    ]},
+
+    {licenses, ["Apache-2.0"]},
+    {links, []}
+]}.

+ 6 - 0
apps/emqx_auth_ext/src/emqx_auth_ext.erl

@@ -0,0 +1,6 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%--------------------------------------------------------------------
+-module(emqx_auth_ext).
+
+-export([]).

+ 42 - 0
apps/emqx_auth_ext/src/emqx_auth_ext_schema.erl

@@ -0,0 +1,42 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%--------------------------------------------------------------------
+
+-module(emqx_auth_ext_schema).
+-behaviour(emqx_schema_hooks).
+
+-include_lib("typerefl/include/types.hrl").
+-include_lib("hocon/include/hoconsc.hrl").
+
+%%------------------------------------------------------------------------------
+%% emqx_schema_hooks callbacks
+%%------------------------------------------------------------------------------
+-export([injected_fields/0]).
+
+-spec injected_fields() -> #{emqx_schema_hooks:hookpoint() => [hocon_schema:field()]}.
+injected_fields() ->
+    #{
+        'common_ssl_opts_schema' => fields(auth_ext)
+    }.
+
+fields(auth_ext) ->
+    [
+        {"partial_chain",
+            sc(
+                hoconsc:enum([true, false, two_cacerts_from_cacertfile, cacert_from_cacertfile]),
+                #{
+                    default => false,
+                    desc => ?DESC(common_ssl_opts_schema_partial_chain)
+                }
+            )},
+        {"verify_peer_ext_key_usage",
+            sc(
+                string(),
+                #{
+                    required => false,
+                    desc => ?DESC(common_ssl_opts_verify_peer_ext_key_usage)
+                }
+            )}
+    ].
+
+sc(Type, Meta) -> hoconsc:mk(Type, Meta).

+ 1 - 15
apps/emqx/src/emqx_const_v2.erl

@@ -1,22 +1,8 @@
 %%--------------------------------------------------------------------
 %% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%%     http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% @doc Never update this module, create a v3 instead.
 %%--------------------------------------------------------------------
 
--module(emqx_const_v2).
+-module(emqx_auth_ext_tls_const_v1).
 -elvis([{elvis_style, atom_naming_convention, #{regex => "^([a-z][a-z0-9A-Z]*_?)*(_SUITE)?$"}}]).
 
 -export([

+ 66 - 0
apps/emqx_auth_ext/src/emqx_auth_ext_tls_lib.erl

@@ -0,0 +1,66 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%--------------------------------------------------------------------
+
+-module(emqx_auth_ext_tls_lib).
+-elvis([{elvis_style, atom_naming_convention, #{regex => "^([a-z][a-z0-9A-Z]*_?)*(_SUITE)?$"}}]).
+
+-export([
+    opt_partial_chain/1,
+    opt_verify_fun/1
+]).
+
+-include_lib("emqx/include/logger.hrl").
+
+-define(CONST_MOD_V1, emqx_auth_ext_tls_const_v1).
+%% @doc enable TLS partial_chain validation if set.
+-spec opt_partial_chain(SslOpts :: map()) -> NewSslOpts :: map().
+opt_partial_chain(#{partial_chain := false} = SslOpts) ->
+    maps:remove(partial_chain, SslOpts);
+opt_partial_chain(#{partial_chain := true} = SslOpts) ->
+    SslOpts#{partial_chain := rootfun_trusted_ca_from_cacertfile(1, SslOpts)};
+opt_partial_chain(#{partial_chain := cacert_from_cacertfile} = SslOpts) ->
+    SslOpts#{partial_chain := rootfun_trusted_ca_from_cacertfile(1, SslOpts)};
+opt_partial_chain(#{partial_chain := two_cacerts_from_cacertfile} = SslOpts) ->
+    SslOpts#{partial_chain := rootfun_trusted_ca_from_cacertfile(2, SslOpts)};
+opt_partial_chain(SslOpts) ->
+    SslOpts.
+
+%% @doc make verify_fun if set.
+-spec opt_verify_fun(SslOpts :: map()) -> NewSslOpts :: map().
+opt_verify_fun(#{verify_peer_ext_key_usage := V} = SslOpts) when V =/= undefined ->
+    SslOpts#{verify_fun => ?CONST_MOD_V1:make_tls_verify_fun(verify_cert_extKeyUsage, V)};
+opt_verify_fun(SslOpts) ->
+    SslOpts.
+
+%% @doc Helper, make TLS root_fun
+rootfun_trusted_ca_from_cacertfile(NumOfCerts, #{cacertfile := Cacertfile}) ->
+    case file:read_file(emqx_schema:naive_env_interpolation(Cacertfile)) of
+        {ok, PemBin} ->
+            try
+                do_rootfun_trusted_ca_from_cacertfile(NumOfCerts, PemBin)
+            catch
+                _Error:_Info:ST ->
+                    %% The cacertfile will be checked by OTP SSL as well and OTP choice to be silent on this.
+                    %% We are touching security sutffs, don't leak extra info..
+                    ?SLOG(error, #{
+                        msg => "trusted_cacert_not_found_in_cacertfile", stacktrace => ST
+                    }),
+                    throw({error, ?FUNCTION_NAME})
+            end;
+        {error, Reason} ->
+            throw({error, {read_cacertfile_error, Cacertfile, Reason}})
+    end;
+rootfun_trusted_ca_from_cacertfile(_NumOfCerts, _SslOpts) ->
+    throw({error, cacertfile_unset}).
+
+do_rootfun_trusted_ca_from_cacertfile(NumOfCerts, PemBin) ->
+    %% The last one or two should be the top parent in the chain if it is a chain
+    Certs = public_key:pem_decode(PemBin),
+    Pos = length(Certs) - NumOfCerts + 1,
+    Trusted = [
+        CADer
+     || {'Certificate', CADer, _} <-
+            lists:sublist(public_key:pem_decode(PemBin), Pos, NumOfCerts)
+    ],
+    ?CONST_MOD_V1:make_tls_root_fun(cacert_from_cacertfile, Trusted).

+ 13 - 23
apps/emqx/test/emqx_listener_tls_verify_chain_SUITE.erl

@@ -1,19 +1,8 @@
 %%--------------------------------------------------------------------
 %% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%%     http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
 %%--------------------------------------------------------------------
--module(emqx_listener_tls_verify_chain_SUITE).
+
+-module(emqx_auth_ext_listener_tls_verify_chain_SUITE).
 
 -compile(export_all).
 -compile(nowarn_export_all).
@@ -22,12 +11,13 @@
 -include_lib("common_test/include/ct.hrl").
 
 -import(
-    emqx_test_tls_certs_helper,
+    emqx_auth_ext_test_tls_certs_helper,
     [
         emqx_start_listener/4,
         fail_when_ssl_error/1,
         fail_when_no_ssl_alert/2,
-        generate_tls_certs/1
+        generate_tls_certs/1,
+        select_free_port/1
     ]
 ).
 
@@ -42,7 +32,7 @@ end_per_suite(_Config) ->
     application:stop(esockd).
 
 t_conn_fail_with_intermediate_ca_cert(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options, [
@@ -68,7 +58,7 @@ t_conn_fail_with_intermediate_ca_cert(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_fail_with_other_intermediate_ca_cert(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options, [
@@ -94,7 +84,7 @@ t_conn_fail_with_other_intermediate_ca_cert(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_success_with_server_client_composed_complete_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Server has root ca cert
     Options = [
@@ -121,7 +111,7 @@ t_conn_success_with_server_client_composed_complete_chain(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_success_with_other_signed_client_composed_complete_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Server has root ca cert
     Options = [
@@ -148,7 +138,7 @@ t_conn_success_with_other_signed_client_composed_complete_chain(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_success_with_renewed_intermediate_root_bundle(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Server has root ca cert
     Options = [
@@ -174,7 +164,7 @@ t_conn_success_with_renewed_intermediate_root_bundle(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_success_with_client_complete_cert_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options, [
@@ -199,7 +189,7 @@ t_conn_success_with_client_complete_cert_chain(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_fail_with_server_partial_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% imcomplete at server side
     Options = [
@@ -225,7 +215,7 @@ t_conn_fail_with_server_partial_chain(Config) ->
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_fail_without_root_cacert(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options, [

+ 15 - 25
apps/emqx/test/emqx_listener_tls_verify_keyusage_SUITE.erl

@@ -1,19 +1,8 @@
 %%--------------------------------------------------------------------
 %% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%%     http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
 %%--------------------------------------------------------------------
--module(emqx_listener_tls_verify_keyusage_SUITE).
+
+-module(emqx_auth_ext_listener_tls_verify_keyusage_SUITE).
 
 -compile(export_all).
 -compile(nowarn_export_all).
@@ -22,13 +11,14 @@
 -include_lib("common_test/include/ct.hrl").
 
 -import(
-    emqx_test_tls_certs_helper,
+    emqx_auth_ext_test_tls_certs_helper,
     [
         fail_when_ssl_error/1,
         fail_when_no_ssl_alert/2,
         generate_tls_certs/1,
         gen_host_cert/4,
-        emqx_start_listener/4
+        emqx_start_listener/4,
+        select_free_port/1
     ]
 ).
 
@@ -66,7 +56,7 @@ end_per_group(_, Config) ->
     Config.
 
 t_conn_success_verify_peer_ext_key_usage_unset(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Given listener keyusage unset
     Options = [{ssl_options, ?config(ssl_config, Config)}],
@@ -87,7 +77,7 @@ t_conn_success_verify_peer_ext_key_usage_unset(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_success_verify_peer_ext_key_usage_undefined(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Give listener keyusage is set to undefined
     Options = [
@@ -113,7 +103,7 @@ t_conn_success_verify_peer_ext_key_usage_undefined(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_success_verify_peer_ext_key_usage_matched_predefined(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Give listener keyusage is set to clientAuth
     Options = [
@@ -141,7 +131,7 @@ t_conn_success_verify_peer_ext_key_usage_matched_predefined(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_success_verify_peer_ext_key_usage_matched_raw_oid(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Give listener keyusage is set to raw OID
 
@@ -170,7 +160,7 @@ t_conn_success_verify_peer_ext_key_usage_matched_raw_oid(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_success_verify_peer_ext_key_usage_matched_ordered_list(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
 
     %% Give listener keyusage is clientAuth,serverAuth
@@ -198,7 +188,7 @@ t_conn_success_verify_peer_ext_key_usage_matched_ordered_list(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_success_verify_peer_ext_key_usage_matched_unordered_list(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Give listener keyusage is clientAuth,serverAuth
     Options = [
@@ -225,7 +215,7 @@ t_conn_success_verify_peer_ext_key_usage_matched_unordered_list(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_fail_verify_peer_ext_key_usage_unmatched_raw_oid(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Give listener keyusage is using OID
     Options = [
@@ -254,7 +244,7 @@ t_conn_fail_verify_peer_ext_key_usage_unmatched_raw_oid(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_fail_verify_peer_ext_key_usage_empty_str(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options, [
@@ -280,7 +270,7 @@ t_conn_fail_verify_peer_ext_key_usage_empty_str(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_fail_client_keyusage_unmatch(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
 
     %% Give listener keyusage is clientAuth
@@ -308,7 +298,7 @@ t_conn_fail_client_keyusage_unmatch(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_fail_client_keyusage_incomplete(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% Give listener keyusage is codeSigning,clientAuth
     Options = [

+ 29 - 28
apps/emqx/test/emqx_listener_tls_verify_partial_chain_SUITE.erl

@@ -13,7 +13,7 @@
 %% See the License for the specific language governing permissions and
 %% limitations under the License.
 %%--------------------------------------------------------------------
--module(emqx_listener_tls_verify_partial_chain_SUITE).
+-module(emqx_auth_ext_listener_tls_verify_partial_chain_SUITE).
 
 -compile(export_all).
 -compile(nowarn_export_all).
@@ -22,12 +22,13 @@
 -include_lib("common_test/include/ct.hrl").
 
 -import(
-    emqx_test_tls_certs_helper,
+    emqx_auth_ext_test_tls_certs_helper,
     [
         emqx_start_listener/4,
         fail_when_ssl_error/1,
         fail_when_no_ssl_alert/2,
-        generate_tls_certs/1
+        generate_tls_certs/1,
+        select_free_port/1
     ]
 ).
 
@@ -42,7 +43,7 @@ end_per_suite(_Config) ->
     application:stop(esockd).
 
 t_conn_success_with_server_intermediate_cacert_and_client_cert(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -68,7 +69,7 @@ t_conn_success_with_server_intermediate_cacert_and_client_cert(Config) ->
     ssl:close(Socket).
 
 t_conn_success_with_intermediate_cacert_bundle(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -94,7 +95,7 @@ t_conn_success_with_intermediate_cacert_bundle(Config) ->
     ssl:close(Socket).
 
 t_conn_success_with_renewed_intermediate_cacert(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -120,7 +121,7 @@ t_conn_success_with_renewed_intermediate_cacert(Config) ->
     ssl:close(Socket).
 
 t_conn_fail_with_renewed_intermediate_cacert_and_client_using_old_complete_bundle(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -145,7 +146,7 @@ t_conn_fail_with_renewed_intermediate_cacert_and_client_using_old_complete_bundl
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_fail_with_renewed_intermediate_cacert_and_client_using_old_bundle(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -172,7 +173,7 @@ t_conn_fail_with_renewed_intermediate_cacert_and_client_using_old_bundle(Config)
 t_conn_success_with_old_and_renewed_intermediate_cacert_and_client_provides_renewed_client_cert(
     Config
 ) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -202,7 +203,7 @@ t_conn_success_with_old_and_renewed_intermediate_cacert_and_client_provides_rene
 t_conn_success_with_new_intermediate_cacert_and_client_provides_renewed_client_cert_signed_by_old_intermediate(
     Config
 ) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -229,7 +230,7 @@ t_conn_success_with_new_intermediate_cacert_and_client_provides_renewed_client_c
 
 %% @doc server should build a partial_chain with old version of ca cert.
 t_conn_success_with_old_and_renewed_intermediate_cacert_and_client_provides_client_cert(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -257,7 +258,7 @@ t_conn_success_with_old_and_renewed_intermediate_cacert_and_client_provides_clie
 
 %% @doc verify when config does not allow two versions of certs from same trusted CA.
 t_conn_fail_with_renewed_and_old_intermediate_cacert_and_client_using_old_bundle(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -285,7 +286,7 @@ t_conn_fail_with_renewed_and_old_intermediate_cacert_and_client_using_old_bundle
 t_001_conn_success_with_old_and_renewed_intermediate_cacert_bundle_and_client_using_old_bundle(
     Config
 ) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -318,7 +319,7 @@ t_001_conn_success_with_old_and_renewed_intermediate_cacert_bundle_and_client_us
 %%  Oldintermediate2Cert (trusted CA cert).
 %% @end
 t_conn_fail_with_old_and_renewed_intermediate_cacert_bundle_and_client_using_all_CAcerts(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -344,7 +345,7 @@ t_conn_fail_with_old_and_renewed_intermediate_cacert_bundle_and_client_using_all
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_fail_with_renewed_intermediate_cacert_other_client(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -369,7 +370,7 @@ t_conn_fail_with_renewed_intermediate_cacert_other_client(Config) ->
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_fail_with_intermediate_cacert_bundle_but_incorrect_order(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -394,7 +395,7 @@ t_conn_fail_with_intermediate_cacert_bundle_but_incorrect_order(Config) ->
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_fail_when_singed_by_other_intermediate_ca(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -419,7 +420,7 @@ t_conn_fail_when_singed_by_other_intermediate_ca(Config) ->
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_success_with_complete_chain_that_server_root_cacert_and_client_complete_cert_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -445,7 +446,7 @@ t_conn_success_with_complete_chain_that_server_root_cacert_and_client_complete_c
     ok = ssl:close(Socket).
 
 t_conn_fail_with_other_client_complete_cert_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -470,7 +471,7 @@ t_conn_fail_with_other_client_complete_cert_chain(Config) ->
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_fail_with_server_intermediate_and_other_client_complete_cert_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -496,7 +497,7 @@ t_conn_fail_with_server_intermediate_and_other_client_complete_cert_chain(Config
     ok = ssl:close(Socket).
 
 t_conn_success_with_server_intermediate_cacert_and_client_complete_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -522,7 +523,7 @@ t_conn_success_with_server_intermediate_cacert_and_client_complete_chain(Config)
     ok = ssl:close(Socket).
 
 t_conn_fail_with_server_intermediate_chain_and_client_other_incomplete_cert_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -547,7 +548,7 @@ t_conn_fail_with_server_intermediate_chain_and_client_other_incomplete_cert_chai
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_fail_with_server_intermediate_and_other_client_root_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -572,7 +573,7 @@ t_conn_fail_with_server_intermediate_and_other_client_root_chain(Config) ->
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_success_with_server_intermediate_and_client_root_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -599,7 +600,7 @@ t_conn_success_with_server_intermediate_and_client_root_chain(Config) ->
 
 %% @doc once rootCA cert present in cacertfile, sibling CA signed Client cert could connect.
 t_conn_success_with_server_all_CA_bundle_and_client_root_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -625,7 +626,7 @@ t_conn_success_with_server_all_CA_bundle_and_client_root_chain(Config) ->
     ok = ssl:close(Socket).
 
 t_conn_fail_with_server_two_IA_bundle_and_client_root_chain(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -650,7 +651,7 @@ t_conn_fail_with_server_two_IA_bundle_and_client_root_chain(Config) ->
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_conn_fail_with_server_partial_chain_false_intermediate_cacert_and_client_cert(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     Options = [
         {ssl_options,
@@ -676,7 +677,7 @@ t_conn_fail_with_server_partial_chain_false_intermediate_cacert_and_client_cert(
     fail_when_no_ssl_alert(Res, unknown_ca).
 
 t_error_handling_invalid_cacertfile(Config) ->
-    Port = emqx_test_tls_certs_helper:select_free_port(ssl),
+    Port = select_free_port(ssl),
     DataDir = ?config(data_dir, Config),
     %% trigger error
     Options = [

+ 1 - 13
apps/emqx/test/emqx_test_tls_certs_helper.erl

@@ -1,20 +1,8 @@
 %%--------------------------------------------------------------------
 %% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%%     http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
 %%--------------------------------------------------------------------
 
--module(emqx_test_tls_certs_helper).
+-module(emqx_auth_ext_test_tls_certs_helper).
 -export([
     gen_ca/2,
     gen_host_cert/3,

+ 1 - 1
apps/emqx_conf/src/emqx_conf.app.src

@@ -1,6 +1,6 @@
 {application, emqx_conf, [
     {description, "EMQX configuration management"},
-    {vsn, "0.2.0"},
+    {vsn, "0.2.1"},
     {registered, []},
     {mod, {emqx_conf_app, []}},
     {applications, [kernel, stdlib]},

+ 10 - 0
apps/emqx_conf/src/emqx_conf_schema.erl

@@ -70,9 +70,19 @@
     emqx_otel_schema,
     emqx_mgmt_api_key_schema
 ]).
+
+-define(AUTH_EXT_SCHEMA_MODS, [emqx_auth_ext_schema]).
+
+-if(defined(EMQX_RELEASE_EDITION) andalso ?EMQX_RELEASE_EDITION == ee).
+-define(OTHER_INJECTING_CONFIGS, ?AUTH_EXT_SCHEMA_MODS).
+-else.
+-define(OTHER_INJECTING_CONFIGS, []).
+-endif.
+
 -define(INJECTING_CONFIGS, [
     {emqx_authn_schema, ?AUTHN_PROVIDER_SCHEMA_MODS},
     {emqx_authz_schema, ?AUTHZ_SOURCE_SCHEMA_MODS}
+    | ?OTHER_INJECTING_CONFIGS
 ]).
 
 %% 1 million default ports counter

+ 1 - 1
apps/emqx_gateway/src/emqx_gateway.app.src

@@ -1,7 +1,7 @@
 %% -*- mode: erlang -*-
 {application, emqx_gateway, [
     {description, "The Gateway management application"},
-    {vsn, "0.1.32"},
+    {vsn, "0.1.33"},
     {registered, []},
     {mod, {emqx_gateway_app, []}},
     {applications, [kernel, stdlib, emqx, emqx_auth, emqx_ctl]},

+ 12 - 2
apps/emqx_gateway/src/emqx_gateway_utils.erl

@@ -588,11 +588,21 @@ ssl_server_opts(SSLOpts, ssl_options) ->
 ssl_server_opts(SSLOpts, dtls_options) ->
     emqx_tls_lib:to_server_opts(dtls, SSLOpts).
 
+-if(defined(EMQX_RELEASE_EDITION) andalso ?EMQX_RELEASE_EDITION == ee).
 ssl_partial_chain(SSLOpts, _Options) ->
-    emqx_tls_lib:opt_partial_chain(SSLOpts).
+    emqx_auth_ext_tls_lib:opt_partial_chain(SSLOpts).
+-else.
+ssl_partial_chain(SSLOpts, _) ->
+    SSLOpts.
+-endif.
 
+-if(defined(EMQX_RELEASE_EDITION) andalso ?EMQX_RELEASE_EDITION == ee).
 ssl_verify_fun(SSLOpts, _Options) ->
-    emqx_tls_lib:opt_verify_fun(SSLOpts).
+    emqx_auth_ext_tls_lib:opt_verify_fun(SSLOpts).
+-else.
+ssl_verify_fun(SSLOpts, _) ->
+    SSLOpts.
+-endif.
 
 ranch_opts(Type, ListenOn, Opts) ->
     NumAcceptors = maps:get(acceptors, Opts, 4),

+ 2 - 1
apps/emqx_machine/priv/reboot_lists.eterm

@@ -129,7 +129,8 @@
             emqx_gateway_ocpp,
             emqx_gateway_jt808,
             emqx_bridge_syskeeper,
-            emqx_bridge_confluent
+            emqx_bridge_confluent,
+            emqx_auth_ext
         ],
     %% must always be of type `load'
     ce_business_apps =>

+ 1 - 1
apps/emqx_machine/src/emqx_machine.app.src

@@ -3,7 +3,7 @@
     {id, "emqx_machine"},
     {description, "The EMQX Machine"},
     % strict semver, bump manually!
-    {vsn, "0.3.0"},
+    {vsn, "0.3.1"},
     {modules, []},
     {registered, []},
     {applications, [kernel, stdlib, emqx_ctl]},

changes/ce/feat-11721.en.md → changes/ee/feat-13128.en.md


+ 2 - 1
mix.exs

@@ -200,7 +200,8 @@ defmodule EMQXUmbrella.MixProject do
       :emqx_gateway_gbt32960,
       :emqx_gateway_ocpp,
       :emqx_gateway_jt808,
-      :emqx_bridge_syskeeper
+      :emqx_bridge_syskeeper,
+      :emqx_auth_ext
     ])
   end
 

+ 1 - 0
rebar.config.erl

@@ -119,6 +119,7 @@ is_community_umbrella_app("apps/emqx_bridge_syskeeper") -> false;
 is_community_umbrella_app("apps/emqx_schema_validation") -> false;
 is_community_umbrella_app("apps/emqx_eviction_agent") -> false;
 is_community_umbrella_app("apps/emqx_node_rebalance") -> false;
+is_community_umbrella_app("apps/emqx_auth_ext") -> false;
 is_community_umbrella_app(_) -> true.
 
 %% BUILD_WITHOUT_JQ