Jelajahi Sumber

feat(shell): add restricted shell and user_default

zhongwencool 4 tahun lalu
induk
melakukan
6a701e098f

+ 5 - 0
apps/emqx/etc/emqx_cloud/vm.args

@@ -36,6 +36,11 @@
 ## Can be one of: inet_tcp, inet6_tcp, inet_tls
 #-proto_dist inet_tcp
 
+## The shell is started in a restricted mode.
+## In this mode, the shell evaluates a function call only if allowed.
+## Prevent user from accidentally calling a function from the prompt that could harm a running system.
+-stdlib restricted_shell emqx_restricted_shell
+
 ## Specify SSL Options in the file if using SSL for Erlang Distribution.
 ## Used only when -proto_dist set to inet_tls
 #-ssl_dist_optfile {{ platform_etc_dir }}/ssl_dist.conf

+ 5 - 0
apps/emqx/etc/emqx_edge/vm.args

@@ -35,6 +35,11 @@
 ## Can be one of: inet_tcp, inet6_tcp, inet_tls
 #-proto_dist inet_tcp
 
+## The shell is started in a restricted mode.
+## In this mode, the shell evaluates a function call only if allowed.
+## Prevent user from accidentally calling a function from the prompt that could harm a running system.
+-stdlib restricted_shell emqx_restricted_shell
+
 ## Specify SSL Options in the file if using SSL for Erlang Distribution.
 ## Used only when -proto_dist set to inet_tls
 #-ssl_dist_optfile {{ platform_etc_dir }}/ssl_dist.conf

+ 2 - 0
apps/emqx/src/emqx_listeners.erl

@@ -48,6 +48,8 @@
 
 -export([post_config_update/5]).
 
+-export([format_addr/1]).
+
 -define(CONF_KEY_PATH, [listeners]).
 -define(TYPES_STRING, ["tcp","ssl","ws","wss","quic"]).
 

+ 6 - 6
apps/emqx_dashboard/src/emqx_dashboard.erl

@@ -62,11 +62,11 @@ start_listeners() ->
         middlewares => [cowboy_router, ?EMQX_MIDDLE, cowboy_handler]
     },
     Res =
