设置临时SSH隧道的Bash脚本

153

在Cygwin上,我想要一个Bash脚本来:

  1. 创建到远程服务器的SSH隧道。
  2. 在本地使用该隧道进行一些工作。
  3. 然后关闭该隧道。

关闭部分让我感到困惑。

目前,我有一个简陋的解决方案。在一个shell中,我运行以下命令来创建隧道:

# Create the tunnel - this works! It runs forever, until the shell is quit.
ssh -nNT -L 50000:localhost:3306 jm@sampledomain.com

接下来,在另一个终端窗口中,我进行我的工作:

# Do some MySQL stuff over local port 50000 (which goes to remote port 3306)

最后,当我完成时,我关闭第一个shell窗口以杀死隧道。

我想把这一切都放在一个脚本里,像这样:

# Create tunnel
# Do work
# Kill tunnel

我该如何跟踪隧道进程,以便知道要终止哪个进程?


我写了一个脚本来帮助进行SSH隧道,你可以在此链接查看:https://github.com/gdbtek/ssh-tunneling.git - Nam Nguyen
7个回答

382

您可以使用ssh的'控制套接字'来干净地完成此操作。使用“控制套接字”(-M用于主机,-S用于套接字)与已经运行的SSH进程进行通信并获取其pid、杀死它等。操作如下:

$ ssh -M -S my-ctrl-socket -fNT -L 50000:localhost:3306 jm@sampledomain.com
$ ssh -S my-ctrl-socket -O check jm@sampledomain.com
Master running (pid=3517) 
$ ssh -S my-ctrl-socket -O exit jm@sampledomain.com
Exit request sent. 

请注意,my-ctrl-socket将是一个实际创建的文件。

我从OpenSSH邮件列表中一条非常详尽的回复中得到了这个信息。


9
到目前为止,这是我见过的关于此话题的最佳答案。非常感谢,这应该成为被采纳的答案。我使用它连接到我的Vagrant虚拟机并运行FlywayDB更新脚本。 - Christian
2
显然,控制套接字并非在所有地方都能正常工作。例如,在drone.io持续集成环境中,我会收到“操作不允许”的错误提示:muxserver_listen: link mux listener ssh-ctrl-socket.wsASkszgSBlK7kqD => ssh-ctrl-socket: Operation not permitted。 - Mikko Ohtamaa
那么在运行后my-ctrl-socket文件会发生什么?当我在当前文件夹中执行ls -la时,我再也看不到这个文件了。 - sachinruk
4
如果你在脚本中使用它,你需要等待几秒钟以使控制套接字可用。我的解决方法是: while [ ! -e $ctrl_socket ]; do sleep 0.1; done - Adam Wallner
1
非常棒的东西,比我在网上找到的所有其他东西都更优雅。谢谢。 - Aleksandar Grbic
显示剩余2条评论

23

你可以使用 -f 选项让 SSH 在后台运行,但是你无法通过 $! 获取进程 ID。 另外,你可以在使用 -f 时使用 -o ExitOnForwardFailure=yes ,SSH 将等待所有远程端口转发成功建立后再将自己置于后台。你可以使用 ps 命令的输出来获取进程 ID。例如,你可以使用以下命令:

...
ssh -Cfo ExitOnForwardFailure=yes -N -L 9999:localhost:5900 $REMOTE_HOST
PID=$(pgrep -f 'N -L 9999:')
[ "$PID" ] || exit 1
...

并且要确信你获取到了期望的PID


可能你没有意识到,但这种方法有点儿天才。我一直在寻找一种跟踪SSH隧道PID的方法,几乎要使用systemd服务脚本了。不再需要了:我可以通过隧道名称grep到所需的SSH进程。这个想法完全跳过了我。非常感谢! - ᴍᴇʜᴏᴠ

19
  • 您可以使用命令行标志让ssh进入后台,不在另一侧创建shell(仅打开隧道)并使用&。 (我看到您已经使用了-N进行了配置)。
  • 保存PID以及运行PID=$!
  • 执行相关操作
  • kill $PID

编辑:更正了$?为$! 并添加了 & 符号。


3
如果我的脚本在到达“KILL”之前中断,我必须小心处理它。 - jm.
3
trap 'kill $PID' 1 2 15 会处理许多脚本失败的情况。 - Norman Ramsey
3
为了确保这个方法可靠,我在创建隧道之后但在使用隧道之前需要稍作“休眠”。 - jm.
@NormanRamsey 我想你的意思是 trap "kill $PID",因为 bash 只会在双引号字符串内插入变量。 - JuanCaicedo
1
@JuanCaicedo 只有在以后重新定义 PID 变量时,这种区别才会很重要。当调用 trap 内置函数(OP 的方法)或捕获信号时(您的方法),变量都会被扩展;这两种方法在此处产生相同的结果。 - Witiko
睡眠并不可靠 ;) - Jonathan

4

