0%

使用 SSH 实现远程登录和内网穿透

前言

在开发过程当中经常会碰到这样的需求,通过某一台外网的跳板机器登录到另一台的内网机器上,或者需要直接访问内网机器上的资源, 比如传输文件、访问 web 资源等。

图1

其实像这种需求有很多种方式可以实现,比如 nginx 反向代理、shadowsocks 代理、iptables 端口转发等等,在这里主要介绍使用 SSH 的端口转发实现。对于服务端的开发人员来说 SSH 简直是神器之一。

SSH 实现端口转发

SSH 共提供了三种端口转发,分别是本地转发、远程转发、动态转发。

本地转发(用-L参数)

将访问本地主机端口的请求转发到远程主机的端口上,命令形式如下:

1
ssh -NL [本地主机]:[本地端口]:[远程主机]:[远程端口] [用户名]@[跳板机]

这里的 -N 参数是指,只建立隧道,不执行远程机器的任何命令,如果没有 -N 话同时还会默认登录到主机,或者执行指定命令。

  • 场景1

    图2

    前提是主机A有权限访问 http://10.0.0.2:8888,且终端X有权限登录主机A(10.0.0.1)。
    这种情况执行以下命令,把终端X的 9999 端口请求通过主机A转发到远程主机B(10.0.0.2)的 8888 端口上,所以,在终端X的浏览器中直接访问 http://127.0.0.1:9999 即可。

    1
    2
    3
    4
    ssh -NL 0.0.0.0:9999:10.0.0.2:8888 user_a@10.0.0.1

    # 本地主机可以省略
    ssh -NL 9999:10.0.0.2:8888 user_a@10.0.0.1
  • 场景2

    图3

    虽然终端X可以直接登录主机B,但可能由于防火墙、内网服务等因素无法直接访问 http://10.0.1.2:8888 的服务。
    这种情况执行以下命令,把终端X的 9999 端口请求转发到远程主机B(10.0.1.2)的 8888 端口上即可,注意这里主机B上有两个网段的 IP 地址,可能是一个外网,一个内网。

    1
    2
    3
    4
    ssh -NL 0.0.0.0:9999:10.0.1.2:8888 user_b@10.0.0.2

    # 本地主机可以省略
    ssh -NL 9999:10.0.1.2:8888 user_b@10.0.0.2

以上两种场景都是终端X把本地主机的请求转发到远程主机,所以称为本地转发。

远程转发(用-R参数)

将远程主机端口的请求本地主机转发到内网中的服务端口上,命令形式如下:

1
ssh -NR [远程主机监听地址]:[远程主机监听端口]:[内网主机]:[内网端口] [用户名]@[远程主机]
  • 场景1

    图4

    这里终端X是无法直接访问主机A的 http://10.0.1.1:8888 服务的,也不能访问主机B,只能访问主机C。
    主机B是可以登录主机C的,而且主机B能访问主机A的 http://10.0.1.1:8888 服务。
    主机C也是无法直接访问主机B和主机A的。

    这种情况就需要在主机B上配置 SSH 的端口转发,将对主机C的 9999 端口的请求转发到主机A的 8888 端口上。
    在这里主机B的功能都是把远程主机端口的请求转发到内网中,所以这种情况称为远程转发。

    1. 这里要实现远程转发需要主机C的 sshd_config 开启了 AllowTcpForwarding 选项,否则远程转发会失败。
    2. 远程主机上的端口绑定的是127.0.0.1,如要绑定0.0.0.0的网段,主机C的 sshd_config 需要开启 GatewayPorts 选项。或者在主机C上再用一次本地转发到0.0.0.0网段也是可以的。

动态转发(用-D参数)

动态转发其实就是 socks5 代理,跟远程主机建立一条隧道,并在本地监听一个端口,所有经过这个端口的请求都统一发到远程主机上由远程主机代理发起请求。所以这样就可以访问代理主机能访问的而且本身直接访问不了的所有服务, 那样就不需要一个端口一个端口去转发了,而且安全性更高。命令格式如下:

