From 6a701e098fe0e182aa0b6e43c037740756f4e4bf Mon Sep 17 00:00:00 2001 From: zhongwencool Date: Sun, 30 Jan 2022 14:09:26 +0800 Subject: [PATCH] feat(shell): add restricted shell and user_default --- apps/emqx/etc/emqx_cloud/vm.args | 5 + apps/emqx/etc/emqx_edge/vm.args | 5 + apps/emqx/src/emqx_listeners.erl | 2 + apps/emqx_dashboard/src/emqx_dashboard.erl | 12 +-- .../src/emqx_restricted_shell.erl | 93 +++++++++++++++++++ apps/emqx_machine/src/user_default.erl | 29 ++++++ 6 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 apps/emqx_machine/src/emqx_restricted_shell.erl create mode 100644 apps/emqx_machine/src/user_default.erl diff --git a/apps/emqx/etc/emqx_cloud/vm.args b/apps/emqx/etc/emqx_cloud/vm.args index 0ee4b1e15..8111eed38 100644 --- a/apps/emqx/etc/emqx_cloud/vm.args +++ b/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 diff --git a/apps/emqx/etc/emqx_edge/vm.args b/apps/emqx/etc/emqx_edge/vm.args index 70ce81f9f..743068538 100644 --- a/apps/emqx/etc/emqx_edge/vm.args +++ b/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 diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index 163e3eb39..c8aaadb70 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/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"]). diff --git a/apps/emqx_dashboard/src/emqx_dashboard.erl b/apps/emqx_dashboard/src/emqx_dashboard.erl index 1777dbd86..5e512e201 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.erl +++ b/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) -> diff --git a/apps/emqx_machine/src/emqx_restricted_shell.erl b/apps/emqx_machine/src/emqx_restricted_shell.erl new file mode 100644 index 000000000..f0bcf3424 --- /dev/null +++ b/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). diff --git a/apps/emqx_machine/src/user_default.erl b/apps/emqx_machine/src/user_default.erl new file mode 100644 index 000000000..0071db837 --- /dev/null +++ b/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().