Просмотр исходного кода

feat: support HAProxy protocol for dashboard API

Stefan Strigler 3 лет назад
Родитель
Сommit
fb763ecebd

+ 10 - 0
apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf

@@ -92,6 +92,16 @@ Note: `sample_interval` should be a divisor of 60."""
       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 {
       en: "Configuration for EMQX dashboard."

+ 14 - 5
apps/emqx_dashboard/src/emqx_dashboard.erl

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

+ 8 - 0
apps/emqx_dashboard/src/emqx_dashboard_schema.erl

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

+ 6 - 1
apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl

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

+ 83 - 0
apps/emqx_dashboard/test/emqx_dashboard_haproxy_SUITE.erl

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

+ 1 - 1
rebar.config

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