508 lines
15 KiB
Erlang
508 lines
15 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.
|
|
%%--------------------------------------------------------------------
|
|
|
|
-module(emqx_gateway_schema).
|
|
|
|
-behaviour(hocon_schema).
|
|
|
|
-dialyzer(no_return).
|
|
-dialyzer(no_match).
|
|
-dialyzer(no_contracts).
|
|
-dialyzer(no_unused).
|
|
-dialyzer(no_fail_call).
|
|
|
|
-include_lib("hocon/include/hoconsc.hrl").
|
|
-include_lib("typerefl/include/types.hrl").
|
|
-include_lib("emqx_auth/include/emqx_authn_chains.hrl").
|
|
|
|
-type ip_port() :: tuple() | integer().
|
|
-type duration() :: non_neg_integer().
|
|
-type duration_s() :: non_neg_integer().
|
|
-type bytesize() :: pos_integer().
|
|
|
|
-typerefl_from_string({ip_port/0, emqx_schema, to_ip_port}).
|
|
-typerefl_from_string({duration/0, emqx_schema, to_duration}).
|
|
-typerefl_from_string({duration_s/0, emqx_schema, to_duration_s}).
|
|
-typerefl_from_string({bytesize/0, emqx_schema, to_bytesize}).
|
|
|
|
-reflect_type([
|
|
duration/0,
|
|
duration_s/0,
|
|
bytesize/0,
|
|
ip_port/0
|
|
]).
|
|
-elvis([{elvis_style, dont_repeat_yourself, disable}]).
|
|
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
|
|
|
|
-export([namespace/0, roots/0, fields/1, desc/1, tags/0]).
|
|
|
|
-export([proxy_protocol_opts/0]).
|
|
|
|
-export([mountpoint/0, mountpoint/1, gateway_common_options/0, gateway_schema/1, gateway_names/0]).
|
|
|
|
-export([ws_listener/0, wss_listener/0, ws_opts/2]).
|
|
|
|
namespace() -> gateway.
|
|
|
|
tags() ->
|
|
[<<"Gateway">>].
|
|
|
|
roots() ->
|
|
[{gateway, sc(ref(?MODULE, gateway), #{importance => ?IMPORTANCE_LOW})}].
|
|
|
|
fields(gateway) ->
|
|
lists:map(
|
|
fun(#{name := Name, config_schema_module := Mod}) ->
|
|
{Name,
|
|
sc(
|
|
ref(Mod, Name),
|
|
#{
|
|
required => {false, recursively},
|
|
desc => ?DESC(Name)
|
|
}
|
|
)}
|
|
end,
|
|
emqx_gateway_utils:find_gateway_definitions()
|
|
);
|
|
fields(clientinfo_override) ->
|
|
[
|
|
{username, sc(binary(), #{desc => ?DESC(gateway_common_clientinfo_override_username)})},
|
|
{password,
|
|
sc(binary(), #{
|
|
desc => ?DESC(gateway_common_clientinfo_override_password),
|
|
sensitive => true,
|
|
format => <<"password">>,
|
|
converter => fun emqx_schema:password_converter/2
|
|
})},
|
|
{clientid, sc(binary(), #{desc => ?DESC(gateway_common_clientinfo_override_clientid)})}
|
|
];
|
|
fields(udp_listeners) ->
|
|
[
|
|
{udp, sc(map(name, ref(udp_listener)), #{desc => ?DESC(listener_name_to_settings_map)})},
|
|
{dtls, sc(map(name, ref(dtls_listener)), #{desc => ?DESC(listener_name_to_settings_map)})}
|
|
];
|
|
fields(tcp_listeners) ->
|
|
[
|
|
{tcp, sc(map(name, ref(tcp_listener)), #{desc => ?DESC(listener_name_to_settings_map)})},
|
|
{ssl, sc(map(name, ref(ssl_listener)), #{desc => ?DESC(listener_name_to_settings_map)})}
|
|
];
|
|
fields(tcp_udp_listeners) ->
|
|
[
|
|
{tcp, sc(map(name, ref(tcp_listener)), #{desc => ?DESC(listener_name_to_settings_map)})},
|
|
{ssl, sc(map(name, ref(ssl_listener)), #{desc => ?DESC(listener_name_to_settings_map)})},
|
|
{udp, sc(map(name, ref(udp_listener)), #{desc => ?DESC(listener_name_to_settings_map)})},
|
|
{dtls, sc(map(name, ref(dtls_listener)), #{desc => ?DESC(listener_name_to_settings_map)})}
|
|
];
|
|
fields(tcp_listener) ->
|
|
%% some special configs for tcp listener
|
|
[
|
|
{acceptors, sc(integer(), #{default => 16, desc => ?DESC(tcp_listener_acceptors)})}
|
|
] ++
|
|
tcp_opts() ++
|
|
proxy_protocol_opts() ++
|
|
common_listener_opts();
|
|
fields(ssl_listener) ->
|
|
fields(tcp_listener) ++
|
|
[
|
|
{ssl_options,
|
|
sc(
|
|
hoconsc:ref(emqx_schema, "listener_ssl_opts"),
|
|
#{
|
|
desc => ?DESC(ssl_listener_options),
|
|
validator => fun emqx_schema:validate_server_ssl_opts/1
|
|
}
|
|
)}
|
|
];
|
|
fields(ws_listener) ->
|
|
emqx_gateway_schema:ws_listener() ++
|
|
[{websocket, sc(ref(websocket), #{})}];
|
|
fields(wss_listener) ->
|
|
emqx_gateway_schema:wss_listener() ++
|
|
[{websocket, sc(ref(websocket), #{})}];
|
|
fields(websocket) ->
|
|
DefaultPath = <<>>,
|
|
SubProtocols = <<>>,
|
|
emqx_gateway_schema:ws_opts(DefaultPath, SubProtocols);
|
|
fields(udp_listener) ->
|
|
[
|
|
%% some special configs for udp listener
|
|
] ++
|
|
udp_opts() ++
|
|
common_listener_opts();
|
|
fields(dtls_listener) ->
|
|
[{acceptors, sc(integer(), #{default => 16, desc => ?DESC(dtls_listener_acceptors)})}] ++
|
|
fields(udp_listener) ++
|
|
[
|
|
{dtls_options,
|
|
sc(ref(dtls_opts), #{
|
|
desc => ?DESC(dtls_listener_dtls_opts),
|
|
validator => fun emqx_schema:validate_server_ssl_opts/1
|
|
})}
|
|
];
|
|
fields(udp_opts) ->
|
|
[
|
|
{active_n,
|
|
sc(
|
|
integer(),
|
|
#{
|
|
default => 100,
|
|
desc => ?DESC(udp_listener_active_n)
|
|
}
|
|
)},
|
|
{recbuf, sc(bytesize(), #{desc => ?DESC(udp_listener_recbuf)})},
|
|
{sndbuf, sc(bytesize(), #{desc => ?DESC(udp_listener_sndbuf)})},
|
|
{buffer, sc(bytesize(), #{desc => ?DESC(udp_listener_buffer)})},
|
|
{reuseaddr, sc(boolean(), #{default => true, desc => ?DESC(udp_listener_reuseaddr)})}
|
|
];
|
|
fields(dtls_opts) ->
|
|
emqx_schema:server_ssl_opts_schema(
|
|
#{
|
|
depth => 10,
|
|
reuse_sessions => true,
|
|
versions => dtls_all_available
|
|
},
|
|
_IsRanchListener = false
|
|
).
|
|
|
|
desc(gateway) ->
|
|
"EMQX Gateway configuration root.";
|
|
desc(clientinfo_override) ->
|
|
"ClientInfo override.";
|
|
desc(udp_listeners) ->
|
|
"Settings for the UDP listeners.";
|
|
desc(tcp_listeners) ->
|
|
"Settings for the TCP listeners.";
|
|
desc(tcp_udp_listeners) ->
|
|
"Settings for TCP and UDP listeners.";
|
|
desc(tcp_listener) ->
|
|
"Settings for TCP listener.";
|
|
desc(ssl_listener) ->
|
|
"Settings for SSL listener.";
|
|
desc(udp_listener) ->
|
|
"Settings for UDP listener.";
|
|
desc(dtls_listener) ->
|
|
"Settings for DTLS listener.";
|
|
desc(udp_opts) ->
|
|
"Settings for UDP sockets.";
|
|
desc(dtls_opts) ->
|
|
"Settings for DTLS protocol.";
|
|
desc(websocket) ->
|
|
"Websocket options";
|
|
desc(_) ->
|
|
undefined.
|
|
|
|
authentication_schema() ->
|
|
sc(
|
|
emqx_authn_schema:authenticator_type(),
|
|
#{
|
|
required => {false, recursively},
|
|
desc => ?DESC(gateway_common_authentication),
|
|
%% we do not expose this to the user for now
|
|
importance => ?IMPORTANCE_HIDDEN,
|
|
examples => emqx_authn_api:authenticator_examples()
|
|
}
|
|
).
|
|
|
|
gateway_common_options() ->
|
|
[
|
|
{enable,
|
|
sc(
|
|
boolean(),
|
|
#{
|
|
default => true,
|
|
desc => ?DESC(gateway_common_enable)
|
|
}
|
|
)},
|
|
{enable_stats,
|
|
sc(
|
|
boolean(),
|
|
#{
|
|
default => true,
|
|
desc => ?DESC(gateway_common_enable_stats)
|
|
}
|
|
)},
|
|
{idle_timeout,
|
|
sc(
|
|
duration(),
|
|
#{
|
|
default => <<"30s">>,
|
|
desc => ?DESC(gateway_common_idle_timeout)
|
|
}
|
|
)},
|
|
{clientinfo_override,
|
|
sc(
|
|
ref(clientinfo_override),
|
|
#{desc => ?DESC(gateway_common_clientinfo_override)}
|
|
)},
|
|
{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication_schema()}
|
|
].
|
|
|
|
mountpoint() ->
|
|
mountpoint(<<"">>).
|
|
mountpoint(Default) ->
|
|
sc(
|
|
binary(),
|
|
#{
|
|
default => iolist_to_binary(Default),
|
|
desc => ?DESC(gateway_mountpoint)
|
|
}
|
|
).
|
|
|
|
ws_listener() ->
|
|
[
|
|
{acceptors, sc(integer(), #{default => 16, desc => ?DESC(tcp_listener_acceptors)})}
|
|
] ++
|
|
tcp_opts() ++
|
|
proxy_protocol_opts() ++
|
|
common_listener_opts().
|
|
|
|
wss_listener() ->
|
|
ws_listener() ++
|
|
[
|
|
{ssl_options,
|
|
sc(
|
|
hoconsc:ref(emqx_schema, "listener_wss_opts"),
|
|
#{
|
|
desc => ?DESC(ssl_listener_options),
|
|
validator => fun emqx_schema:validate_server_ssl_opts/1
|
|
}
|
|
)}
|
|
].
|
|
|
|
ws_opts(DefaultPath, DefaultSubProtocols) when
|
|
is_binary(DefaultPath), is_binary(DefaultSubProtocols)
|
|
->
|
|
[
|
|
{"path",
|
|
sc(
|
|
string(),
|
|
#{
|
|
default => DefaultPath,
|
|
desc => ?DESC(fields_ws_opts_path)
|
|
}
|
|
)},
|
|
{"piggyback",
|
|
sc(
|
|
hoconsc:enum([single, multiple]),
|
|
#{
|
|
default => single,
|
|
desc => ?DESC(fields_ws_opts_piggyback)
|
|
}
|
|
)},
|
|
{"compress",
|
|
sc(
|
|
boolean(),
|
|
#{
|
|
default => false,
|
|
desc => ?DESC(fields_ws_opts_compress)
|
|
}
|
|
)},
|
|
{"idle_timeout",
|
|
sc(
|
|
duration(),
|
|
#{
|
|
default => <<"7200s">>,
|
|
desc => ?DESC(fields_ws_opts_idle_timeout)
|
|
}
|
|
)},
|
|
{"max_frame_size",
|
|
sc(
|
|
hoconsc:union([infinity, integer()]),
|
|
#{
|
|
default => infinity,
|
|
desc => ?DESC(fields_ws_opts_max_frame_size)
|
|
}
|
|
)},
|
|
{"fail_if_no_subprotocol",
|
|
sc(
|
|
boolean(),
|
|
#{
|
|
default => true,
|
|
desc => ?DESC(fields_ws_opts_fail_if_no_subprotocol)
|
|
}
|
|
)},
|
|
{"supported_subprotocols",
|
|
sc(
|
|
emqx_schema:comma_separated_list(),
|
|
#{
|
|
default => DefaultSubProtocols,
|
|
desc => ?DESC(fields_ws_opts_supported_subprotocols)
|
|
}
|
|
)},
|
|
{"check_origin_enable",
|
|
sc(
|
|
boolean(),
|
|
#{
|
|
default => false,
|
|
desc => ?DESC(fields_ws_opts_check_origin_enable)
|
|
}
|
|
)},
|
|
{"allow_origin_absence",
|
|
sc(
|
|
boolean(),
|
|
#{
|
|
default => true,
|
|
desc => ?DESC(fields_ws_opts_allow_origin_absence)
|
|
}
|
|
)},
|
|
{"check_origins",
|
|
sc(
|
|
emqx_schema:comma_separated_binary(),
|
|
#{
|
|
default => <<"http://localhost:18083, http://127.0.0.1:18083">>,
|
|
desc => ?DESC(fields_ws_opts_check_origins)
|
|
}
|
|
)},
|
|
{"proxy_address_header",
|
|
sc(
|
|
string(),
|
|
#{
|
|
default => <<"x-forwarded-for">>,
|
|
desc => ?DESC(fields_ws_opts_proxy_address_header)
|
|
}
|
|
)},
|
|
{"proxy_port_header",
|
|
sc(
|
|
string(),
|
|
#{
|
|
default => <<"x-forwarded-port">>,
|
|
desc => ?DESC(fields_ws_opts_proxy_port_header)
|
|
}
|
|
)},
|
|
{"deflate_opts",
|
|
sc(
|
|
ref(emqx_schema, "deflate_opts"),
|
|
#{}
|
|
)}
|
|
].
|
|
|
|
common_listener_opts() ->
|
|
[
|
|
{enable,
|
|
sc(
|
|
boolean(),
|
|
#{
|
|
default => true,
|
|
desc => ?DESC(gateway_common_listener_enable)
|
|
}
|
|
)},
|
|
{bind,
|
|
sc(
|
|
ip_port(),
|
|
#{desc => ?DESC(gateway_common_listener_bind)}
|
|
)},
|
|
{max_connections,
|
|
sc(
|
|
hoconsc:union([pos_integer(), infinity]),
|
|
#{
|
|
default => 1024,
|
|
desc => ?DESC(gateway_common_listener_max_connections)
|
|
}
|
|
)},
|
|
{max_conn_rate,
|
|
sc(
|
|
integer(),
|
|
#{
|
|
default => 1000,
|
|
desc => ?DESC(gateway_common_listener_max_conn_rate)
|
|
}
|
|
)},
|
|
{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication_schema()},
|
|
{"enable_authn",
|
|
sc(
|
|
boolean(),
|
|
#{
|
|
desc => ?DESC(gateway_common_listener_enable_authn),
|
|
default => true
|
|
}
|
|
)},
|
|
{mountpoint,
|
|
sc(
|
|
binary(),
|
|
#{
|
|
default => undefined,
|
|
desc => ?DESC(gateway_mountpoint)
|
|
}
|
|
)},
|
|
{access_rules,
|
|
sc(
|
|
hoconsc:array(string()),
|
|
#{
|
|
default => [],
|
|
desc => ?DESC(gateway_common_listener_access_rules)
|
|
}
|
|
)}
|
|
].
|
|
|
|
tcp_opts() ->
|
|
[{tcp_options, sc(ref(emqx_schema, "tcp_opts"), #{desc => ?DESC(tcp_listener_tcp_opts)})}].
|
|
|
|
udp_opts() ->
|
|
[{udp_options, sc(ref(udp_opts), #{})}].
|
|
|
|
proxy_protocol_opts() ->
|
|
[
|
|
{proxy_protocol,
|
|
sc(
|
|
boolean(),
|
|
#{
|
|
default => false,
|
|
desc => ?DESC(tcp_listener_proxy_protocol)
|
|
}
|
|
)},
|
|
{proxy_protocol_timeout,
|
|
sc(
|
|
duration(),
|
|
#{
|
|
default => <<"3s">>,
|
|
desc => ?DESC(tcp_listener_proxy_protocol_timeout)
|
|
}
|
|
)}
|
|
].
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% dynamic schemas
|
|
|
|
gateway_schema(Name) ->
|
|
case emqx_gateway_utils:find_gateway_definition(Name) of
|
|
{ok, #{config_schema_module := SchemaMod}} ->
|
|
SchemaMod:fields(Name);
|
|
{error, _} = Error ->
|
|
throw(Error)
|
|
end.
|
|
|
|
gateway_names() ->
|
|
Definations = emqx_gateway_utils:find_gateway_definitions(),
|
|
[
|
|
Name
|
|
|| #{name := Name} = Defination <- Definations,
|
|
emqx_gateway_utils:check_gateway_edition(Defination)
|
|
].
|
|
%%--------------------------------------------------------------------
|
|
%% helpers
|
|
|
|
sc(Type, Meta) ->
|
|
hoconsc:mk(Type, Meta).
|
|
|
|
map(Name, Type) ->
|
|
hoconsc:map(Name, Type).
|
|
|
|
ref(StructName) ->
|
|
ref(?MODULE, StructName).
|
|
|
|
ref(Mod, Field) ->
|
|
hoconsc:ref(Mod, Field).
|