我喜欢为不同的任务启动新的终端,通常使用以下命令组合:

  $ sudo bash; exit

有时候会出现以下情况:
  $ : > sensitive-temporary-data.txt; bash; rm -f sensitive-temporary-data.txt; exit

这些命令创建了一个嵌套的 shell,我可以在其中完成所有工作;当我完成后,按下 CTRL-D,父 shell 也会清理并退出。您可以在 ssh 隧道脚本中在 kill 部分之前轻松添加 bash;,这样当您退出嵌套的 shell 时,隧道将被关闭:

#!/bin/bash
ssh -nNT ... &
PID=$!
bash
kill $PID

非常有趣。这可能更好地处理“陷阱”问题。我得试试。 - jm.

2

您可以在ssh命令后加上&,将其放到后台并获取其id。然后,当您完成时,只需使用该id执行kill命令即可。


注意使用和号(“&”)。这是一种丑陋的方法,因为您将不得不自己确定实际建立的连接。它可能导致进一步执行代码,而不是等待实际连接完全建立。此外,如果脚本中断,连接不会自动终止。 - Jonathan

0
一个解决问题的简单bash脚本。
# Download then put in $PATH
wget https://raw.githubusercontent.com/ijortengab/bash/master/commands/command-keep-alive.sh
mv command-keep-alive.sh -t /usr/local/bin

# open tunnel, put script in background
command-keep-alive.sh "ssh -fN -o ServerAliveInterval=10 -o ServerAliveCountMax=2 -L 33306:localhost:3306 myserver" /tmp/my.pid &
# do something
mysql --port 33306
# close tunnel
kill $(cat /tmp/my.pid)

-1

https://github.com/aronpc/remina-ssh-tunnel

#!/usr/bin/env sh

scriptname="$(basename $0)"
actionname="$1"
tunnelname=$(echo "$2" | iconv -t ascii//TRANSLIT | sed -E 's/[^a-zA-Z0-9-]+/-/g' | sed -E 's/^-+|-+$//g' | tr A-Z a-z)
remotedata="$3"
tunnelssh="$4"

if [ $# -lt 4 ] 
 then
    echo "Usage: $scriptname start | stop LOCAL_PORT:RDP_IP:RDP_PORT SSH_NODE_IP"
    exit
fi

case "$actionname" in

start)

  echo "Starting tunnel to $tunnelssh"
  ssh -M -S ~/.ssh/sockets/$tunnelname.control -fnNT -L $remotedata $tunnelssh
  ssh -S ~/.ssh/sockets/$tunnelname.control -O check $tunnelssh
  ;;

stop)
  echo "Stopping tunnel to $tunnelssh"
  ssh -S ~/.ssh/sockets/$tunnelname.control -O exit $tunnelssh 
 ;;

*)
  echo "Did not understand your argument, please use start|stop"
  ;;

esac

使用示例

编辑或创建新的Remmina服务器连接

模式

~/.ssh/rdp-tunnel.sh ACTION TUNNELNAME LOCAL_PORT:REMOTE_SERVER:REMOTE_PORT TUNNEL_PROXY

名称 描述
ACTION 启动|停止
TUNNELNAME "字符串标识套接字",slugify 以创建套接字文件到 ~/.ssh/sockets/string-identify-socket.control
LOCAL_PORT 将要本地公开的端口,如果我们为两个连接使用相同的端口,它将崩溃
REMOTE_SERVER 您将访问的服务器的 IP,如果您在代理服务器上拥有它,则会使用该服务器
REMOTE_PORT 在服务器上运行的服务端口
TUNNEL_PROXY 您将用作代理的连接,最好在您的 ~/.ssh/config 中使用访问密钥

我使用组名和连接名的组合(% g-% p)作为我的TUNNELNAME(这需要是唯一的,它将查看套接字名称)

预命令

~/.ssh/rdp-tunnel.sh start "%g-%p" 63394:192.168.8.176:3389 tunnel-name-1

后命令

~/.ssh/rdp-tunnel.sh stop "%g-%p" 63394:192.168.8.176:3389 tunnel-name-1

image

你可以并且应该使用这个脚本来访问任何东西,我经常使用它来访问没有公共IP的系统和服务,通过1、2、3、4、5或更多的SSH代理。

了解更多信息:

  1. SSH配置
  2. SSH机器
  3. SSH跳板主机
  4. SSHuttle Python SSH

参考资料:

  1. https://remmina.org/remmina-rdp-ssh-tunnel/
  2. https://kgibran.wordpress.com/2019/03/13/remmina-rdp-ssh-tunnel-with-pre-and-post-scripts/
  3. Bash脚本用于设置临时SSH隧道
  4. https://gist.github.com/oneohthree/f528c7ae1e701ad990e6

2
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅链接的答案可能会失效。- 来自审查 - desertnaut
谢谢,我成功放置了readme.md文件,我之前不知道我有这个文件。 - Aron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接