diff --git a/.ci/docker-compose-file/docker-compose.yaml b/.ci/docker-compose-file/docker-compose.yaml index c88613d12..8c3fb290f 100644 --- a/.ci/docker-compose-file/docker-compose.yaml +++ b/.ci/docker-compose-file/docker-compose.yaml @@ -2,6 +2,7 @@ version: '3.9' services: erlang: + hostname: erlang.emqx.net container_name: erlang image: ${DOCKER_CT_RUNNER_IMAGE:-ghcr.io/emqx/emqx-builder/5.3-9:1.15.7-26.2.5-3-ubuntu22.04} env_file: diff --git a/apps/emqx/src/emqx_channel.erl b/apps/emqx/src/emqx_channel.erl index 1e68d74d6..b2bdf530d 100644 --- a/apps/emqx/src/emqx_channel.erl +++ b/apps/emqx/src/emqx_channel.erl @@ -394,6 +394,16 @@ handle_in( case ConnState of connecting -> process_connect(NProperties, NChannel); + reauthenticating -> + {ok, Auth, NChannel1} = + handle_out( + auth, + {?RC_SUCCESS, NProperties}, + NChannel#channel{conn_state = connected} + ), + {ok, Replies, NChannel2} = + process_connect(NProperties, NChannel1), + {ok, [?REPLY_OUTGOING(Auth) | Replies], NChannel2}; _ -> handle_out( auth, diff --git a/apps/emqx_auth_gssapi/BSL.txt b/apps/emqx_auth_gssapi/BSL.txt new file mode 100644 index 000000000..f0cd31c6f --- /dev/null +++ b/apps/emqx_auth_gssapi/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. diff --git a/apps/emqx_auth_gssapi/include/emqx_auth_gssapi.hrl b/apps/emqx_auth_gssapi/include/emqx_auth_gssapi.hrl new file mode 100644 index 000000000..3022e4abb --- /dev/null +++ b/apps/emqx_auth_gssapi/include/emqx_auth_gssapi.hrl @@ -0,0 +1,16 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_GSSAPI_HRL). +-define(EMQX_AUTH_GSSAPI_HRL, true). + +-define(AUTHN_MECHANISM_GSSAPI, gssapi). +-define(AUTHN_MECHANISM_GSSAPI_BIN, <<"gssapi">>). + +-define(AUTHN_BACKEND, gssapi). +-define(AUTHN_BACKEND_BIN, <<"gssapi">>). + +-define(AUTHN_TYPE_GSSAPI, {?AUTHN_MECHANISM_GSSAPI, ?AUTHN_BACKEND}). + +-endif. diff --git a/apps/emqx_auth_gssapi/rebar.config b/apps/emqx_auth_gssapi/rebar.config new file mode 100644 index 000000000..5c82824a9 --- /dev/null +++ b/apps/emqx_auth_gssapi/rebar.config @@ -0,0 +1,7 @@ +%% -*- mode: erlang -*- + +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {sasl_auth, {git, "https://github.com/kafka4beam/sasl_auth.git", {tag, "v2.1.0"}}} +]}. diff --git a/apps/emqx_auth_gssapi/src/emqx_auth_gssapi.app.src b/apps/emqx_auth_gssapi/src/emqx_auth_gssapi.app.src new file mode 100644 index 000000000..e29af9fef --- /dev/null +++ b/apps/emqx_auth_gssapi/src/emqx_auth_gssapi.app.src @@ -0,0 +1,18 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_gssapi, [ + {description, "EMQX gssapi Authentication"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_gssapi_app, []}}, + {applications, [ + kernel, + stdlib, + emqx_auth, + sasl_auth + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_gssapi/src/emqx_auth_gssapi_app.erl b/apps/emqx_auth_gssapi/src/emqx_auth_gssapi_app.erl new file mode 100644 index 000000000..9be82bbf8 --- /dev/null +++ b/apps/emqx_auth_gssapi/src/emqx_auth_gssapi_app.erl @@ -0,0 +1,20 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_auth_gssapi_app). + +-include("emqx_auth_gssapi.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ok = emqx_authn:register_provider(?AUTHN_TYPE_GSSAPI, emqx_authn_gssapi), + {ok, Sup} = emqx_auth_gssapi_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authn:deregister_provider(?AUTHN_TYPE_GSSAPI), + ok. diff --git a/apps/emqx_auth_gssapi/src/emqx_auth_gssapi_sup.erl b/apps/emqx_auth_gssapi/src/emqx_auth_gssapi_sup.erl new file mode 100644 index 000000000..2e97af182 --- /dev/null +++ b/apps/emqx_auth_gssapi/src/emqx_auth_gssapi_sup.erl @@ -0,0 +1,25 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_auth_gssapi_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_auth_gssapi/src/emqx_authn_gssapi.erl b/apps/emqx_auth_gssapi/src/emqx_authn_gssapi.erl new file mode 100644 index 000000000..9420e1993 --- /dev/null +++ b/apps/emqx_auth_gssapi/src/emqx_authn_gssapi.erl @@ -0,0 +1,111 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_authn_gssapi). + +-include("emqx_auth_gssapi.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). +-include_lib("typerefl/include/types.hrl"). + +-behaviour(emqx_authn_provider). + +-export([ + create/2, + update/2, + destroy/1, + authenticate/2 +]). + +create( + AuthenticatorID, + #{ + principal := Principal, + keytab_file := KeyTabFile + } +) -> + KeyTabPath = emqx_schema:naive_env_interpolation(KeyTabFile), + case sasl_auth:kinit(KeyTabPath, Principal) of + ok -> + {ok, #{ + id => AuthenticatorID, + principal => Principal, + keytab_file => KeyTabFile + }}; + Error -> + Error + end. + +update(Config, #{id := ID}) -> + create(ID, Config). + +destroy(_) -> + ok. + +authenticate( + #{ + auth_method := <<"GSSAPI">>, + auth_data := AuthData, + auth_cache := AuthCache + }, + #{principal := Principal} +) when AuthData =/= undefined -> + case AuthCache of + #{sasl_conn := SaslConn} -> + auth_continue(SaslConn, AuthData); + _ -> + case auth_new(Principal) of + {ok, SaslConn} -> auth_begin(SaslConn, AuthData); + Error -> Error + end + end; +authenticate(_Credential, _State) -> + ignore. + +%%------------------------------------------------------------------------------ +%% Internal functions +%%------------------------------------------------------------------------------ + +auth_new(Principal) -> + case sasl_auth:server_new(<<"emqx">>, Principal) of + {ok, SaslConn} -> + {ok, SaslConn}; + Error -> + ?TRACE_AUTHN_PROVIDER("sasl_gssapi_new_failed", #{ + reason => Error, + sasl_function => "server_server_new" + }), + {error, not_authorized} + end. + +auth_begin(SaslConn, ClientToken) -> + case sasl_auth:server_start(SaslConn, ClientToken) of + {ok, {sasl_continue, ServerToken}} -> + {continue, ServerToken, #{sasl_conn => SaslConn}}; + {ok, {sasl_ok, ServerToken}} -> + sasl_auth:server_done(SaslConn), + {ok, #{}, ServerToken}; + Reason -> + ?TRACE_AUTHN_PROVIDER("sasl_gssapi_start_failed", #{ + reason => Reason, + sasl_function => "server_server_start" + }), + sasl_auth:server_done(SaslConn), + {error, not_authorized} + end. + +auth_continue(SaslConn, ClientToken) -> + case sasl_auth:server_step(SaslConn, ClientToken) of + {ok, {sasl_continue, ServerToken}} -> + {continue, ServerToken, #{sasl_conn => SaslConn}}; + {ok, {sasl_ok, ServerToken}} -> + sasl_auth:server_done(SaslConn), + {ok, #{}, ServerToken}; + Reason -> + ?TRACE_AUTHN_PROVIDER("sasl_gssapi_step_failed", #{ + reason => Reason, + sasl_function => "server_server_step" + }), + sasl_auth:server_done(SaslConn), + {error, not_authorized} + end. diff --git a/apps/emqx_auth_gssapi/src/emqx_authn_gssapi_schema.erl b/apps/emqx_auth_gssapi/src/emqx_authn_gssapi_schema.erl new file mode 100644 index 000000000..267c75676 --- /dev/null +++ b/apps/emqx_auth_gssapi/src/emqx_authn_gssapi_schema.erl @@ -0,0 +1,57 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_authn_gssapi_schema). + +-include("emqx_auth_gssapi.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + namespace/0, + fields/1, + desc/1, + refs/0, + select_union_member/1 +]). + +namespace() -> "authn". + +refs() -> + [?R_REF(gssapi)]. + +select_union_member(#{ + <<"mechanism">> := ?AUTHN_MECHANISM_GSSAPI_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN +}) -> + refs(); +select_union_member(#{<<"mechanism">> := ?AUTHN_MECHANISM_GSSAPI_BIN}) -> + throw(#{ + reason => "unknown_backend", + expected => ?AUTHN_BACKEND + }); +select_union_member(_) -> + undefined. + +fields(gssapi) -> + emqx_authn_schema:common_fields() ++ + [ + {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM_GSSAPI)}, + {backend, emqx_authn_schema:backend(?AUTHN_BACKEND)}, + {principal, + ?HOCON(binary(), #{ + required => true, + desc => ?DESC(emqx_bridge_kafka, auth_kerberos_principal) + })}, + {keytab_file, + ?HOCON(binary(), #{ + required => true, + desc => ?DESC(emqx_bridge_kafka, auth_kerberos_keytab_file) + })} + ]. + +desc(gssapi) -> + "Settings for GSSAPI authentication."; +desc(_) -> + undefined. diff --git a/apps/emqx_bridge_kafka/rebar.config b/apps/emqx_bridge_kafka/rebar.config index b89c9190f..87fca579c 100644 --- a/apps/emqx_bridge_kafka/rebar.config +++ b/apps/emqx_bridge_kafka/rebar.config @@ -9,7 +9,8 @@ {snappyer, "1.2.9"}, {emqx_connector, {path, "../../apps/emqx_connector"}}, {emqx_resource, {path, "../../apps/emqx_resource"}}, - {emqx_bridge, {path, "../../apps/emqx_bridge"}} + {emqx_bridge, {path, "../../apps/emqx_bridge"}}, + {sasl_auth, {git, "https://github.com/kafka4beam/sasl_auth.git", {tag, "v2.1.0"}}} ]}. {shell, [ diff --git a/apps/emqx_conf/src/emqx_conf_schema_inject.erl b/apps/emqx_conf/src/emqx_conf_schema_inject.erl index 0e0f36401..3191cd850 100644 --- a/apps/emqx_conf/src/emqx_conf_schema_inject.erl +++ b/apps/emqx_conf/src/emqx_conf_schema_inject.erl @@ -55,7 +55,10 @@ authn_mods(ce) -> ]; authn_mods(ee) -> authn_mods(ce) ++ - [emqx_gcp_device_authn_schema]. + [ + emqx_gcp_device_authn_schema, + emqx_authn_gssapi_schema + ]. authz() -> [{emqx_authz_schema, authz_mods()}]. diff --git a/apps/emqx_machine/priv/reboot_lists.eterm b/apps/emqx_machine/priv/reboot_lists.eterm index 4830f51c4..1a400fb3e 100644 --- a/apps/emqx_machine/priv/reboot_lists.eterm +++ b/apps/emqx_machine/priv/reboot_lists.eterm @@ -136,6 +136,7 @@ emqx_bridge_syskeeper, emqx_bridge_confluent, emqx_ds_shared_sub, + emqx_auth_gssapi, emqx_auth_ext, emqx_cluster_link, emqx_ds_builtin_raft diff --git a/mix.exs b/mix.exs index dc2953683..e45907d5a 100644 --- a/mix.exs +++ b/mix.exs @@ -376,6 +376,7 @@ defmodule EMQXUmbrella.MixProject do :emqx_gateway_jt808, :emqx_bridge_syskeeper, :emqx_ds_shared_sub, + :emqx_auth_gssapi, :emqx_auth_ext, :emqx_cluster_link, :emqx_ds_builtin_raft