1
ssh -ND [本地主机监听地址]:[本地主机监听端口] [用户名]@[远程主机]
  • 场景1

    图5

    这里终端X只能登录主机A,其它的主机B ~ D 都无法访问。
    这个时候如果配置一条动态转发,只要在主机A中能访问的服务,在终端X就能访问,不管是内网还是外网。

    1
    ssh -ND user_a@10.0.0.1

    这种方式如果终端A需要访问主机B ~ D的话,就需要配置走 socks5 的代理方式,比如浏览器访问网页、SSH远程登录等等。

因为这种转发完全不需要考虑具体的要访问的服务,只要能访问的都自动转发,所以称为动态转发。

通过跳板机直接登录内网主机

最简单粗暴的方式就是先从终端X登录到主机A,再从主机A登录到主机B,这样的话就非常烦琐,想访问主机B还需要两次登录操作,如果后面还有主机C、主机D,那就需要更多次登录操作了,其实 SSH 提供了更简单的方式可以做到。其实你会发现,只要了解了上面几种端口转发方式,无论是跳板机登录、拷贝文件、或者访问 web 服务,都轻而易举了。

  • 使用 -J 参数的方式

    1
    2
    3
    4
    ssh -At user_b@10.0.0.2 -J user_a@10.0.0.1

    # 中间有多台跳板机器
    ssh -At user_c@10.0.0.3 -J user_a@10.0.0.1,user_b@10.0.0.2
  • 使用 ProxyCommand 的方式

    1
    ssh -At user_b@10.0.0.2 -o ProxyCommand="ssh user_a@10.0.0.1 -W %h:%p"
  • 端口转发的方式

    1
    2
    3
    4
    5
    # 先配置动态转发
    ssh -ND localhost:8888 user_a@10.0.0.1

    # 登录主机
    ssh -At -o ProxyCommand='/usr/bin/nc -X 5 -x localhost:8888 %h %p' user_b@10.0.0.2
  • ~/.ssh/config 配置文件的方式

使用 scprsyncgit 等命令穿透跳板机器进行文件同步

  • 使用 -J 参数的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # rsync
    rsync -e "ssh -J user_a@10.0.0.1" user_b@10.0.0.2:/path/to/file ./out/

    # git 假设 git.example.com 仅仅是一个内网域名, 外网无法访问, 而 10.0.0.1 是外网ip
    GIT_SSH_COMMAND='ssh -J user_a@10.0.0.1' git clone git@git.example.com:xx/yy.git

    # scp 不支持这种方式

    # ansible
    ansible_ssh_common_args="-J user_a@10.0.0.1"
  • 使用 ProxyCommand 的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # scp
    scp -o "ProxyCommand ssh user_a@10.0.0.1 -W %h:%p" user_b@10.0.0.2:/path/to/file ./out/

    # rsync
    rsync -e "ssh -o 'ProxyCommand ssh user_a@10.0.0.1 -W %h:%p'" user_b@10.0.0.2:/path/to/file ./out/

    # git
    GIT_SSH_COMMAND="ssh -o 'ProxyCommand ssh user_a@10.0.0.1 -W %h:%p'" git clone git@git.example.com:xx/yy.git

    # ansible 使用需要配置以下参数
    ansible_ssh_common_args="-o ProxyCommand='ssh user_a@10.0.0.1 -W %h:%p'"

    其中 10.0.0.1 是跳板主机,10.0.0.2 是内网机器。

  • 端口转发的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 先配置动态转发
    ssh -ND localhost:8888 user_a@10.0.0.1

    # scp
    scp -o "ProxyCommand /usr/bin/nc -X 5 -x localhost:8888 %h %p" user_b@10.0.0.2:/path/to/file ./out/

    # rsync
    rsync -e "ssh -o 'ProxyCommand /usr/bin/nc -X 5 -x localhost:8888 %h %p'" user_b@10.0.0.2:/path/to/file ./out/

    # git
    GIT_SSH_COMMAND="ssh -o 'ProxyCommand /usr/bin/nc -X 5 -x localhost:8888 %h %p'" git clone git@git.example.com:xx/yy.git

Chrome 浏览器插件 SwitchyOmega

谈到代理、端口转发,不得不让我联想到这个神器插件,可能你的电脑上配置了各种代理、转发,有时候各种冲突让人晕头转向。
有了这个插件,只需要简单配置一下,一键切换、自动路由,非常方便。