emqx/apps/emqx_gateway/src/emqx_gateway_conf.erl

899 lines
30 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2021-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
%% @doc The gateway configuration management module
-module(emqx_gateway_conf).
-behaviour(emqx_config_handler).
-behaviour(emqx_config_backup).
%% Load/Unload
-export([
load/0,
unload/0
]).
%% APIs
-export([
gateway/1,
load_gateway/2,
update_gateway/2,
unload_gateway/1
]).
-export([
listeners/1,
listener/1,
add_listener/3,
update_listener/3,
remove_listener/2
]).
-export([
add_authn/2,
add_authn/3,
update_authn/2,
update_authn/3,
remove_authn/1,
remove_authn/2
]).
-export([get_bind/1]).
%% internal exports
-export([
unconvert_listeners/1,
convert_listeners/2
]).
%% callbacks for emqx_config_handler
-export([
pre_config_update/3,
post_config_update/5
]).
%% Data backup
-export([
import_config/1
]).
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx_auth/include/emqx_authn_chains.hrl").
-define(AUTHN_BIN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY).
-type atom_or_bin() :: atom() | binary().
-type ok_or_err() :: ok | {error, term()}.
-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_options">> orelse T == <<"dtls_options">>)).
-define(IGNORE_KEYS, [<<"listeners">>, ?AUTHN_BIN]).
%%----------------------------------------------------------------------------------------
%% Load/Unload
%%----------------------------------------------------------------------------------------
-define(GATEWAY, [gateway]).
-spec load() -> ok.
load() ->
emqx_conf:add_handler(?GATEWAY, ?MODULE).
-spec unload() -> ok.
unload() ->
emqx_conf:remove_handler(?GATEWAY).
%%----------------------------------------------------------------------------------------
%% APIs
-spec load_gateway(atom_or_bin(), map()) -> map_or_err().
load_gateway(GwName, Conf) ->
NConf =
case maps:take(<<"listeners">>, Conf) of
error -> Conf;
{Ls, Conf1} -> Conf1#{<<"listeners">> => unconvert_listeners(Ls)}
end,
ret_gw(GwName, update({?FUNCTION_NAME, bin(GwName), NConf})).
%% @doc convert listener array to map
unconvert_listeners(Ls) when is_list(Ls) ->
lists:foldl(
fun(Lis, Acc) ->
{[Type, Name], Lis1} = maps_key_take([<<"type">>, <<"name">>], Lis),
_ = validate_listener_name(Name),
NLis1 = maps:without([<<"id">>, <<"running">>], Lis1),
emqx_utils_maps:deep_merge(Acc, #{Type => #{Name => NLis1}})
end,
#{},
Ls
).
maps_key_take(Ks, M) ->
maps_key_take(Ks, M, []).
maps_key_take([], M, Acc) ->
{lists:reverse(Acc), M};
maps_key_take([K | Ks], M, Acc) ->
case maps:take(K, M) of
error -> error(bad_key);
{V, M1} -> maps_key_take(Ks, M1, [V | Acc])
end.
validate_listener_name(Name) ->
try
{match, _} = re:run(Name, "^[a-zA-Z][0-9a-zA-Z_-]*$"),
ok
catch
_:_ ->
error(
{badconf, #{
key => name,
value => Name,
reason => bad_listener_name
}}
)
end.
-spec update_gateway(atom_or_bin(), map()) -> map_or_err().
update_gateway(GwName, Conf0) ->
Exclude0 = [listeners, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM],
Exclude1 = [atom_to_binary(K, utf8) || K <- Exclude0],
Conf = maps:without(Exclude0 ++ Exclude1, Conf0),
ret_gw(GwName, update({?FUNCTION_NAME, bin(GwName), Conf})).
%% FIXME: delete cert files ??
-spec unload_gateway(atom_or_bin()) -> ok_or_err().
unload_gateway(GwName) ->
ret_ok_err(update({?FUNCTION_NAME, bin(GwName)})).
%% @doc Get the gateway configurations.
%% Missing fields are filled with default values. This function is typically
%% used to show the user what configuration value is currently in effect.
-spec gateway(atom_or_bin()) -> map().
gateway(GwName0) ->
GwName = bin(GwName0),
Path = [<<"gateway">>, GwName],
RawConf = emqx_config:fill_defaults(
emqx_config:get_root_raw(Path)
),
Confs = emqx_utils_maps:jsonable_map(
emqx_utils_maps:deep_get(Path, RawConf)
),
LsConf = maps:get(<<"listeners">>, Confs, #{}),
Confs#{<<"listeners">> => convert_listeners(GwName, LsConf)}.
%% @doc convert listeners map to array
convert_listeners(GwName, Ls) when is_map(Ls) ->
lists:append([
do_convert_listener(GwName, Type, maps:to_list(Conf))
|| {Type, Conf} <- maps:to_list(Ls)
]).
do_convert_listener(GwName, LType, Conf) ->
[
do_convert_listener2(GwName, LType, LName, LConf)
|| {LName, LConf} <- Conf, is_map(LConf)
].
do_convert_listener2(GwName, LType, LName, LConf) ->
ListenerId = emqx_gateway_utils:listener_id(GwName, LType, LName),
LConf#{
id => ListenerId,
type => LType,
name => LName
}.
get_bind(#{bind := Bind}) ->
emqx_gateway_utils:parse_listenon(Bind);
get_bind(#{<<"bind">> := Bind}) ->
emqx_gateway_utils:parse_listenon(Bind).
-spec listeners(atom_or_bin()) -> [map()].
listeners(GwName0) ->
GwName = bin(GwName0),
RawConf = emqx_config:fill_defaults(
emqx_config:get_root_raw([<<"gateway">>])
),
Listeners = emqx_utils_maps:jsonable_map(
emqx_utils_maps:deep_get(
[<<"gateway">>, GwName, <<"listeners">>], RawConf
)
),
convert_listeners(GwName, Listeners).
-spec listener(binary()) -> {ok, map()} | {error, not_found} | {error, any()}.
listener(ListenerId) ->
{GwName, Type, LName} = emqx_gateway_utils:parse_listener_id(ListenerId),
RootConf = emqx_config:fill_defaults(
emqx_config:get_root_raw([<<"gateway">>])
),
try
Path = [<<"gateway">>, GwName, <<"listeners">>, Type, LName],
LConf = emqx_utils_maps:deep_get(Path, RootConf),
Running = emqx_gateway_utils:is_running(
binary_to_existing_atom(ListenerId), LConf
),
{ok,
emqx_utils_maps:jsonable_map(
LConf#{
id => ListenerId,
type => Type,
name => LName,
running => Running
}
)}
catch
error:{config_not_found, _} ->
{error, not_found};
_Class:Reason ->
{error, Reason}
end.
-spec add_listener(atom_or_bin(), listener_ref(), map()) -> map_or_err().
add_listener(GwName, ListenerRef, Conf) ->
ret_listener_or_err(
GwName,
ListenerRef,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})
).
-spec update_listener(atom_or_bin(), listener_ref(), map()) -> map_or_err().
update_listener(GwName, ListenerRef, Conf) ->
ret_listener_or_err(
GwName,
ListenerRef,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})
).
-spec remove_listener(atom_or_bin(), listener_ref()) -> ok_or_err().
remove_listener(GwName, ListenerRef) ->
ret_ok_err(update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef)})).
-spec add_authn(atom_or_bin(), map()) -> map_or_err().
add_authn(GwName, Conf) ->
ret_authn(GwName, update({?FUNCTION_NAME, bin(GwName), Conf})).
-spec add_authn(atom_or_bin(), listener_ref(), map()) -> map_or_err().
add_authn(GwName, ListenerRef, Conf) ->
ret_authn(
GwName,
ListenerRef,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})
).
-spec update_authn(atom_or_bin(), map()) -> map_or_err().
update_authn(GwName, Conf) ->
ret_authn(GwName, update({?FUNCTION_NAME, bin(GwName), Conf})).
-spec update_authn(atom_or_bin(), listener_ref(), map()) -> map_or_err().
update_authn(GwName, ListenerRef, Conf) ->
ret_authn(
GwName,
ListenerRef,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})
).
-spec remove_authn(atom_or_bin()) -> ok_or_err().
remove_authn(GwName) ->
ret_ok_err(update({?FUNCTION_NAME, bin(GwName)})).
-spec remove_authn(atom_or_bin(), listener_ref()) -> ok_or_err().
remove_authn(GwName, ListenerRef) ->
ret_ok_err(update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef)})).
%% @private
update(Req) ->
res(emqx_conf:update([gateway], Req, #{override_to => cluster})).
res({ok, Result}) -> {ok, Result};
res({error, {pre_config_update, ?MODULE, Reason}}) -> {error, Reason};
res({error, {post_config_update, ?MODULE, Reason}}) -> {error, Reason};
res({error, Reason}) -> {error, Reason}.
bin({LType, LName}) ->
{bin(LType), bin(LName)};
bin(A) when is_atom(A) ->
atom_to_binary(A);
bin(B) when is_binary(B) ->
B.
ret_ok_err({ok, _}) -> ok;
ret_ok_err(Err) -> Err.
ret_gw(GwName, {ok, #{raw_config := GwConf}}) ->
GwConf1 = emqx_utils_maps:deep_get([bin(GwName)], GwConf),
LsConf = emqx_utils_maps:deep_get(
[bin(GwName), <<"listeners">>],
GwConf,
#{}
),
NLsConf =
lists:foldl(
fun({LType, SubConf}, Acc) ->
NLConfs =
lists:map(
fun({LName, LConf}) ->
do_convert_listener2(GwName, LType, LName, LConf)
end,
maps:to_list(SubConf)
),
[NLConfs | Acc]
end,
[],
maps:to_list(LsConf)
),
{ok, maps:merge(GwConf1, #{<<"listeners">> => lists:append(NLsConf)})};
ret_gw(_GwName, Err) ->
Err.
ret_authn(GwName, {ok, #{raw_config := GwConf}}) ->
Authn = emqx_utils_maps:deep_get(
[bin(GwName), <<"authentication">>],
GwConf
),
{ok, Authn};
ret_authn(_GwName, Err) ->
Err.
ret_authn(GwName, {LType, LName}, {ok, #{raw_config := GwConf}}) ->
Authn = emqx_utils_maps:deep_get(
[
bin(GwName),
<<"listeners">>,
bin(LType),
bin(LName),
<<"authentication">>
],
GwConf
),
{ok, Authn};
ret_authn(_, _, Err) ->
Err.
ret_listener_or_err(GwName, {LType, LName}, {ok, #{raw_config := GwConf}}) ->
LConf = emqx_utils_maps:deep_get(
[bin(GwName), <<"listeners">>, bin(LType), bin(LName)],
GwConf
),
{ok, do_convert_listener2(GwName, LType, LName, LConf)};
ret_listener_or_err(_, _, Err) ->
Err.
%%----------------------------------------------------------------------------------------
%% Data backup
%%----------------------------------------------------------------------------------------
import_config(RawConf) ->
GatewayConf = maps:get(<<"gateway">>, RawConf, #{}),
OldGatewayConf = emqx:get_raw_config([<<"gateway">>], #{}),
MergedConf = maps:merge(OldGatewayConf, GatewayConf),
case emqx_conf:update([gateway], MergedConf, #{override_to => cluster}) of
{ok, #{raw_config := NewRawConf}} ->
Changed = maps:get(changed, emqx_utils_maps:diff_maps(NewRawConf, OldGatewayConf)),
ChangedPaths = [[gateway, GwName] || GwName <- maps:keys(Changed)],
{ok, #{root_key => gateway, changed => ChangedPaths}};
Error ->
{error, #{root_key => gateway, reason => Error}}
end.
%%----------------------------------------------------------------------------------------
%% Config Handler
%%----------------------------------------------------------------------------------------
-spec pre_config_update(
list(atom()),
emqx_config:update_request(),
emqx_config:raw_config()
) ->
{ok, emqx_config:update_request()} | {error, term()}.
pre_config_update(?GATEWAY, {load_gateway, GwName, Conf}, RawConf) ->
case maps:get(GwName, RawConf, undefined) of
undefined ->
NConf = tune_gw_certs(fun convert_certs/2, GwName, Conf),
{ok, emqx_utils_maps:deep_put([GwName], RawConf, NConf)};
_ ->
badres_gateway(already_exist, GwName)
end;
pre_config_update(?GATEWAY, {update_gateway, GwName, Conf}, RawConf) ->
case maps:get(GwName, RawConf, undefined) of
undefined ->
badres_gateway(not_found, GwName);
GwRawConf ->
Conf1 = maps:without(?IGNORE_KEYS, Conf),
NConf = tune_gw_certs(fun convert_certs/2, GwName, Conf1),
NConf1 = maps:merge(GwRawConf, NConf),
{ok, emqx_utils_maps:deep_put([GwName], RawConf, NConf1)}
end;
pre_config_update(?GATEWAY, {unload_gateway, GwName}, RawConf) ->
{ok, maps:remove(GwName, RawConf)};
pre_config_update(?GATEWAY, {add_listener, GwName, {LType, LName}, Conf}, RawConf) ->
case get_listener(GwName, LType, LName, RawConf) of
undefined ->
NConf = convert_certs(certs_dir(GwName), Conf),
NListener = #{LType => #{LName => NConf}},
{ok,
emqx_utils_maps:deep_merge(
RawConf,
#{GwName => #{<<"listeners">> => NListener}}
)};
_ ->
badres_listener(already_exist, GwName, LType, LName)
end;
pre_config_update(?GATEWAY, {update_listener, GwName, {LType, LName}, Conf}, RawConf) ->
case get_listener(GwName, LType, LName, RawConf) of
undefined ->
badres_listener(not_found, GwName, LType, LName);
_OldConf ->
NConf = convert_certs(certs_dir(GwName), Conf),
NRawConf = emqx_utils_maps:deep_put(
[GwName, <<"listeners">>, LType, LName],
RawConf,
NConf
),
{ok, NRawConf}
end;
pre_config_update(?GATEWAY, {remove_listener, GwName, {LType, LName}}, RawConf) ->
case get_listener(GwName, LType, LName, RawConf) of
undefined ->
{ok, RawConf};
_OldConf ->
Path = [GwName, <<"listeners">>, LType, LName],
{ok, emqx_utils_maps:deep_remove(Path, RawConf)}
end;
pre_config_update(?GATEWAY, {add_authn, GwName, Conf}, RawConf) ->
case get_authn(GwName, RawConf) of
undefined ->
CertsDir = authn_certs_dir(GwName, Conf),
Conf1 = emqx_authn_config:convert_certs(CertsDir, Conf),
{ok,
emqx_utils_maps:deep_merge(
RawConf,
#{GwName => #{?AUTHN_BIN => Conf1}}
)};
_ ->
badres_authn(already_exist, GwName)
end;
pre_config_update(?GATEWAY, {add_authn, GwName, {LType, LName}, Conf}, RawConf) ->
case get_listener(GwName, LType, LName, RawConf) of
undefined ->
badres_listener(not_found, GwName, LType, LName);
Listener ->
case maps:get(?AUTHN_BIN, Listener, undefined) of
undefined ->
CertsDir = authn_certs_dir(GwName, LType, LName, Conf),
Conf1 = emqx_authn_config:convert_certs(CertsDir, Conf),
NListener = maps:put(?AUTHN_BIN, Conf1, Listener),
NGateway = #{
GwName =>
#{
<<"listeners">> =>
#{LType => #{LName => NListener}}
}
},
{ok, emqx_utils_maps:deep_merge(RawConf, NGateway)};
_ ->
badres_listener_authn(already_exist, GwName, LType, LName)
end
end;
pre_config_update(?GATEWAY, {update_authn, GwName, Conf}, RawConf) ->
Path = [GwName, ?AUTHN_BIN],
case get_authn(GwName, RawConf) of
undefined ->
badres_authn(not_found, GwName);
_OldConf ->
CertsDir = authn_certs_dir(GwName, Conf),
Conf1 = emqx_authn_config:convert_certs(CertsDir, Conf),
{ok, emqx_utils_maps:deep_put(Path, RawConf, Conf1)}
end;
pre_config_update(?GATEWAY, {update_authn, GwName, {LType, LName}, Conf}, RawConf) ->
Path = [GwName, <<"listeners">>, LType, LName],
case get_listener(GwName, LType, LName, RawConf) of
undefined ->
badres_listener(not_found, GwName, LType, LName);
Listener ->
case maps:get(?AUTHN_BIN, Listener, undefined) of
undefined ->
badres_listener_authn(not_found, GwName, LType, LName);
OldAuthnConf ->
CertsDir = authn_certs_dir(GwName, LType, LName, OldAuthnConf),
Conf1 = emqx_authn_config:convert_certs(CertsDir, Conf),
NListener = maps:put(
?AUTHN_BIN,
Conf1,
Listener
),
{ok, emqx_utils_maps:deep_put(Path, RawConf, NListener)}
end
end;
pre_config_update(?GATEWAY, {remove_authn, GwName}, RawConf) ->
Path = [GwName, ?AUTHN_BIN],
{ok, emqx_utils_maps:deep_remove(Path, RawConf)};
pre_config_update(?GATEWAY, {remove_authn, GwName, {LType, LName}}, RawConf) ->
Path = [GwName, <<"listeners">>, LType, LName, ?AUTHN_BIN],
{ok, emqx_utils_maps:deep_remove(Path, RawConf)};
pre_config_update(?GATEWAY, NewRawConf0 = #{}, OldRawConf = #{}) ->
%% FIXME don't support gateway's listener's authn update.
%% load all authentications
NewRawConf1 = pre_load_authentications(NewRawConf0, OldRawConf),
%% load all listeners
NewRawConf2 = pre_load_listeners(NewRawConf1, OldRawConf),
%% load all gateway
NewRawConf3 = pre_load_gateways(NewRawConf2, OldRawConf),
{ok, NewRawConf3};
pre_config_update(Path, UnknownReq, _RawConf) ->
?SLOG(error, #{
msg => "unknown_gateway_update_request",
request => UnknownReq,
path => Path
}),
{error, badreq}.
pre_load_gateways(NewConf, OldConf) ->
%% unload old gateways
maps:foreach(
fun(GwName, _OldGwConf) ->
case maps:find(GwName, NewConf) of
error -> pre_config_update(?GATEWAY, {unload_gateway, GwName}, OldConf);
_ -> ok
end
end,
OldConf
),
%% load/update gateways
maps:map(
fun(GwName, NewGwConf) ->
case maps:find(GwName, OldConf) of
{ok, NewGwConf} ->
NewGwConf;
{ok, _OldGwConf} ->
{ok, #{GwName := NewGwConf1}} = pre_config_update(
?GATEWAY, {update_gateway, GwName, NewGwConf}, OldConf
),
%% update gateway should pass through ignore keys(listener/authn)
PassThroughConf = maps:with(?IGNORE_KEYS, NewGwConf),
NewGwConf2 = maps:without(?IGNORE_KEYS, NewGwConf1),
maps:merge(NewGwConf2, PassThroughConf);
error ->
{ok, #{GwName := NewGwConf1}} = pre_config_update(
?GATEWAY, {load_gateway, GwName, NewGwConf}, OldConf
),
NewGwConf1
end
end,
NewConf
).
pre_load_listeners(NewConf, OldConf) ->
%% remove listeners
maps:foreach(
fun(GwName, GwConf) ->
Listeners = maps:get(<<"listeners">>, GwConf, #{}),
remove_listeners(GwName, NewConf, OldConf, Listeners)
end,
OldConf
),
%% add/update listeners
maps:map(
fun(GwName, GwConf) ->
Listeners = maps:get(<<"listeners">>, GwConf, #{}),
NewListeners = create_or_update_listeners(GwName, OldConf, Listeners),
maps:put(<<"listeners">>, NewListeners, GwConf)
end,
NewConf
).
create_or_update_listeners(GwName, OldConf, Listeners) ->
maps:map(
fun(LType, LConf) ->
maps:map(
fun(LName, LConf1) ->
NConf =
case get_listener(GwName, LType, LName, OldConf) of
undefined ->
{ok, NConf0} =
pre_config_update(
?GATEWAY,
{add_listener, GwName, {LType, LName}, LConf1},
OldConf
),
NConf0;
_ ->
{ok, NConf0} =
pre_config_update(
?GATEWAY,
{update_listener, GwName, {LType, LName}, LConf1},
OldConf
),
NConf0
end,
get_listener(GwName, LType, LName, NConf)
end,
LConf
)
end,
Listeners
).
remove_listeners(GwName, NewConf, OldConf, Listeners) ->
maps:foreach(
fun(LType, LConf) ->
maps:foreach(
fun(LName, _LConf1) ->
case get_listener(GwName, LType, LName, NewConf) of
undefined ->
pre_config_update(
?GATEWAY, {remove_listener, GwName, {LType, LName}}, OldConf
);
_ ->
ok
end
end,
LConf
)
end,
Listeners
).
get_listener(GwName, LType, LName, NewConf) ->
emqx_utils_maps:deep_get(
[GwName, <<"listeners">>, LType, LName], NewConf, undefined
).
get_authn(GwName, Conf) ->
emqx_utils_maps:deep_get([GwName, ?AUTHN_BIN], Conf, undefined).
pre_load_authentications(NewConf, OldConf) ->
%% remove authentications when not in new config
maps:foreach(
fun(GwName, OldGwConf) ->
case
maps:get(?AUTHN_BIN, OldGwConf, undefined) =/= undefined andalso
get_authn(GwName, NewConf) =:= undefined
of
true ->
pre_config_update(?GATEWAY, {remove_authn, GwName}, OldConf);
false ->
ok
end
end,
OldConf
),
%% add/update authentications
maps:map(
fun(GwName, NewGwConf) ->
case get_authn(GwName, OldConf) of
undefined ->
case maps:get(?AUTHN_BIN, NewGwConf, undefined) of
undefined ->
NewGwConf;
AuthN ->
{ok, #{GwName := #{?AUTHN_BIN := NAuthN}}} =
pre_config_update(?GATEWAY, {add_authn, GwName, AuthN}, OldConf),
maps:put(?AUTHN_BIN, NAuthN, NewGwConf)
end;
OldAuthN ->
case maps:get(?AUTHN_BIN, NewGwConf, undefined) of
undefined ->
NewGwConf;
OldAuthN ->
NewGwConf;
NewAuthN ->
{ok, #{GwName := #{?AUTHN_BIN := NAuthN}}} =
pre_config_update(
?GATEWAY, {update_authn, GwName, NewAuthN}, OldConf
),
maps:put(?AUTHN_BIN, NAuthN, NewGwConf)
end
end
end,
NewConf
).
badres_gateway(not_found, GwName) ->
{error,
{badres, #{
resource => gateway,
gateway => GwName,
reason => not_found
}}};
badres_gateway(already_exist, GwName) ->
{error,
{badres, #{
resource => gateway,
gateway => GwName,
reason => already_exist
}}}.
badres_listener(not_found, GwName, LType, LName) ->
{error,
{badres, #{
resource => listener,
gateway => GwName,
listener => {GwName, LType, LName},
reason => not_found
}}};
badres_listener(already_exist, GwName, LType, LName) ->
{error,
{badres, #{
resource => listener,
gateway => GwName,
listener => {GwName, LType, LName},
reason => already_exist
}}}.
badres_authn(not_found, GwName) ->
{error,
{badres, #{
resource => authn,
gateway => GwName,
reason => not_found
}}};
badres_authn(already_exist, GwName) ->
{error,
{badres, #{
resource => authn,
gateway => GwName,
reason => already_exist
}}}.
badres_listener_authn(not_found, GwName, LType, LName) ->
{error,
{badres, #{
resource => listener_authn,
gateway => GwName,
listener => {GwName, LType, LName},
reason => not_found
}}};
badres_listener_authn(already_exist, GwName, LType, LName) ->
{error,
{badres, #{
resource => listener_authn,
gateway => GwName,
listener => {GwName, LType, LName},
reason => already_exist
}}}.
-spec post_config_update(
list(atom()),
emqx_config:update_request(),
emqx_config:config(),
emqx_config:config(),
emqx_config:app_envs()
) ->
ok | {ok, Result :: any()} | {error, Reason :: term()}.
post_config_update(?GATEWAY, Req, NewConfig, OldConfig, _AppEnvs) when is_tuple(Req) ->
[_Tag, GwName0 | _] = tuple_to_list(Req),
GwName = binary_to_existing_atom(GwName0),
case {maps:get(GwName, NewConfig, undefined), maps:get(GwName, OldConfig, undefined)} of
{undefined, undefined} ->
%% nothing to change
ok;
{undefined, Old} when is_map(Old) ->
emqx_gateway:unload(GwName);
{New, undefined} when is_map(New) ->
emqx_gateway:load(GwName, New);
{New, Old} when is_map(New), is_map(Old) ->
emqx_gateway:update(GwName, New)
end;
post_config_update(?GATEWAY, _Req = #{}, NewConfig, OldConfig, _AppEnvs) ->
%% unload gateways
maps:foreach(
fun(GwName, _OldGwConf) ->
case maps:get(GwName, NewConfig, undefined) of
undefined ->
emqx_gateway:unload(GwName);
_ ->
ok
end
end,
OldConfig
),
%% load/update gateways
maps:foreach(
fun(GwName, NewGwConf) ->
case maps:get(GwName, OldConfig, undefined) of
undefined ->
emqx_gateway:load(GwName, NewGwConf);
_ ->
emqx_gateway:update(GwName, NewGwConf)
end
end,
NewConfig
),
ok.
%%----------------------------------------------------------------------------------------
%% Internal funcs
%%----------------------------------------------------------------------------------------
tune_gw_certs(Fun, GwName, Conf) ->
apply_to_gateway_basic_confs(
Fun,
GwName,
apply_to_listeners(Fun, GwName, Conf)
).
apply_to_listeners(Fun, GwName, Conf) ->
SubDir = certs_dir(GwName),
case maps:get(<<"listeners">>, Conf, undefined) of
undefined ->
Conf;
Liss ->
maps:put(
<<"listeners">>,
maps:map(
fun(_, Lis) ->
maps:map(
fun(_, LisConf) ->
erlang:apply(Fun, [SubDir, LisConf])
end,
Lis
)
end,
Liss
),
Conf
)
end.
apply_to_gateway_basic_confs(Fun, <<"exproto">>, Conf) ->
SvrDir = filename:join(["exproto", "server"]),
HdrDir = filename:join(["exproto", "handler"]),
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.
certs_dir(GwName) when is_binary(GwName) ->
GwName.
authn_certs_dir(GwName, ListenerType, ListenerName, AuthnConf) ->
ChainName = emqx_gateway_utils:listener_chain(GwName, ListenerType, ListenerName),
emqx_authn_config:certs_dir(ChainName, AuthnConf).
authn_certs_dir(GwName, AuthnConf) when is_binary(GwName) ->
authn_certs_dir(binary_to_existing_atom(GwName), AuthnConf);
authn_certs_dir(GwName, AuthnConf) ->
emqx_authn_config:certs_dir(
emqx_gateway_utils:global_chain(GwName),
AuthnConf
).
convert_certs(SubDir, Conf) ->
convert_certs(<<"dtls_options">>, SubDir, convert_certs(<<"ssl_options">>, SubDir, Conf)).
convert_certs(Type, SubDir, Conf) ->
SSL = maps:get(Type, Conf, undefined),
case is_map(SSL) andalso emqx_tls_lib:ensure_ssl_files(SubDir, SSL) of
false ->
Conf;
{ok, NSSL = #{}} ->
Conf#{Type => NSSL};
{error, Reason} ->
?SLOG(error, Reason#{msg => "bad_ssl_config", reason => Reason}),
throw({bad_ssl_config, Reason})
end.