diff --git a/apps/emqx/src/emqx_authentication_config.erl b/apps/emqx/src/emqx_authentication_config.erl index 93fcafc35..c826a0fac 100644 --- a/apps/emqx/src/emqx_authentication_config.erl +++ b/apps/emqx/src/emqx_authentication_config.erl @@ -52,6 +52,7 @@ -type update_request() :: {create_authenticator, chain_name(), map()} | {delete_authenticator, chain_name(), authenticator_id()} + | {delete_authenticators, chain_name()} | {update_authenticator, chain_name(), authenticator_id(), map()} | {move_authenticator, chain_name(), authenticator_id(), position()}. @@ -88,6 +89,8 @@ do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldCon OldConfig ), {ok, NewConfig}; +do_pre_config_update({delete_authenticators, _ChainName}, _OldConfig) -> + {ok, []}; do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) -> CertsDir = certs_dir(ChainName, AuthenticatorID), NewConfig = lists:map( @@ -156,6 +159,25 @@ do_post_config_update( {error, Reason} -> {error, Reason} end; +do_post_config_update( + {delete_authenticators, ChainName}, + _NewConfig, + OldConfig, + _AppEnvs +) -> + case emqx_authentication:delete_chain(ChainName) of + ok -> + lists:foreach( + fun(Config) -> + AuthenticatorID = authenticator_id(Config), + CertsDir = certs_dir(ChainName, AuthenticatorID), + ok = clear_certs(CertsDir, Config) + end, + to_list(OldConfig) + ); + {error, Reason} -> + {error, Reason} + end; do_post_config_update( {update_authenticator, ChainName, AuthenticatorID, Config}, NewConfig, diff --git a/apps/emqx_authn/i18n/emqx_authn_api_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_api_i18n.conf index 3b8093e63..44725e935 100644 --- a/apps/emqx_authn/i18n/emqx_authn_api_i18n.conf +++ b/apps/emqx_authn/i18n/emqx_authn_api_i18n.conf @@ -49,6 +49,13 @@ emqx_authn_api { } } + listeners_listener_id_authentication_delete { + desc { + en: """Delete listener-specific authentication.""" + zh: """删除特定于侦听器的身份验证。""" + } + } + listeners_listener_id_authentication_post { desc { en: """Create authenticator for listener authentication.""" diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 0369ab541..ae0dffb01 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -257,6 +257,15 @@ schema("/listeners/:listener_id/authentication") -> ) } }, + delete => #{ + tags => ?API_TAGS_SINGLE, + description => ?DESC(listeners_listener_id_authentication_delete), + parameters => [param_listener_id()], + responses => #{ + 204 => <<"Authentication chain deleted">>, + 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + } + }, post => #{ tags => ?API_TAGS_SINGLE, description => ?DESC(listeners_listener_id_authentication_post), @@ -646,6 +655,13 @@ listener_authenticators(get, #{bindings := #{listener_id := ListenerID}}) -> fun(Type, Name, _) -> list_authenticators([listeners, Type, Name, authentication]) end + ); +listener_authenticators(delete, #{bindings := #{listener_id := ListenerID}}) -> + with_listener( + ListenerID, + fun(Type, Name, ChainName) -> + delete_authenticators([listeners, Type, Name, authentication], ChainName) + end ). listener_authenticator(get, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}) -> @@ -1098,6 +1114,16 @@ delete_authenticator(ConfKeyPath, ChainName, AuthenticatorID) -> serialize_error(Reason) end. +delete_authenticators(ConfKeyPath, ChainName) -> + case update_config(ConfKeyPath, {delete_authenticators, ChainName}) of + {ok, _} -> + {204}; + {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} -> + serialize_error(Reason); + {error, Reason} -> + serialize_error(Reason) + end. + move_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Position) -> case parse_position(Position) of {ok, NPosition} -> diff --git a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl index d5f049836..668a91798 100644 --- a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl @@ -693,6 +693,84 @@ test_authenticator_upload_users(PathPrefix) -> {filename, "user-credentials.csv", CSVData} ]). +t_switch_to_global_chain(_) -> + {ok, 200, _} = request( + post, + uri([?CONF_NS]), + emqx_authn_test_lib:built_in_database_example() + ), + + {ok, 200, _} = request( + post, + uri([listeners, "tcp:default", ?CONF_NS]), + emqx_authn_test_lib:built_in_database_example() + ), + + GlobalUser = #{user_id => <<"global_user">>, password => <<"p1">>}, + + {ok, 201, _} = request( + post, + uri([?CONF_NS, "password_based:built_in_database", "users"]), + GlobalUser + ), + + ListenerUser = #{user_id => <<"listener_user">>, password => <<"p1">>}, + + {ok, 201, _} = request( + post, + uri([listeners, "tcp:default", ?CONF_NS, "password_based:built_in_database", "users"]), + ListenerUser + ), + + process_flag(trap_exit, true), + + %% Listener user should be OK + {ok, Client0} = emqtt:start_link([ + {username, <<"listener_user">>}, + {password, <<"p1">>} + ]), + ?assertMatch( + {ok, _}, + emqtt:connect(Client0) + ), + ok = emqtt:disconnect(Client0), + + %% Global user should not be OK + {ok, Client1} = emqtt:start_link([ + {username, <<"global_user">>}, + {password, <<"p1">>} + ]), + ?assertMatch( + {error, {unauthorized_client, _}}, + emqtt:connect(Client1) + ), + + {ok, 204, _} = request( + delete, + uri([listeners, "tcp:default", ?CONF_NS]) + ), + + %% Listener user should not be OK — local chain removed + {ok, Client2} = emqtt:start_link([ + {username, <<"listener_user">>}, + {password, <<"p1">>} + ]), + ?assertMatch( + {error, {unauthorized_client, _}}, + emqtt:connect(Client2) + ), + + %% Global user should be now OK, switched back to the global chain + {ok, Client3} = emqtt:start_link([ + {username, <<"global_user">>}, + {password, <<"p1">>} + ]), + ?assertMatch( + {ok, _}, + emqtt:connect(Client3) + ), + ok = emqtt:disconnect(Client3). + %%------------------------------------------------------------------------------ %% Helpers %%------------------------------------------------------------------------------ diff --git a/apps/emqx_conf/src/emqx_cluster_rpc.erl b/apps/emqx_conf/src/emqx_cluster_rpc.erl index 5be474601..da49e31a5 100644 --- a/apps/emqx_conf/src/emqx_cluster_rpc.erl +++ b/apps/emqx_conf/src/emqx_cluster_rpc.erl @@ -512,11 +512,5 @@ get_retry_ms() -> maybe_init_tnx_id(_Node, TnxId) when TnxId < 0 -> ok; maybe_init_tnx_id(Node, TnxId) -> - {atomic, _} = transaction(fun init_node_tnx_id/2, [Node, TnxId]), + {atomic, _} = transaction(fun commit/2, [Node, TnxId]), ok. - -init_node_tnx_id(Node, TnxId) -> - case mnesia:read(?CLUSTER_COMMIT, Node) of - [] -> commit(Node, TnxId); - _ -> ok - end. diff --git a/apps/emqx_gateway/src/emqx_gateway_api.erl b/apps/emqx_gateway/src/emqx_gateway_api.erl index 162fa2b62..609a02149 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api.erl @@ -352,6 +352,8 @@ fields(Gw) when -> [{name, mk(Gw, #{desc => ?DESC(gateway_name)})}] ++ convert_listener_struct(emqx_gateway_schema:fields(Gw)); +fields(update_disable_enable_only) -> + [{enable, mk(boolean(), #{desc => <<"Enable/Disable the gateway">>})}]; fields(Gw) when Gw == update_stomp; Gw == update_mqttsn; @@ -411,7 +413,8 @@ schema_update_gateways_conf() -> ref(?MODULE, update_mqttsn), ref(?MODULE, update_coap), ref(?MODULE, update_lwm2m), - ref(?MODULE, update_exproto) + ref(?MODULE, update_exproto), + ref(?MODULE, update_disable_enable_only) ]), examples_update_gateway_confs() ). diff --git a/apps/emqx_gateway/src/emqx_gateway_conf.erl b/apps/emqx_gateway/src/emqx_gateway_conf.erl index 43236f42d..7c883c1c5 100644 --- a/apps/emqx_gateway/src/emqx_gateway_conf.erl +++ b/apps/emqx_gateway/src/emqx_gateway_conf.erl @@ -73,6 +73,8 @@ -type map_or_err() :: {ok, map()} | {error, term()}. -type listener_ref() :: {ListenerType :: atom_or_bin(), ListenerName :: atom_or_bin()}. +-define(IS_SSL(T), (T == <<"ssl">> orelse T == <<"dtls">>)). + %%-------------------------------------------------------------------- %% Load/Unload %%-------------------------------------------------------------------- @@ -403,10 +405,7 @@ pre_config_update(_, {update_gateway, GwName, Conf}, RawConf) -> GwRawConf -> Conf1 = maps:without([<<"listeners">>, ?AUTHN_BIN], Conf), NConf = tune_gw_certs(fun convert_certs/2, GwName, Conf1), - NConf1 = maps:merge( - maps:with([<<"listeners">>, ?AUTHN_BIN], GwRawConf), - NConf - ), + NConf1 = maps:merge(GwRawConf, NConf), {ok, emqx_map_lib:deep_put([GwName], RawConf, NConf1)} end; pre_config_update(_, {unload_gateway, GwName}, RawConf) -> @@ -680,9 +679,17 @@ apply_to_listeners(Fun, GwName, Conf) -> apply_to_gateway_basic_confs(Fun, <<"exproto">>, Conf) -> SvrDir = filename:join(["exproto", "server"]), HdrDir = filename:join(["exproto", "handler"]), - NServerConf = erlang:apply(Fun, [SvrDir, maps:get(<<"server">>, Conf, #{})]), - NHandlerConf = erlang:apply(Fun, [HdrDir, maps:get(<<"handler">>, Conf, #{})]), - maps:put(<<"handler">>, NHandlerConf, maps:put(<<"server">>, NServerConf, Conf)); + Conf1 = + case maps:get(<<"server">>, Conf, undefined) of + undefined -> + Conf; + ServerConf -> + maps:put(<<"server">>, erlang:apply(Fun, [SvrDir, ServerConf]), Conf) + end, + case maps:get(<<"handler">>, Conf1, undefined) of + undefined -> Conf1; + HandlerConf -> maps:put(<<"handler">>, erlang:apply(Fun, [HdrDir, HandlerConf]), Conf1) + end; apply_to_gateway_basic_confs(_Fun, _GwName, Conf) -> Conf. @@ -690,34 +697,43 @@ certs_dir(GwName) when is_binary(GwName) -> GwName. convert_certs(SubDir, Conf) -> + convert_certs(<<"dtls">>, SubDir, convert_certs(<<"ssl">>, SubDir, Conf)). + +convert_certs(Type, SubDir, Conf) when ?IS_SSL(Type) -> case emqx_tls_lib:ensure_ssl_files( SubDir, - maps:get(<<"ssl">>, Conf, undefined) + maps:get(Type, Conf, undefined) ) of {ok, SSL} -> - new_ssl_config(Conf, SSL); + new_ssl_config(Type, Conf, SSL); {error, Reason} -> ?SLOG(error, Reason#{msg => bad_ssl_config}), throw({bad_ssl_config, Reason}) - end. + end; +convert_certs(SubDir, NConf, OConf) when is_map(NConf); is_map(OConf) -> + convert_certs(<<"dtls">>, SubDir, convert_certs(<<"ssl">>, SubDir, NConf, OConf), OConf). -convert_certs(SubDir, NConf, OConf) -> - OSSL = maps:get(<<"ssl">>, OConf, undefined), - NSSL = maps:get(<<"ssl">>, NConf, undefined), +convert_certs(Type, SubDir, NConf, OConf) when ?IS_SSL(Type) -> + OSSL = maps:get(Type, OConf, undefined), + NSSL = maps:get(Type, NConf, undefined), case emqx_tls_lib:ensure_ssl_files(SubDir, NSSL) of {ok, NSSL1} -> ok = emqx_tls_lib:delete_ssl_files(SubDir, NSSL1, OSSL), - new_ssl_config(NConf, NSSL1); + new_ssl_config(Type, NConf, NSSL1); {error, Reason} -> ?SLOG(error, Reason#{msg => bad_ssl_config}), throw({bad_ssl_config, Reason}) end. -new_ssl_config(Conf, undefined) -> Conf; -new_ssl_config(Conf, SSL) -> Conf#{<<"ssl">> => SSL}. +new_ssl_config(_Type, Conf, undefined) -> Conf; +new_ssl_config(Type, Conf, SSL) when ?IS_SSL(Type) -> Conf#{Type => SSL}. clear_certs(SubDir, Conf) -> - SSL = maps:get(<<"ssl">>, Conf, undefined), + clear_certs(<<"ssl">>, SubDir, Conf), + clear_certs(<<"dtls">>, SubDir, Conf). + +clear_certs(Type, SubDir, Conf) when ?IS_SSL(Type) -> + SSL = maps:get(Type, Conf, undefined), ok = emqx_tls_lib:delete_ssl_files(SubDir, undefined, SSL).