feat(gw): keep authenticator resource after gateway disabled

After a gateway is disabled, we should turn off the authenticator instead of destroying it.
Once built-in-database authenticator is destroyed, it discards all
user's data and can't be managed
This commit is contained in:
JianBo He 2022-06-14 11:40:45 +08:00
parent 6c465cfb8f
commit 7d38f83aef
1 changed files with 76 additions and 41 deletions

View File

@ -45,7 +45,7 @@
name :: gateway_name(),
config :: emqx_config:config(),
ctx :: emqx_gateway_ctx:context(),
authns :: [emqx_authentication:chain_name()],
authns :: [{emqx_authentication:chain_name(), map()}],
status :: stopped | running,
child_pids :: [pid()],
gw_state :: emqx_gateway_impl:state() | undefined,
@ -115,11 +115,11 @@ init([Gateway, Ctx, _GwDscrptr]) ->
}),
{ok, State};
true ->
case cb_gateway_load(State) of
case cb_gateway_load(ensure_authn_created(State)) of
{error, Reason} ->
{stop, Reason};
{ok, NState} ->
{ok, NState}
{ok, NState1} ->
{ok, NState1}
end
end.
@ -130,7 +130,7 @@ handle_call(disable, _From, State = #state{status = Status}) ->
running ->
case cb_gateway_unload(State) of
{ok, NState} ->
{reply, ok, NState};
{reply, ok, disable_authns(NState)};
{error, Reason} ->
{reply, {error, Reason}, State}
end;
@ -140,11 +140,11 @@ handle_call(disable, _From, State = #state{status = Status}) ->
handle_call(enable, _From, State = #state{status = Status}) ->
case Status of
stopped ->
case cb_gateway_load(State) of
case cb_gateway_load(ensure_authn_running(State)) of
{error, Reason} ->
{reply, {error, Reason}, State};
{ok, NState} ->
{reply, ok, NState}
{ok, NState1} ->
{reply, ok, NState1}
end;
_ ->
{reply, {error, already_started}, State}
@ -233,15 +233,52 @@ detailed_gateway_info(State) ->
%% Internal funcs
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
%% Authn resources managing funcs
%% ensure authentication chain, authenticator created and keep its status
%% as expected
ensure_authn_created(State = #state{ctx = Ctx, name = GwName, config = Config}) ->
Authns = init_authn(GwName, Config),
AuthnNames = lists:map(fun({ChainName, _}) -> ChainName end, Authns),
State#state{authns = Authns, ctx = maps:put(auth, AuthnNames, Ctx)}.
%% temporarily disable authenticators after gateway disabled
disable_authns(State = #state{ctx = Ctx, authns = Authns}) ->
lists:foreach(
fun({ChainName, AuthConf}) ->
TempConf = maps:put(enable, false, AuthConf),
do_update_authenticator(ChainName, TempConf)
end,
Authns
),
State#state{ctx = maps:remove(auth, Ctx)}.
%% keep authenticators running as expected
ensure_authn_running(State = #state{ctx = Ctx, authns = Authns}) ->
AuthnNames = lists:map(
fun({ChainName, AuthConf}) ->
ok = do_update_authenticator(ChainName, AuthConf),
ChainName
end,
Authns
),
State#state{ctx = maps:put(auth, AuthnNames, Ctx)}.
do_update_authenticator(ChainName, Confs) ->
[#{id := AuthenticatorId}] = emqx_authentication:list_authenticators(ChainName),
{ok, _} = emqx_authentication:update_authenticator(ChainName, AuthenticatorId, Confs),
ok.
%% There are two layer authentication configs
%% stomp.authn
%% / \
%% listeners.tcp.default.authn *.ssl.default.authn
%%
init_authn(GwName, Config) ->
Authns = authns(GwName, Config),
try
_ = application:ensure_all_started(emqx_authn),
do_init_authn(Authns, [])
catch
throw:Reason = {badauth, _} ->
@ -249,32 +286,37 @@ init_authn(GwName, Config) ->
throw(Reason)
end.
do_init_authn([], Names) ->
Names;
do_init_authn([{_ChainName, _AuthConf = #{enable := false}} | More], Names) ->
do_init_authn(More, Names);
do_init_authn([{ChainName, AuthConf} | More], Names) when is_map(AuthConf) ->
_ = application:ensure_all_started(emqx_authn),
do_create_authn_chain(ChainName, AuthConf),
do_init_authn(More, [ChainName | Names]);
do_init_authn([_BadConf | More], Names) ->
do_init_authn(More, Names).
do_init_authn([], Authns) ->
lists:reverse(Authns);
do_init_authn([{ChainName, AuthConf} | More], Authns) when is_map(AuthConf) ->
ok = do_create_authn_chain(ChainName, AuthConf),
do_init_authn(More, [{ChainName, AuthConf} | Authns]);
do_init_authn([_BadConf | More], Authns) ->
do_init_authn(More, Authns).
authns(GwName, Config) ->
Listeners = maps:to_list(maps:get(listeners, Config, #{})),
lists:append(
[
Authns0 =
lists:append(
[
{emqx_gateway_utils:listener_chain(GwName, LisType, LisName), authn_conf(Opts)}
|| {LisName, Opts} <- maps:to_list(LisNames)
[
{emqx_gateway_utils:listener_chain(GwName, LisType, LisName), authn_conf(Opts)}
|| {LisName, Opts} <- maps:to_list(LisNames)
]
|| {LisType, LisNames} <- Listeners
]
|| {LisType, LisNames} <- Listeners
]
) ++
[{emqx_gateway_utils:global_chain(GwName), authn_conf(Config)}].
) ++
[{emqx_gateway_utils:global_chain(GwName), authn_conf(Config)}],
lists:filter(
fun
({_, undefined}) -> false;
(_) -> true
end,
Authns0
).
authn_conf(Conf) ->
maps:get(authentication, Conf, #{enable => false}).
maps:get(authentication, Conf, undefined).
do_create_authn_chain(ChainName, AuthConf) ->
case emqx_authentication:create_authenticator(ChainName, AuthConf) of
@ -325,7 +367,7 @@ do_update_one_by_one(
case {Status, NEnable} of
{stopped, true} ->
NState = State#state{config = NCfg},
cb_gateway_load(NState);
cb_gateway_load(ensure_authn_running(NState));
{stopped, false} ->
{ok, State#state{config = NCfg}};
{running, true} ->
@ -336,14 +378,14 @@ do_update_one_by_one(
false ->
%% Reset Authentication first
_ = do_deinit_authn(State#state.authns),
AuthnNames = init_authn(State#state.name, NCfg),
State#state{authns = AuthnNames}
Authns = init_authn(State#state.name, NCfg),
State#state{authns = Authns}
end,
%% TODO: minimum impact update ???
cb_gateway_update(NCfg, NState);
{running, false} ->
case cb_gateway_unload(State) of
{ok, NState} -> {ok, NState#state{config = NCfg}};
{ok, NState} -> {ok, disable_authns(NState#state{config = NCfg})};
{error, Reason} -> {error, Reason}
end;
_ ->
@ -378,8 +420,6 @@ cb_gateway_unload(
stacktrace => Stk
}),
{error, Reason}
after
_ = do_deinit_authn(State#state.authns)
end.
%% @doc 1. Create Authentcation Context
@ -389,24 +429,19 @@ cb_gateway_unload(
cb_gateway_load(
State = #state{
name = GwName,
config = Config,
ctx = Ctx
}
) ->
Gateway = detailed_gateway_info(State),
try
AuthnNames = init_authn(GwName, Config),
NCtx = Ctx#{auth => AuthnNames},
#{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwName),
case CbMod:on_gateway_load(Gateway, NCtx) of
case CbMod:on_gateway_load(Gateway, Ctx) of
{error, Reason} ->
do_deinit_authn(AuthnNames),
{error, Reason};
{ok, ChildPidOrSpecs, GwState} ->
ChildPids = start_child_process(ChildPidOrSpecs),
{ok, State#state{
ctx = NCtx,
authns = AuthnNames,
ctx = Ctx,
status = running,
child_pids = ChildPids,
gw_state = GwState,