SSH
本文的受众
如果你遇到了以下问题,那么你应该阅读这篇文章
- 我听说过这种技术,我对它很感兴趣
- 我想在家里访问我在公司的机器(写程序,查数据,下电影)。
- 公司为了防止我们用XX软件封锁了它的端口或者服务器地址。
- 公司不让我们上XX网站,限制了网址甚至IP。
- 公司不让我们看关于XX的信息,甚至花血本买了XX设备,能够对内容进行过滤。一看XX内容,链接就中断了。
- 我爸是搞电脑的,他在家里的路由器上动了手脚,我不能看XXX了。
带着这些问题,我们先从什么是ssh隧道开始。
什么是SSH隧道
首先看下面这张图,我们所面临的大部分情况都和它类似。我们的电脑在右上角,通过公司带有防火墙功能的路由器接入互联网(当然可能还有交换机什么的在中间连接着你和路由器,但是在我们的问题中交换机并不起到什么关键性的作用)。右下脚的部分是一个网站的服务器,它是我们公司防火墙策略的一部分,也就是说公司不希望我们访问这个服务器。在右上角还有一台机器,它也是属于我们的。但是这台机器并不在我们公司里面,换句话说他不受到公司防火墙的限制。最后也是最重要的一点是,我们能够在公司通过互联网直接访问这台机器。或者说这台位于公司防火墙外面的机器需要拥有一个独立的互联网IP,同时公司的防火墙规则不会屏蔽这台机器,并且这台机器运行着一个OpenSSH服务器。

现在,我们清楚地知道了自己所处的网络环境。并且不难理解我们在公司无法访问那个服务器的原因是:线路A-B-C上A-B之间的防火墙屏蔽了对那个服务器的访问。与此同时,我们也很快注意到,线路A-B-D之间、D-C之间是不受阻碍的。相信你已经想到了,在A-B之间的防火墙不会屏蔽对机器d的访问。因此我们可以通过机器d建立一个通道A-B-D-C,从而访问到机器c上的数据。
这条通道可以用很多技术来建立,这里我们仅仅介绍如何使用SSH服务器来建立这样一个通道-他被称为SSH隧道。
如何建立本地SSH隧道
在我们计划建立一个本地SSH隧道之前,我们必须清楚下面这些数据:
- 中间服务器d的IP地址
- 要访问服务器c的IP地址
- 要访问服务器c的端口
现在,我们把上面这张图变得具体一些,给这些机器加上IP地址。并且根据下面这张图列出我们的计划:

