Merge pull request #9802 from sstrigler/EMQX-8469-enable-proxy-protocol-support-in-emqx-dashboard-schema
feat: support HAProxy protocol for dashboard API
This commit is contained in:
commit
ade5316419
|
@ -96,6 +96,16 @@ The configuration is only valid when the inet6 is true."""
|
||||||
zh: "IPv6 only"
|
zh: "IPv6 only"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
proxy_header {
|
||||||
|
desc {
|
||||||
|
en: "Enable support for `HAProxy` header. Be aware once enabled regular HTTP requests can't be handled anymore."
|
||||||
|
zh: "开启对 `HAProxy` 的支持,注意:一旦开启了这个功能,就无法再处理普通的 HTTP 请求了。"
|
||||||
|
}
|
||||||
|
label: {
|
||||||
|
en: "Enable support for HAProxy header"
|
||||||
|
zh: "开启对 `HAProxy` 的支持"
|
||||||
|
}
|
||||||
|
}
|
||||||
desc_dashboard {
|
desc_dashboard {
|
||||||
desc {
|
desc {
|
||||||
en: "Configuration for EMQX dashboard."
|
en: "Configuration for EMQX dashboard."
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{application, emqx_dashboard, [
|
{application, emqx_dashboard, [
|
||||||
{description, "EMQX Web Dashboard"},
|
{description, "EMQX Web Dashboard"},
|
||||||
% strict semver, bump manually!
|
% strict semver, bump manually!
|
||||||
{vsn, "5.0.12"},
|
{vsn, "5.0.13"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, [emqx_dashboard_sup]},
|
{registered, [emqx_dashboard_sup]},
|
||||||
{applications, [kernel, stdlib, mnesia, minirest, emqx]},
|
{applications, [kernel, stdlib, mnesia, minirest, emqx]},
|
||||||
|
|
|
@ -92,8 +92,8 @@ start_listeners(Listeners) ->
|
||||||
},
|
},
|
||||||
Res =
|
Res =
|
||||||
lists:foldl(
|
lists:foldl(
|
||||||
fun({Name, Protocol, Bind, RanchOptions}, Acc) ->
|
fun({Name, Protocol, Bind, RanchOptions, ProtoOpts}, Acc) ->
|
||||||
Minirest = BaseMinirest#{protocol => Protocol},
|
Minirest = BaseMinirest#{protocol => Protocol, protocol_options => ProtoOpts},
|
||||||
case minirest:start(Name, RanchOptions, Minirest) of
|
case minirest:start(Name, RanchOptions, Minirest) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
?ULOG("Listener ~ts on ~ts started.~n", [
|
?ULOG("Listener ~ts on ~ts started.~n", [
|
||||||
|
@ -125,7 +125,7 @@ stop_listeners(Listeners) ->
|
||||||
?SLOG(warning, #{msg => "stop_listener_failed", name => Name, port => Port})
|
?SLOG(warning, #{msg => "stop_listener_failed", name => Name, port => Port})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|| {Name, _, Port, _} <- listeners(Listeners)
|
|| {Name, _, Port, _, _} <- listeners(Listeners)
|
||||||
],
|
],
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
@ -164,7 +164,13 @@ listeners(Listeners) ->
|
||||||
maps:get(enable, Conf) andalso
|
maps:get(enable, Conf) andalso
|
||||||
begin
|
begin
|
||||||
{Conf1, Bind} = ip_port(Conf),
|
{Conf1, Bind} = ip_port(Conf),
|
||||||
{true, {listener_name(Protocol), Protocol, Bind, ranch_opts(Conf1)}}
|
{true, {
|
||||||
|
listener_name(Protocol),
|
||||||
|
Protocol,
|
||||||
|
Bind,
|
||||||
|
ranch_opts(Conf1),
|
||||||
|
proto_opts(Conf1)
|
||||||
|
}}
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
maps:to_list(Listeners)
|
maps:to_list(Listeners)
|
||||||
|
@ -197,7 +203,7 @@ ranch_opts(Options) ->
|
||||||
SocketOpts = maps:fold(
|
SocketOpts = maps:fold(
|
||||||
fun filter_false/3,
|
fun filter_false/3,
|
||||||
[],
|
[],
|
||||||
maps:without([enable, inet6, ipv6_v6only | Keys], Options)
|
maps:without([enable, inet6, ipv6_v6only, proxy_header | Keys], Options)
|
||||||
),
|
),
|
||||||
InetOpts =
|
InetOpts =
|
||||||
case Options of
|
case Options of
|
||||||
|
@ -210,6 +216,9 @@ ranch_opts(Options) ->
|
||||||
end,
|
end,
|
||||||
RanchOpts#{socket_opts => InetOpts ++ SocketOpts}.
|
RanchOpts#{socket_opts => InetOpts ++ SocketOpts}.
|
||||||
|
|
||||||
|
proto_opts(Options) ->
|
||||||
|
maps:with([proxy_header], Options).
|
||||||
|
|
||||||
filter_false(_K, false, S) -> S;
|
filter_false(_K, false, S) -> S;
|
||||||
filter_false(K, V, S) -> [{K, V} | S].
|
filter_false(K, V, S) -> [{K, V} | S].
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,14 @@ common_listener_fields() ->
|
||||||
default => false,
|
default => false,
|
||||||
desc => ?DESC(ipv6_v6only)
|
desc => ?DESC(ipv6_v6only)
|
||||||
}
|
}
|
||||||
|
)},
|
||||||
|
{"proxy_header",
|
||||||
|
?HOCON(
|
||||||
|
boolean(),
|
||||||
|
#{
|
||||||
|
desc => ?DESC(proxy_header),
|
||||||
|
default => false
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
-export([
|
-export([
|
||||||
set_default_config/0,
|
set_default_config/0,
|
||||||
set_default_config/1,
|
set_default_config/1,
|
||||||
|
set_default_config/2,
|
||||||
request/2,
|
request/2,
|
||||||
request/3,
|
request/3,
|
||||||
request/4,
|
request/4,
|
||||||
|
@ -36,6 +37,9 @@ set_default_config() ->
|
||||||
set_default_config(<<"admin">>).
|
set_default_config(<<"admin">>).
|
||||||
|
|
||||||
set_default_config(DefaultUsername) ->
|
set_default_config(DefaultUsername) ->
|
||||||
|
set_default_config(DefaultUsername, false).
|
||||||
|
|
||||||
|
set_default_config(DefaultUsername, HAProxyEnabled) ->
|
||||||
Config = #{
|
Config = #{
|
||||||
listeners => #{
|
listeners => #{
|
||||||
http => #{
|
http => #{
|
||||||
|
@ -46,7 +50,8 @@ set_default_config(DefaultUsername) ->
|
||||||
max_connections => 512,
|
max_connections => 512,
|
||||||
num_acceptors => 4,
|
num_acceptors => 4,
|
||||||
send_timeout => 5000,
|
send_timeout => 5000,
|
||||||
backlog => 512
|
backlog => 512,
|
||||||
|
proxy_header => HAProxyEnabled
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
default_username => DefaultUsername,
|
default_username => DefaultUsername,
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2020-2023 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_dashboard_haproxy_SUITE).
|
||||||
|
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-compile(export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-include("emqx_dashboard.hrl").
|
||||||
|
|
||||||
|
all() ->
|
||||||
|
emqx_common_test_helpers:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_suite(Config) ->
|
||||||
|
emqx_common_test_helpers:start_apps(
|
||||||
|
[emqx_management, emqx_dashboard],
|
||||||
|
fun set_special_configs/1
|
||||||
|
),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
set_special_configs(emqx_dashboard) ->
|
||||||
|
emqx_dashboard_api_test_helpers:set_default_config(<<"admin">>, true),
|
||||||
|
ok;
|
||||||
|
set_special_configs(_) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
end_per_suite(Config) ->
|
||||||
|
application:unload(emqx_management),
|
||||||
|
mnesia:clear_table(?ADMIN),
|
||||||
|
emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_management]),
|
||||||
|
mria:stop(),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
t_status(_Config) ->
|
||||||
|
ProxyInfo = #{
|
||||||
|
version => 1,
|
||||||
|
command => proxy,
|
||||||
|
transport_family => ipv4,
|
||||||
|
transport_protocol => stream,
|
||||||
|
src_address => {127, 0, 0, 1},
|
||||||
|
src_port => 444,
|
||||||
|
dest_address => {192, 168, 0, 1},
|
||||||
|
dest_port => 443
|
||||||
|
},
|
||||||
|
{ok, Socket} = gen_tcp:connect(
|
||||||
|
"localhost",
|
||||||
|
18083,
|
||||||
|
[binary, {active, false}, {packet, raw}]
|
||||||
|
),
|
||||||
|
ok = gen_tcp:send(Socket, ranch_proxy_header:header(ProxyInfo)),
|
||||||
|
{ok, Token} = emqx_dashboard_admin:sign_token(<<"admin">>, <<"public">>),
|
||||||
|
ok = gen_tcp:send(
|
||||||
|
Socket,
|
||||||
|
"GET /status HTTP/1.1\r\n"
|
||||||
|
"Host: localhost\r\n"
|
||||||
|
"Authorization: Bearer " ++ binary_to_list(Token) ++
|
||||||
|
"\r\n"
|
||||||
|
"\r\n"
|
||||||
|
),
|
||||||
|
{_, 200, _, Rest0} = cow_http:parse_status_line(raw_recv_head(Socket)),
|
||||||
|
{Headers, Body0} = cow_http:parse_headers(Rest0),
|
||||||
|
{_, LenBin} = lists:keyfind(<<"content-length">>, 1, Headers),
|
||||||
|
Len = binary_to_integer(LenBin),
|
||||||
|
Body =
|
||||||
|
if
|
||||||
|
byte_size(Body0) =:= Len ->
|
||||||
|
Body0;
|
||||||
|
true ->
|
||||||
|
{ok, Body1} = gen_tcp:recv(Socket, Len - byte_size(Body0), 5000),
|
||||||
|
<<Body0/bits, Body1/bits>>
|
||||||
|
end,
|
||||||
|
?assertMatch({match, _}, re:run(Body, "Node .+ is started\nemqx is running")),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
raw_recv_head(Socket) ->
|
||||||
|
{ok, Data} = gen_tcp:recv(Socket, 0, 10000),
|
||||||
|
raw_recv_head(Socket, Data).
|
||||||
|
|
||||||
|
raw_recv_head(Socket, Buffer) ->
|
||||||
|
case binary:match(Buffer, <<"\r\n\r\n">>) of
|
||||||
|
nomatch ->
|
||||||
|
{ok, Data} = gen_tcp:recv(Socket, 0, 10000),
|
||||||
|
raw_recv_head(Socket, <<Buffer/binary, Data/binary>>);
|
||||||
|
{_, _} ->
|
||||||
|
Buffer
|
||||||
|
end.
|
|
@ -0,0 +1 @@
|
||||||
|
Support HAProxy protocol for dashboard API.
|
|
@ -0,0 +1 @@
|
||||||
|
现在 dashboard 增加了对 `HAProxy` 协议的支持。
|
2
mix.exs
2
mix.exs
|
@ -56,7 +56,7 @@ defmodule EMQXUmbrella.MixProject do
|
||||||
{:ekka, github: "emqx/ekka", tag: "0.13.9", override: true},
|
{:ekka, github: "emqx/ekka", tag: "0.13.9", override: true},
|
||||||
{:gen_rpc, github: "emqx/gen_rpc", tag: "2.8.1", override: true},
|
{:gen_rpc, github: "emqx/gen_rpc", tag: "2.8.1", override: true},
|
||||||
{:grpc, github: "emqx/grpc-erl", tag: "0.6.7", override: true},
|
{:grpc, github: "emqx/grpc-erl", tag: "0.6.7", override: true},
|
||||||
{:minirest, github: "emqx/minirest", tag: "1.3.7", override: true},
|
{:minirest, github: "emqx/minirest", tag: "1.3.8", override: true},
|
||||||
{:ecpool, github: "emqx/ecpool", tag: "0.5.2", override: true},
|
{:ecpool, github: "emqx/ecpool", tag: "0.5.2", override: true},
|
||||||
{:replayq, github: "emqx/replayq", tag: "0.3.6", override: true},
|
{:replayq, github: "emqx/replayq", tag: "0.3.6", override: true},
|
||||||
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},
|
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.13.9"}}}
|
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.13.9"}}}
|
||||||
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}}
|
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}}
|
||||||
, {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.7"}}}
|
, {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.7"}}}
|
||||||
, {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.3.7"}}}
|
, {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.3.8"}}}
|
||||||
, {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.2"}}}
|
, {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.2"}}}
|
||||||
, {replayq, {git, "https://github.com/emqx/replayq.git", {tag, "0.3.6"}}}
|
, {replayq, {git, "https://github.com/emqx/replayq.git", {tag, "0.3.6"}}}
|
||||||
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
|
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
|
||||||
|
|
Loading…
Reference in New Issue