|
|
@@ -43,6 +43,7 @@
|
|
|
name :: gateway_name(),
|
|
|
config :: emqx_config:config(),
|
|
|
ctx :: emqx_gateway_ctx:context(),
|
|
|
+ authns :: [emqx_authentication:chain_name()],
|
|
|
status :: stopped | running,
|
|
|
child_pids :: [pid()],
|
|
|
gw_state :: emqx_gateway_impl:state() | undefined,
|
|
|
@@ -174,9 +175,9 @@ handle_info(Info, State) ->
|
|
|
?LOG(warning, "Unexcepted info: ~p", [Info]),
|
|
|
{noreply, State}.
|
|
|
|
|
|
-terminate(_Reason, State = #state{ctx = Ctx, child_pids = Pids}) ->
|
|
|
+terminate(_Reason, State = #state{child_pids = Pids}) ->
|
|
|
Pids /= [] andalso (_ = cb_gateway_unload(State)),
|
|
|
- _ = do_deinit_authn(maps:get(auth, Ctx, undefined)),
|
|
|
+ _ = do_deinit_authn(State#state.authns),
|
|
|
ok.
|
|
|
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
|
@@ -197,52 +198,100 @@ detailed_gateway_info(State) ->
|
|
|
%% Internal funcs
|
|
|
%%--------------------------------------------------------------------
|
|
|
|
|
|
-do_init_authn(GwName, Config) ->
|
|
|
- case maps:get(authentication, Config, #{enable => false}) of
|
|
|
- #{enable := false} -> undefined;
|
|
|
- AuthCfg when is_map(AuthCfg) ->
|
|
|
- case maps:get(enable, AuthCfg, true) of
|
|
|
- false ->
|
|
|
- undefined;
|
|
|
- _ ->
|
|
|
- %% TODO: Implement Authentication
|
|
|
- GwName
|
|
|
- %case emqx_authn:create_chain(#{id => ChainId}) of
|
|
|
- % {ok, _ChainInfo} ->
|
|
|
- % case emqx_authn:create_authenticator(ChainId, AuthCfg) of
|
|
|
- % {ok, _} -> ChainId;
|
|
|
- % {error, Reason} ->
|
|
|
- % ?LOG(error, "Failed to create authentication ~p", [Reason]),
|
|
|
- % throw({bad_authentication, Reason})
|
|
|
- % end;
|
|
|
- % {error, Reason} ->
|
|
|
- % ?LOG(error, "Failed to create authentication chain: ~p", [Reason]),
|
|
|
- % throw({bad_chain, {ChainId, Reason}})
|
|
|
- %end.
|
|
|
+%% same with emqx_authentication:global_chain/1
|
|
|
+global_chain(mqtt) ->
|
|
|
+ 'mqtt:global';
|
|
|
+global_chain('mqtt-sn') ->
|
|
|
+ 'mqtt-sn:global';
|
|
|
+global_chain(coap) ->
|
|
|
+ 'coap:global';
|
|
|
+global_chain(lwm2m) ->
|
|
|
+ 'lwm2m:global';
|
|
|
+global_chain(stomp) ->
|
|
|
+ 'stomp:global';
|
|
|
+global_chain(_) ->
|
|
|
+ 'unknown:global'.
|
|
|
+
|
|
|
+listener_chain(GwName, Type, LisName) ->
|
|
|
+ emqx_gateway_utils:listener_id(GwName, Type, LisName).
|
|
|
+
|
|
|
+%% There are two layer authentication configs
|
|
|
+%% stomp.authn
|
|
|
+%% / \
|
|
|
+%% listeners.tcp.defautl.authn *.ssl.default.authn
|
|
|
+%%
|
|
|
+
|
|
|
+init_authn(GwName, Config) ->
|
|
|
+ Authns = authns(GwName, Config),
|
|
|
+ try
|
|
|
+ do_init_authn(Authns, [])
|
|
|
+ catch
|
|
|
+ throw : Reason = {badauth, _} ->
|
|
|
+ do_deinit_authn(proplists:get_keys(Authns)),
|
|
|
+ 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) ->
|
|
|
+ _ = application:ensure_all_started(emqx_authn),
|
|
|
+ do_create_authn_chain(ChainName, AuthConf),
|
|
|
+ do_init_authn(More, [ChainName|Names]).
|
|
|
+
|
|
|
+authns(GwName, Config) ->
|
|
|
+ Listeners = maps:to_list(maps:get(listeners, Config, #{})),
|
|
|
+ lists:append(
|
|
|
+ [ [{listener_chain(GwName, LisType, LisName), authn_conf(Opts)}
|
|
|
+ || {LisName, Opts} <- maps:to_list(LisNames) ]
|
|
|
+ || {LisType, LisNames} <- Listeners])
|
|
|
+ ++ [{global_chain(GwName), authn_conf(Config)}].
|
|
|
+
|
|
|
+authn_conf(Conf) ->
|
|
|
+ maps:get(authentication, Conf, #{enable => false}).
|
|
|
+
|
|
|
+do_create_authn_chain(ChainName, AuthConf) ->
|
|
|
+ case ensure_chain(ChainName) of
|
|
|
+ ok ->
|
|
|
+ case emqx_authentication:create_authenticator(ChainName, AuthConf) of
|
|
|
+ {ok, _} -> ok;
|
|
|
+ {error, Reason} ->
|
|
|
+ ?LOG(error, "Failed to create authenticator chain ~s, "
|
|
|
+ "reason: ~p, config: ~p",
|
|
|
+ [ChainName, Reason, AuthConf]),
|
|
|
+ throw({badauth, Reason})
|
|
|
end;
|
|
|
- _ ->
|
|
|
- undefined
|
|
|
+ {error, Reason} ->
|
|
|
+ ?LOG(error, "Falied to create authn chain ~s, reason ~p",
|
|
|
+ [ChainName, Reason]),
|
|
|
+ throw({badauth, Reason})
|
|
|
+ end.
|
|
|
+
|
|
|
+ensure_chain(ChainName) ->
|
|
|
+ case emqx_authentication:create_chain(ChainName) of
|
|
|
+ {ok, _ChainInfo} ->
|
|
|
+ ok;
|
|
|
+ {error, {already_exists, _}} ->
|
|
|
+ ok;
|
|
|
+ {error, Reason} ->
|
|
|
+ {error, Reason}
|
|
|
end.
|
|
|
|
|
|
-do_deinit_authn(undefined) ->
|
|
|
- ok;
|
|
|
-do_deinit_authn(AuthnRef) ->
|
|
|
- %% TODO:
|
|
|
- ?LOG(warning, "Failed to clean authn ~p, not suppported now", [AuthnRef]).
|
|
|
- %case emqx_authn:delete_chain(AuthnRef) of
|
|
|
- % ok -> ok;
|
|
|
- % {error, {not_found, _}} ->
|
|
|
- % ?LOG(warning, "Failed to clean authentication chain: ~s, "
|
|
|
- % "reason: not_found", [AuthnRef]);
|
|
|
- % {error, Reason} ->
|
|
|
- % ?LOG(error, "Failed to clean authentication chain: ~s, "
|
|
|
- % "reason: ~p", [AuthnRef, Reason])
|
|
|
- %end.
|
|
|
+do_deinit_authn(Names) ->
|
|
|
+ lists:foreach(fun(ChainName) ->
|
|
|
+ case emqx_authentication:delete_chain(ChainName) of
|
|
|
+ ok -> ok;
|
|
|
+ {error, {not_found, _}} -> ok;
|
|
|
+ {error, Reason} ->
|
|
|
+ ?LOG(error, "Failed to clean authentication chain: ~s, "
|
|
|
+ "reason: ~p", [ChainName, Reason])
|
|
|
+ end
|
|
|
+ end, Names).
|
|
|
|
|
|
do_update_one_by_one(NCfg0, State = #state{
|
|
|
- ctx = Ctx,
|
|
|
- config = OCfg,
|
|
|
- status = Status}) ->
|
|
|
+ config = OCfg,
|
|
|
+ status = Status}) ->
|
|
|
|
|
|
NCfg = emqx_map_lib:deep_merge(OCfg, NCfg0),
|
|
|
|
|
|
@@ -263,14 +312,9 @@ do_update_one_by_one(NCfg0, State = #state{
|
|
|
true -> State;
|
|
|
false ->
|
|
|
%% Reset Authentication first
|
|
|
- _ = do_deinit_authn(maps:get(auth, Ctx, undefined)),
|
|
|
- NCtx = Ctx#{
|
|
|
- auth => do_init_authn(
|
|
|
- State#state.name,
|
|
|
- NCfg
|
|
|
- )
|
|
|
- },
|
|
|
- State#state{ctx = NCtx}
|
|
|
+ _ = do_deinit_authn(State#state.authns),
|
|
|
+ AuthnNames = init_authn(State#state.name, NCfg),
|
|
|
+ State#state{authns = AuthnNames}
|
|
|
end,
|
|
|
cb_gateway_update(NCfg, NState);
|
|
|
Status == running, NEnable == false ->
|
|
|
@@ -289,6 +333,7 @@ cb_gateway_unload(State = #state{name = GwName,
|
|
|
#{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwName),
|
|
|
CbMod:on_gateway_unload(Gateway, GwState),
|
|
|
{ok, State#state{child_pids = [],
|
|
|
+ authns = [],
|
|
|
status = stopped,
|
|
|
gw_state = undefined,
|
|
|
started_at = undefined,
|
|
|
@@ -300,6 +345,8 @@ cb_gateway_unload(State = #state{name = GwName,
|
|
|
[GwName, GwState,
|
|
|
Class, Reason, Stk]),
|
|
|
{error, {Class, Reason, Stk}}
|
|
|
+ after
|
|
|
+ _ = do_deinit_authn(State#state.authns)
|
|
|
end.
|
|
|
|
|
|
%% @doc 1. Create Authentcation Context
|
|
|
@@ -317,17 +364,18 @@ cb_gateway_load(State = #state{name = GwName,
|
|
|
?LOG(info, "Skipp to start ~s gateway due to disabled", [GwName]);
|
|
|
true ->
|
|
|
try
|
|
|
- AuthnRef = do_init_authn(GwName, Config),
|
|
|
- NCtx = Ctx#{auth => AuthnRef},
|
|
|
+ AuthnNames = init_authn(GwName, Config),
|
|
|
+ NCtx = Ctx#{auth => AuthnNames},
|
|
|
#{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwName),
|
|
|
case CbMod:on_gateway_load(Gateway, NCtx) of
|
|
|
{error, Reason} ->
|
|
|
- do_deinit_authn(AuthnRef),
|
|
|
+ do_deinit_authn(AuthnNames),
|
|
|
throw({callback_return_error, Reason});
|
|
|
{ok, ChildPidOrSpecs, GwState} ->
|
|
|
ChildPids = start_child_process(ChildPidOrSpecs),
|
|
|
{ok, State#state{
|
|
|
ctx = NCtx,
|
|
|
+ authns = AuthnNames,
|
|
|
status = running,
|
|
|
child_pids = ChildPids,
|
|
|
gw_state = GwState,
|