- 需要访问234.234.234.234的FTP服务,也就是端口21
- 中间服务器是123.123.123.123
现在我们使用下面这条命令来达成我们的目的
ssh -N -f -L 2121:234.234.234.234:21 123.123.123.123 ftp localhost:2121 # 现在访问本地2121端口,就能连接234.234.234.234的21端口了
这里我们用到了SSH客户端的三个参数,下面我们一一做出解释:
- -N 告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发
- -f 告诉SSH客户端在后台运行
- -L 做本地映射端口,被冒号分割的三个部分含义分别是
- 需要使用的本地端口号
- 需要访问的目标机器IP地址(IP: 234.234.234.234)
- 需要访问的目标机器端口(端口: 21)
- 最后一个参数是我们用来建立隧道的中间机器的IP地址(IP: 123.123.123.123)
我们再重复一下-L参数的行为。-L X:Y:Z的含义是,将IP为Y的机器的Z端口通过中间服务器映射到本地机器的X端口。
在这条命令成功执行之后,我们已经具有绕过公司防火墙的能力,并且成功访问到了我们喜欢的一个FTP服务器了。
如何建立远程SSH隧道
通过建立本地SSH隧道,我们成功地绕过防火墙开始下载FTP上的资源了。那么当我们在家里的时候想要察看下载进度怎么办呢?大多数公司的网络是通过路由器接入互联网的,公司内部的机器不会直接与互联网连接,也就是不能通过互联网直接访问。通过线路D-B-A访问公司里的机器a便是不可能的。也许你已经注意到了,虽然D-B-A这个方向的连接不通,但是A-B-D这个方向的连接是没有问题的。那么,我们能否利用一条已经连接好的A-B-D方向的连接来完成D-B-A方向的访问呢?答案是肯定的,这就是远程SSH隧道的用途。
与本地SSH一样,我们在建立远程SSH隧道之前要清楚下面几个参数:
- 需要访问内部机器的远程机器的IP地址(这里是123.123.123.123)
- 需要让远程机器能访问的内部机器的IP地址(这里因为是想把本机映射出去,因此IP是127.0.0.1)
- 需要让远程机器能访问的内部机器的端口号(端口:22)
在清楚了上面的参数后,我们使用下面的命令来建立一个远程SSH隧道
ssh -N -f -R 2222:127.0.0.1:22 123.123.123.123
现在,在IP是123.123.123.123的机器上我们用下面的命令就可以登陆公司的IP是192.168.0.100的机器了。
ssh -p 2222 localhost
-N,-f 这两个参数我们已经在本地SSH隧道中介绍过了。我们现在重点说说参数-R。该参数的三个部分的含义分别是:
- 远程机器使用的端口(2222)
- 需要映射的内部机器的IP地址(127.0.0.1)
- 需要映射的内部机器的端口(22)
例如:-R X:Y:Z 就是把我们内部的Y机器的Z端口映射到远程机器的X端口上。
建立SSH隧道的几个技巧
自动重连
隧道可能因为某些原因断开,例如:机器重启,长时间没有数据通信而被路由器切断等等。因此我们可以用程序控制隧道的重新连接,例如一个简单的循环或者使用 djb’s daemontools . 不管用哪种方法,重连时都应避免因输入密码而卡死程序。关于如何安全的避免输入密码的方法,请参考我的 如何实现安全的免密码ssh登录 。这里请注意,如果通过其他程序控制隧道连接,应当避免将SSH客户端放到后台执行,也就是去掉-f参数。
保持长时间连接
有些路由器会把长时间没有通信的连接断开。SSH客户端的TCPKeepAlive选项可以避免这个问题的发生,默认情况下它是被开启的。如果它被关闭了,可以在ssh的命令上加上-o TCPKeepAlive=yes来开启。
另一种方法是,去掉-N参数,加入一个定期能产生输出的命令。例如: top或者vmstat。下面给出一个这种方法的例子:
ssh -R 2222:localhost:22 123.123.123.123 "vmstat 30"
检查隧道状态
有些时候隧道会因为一些原因通信不畅而卡死,例如:由于传输数据量太大,被路由器带入stalled状态。这种时候,往往SSH客户端并不退出,而是卡死在那里。一种应对方法是,使用SSH客户端的ServerAliveInterval和ServerAliveCountMax选项。ServerAliveInterval会在隧道无通信后的一段设置好的时间后发送一个请求给服务器要求服务器响应。如果服务器在ServerAliveCountMax次请求后都没能响应,那么SSH客户端就自动断开连接并退出,将控制权交给你的监控程序。这两个选项的设置方法分别是在ssh时加入-o ServerAliveInterval=n和-o ServerAliveCountMax=m。其中n, m可以自行定义。
如何将端口绑定到外部地址上
使用上面的方法,映射的端口只能绑定在127.0.0.1这个接口上。也就是说,只能被本机自己访问到。如何才能让其他机器访问这个端口呢?我们可以把这个映射的端口绑定在0.0.0.0的接口上,方法是加上参数-b 0.0.0.0。同时还需要打开SSH服务器端的一个选项-GatewayPorts。默认情况下它应当是被打开的。如果被关闭的话,可以在/etc/sshd_config中修改GatewayPorts no为GatewayPorts yes来打开它。
如何寻找中间服务器
如果你家里使用ADSL上网,多半你会比较幸运。一般的ADSL(例如 联通 的ADSL)都是有互联网地址的。你只需要在家里的路由器上一台装有OpenSSH server机器的SSH端口映射出去即可。同时一些提供SSH访问的虚拟主机也可以用于这一用途。例如: Hostmonser 或者 Dreamhost .
通过SSH隧道建立SOCKS服务器
如果我们需要借助一台中间服务器访问很多资源,一个个映射显然不是高明的办法(事实上,高明确实没有用这个方法)。幸好,SSH客户端为我们提供了通过SSH隧道建立SOCKS服务器的功能。
通过下面的命令我们可以建立一个通过123.123.123.123的SOCKS服务器。
ssh -N -f -D 1080 123.123.123 # 将端口绑定在127.0.0.1上 ssh -N -f -D 0.0.0.0:1080 123.123.123.123 # 将端口绑定在0.0.0.0上
通过SSH建立的SOCKS服务器使用的是SOCKS5协议,在为应用程序设置SOCKS代理的时候要特别注意。
总结
至此,我们已经对如何利用SSH隧道有一个基本的认识了。现在,文章开始时的那些问题应该迎刃而解了吧。这里要特别说一下,由于SSH隧道也使用了SSH加密协议,因此是不会被防火墙上的内容过滤器监控到的。也就是说一切在隧道中传输的数据都是被加密的。当然,离开隧道后的数据还是会保持自己原有的样子,没有加密的数据还是会被后续的路由设备监控到。
参考文献
通过/etc/ssh/ssh_config可以影响所有用户的ssh配置。而~/.ssh/ssh_config则是专门用来个性化你的ssh配置的。
例如: 主机dummy.foo.com的ssh端口在12345上面(为了防止密码猜解很多internet上的ssh都换了端口)。ssh_config里可以写入
Host dummy.foo.com Port 12345
酱紫在命令行上使用ssh dummy.foo.com的时候端口号就默认为12345了。能够少输入不少字符。
背景
ssh实现了加密的远程登录. 但做为一个系统管理员,当需要操作的机器很多时频繁的输入密码往往会让你崩溃掉. 幸好你看到了这篇文章, 它将告诉你如何实现即安全又方便的免密码ssh登录.
创建ssh公私钥
首先你需要放弃使用的密码登录的方式,因为那种方式实在太让人崩溃了. 下面我们开始使用一种被称为“公私钥“认证的方式来进行ssh登录. “公私钥“认证方式简单的解释是
- 你需要创建一对公私钥
- 然后把公钥放到服务器上, 自己保留好私钥
- 当ssh登录时,ssh程序会发送私钥去和服务器上的公钥做匹配.如果匹配成功就可以登录了
我们现在来点实际的, 我们有两台机器: qian.jianingy.com(这是我们的服务器, 实际上它是我的), nby.jianingy.com(这是我的笔记本). 首先,使用ssh-keygen程序在笔记本上创建公私钥对
生成公私钥
jianingy@nby:~$ ssh-keygenGenerating public/private rsa key pair. Enter file in which to save the key (/home/jianingy/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in id_rsa. Your public key has been saved in id_rsa.pub. The key fingerprint is: 15:c6:44:59:bf:6f:7a:11:eb:cb:02:d4:aa:33:e0:ae jianingy@nby
ssh-keygen程序会寻问你把公私钥文件存放在哪里.根据传统我们会把它放在~/.ssh里面.如果你不想追随这个传统, 你可能给花些时间游说那些ssh程序.至于公私钥的名字, 用默认的id_rsa吧,道理是一样的. 接下来输入一个口令,用来保护这个私钥. 我强烈建议你输入这个口令而不是让它为空(稍后我会解释的). 现在公私钥生成好了, 私钥(identification)被保存成了id_rsa, 公钥(public key)被保存成id_rsa.pub. 注意: 一定要保护好你的私钥,他就像你家里存家底的保险柜的钥匙一样重要.
现在把doggy.pub拷贝到服务器上去, 并且将其内容追加到一个叫~/.ssh/authorized_keys的文件里去
jianingy@nby:~$ cat /home/jianingy/.ssh/doggy.pub | ssh 192.168.1.9 "cat - >> ~/.ssh/authorized_keys"
好现在试试看ssh登录吧. 如果你想了解更多可以试试看用ssh -v登录.
使用密钥登录
jianingy@nby:~$ ssh qian.jianingy.com
Enter passphrase for key '/home/jianingy/.ssh/id_rsa':
现在输入你的passphrase, 然后就能成功登录了.的确, 到现在我们还不能无密码登录, 请再耐心点. 有个特例是, 如果你使用了空的passphrase, 那么现在已经可以无密码登录了.但是我也必须提醒你这是很不安全的做法,一旦有人复制了你的passphrase, 它就真的成了机房“任我行“了.
使用ssh-agent提供私钥
接下来是最关键的部分. 前面我们把输入密码变成了输入passphrase, 这没有带来任何方便.但是,要知道有个家伙能帮助我们自动输入这个passphrase(只是看起来像是自动输入而已). 我们只要输入一次passphrase, 以后的工作就可以交给那家伙了. 它就是ssh-agent(据我所知他和agent smith没有太多关系). 运行ssh-agent吧
jianingy@nby:~$ eval `ssh-agent`
jianingy@nby:~$ ssh-add
运行ssh-add添加密钥
jianingy@nby:~$ ssh-add
Enter passphrase for /home/jianingy/.ssh/id_rsa:
Identity added: /home/jianingy/.ssh/id_rsa (/home/jianingy/.ssh/id_rsa)
好了,现在再次登录服务器, 我们已经没有密码的烦恼了.至于为什么要用这么奇怪的方式运行ssh-agent, 你可以试着在命令行上输入一个ssh-agent回车, 看看输出什么你就会明白了.
复用已经存在的ssh-agent
如果你看了上面直接运行ssh-agent的输出,你应该已经明白了ssh和ssh-agent是如何相互了解的. 没错, 通过环境变量ssh知道了与ssh-agent通信的unix socket在哪里.然而当我们打开一个新的终端时我们必须要重新设置这个环境变量.做为一个慵懒的系统管理员,这是不可忍受的.所以我用perl写了这个脚本, 他可以找到以前的ssh-agent.
#!/usr/bin/env perl
# author: jianingy
# 2007/06/24
use strict;
my $exist;
my @dirs = grep {-r $_} glob "/tmp/ssh-*";
foreach (@dirs) {
my @socks = glob("{$_}/agent.*");
next unless (@socks);
foreach (@socks) {
$exist = $_;
# check agent
unless (system "SSH_AUTH_SOCK=$_ ssh-add -l >/dev/null 2>&1") {
print "export SSH_AUTH_SOCK=$_\n";
exit 0;
}
}
}
if ($exist) {
system "SSH_AUTH_SOCK=$exist ssh-add";
print "export SSH_AUTH_SOCK=$exist\n";
} else {
system "ssh-agent";
exec $0;
}
现在只需要在.bashrc里加入
eval `ssh-attach`
便可以在每次启动终端的时候就能复用ssh-agent了.到了这里我们已经实现了免密码的ssh登录(当然至少要输入一次passphrase, 但是相对于无数次的密码输入相信你不会在乎这一次的:P).
最新评论
1 周 6 天之前
6 周 4 天之前
10 周 4 天之前