feat: support HAProxy protocol for dashboard API

This commit is contained in:
Stefan Strigler 2023-01-18 15:49:57 +01:00
parent 74ae7c4264
commit fb763ecebd
6 changed files with 122 additions and 7 deletions

View File

@ -92,6 +92,16 @@ Note: `sample_interval` should be a divisor of 60."""
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: "[FIXME]"
}
label: {
en: "Enable support for HAProxy header"
zh: "[FIXME]"
}
}
desc_dashboard { desc_dashboard {
desc { desc {
en: "Configuration for EMQX dashboard." en: "Configuration for EMQX dashboard."

View File

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

View File

@ -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
}
)} )}
]. ].

View File

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

View File

@ -0,0 +1,83 @@
%%--------------------------------------------------------------------
%% 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).
-import(
emqx_common_test_http,
[
request_api/3
]
).
-include_lib("eunit/include/eunit.hrl").
-include("emqx_dashboard.hrl").
-define(HOST, "http://127.0.0.1:18083").
-define(BASE_PATH, "/").
all() ->
emqx_common_test_helpers:all(?MODULE).
end_suite() ->
end_suite([]).
end_suite(Apps) ->
application:unload(emqx_management),
mnesia:clear_table(?ADMIN),
emqx_common_test_helpers:stop_apps(Apps ++ [emqx_dashboard]).
init_per_suite(Config) ->
emqx_common_test_helpers:start_apps(
[emqx_management, emqx_dashboard],
fun set_special_configs/1
),
Config.
end_per_suite(_Config) ->
emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_management]),
mria:stop().
set_special_configs(emqx_dashboard) ->
emqx_dashboard_api_test_helpers:set_default_config(<<"admin">>, true),
ok;
set_special_configs(_) ->
ok.
disabled_t_status(_) ->
%% no easy way since httpc doesn't support emulating the haproxy protocol
{ok, 200, _Res} = http_get(["status"]),
ok.
%%------------------------------------------------------------------------------
%% Internal functions
%%------------------------------------------------------------------------------
http_get(Parts) ->
request_api(get, api_path(Parts), auth_header_()).
auth_header_() ->
auth_header_(<<"admin">>, <<"public">>).
auth_header_(Username, Password) ->
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
{"Authorization", "Bearer " ++ binary_to_list(Token)}.
api_path(Parts) ->
?HOST ++ filename:join([?BASE_PATH | Parts]).

View File

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