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:
Stefan Strigler 2023-01-31 14:19:56 +01:00 committed by GitHub
commit ade5316419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 143 additions and 9 deletions

View File

@ -96,6 +96,16 @@ The configuration is only valid when the inet6 is true."""
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 {
en: "Configuration for EMQX dashboard."

View File

@ -2,7 +2,7 @@
{application, emqx_dashboard, [
{description, "EMQX Web Dashboard"},
% strict semver, bump manually!
{vsn, "5.0.12"},
{vsn, "5.0.13"},
{modules, []},
{registered, [emqx_dashboard_sup]},
{applications, [kernel, stdlib, mnesia, minirest, emqx]},

View File

@ -92,8 +92,8 @@ start_listeners(Listeners) ->
},
Res =
lists:foldl(
fun({Name, Protocol, Bind, RanchOptions}, Acc) ->
Minirest = BaseMinirest#{protocol => Protocol},
fun({Name, Protocol, Bind, RanchOptions, ProtoOpts}, Acc) ->
Minirest = BaseMinirest#{protocol => Protocol, protocol_options => ProtoOpts},
case minirest:start(Name, RanchOptions, Minirest) of
{ok, _} ->
?ULOG("Listener ~ts on ~ts started.~n", [
@ -125,7 +125,7 @@ stop_listeners(Listeners) ->
?SLOG(warning, #{msg => "stop_listener_failed", name => Name, port => Port})
end
end
|| {Name, _, Port, _} <- listeners(Listeners)
|| {Name, _, Port, _, _} <- listeners(Listeners)
],
ok.
@ -164,7 +164,13 @@ listeners(Listeners) ->
maps:get(enable, Conf) andalso
begin
{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,
maps:to_list(Listeners)
@ -197,7 +203,7 @@ ranch_opts(Options) ->
SocketOpts = maps:fold(
fun filter_false/3,
[],
maps:without([enable, inet6, ipv6_v6only | Keys], Options)
maps:without([enable, inet6, ipv6_v6only, proxy_header | Keys], Options)
),
InetOpts =
case Options of
@ -210,6 +216,9 @@ ranch_opts(Options) ->
end,
RanchOpts#{socket_opts => InetOpts ++ SocketOpts}.
proto_opts(Options) ->
maps:with([proxy_header], Options).
filter_false(_K, false, S) -> S;
filter_false(K, V, S) -> [{K, V} | S].

View File

@ -160,6 +160,14 @@ common_listener_fields() ->
default => false,
desc => ?DESC(ipv6_v6only)
}
)},
{"proxy_header",
?HOCON(
boolean(),
#{
desc => ?DESC(proxy_header),
default => false
}
)}
].

View File

@ -19,6 +19,7 @@
-export([
set_default_config/0,
set_default_config/1,
set_default_config/2,
request/2,
request/3,
request/4,
@ -36,6 +37,9 @@ set_default_config() ->
set_default_config(<<"admin">>).
set_default_config(DefaultUsername) ->
set_default_config(DefaultUsername, false).
set_default_config(DefaultUsername, HAProxyEnabled) ->
Config = #{
listeners => #{
http => #{
@ -46,7 +50,8 @@ set_default_config(DefaultUsername) ->
max_connections => 512,
num_acceptors => 4,
send_timeout => 5000,
backlog => 512
backlog => 512,
proxy_header => HAProxyEnabled
}
},
default_username => DefaultUsername,

View File

@ -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.

View File

@ -0,0 +1 @@
Support HAProxy protocol for dashboard API.

View File

@ -0,0 +1 @@
现在 dashboard 增加了对 `HAProxy` 协议的支持。

View File

@ -56,7 +56,7 @@ defmodule EMQXUmbrella.MixProject do
{:ekka, github: "emqx/ekka", tag: "0.13.9", 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},
{: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},
{:replayq, github: "emqx/replayq", tag: "0.3.6", override: true},
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},

View File

@ -58,7 +58,7 @@
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.13.9"}}}
, {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"}}}
, {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"}}}
, {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"}}}