refactor(gw): integrate with authn

This commit is contained in:
JianBo He 2021-09-16 14:04:31 +08:00
parent f3c675b139
commit 3e033b419c
8 changed files with 159 additions and 100 deletions

View File

@ -30,8 +30,8 @@ gateway.stomp {
}
authentication: {
name = "authenticator1"
type = "password-based:built-in-database"
mechanism = password-based
backend = built-in-database
user_id_type = clientid
}
@ -45,6 +45,12 @@ gateway.stomp {
"allow all"
]
authentication: {
mechanism = password-based
backend = built-in-database
user_id_type = username
}
## TCP options
## See ${example_common_tcp_options} for more information
tcp.active_n = 100

View File

@ -19,8 +19,6 @@
-type gateway_name() :: atom().
-type listener() :: #{}.
%% @doc The Gateway defination
-type gateway() ::
#{ name := gateway_name()

View File

@ -81,10 +81,13 @@
%% Frame Module
frame_mod :: atom(),
%% Channel Module
chann_mod :: atom()
chann_mod :: atom(),
%% Listener Tag
listener :: listener() | undefined
}).
-type(state() :: #state{}).
-type listener() :: {GwName :: atom(), LisType :: atom(), LisName :: atom()}.
-type state() :: #state{}.
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n]).
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
@ -279,7 +282,8 @@ init_state(WrappedSock, Peername, Options, FrameMod, ChannMod) ->
idle_timer = IdleTimer,
oom_policy = OomPolicy,
frame_mod = FrameMod,
chann_mod = ChannMod
chann_mod = ChannMod,
listener = maps:get(listener, Options, undefined)
}.
run_loop(Parent, State = #state{socket = Socket,

View File

@ -30,7 +30,7 @@
#{ %% Gateway Name
gwname := gateway_name()
%% Autenticator
, auth := emqx_authn:chain_id() | undefined
, auth := emqx_authentication:chain_name() | undefined
%% The ConnectionManager PID
, cm := pid()
}.
@ -66,12 +66,8 @@
| {error, any()}.
authenticate(_Ctx = #{auth := undefined}, ClientInfo) ->
{ok, mountpoint(ClientInfo)};
authenticate(_Ctx = #{auth := ChainId}, ClientInfo0) ->
ClientInfo = ClientInfo0#{
zone => default,
listener => {tcp, default},
chain_id => ChainId
},
authenticate(_Ctx = #{auth := _ChainName}, ClientInfo0) ->
ClientInfo = ClientInfo0#{zone => default},
case emqx_access_control:authenticate(ClientInfo) of
{ok, _} ->
{ok, mountpoint(ClientInfo)};

View File

@ -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.
end;
_ ->
undefined
%% 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_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_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;
{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(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,

View File

@ -92,10 +92,10 @@ fields(coap) ->
fields(lwm2m) ->
[ {xml_dir, sc(binary())}
, {lifetime_min, sc(duration())}
, {lifetime_max, sc(duration())}
, {qmode_time_windonw, sc(integer())}
, {auto_observe, sc(boolean())}
, {lifetime_min, sc(duration(), "1s")}
, {lifetime_max, sc(duration(), "86400s")}
, {qmode_time_window, sc(integer(), 22)}
, {auto_observe, sc(boolean(), false)}
, {update_msg_publish_condition, sc(hoconsc:union([always, contains_object_list]))}
, {translators, sc(ref(translators))}
, {listeners, sc(ref(udp_listeners))}
@ -154,8 +154,8 @@ fields(udp_tcp_listeners) ->
];
fields(tcp_listener) ->
[
%% some special confs for tcp listener
[ %% some special confs for tcp listener
{acceptors, sc(integer(), 16)}
] ++
tcp_opts() ++
proxy_protocol_opts() ++
@ -175,6 +175,8 @@ fields(udp_listener) ->
common_listener_opts();
fields(dtls_listener) ->
[ {acceptors, sc(integer(), 16)}
] ++
fields(udp_listener) ++
[{dtls, sc_meta(ref(dtls_opts),
#{desc => "DTLS listener options"})}];
@ -195,25 +197,25 @@ fields(dtls_opts) ->
, ciphers => dtls
}, false).
% authentication() ->
% hoconsc:union(
% [ undefined
% , hoconsc:ref(emqx_authn_mnesia, config)
% , hoconsc:ref(emqx_authn_mysql, config)
% , hoconsc:ref(emqx_authn_pgsql, config)
% , hoconsc:ref(emqx_authn_mongodb, standalone)
% , hoconsc:ref(emqx_authn_mongodb, 'replica-set')
% , hoconsc:ref(emqx_authn_mongodb, 'sharded-cluster')
% , hoconsc:ref(emqx_authn_redis, standalone)
% , hoconsc:ref(emqx_authn_redis, cluster)
% , hoconsc:ref(emqx_authn_redis, sentinel)
% , hoconsc:ref(emqx_authn_http, get)
% , hoconsc:ref(emqx_authn_http, post)
% , hoconsc:ref(emqx_authn_jwt, 'hmac-based')
% , hoconsc:ref(emqx_authn_jwt, 'public-key')
% , hoconsc:ref(emqx_authn_jwt, 'jwks')
% , hoconsc:ref(emqx_enhanced_authn_scram_mnesia, config)
% ]).
authentication() ->
hoconsc:union(
[ undefined
, hoconsc:ref(emqx_authn_mnesia, config)
, hoconsc:ref(emqx_authn_mysql, config)
, hoconsc:ref(emqx_authn_pgsql, config)
, hoconsc:ref(emqx_authn_mongodb, standalone)
, hoconsc:ref(emqx_authn_mongodb, 'replica-set')
, hoconsc:ref(emqx_authn_mongodb, 'sharded-cluster')
, hoconsc:ref(emqx_authn_redis, standalone)
, hoconsc:ref(emqx_authn_redis, cluster)
, hoconsc:ref(emqx_authn_redis, sentinel)
, hoconsc:ref(emqx_authn_http, get)
, hoconsc:ref(emqx_authn_http, post)
, hoconsc:ref(emqx_authn_jwt, 'hmac-based')
, hoconsc:ref(emqx_authn_jwt, 'public-key')
, hoconsc:ref(emqx_authn_jwt, 'jwks')
, hoconsc:ref(emqx_enhanced_authn_scram_mnesia, config)
]).
gateway_common_options() ->
[ {enable, sc(boolean(), true)}
@ -221,16 +223,15 @@ gateway_common_options() ->
, {idle_timeout, sc(duration(), <<"30s">>)}
, {mountpoint, sc(binary(), <<>>)}
, {clientinfo_override, sc(ref(clientinfo_override))}
, {authentication, sc(hoconsc:lazy(map()))}
, {authentication, authentication()}
].
common_listener_opts() ->
[ {enable, sc(boolean(), true)}
, {bind, sc(union(ip_port(), integer()))}
, {acceptors, sc(integer(), 16)}
, {max_connections, sc(integer(), 1024)}
, {max_conn_rate, sc(integer())}
%, {rate_limit, sc(comma_separated_list())}
, {authentication, authentication()}
, {mountpoint, sc(binary(), undefined)}
, {access_rules, sc(hoconsc:array(string()), [])}
].
@ -242,8 +243,8 @@ udp_opts() ->
[{udp, sc_meta(ref(udp_opts), #{})}].
proxy_protocol_opts() ->
[ {proxy_protocol, sc(boolean())}
, {proxy_protocol_timeout, sc(duration())}
[ {proxy_protocol, sc(boolean(), false)}
, {proxy_protocol_timeout, sc(duration(), "15s")}
].
sc(Type) ->

View File

@ -109,10 +109,15 @@ init(ConnInfo = #{peername := {PeerHost, _},
sockname := {_, SockPort}}, Option) ->
Peercert = maps:get(peercert, ConnInfo, undefined),
Mountpoint = maps:get(mountpoint, Option, undefined),
ListenerId = case maps:get(listener, Option, undefined) of
undefined -> undefined;
{GwName, Type, LisName} ->
emqx_gateway_utils:listener_id(GwName, Type, LisName)
end,
ClientInfo = setting_peercert_infos(
Peercert,
#{ zone => default
, listener => {tcp, default}
, listener => ListenerId
, protocol => stomp
, peerhost => PeerHost
, sockport => SockPort

View File

@ -106,6 +106,7 @@ start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) ->
Name = emqx_gateway_utils:listener_id(GwName, Type, LisName),
NCfg = Cfg#{
ctx => Ctx,
listener => {GwName, Type, LisName}, %% Used for authn
frame_mod => emqx_stomp_frame,
chann_mod => emqx_stomp_channel
},