From 4af11662047460e4c068b9f396ba28d2d223362b Mon Sep 17 00:00:00 2001 From: Stefan Grundmann Date: Tue, 11 Mar 2025 03:34:22 +0000 Subject: [PATCH] ssh: tcpip_tunnel_in callback function allow/deny/log "direct-tcpip" channel open in daemon --- lib/ssh/src/ssh.hrl | 4 +- lib/ssh/src/ssh_connection.erl | 16 ++++++-- lib/ssh/src/ssh_options.erl | 2 +- lib/ssh/test/ssh_to_openssh_SUITE.erl | 57 +++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index de0396d4340e..c2a41bd7a508 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -889,9 +889,11 @@ connection out of a [server](`daemon/2`). Disabled per default. -doc """ Enables (`true`) or disables (`false`) the possibility to tunnel a TCP/IP connection in to a [server](`daemon/2`). Disabled per default. + +Set `Callback` function to allow/deny/log tunnel connections. """. -doc(#{group => <<"Daemon Options">>}). --type tcpip_tunnel_in_daemon_option() :: {tcpip_tunnel_in, boolean()} . +-type tcpip_tunnel_in_daemon_option() :: {tcpip_tunnel_in, boolean() | Callback::fun((HostName::string(), inet:port_number()) -> boolean() | denied)} . -doc """ Make the server (daemon) tell the client that the server accepts extension diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index cc722ee05975..894b0a0d6f27 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -946,9 +946,19 @@ handle_msg(#ssh_msg_channel_open{channel_type = "direct-tcpip", connection_supervisor = ConnectionSup } = C, server, _SSH) -> + HostName = binary_to_list(HostToConnect), + Allowed = case ?GET_OPT(tcpip_tunnel_in, Options) of + T when is_boolean(T) -> T; + AllowedFun when is_function(AllowedFun, 2) -> AllowedFun(HostName, PortToConnect) + end, {ReplyMsg, NextChId} = - case ?GET_OPT(tcpip_tunnel_in, Options) of - %% May add more to the option, like allowed ip/port pairs to connect to + case Allowed of + denied -> + {channel_open_failure_msg(RemoteId, + ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "Not allowed", "en"), + ChId}; + false -> {channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, @@ -956,7 +966,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "direct-tcpip", ChId}; true -> - case gen_tcp:connect(binary_to_list(HostToConnect), PortToConnect, + case gen_tcp:connect(HostName, PortToConnect, [{active,false}, binary]) of {ok,Sock} -> {ok,Pid} = ssh_connection_sup:start_channel(server, ConnectionSup, self(), diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 6a55954bd0ac..62913c7bfc00 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -454,7 +454,7 @@ default(server) -> tcpip_tunnel_in => #{default => false, - chk => fun(V) -> erlang:is_boolean(V) end, + chk => fun(V) -> check_function2(V) orelse erlang:is_boolean(V) end, class => user_option }, diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl index 183034f985a8..ac5ed4892d59 100644 --- a/lib/ssh/test/ssh_to_openssh_SUITE.erl +++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl @@ -45,6 +45,8 @@ exec_direct_with_io_in_sshc/1, exec_with_io_in_sshc/1, tunnel_in_erlclient_erlserver/1, + tunnel_in_erlclient_erlserver_allowed/1, + tunnel_in_erlclient_erlserver_denied/1, tunnel_in_erlclient_openssh_server/1, tunnel_in_non_erlclient_erlserver/1, tunnel_out_erlclient_erlserver/1, @@ -74,6 +76,8 @@ all() -> groups() -> [{erlang_client, [], [tunnel_in_erlclient_erlserver, + tunnel_in_erlclient_erlserver_allowed, + tunnel_in_erlclient_erlserver_denied, tunnel_out_erlclient_erlserver, {group, tunnel_distro_server}, erlang_shell_client_openssh_server, @@ -414,6 +418,59 @@ tunnel_in_erlclient_erlserver(Config) -> test_tunneling(ToSock, ListenHost, ListenPort). +%%-------------------------------------------------------------------- +tunnel_in_erlclient_erlserver_allowed(Config) -> + SystemDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(priv_dir, Config), + {ToSock, ToHost, ToPort} = tunneling_listner(), + Self = self(), + AllowedFun = fun(HostToConnect, PortToConnect) -> + Self ! {allowed, {HostToConnect, PortToConnect}}, + true + end, + {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, AllowedFun}, + {system_dir, SystemDir}, + {user_dir, UserDir}, + {user_passwords, [{"foo", "bar"}]}, + {failfun, fun ssh_test_lib:failfun/2}]), + C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user,"foo"},{password,"bar"}, + {user_interaction, false}]), + + ListenHost = inet:ntoa({127,0,0,1}), + {ok,ListenPort} = ssh:tcpip_tunnel_to_server(C, ListenHost,0, ToHost, ToPort, 2000), + test_tunneling(ToSock, ListenHost, ListenPort), + {allowed, {ListenHost, ToPort}} = receive X -> X after 500 -> timeout end, + {allowed, {ListenHost, ToPort}} = receive Y -> Y after 500 -> timeout end. + +%%-------------------------------------------------------------------- +tunnel_in_erlclient_erlserver_denied(Config) -> + SystemDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(priv_dir, Config), + {ToSock, ToHost, ToPort} = tunneling_listner(), + Self = self(), + DeniedFun = fun(HostToConnect, PortToConnect) -> + Self ! {denied, {HostToConnect, PortToConnect}}, + denied + end, + {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, DeniedFun}, + {system_dir, SystemDir}, + {user_dir, UserDir}, + {user_passwords, [{"foo", "bar"}]}, + {failfun, fun ssh_test_lib:failfun/2}]), + C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user,"foo"},{password,"bar"}, + {user_interaction, false}]), + + ListenHost = inet:ntoa({127,0,0,1}), + {ok,ListenPort} = ssh:tcpip_tunnel_to_server(C, ListenHost,0, ToHost, ToPort, 2000), + {ok, Sock} = gen_tcp:connect(ListenHost, ListenPort, [{active, false}]), + {denied, {ListenHost, ToPort}} = receive Y -> Y after 500 -> timeout end, + {error, timeout} = gen_tcp:accept(ToSock, 2000), + {error, closed} = gen_tcp:recv(Sock, 0, 5000). + %%-------------------------------------------------------------------- tunnel_in_erlclient_openssh_server(_Config) -> C = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),