-        lists:foldl(fun({Name, Protocol, Port, RanchOptions}, Acc) ->
+        lists:foldl(fun({Name, Protocol, Bind, RanchOptions}, Acc) ->
             Minirest = BaseMinirest#{protocol => Protocol},
             case minirest:start(Name, RanchOptions, Minirest) of
                 {ok, _} ->
-                    ?ULOG("Start listener ~ts on ~p successfully.~n", [Name, Port]),
+                    ?ULOG("Listener ~ts on ~ts started.~n", [Name, emqx_listeners:format_addr(Bind)]),
                     Acc;
                 {error, _Reason} ->
                     %% Don't record the reason because minirest already does(too much logs noise).
@@ -82,7 +82,7 @@ stop_listeners() ->
     [begin
         case minirest:stop(Name) of
             ok ->
-                ?ULOG("Stop listener ~ts on ~p successfully.~n", [Name, Port]);
+                ?ULOG("Listener ~ts on ~ts stopped.~n", [Name, emqx_listeners:format_addr(Port)]);
             {error, not_found} ->
                 ?SLOG(warning, #{msg => "stop_listener_failed", name => Name, port => Port})
         end
@@ -101,17 +101,17 @@ apps() ->
 listeners() ->
     [begin
         Protocol = maps:get(protocol, ListenerOption0, http),
-        {ListenerOption, Port} = ip_port(ListenerOption0),
+        {ListenerOption, Bind} = ip_port(ListenerOption0),
         Name = listener_name(Protocol, ListenerOption),
         RanchOptions = ranch_opts(maps:without([protocol], ListenerOption)),
-        {Name, Protocol, Port, RanchOptions}
+        {Name, Protocol, Bind, RanchOptions}
     end || ListenerOption0 <- emqx_conf:get([dashboard, listeners], [])].
 
 ip_port(Opts) -> ip_port(maps:take(bind, Opts), Opts).
 
 ip_port(error, Opts)  -> {Opts#{port => 18083}, 18083};
 ip_port({Port, Opts}, _) when is_integer(Port) -> {Opts#{port => Port}, Port};
-ip_port({{IP, Port}, Opts}, _) -> {Opts#{port => Port, ip => IP}, Port}.
+ip_port({{IP, Port}, Opts}, _) -> {Opts#{port => Port, ip => IP}, {IP, Port}}.
 
 
 ranch_opts(RanchOptions) ->

+ 93 - 0
apps/emqx_machine/src/emqx_restricted_shell.erl

@@ -0,0 +1,93 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2021-2022 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_restricted_shell).
+
+-export([local_allowed/3, non_local_allowed/3]).
+-export([lock/0, unlock/0, is_locked/0]).
+
+-include_lib("emqx/include/logger.hrl").
+
+-define(APP, 'emqx_machine').
+-define(IS_LOCKED, 'restricted.is_locked').
+-define(MAX_HEAP_SIZE, 1024 * 1024 * 1).
+-define(MAX_ARGS_SIZE, 1024 * 10).
+
+-define(RED_BG, "\e[48;2;184;0;0m").
+-define(RESET, "\e[0m").
+
+-define(LOCAL_NOT_ALLOWED, [halt, q]).
+-define(NON_LOCAL_NOT_ALLOWED, [{erlang, halt}, {c, q}, {init, stop}, {init, restart}, {init, reboot}]).
+
+is_locked() ->
+    {ok, false} =/= application:get_env(?APP, ?IS_LOCKED).
+
+lock() -> application:set_env(?APP, ?IS_LOCKED, true).
+unlock() -> application:set_env(?APP, ?IS_LOCKED, false).
+
+local_allowed(MF, Args, State) ->
+    IsAllowed = is_allowed(MF, ?LOCAL_NOT_ALLOWED),
+    log(IsAllowed, MF, Args),
+    {IsAllowed, State}.
+
+non_local_allowed(MF, Args, State) ->
+    IsAllowed = is_allowed(MF, ?NON_LOCAL_NOT_ALLOWED),
+    log(IsAllowed, MF, Args),
+    {IsAllowed, State}.
+
+is_allowed(MF, NotAllowed) ->
+    case lists:member(MF, NotAllowed) of
+        true -> not is_locked();
+        false -> true
+    end.
+
+limit_warning(MF, Args) ->
+    max_heap_size_warning(MF, Args),
+    max_args_warning(MF, Args).
+
+max_args_warning(MF, Args) ->
+    ArgsSize = erts_debug:flat_size(Args),
+    case ArgsSize < ?MAX_ARGS_SIZE of
+        true -> ok;
+        false ->
+            warning("[WARNING] current_args_size:~w, max_args_size:~w", [ArgsSize, ?MAX_ARGS_SIZE]),
+            ?SLOG(warning, #{msg => "execute_function_in_shell_max_args_size",
+                function => MF,
+                args => Args,
+                args_size => ArgsSize,
+                max_heap_size => ?MAX_ARGS_SIZE})
+    end.
+
+max_heap_size_warning(MF, Args) ->
+    {heap_size, HeapSize} = erlang:process_info(self(), heap_size),
+    case HeapSize < ?MAX_HEAP_SIZE of
+        true -> ok;
+        false ->
+            warning("[WARNING] current_heap_size:~w, max_heap_size_warning:~w", [HeapSize, ?MAX_HEAP_SIZE]),
+            ?SLOG(warning, #{msg => "shell_process_exceed_max_heap_size",
+                current_heap_size => HeapSize,
+                function => MF,
+                args => Args,
+                max_heap_size => ?MAX_HEAP_SIZE})
+    end.
+
+log(true, MF, Args) -> limit_warning(MF, Args);
+log(false, MF, Args) ->
+    warning("DANGEROUS FUNCTION: DO NOT ALLOWED IN SHELL!!!!!", []),
+    ?SLOG(error, #{msg => "execute_function_in_shell_not_allowed", function => MF, args => Args}).
+
+warning(Format, Args) ->
+    io:format(?RED_BG ++ Format ++ ?RESET ++ "~n", Args).

+ 29 - 0
apps/emqx_machine/src/user_default.erl

@@ -0,0 +1,29 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2021-2022 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(user_default).
+
+-include_lib("emqx/include/emqx.hrl").
+-include_lib("emqx/include/logger.hrl").
+-include_lib("emqx/include/emqx_mqtt.hrl").
+-include_lib("emqx_conf/include/emqx_conf.hrl").
+-include_lib("emqx_dashboard/include/emqx_dashboard.hrl").
+-include_lib("emqx_slow_subs/include/emqx_slow_subs.hrl").
+
+%% API
+-export([lock/0, unlock/0]).
+
+lock() -> emqx_restricted_shell:lock().
+unlock() -> emqx_restricted_shell:unlock().