SSH使用指南

Last Updated: 2021-09-21 14:04:51 Tuesday

-- TOC --

使用Linux环境的同学,一定离不开SSH(Secure Shell)这个重要工具!

openssh

SSH的历史和作用

历史上,网络主机之间的通信是明文通信,不加密。这使得通信很不安全,一个典型的例子就是远程服务器的登录。登录远程服务器,需要用户输入的账户密码,并传给服务器,如果这个过程是明文通信,就意味着传递过程中,线路经过的所有中间设备都能看到账号密码,这是很可怕的。

SSH 就是为了解决这个问题而诞生的,它能够加密计算机之间的通信,保证不被窃听或篡改。它还能对操作者进行认证(authentication)和授权(authorization)。明文网络协议可以套用在它的里面,从而实现加密通信。

1995年,芬兰赫尔辛基工业大学的研究员 Tatu Ylönen 设计了SSH 协议的第一个版本,同时给出了第一个实现(称为 SSH1)。当时,他所在的大学网络一直发生密码嗅探攻击(sniffing),他不得不为服务器设计一个更加安全的登录方式。写完SSH1以后,他就把这个工具公开了,并允许其他人免费使用。

嗅探(Sniffing)是指不间断地监测网络通信设备,截获用户传给服务器的账户密码,记录下来后另外使用。

SSH1可以替换 rlogin、TELNET、FTP 和 rsh 这些不安全的协议,所以大受欢迎,用户快速增长,1995年底已经发展到五十个国家的20,000多个用户。SSH1协议也变成 IETF 的标准文档。

1995年12月,由于客服需求越来越大,Tatu Ylönen 就成立了一家公司叫 SCS,专门销售和开发 SSH软件。这个软件的后续版本,逐渐从免费软件变成了专有的商业软件。由于SSH1 协议存在一些安全漏洞,所以1996年SCS公司又提出了 SSH2 协议(或者称为 SSH 2.0)。这个协议与SSH1.0版不兼容。1997年SSH2进行了标准化,1998年推出了软件实现 的SSH2。但是,官方的 SSH2 软件是一个专有软件,不能免费使用,而且 SSH1 的有些功能也没有提供。

1999年,OpenBSD的开发人员决定写一个SSH2协议的开源实现,这就是著名的OpenSSH 项目。该项目最初是基于SSH 1.2.12版本,那是当时SSH1 最后一个开源版本。但是,OpenSSH 很快就完全摆脱了原始的官方代码,在许多开发者的参与下,按照自己的路线发展。OpenSSH随OpenBSD 2.6版本一起提供,以后又移植到其他操作系统,成为最流行的SSH 实现。目前,Linux的所有发行版几乎都自带OpenSSH。现在,SSH2 有多种实现,既有免费的,也有收费的。OpenSSH还提供一些辅助工具软件(比如 ssh-keygen 、ssh-agent)和专门的客户端工具(比如 scp 和 sftp)。

现在还有谁会去使用收费的SSH2呢?Tatu Ylönen如果不成立公司,一直保持SSH的开源免费状态,现在会是什么样呢?不过,1995年发生的这些,的确太早了......我一直深深感到自己的落后......

除了安全登录服务器,SSH还可以用来创建Tunnel(隧道),为其它自身不带安全加密的业务提供一条安全的通道。

SSH服务器的安装

$ sudo apt install openssh-server

安装成功后,sshd进程(SSH服务器后台进程的名称)就运行起来了,非root账户可以直接登录(有的云服务器在初始化后,只有root账户,此时只能用root登录)。同时,SFTP也自动运行起来,SFTP与SSH使用相同端口。(注意:SFTP与FTPS是不一样的)

树莓派官方提供的系统自带SSH Server,不过要通过配置启动才可以使用。Lite系统启动SSH Server的方法如下:

$ sudo raspi-config

然后选择 3 Interface Options --> P2 SSH,按提示打开启动即可。

SSH服务器的配置

SSH服务器的配置文件如下:

$ sudo vim /etc/ssh/sshd_config

修改配置后,需要重启sshd进程!

Ubuntu安装的sshd的配置文件中,没有指定具体的版本,anyway,增加一个版本信息不复杂,还可以提高安全性:

Protocol 2

SSH服务器默认的监听IP为运行主机上所有IP地址,默认端口号为22。

Port 22
Port 12345
ListenAddress 0.0.0.0  # listen on all ip, but no port specified
ListenAddress 0.0.0.0:12345  # listen on all ip and with port 12345
ListenAddress 192.168.1.8:12345  # listen only on 192.168.1.8

SSH服务器可以同时在多个端口上监听,这时就配置多条Port。公网上的服务器为了安全,很多时候不会现在在22号默认端口上监听,而会选择一个自定义的端口号。

在切换SSH监听端口的时候,可以先让其同时在两个端口上监听,然后再去掉其中一个,这样可以保证SSH的连接在修改配置时,不会被中断。如果直接将端口号从一个端口号修改到另一个,然后重启,可能当前连接就会中断,然后你再去连新端口号,可能会由于一些其它配置方面的问题,导致无法连接,这会很麻烦。

有的时候修改SSH监听端口后,无法成功连接,可能的原因是你的服务器外有一层防火墙,比如云服务器供应商可能会在外围提供一些安全机制,让你可以配置防火墙规则。

禁止root用户登录,是为安全考虑,阿里云的云盾就有一个检查项,叫做SSH登录基线检查,就是检查这个。

PermitRootLogin no  # yes
AllowUsers xinlin abc xyz

登录需要认证,禁止密码认证,实际上就是禁止用户用密码登录。比如:如果设置了使用SSH秘钥登录,这个时候取消密码认证,就没有人可以再使用密码登录系统,只能用你的私钥登录。

PasswordAuthentication no

默认是不允许的,但是如果你的环境非常安全,或者因为其他原因无法输入密码,或者可以把输入密码省掉:

PermitEmptyPasswords yes  # no

如果不配置服务器心跳,常常遇到的情况就是,ssh session无操作一段时间,连接就终止了。要不断地重连,挺烦人的。

ClientAliveInterval 30
ClientAliveCountMax 5

30表示:服务器每隔30秒向客户端发出一次心跳;5表示:如果服务器连续5次都收不到客户端的心跳回复,就断开连接。30和5这两个数字可以自己按需配置。

SSH一段时间不操作后断开连接,常常是因为长时间没有数据传输,这条连接被中间的路由器掐断了。有了心跳就可以解决这个问题。

有了X11Forwarding的加持,我们就可以通过SSH客户端直接运行对端主机上的GUI程序。这个功能在调试GUI程序的时候,异常的方便。

X11Forwarding yes

sftp是ssh服务器自带的一个功能,默认的配置和建议配置如下:

Subsystem sftp /usr/lib/openssh/sftp-server  # default
Subsystem sftp internal-sftp  # recommend

以上两行配置,任何时候只能保留一行。(用#注释掉其中一行即可)

使用internal-sftp有两个好处:

  1. 性能更好,在连接sftp的时候,系统不会fork出一个新的进程;
  2. 可以在sshd_config配置文件中,使用ChrootDirectory,Match,ForceCommand等指令来限制登录sftp用户的性能。

--

远程登录前可以显示给用户的信息,通过在Banner字段后指定一个文件来实现,文件内容不支持转义:

# Banner none  # this is default config
Banner /etc/issue.net

传统都是使用/etc/issue.net文件,具体参考:issue,motd文件

登录后是否显示Motd,由下面这条配置控制:

PrintMotd no

看到一个别人的故障说明:motd信息在登录的时候,显示了两次!因为sshd配置了PrintMotd yes,pam也配置了显示motd,去掉其中一个就OK。

SSH客户端的配置

OS系统一般都自带SSH客户端,Windows10系统除了可以在cmd窗口里直接使用ssh之外,还有很多独立的可以用来做SSH客户端的软件,比如我比较喜欢的MobaXterm。Putty也是一个非常著名的SSH客户端软件。

Windows7系统没有自带的ssh客户端,需要自己安装。Windows10系统有自带的,但你可以对版本不满意。搜索openssh for windows,或者访问https://www.mls-software.com/opensshd.html,装好后再cmd窗口就可以使用ssh客户端了。

# on my raspberry pi
$ ssh -V
OpenSSH_7.9p1 Raspbian-10+deb10u2+rpt1, OpenSSL 1.1.1d  10 Sep 2019
$ ssh -l pi 192.168.2.107 -p 22
$ ssh pi@192.168.2.107:22

-l(小写L)跟户名,-p跟端口号;上面两种写法等效!如果不指定端口号,端口号默认使用22。如果不指定用户名,默认使用登录当前系统的用户名(Windows系统下基本上就是Administrator)。

在使用ssh连接某台主机的时候,如果加上-v参数,会打印出非常多的debug信息,这就是verbose(-v)参数的作用。

如果你无法配置服务器,而正好这台服务器没有配置心跳,你可以配置客户端发起心跳。客户端配置文件如下:

$ sudo vim /etc/ssh/ssh_config  # different with sshd_config
# configurate items
TCPKeepAlive yes
ServerAliveInterval 30
ServerAliveCountMax 5

保持TCP连接,客户端每30秒发一次心跳给服务器,如果连续5次都收不到服务器的心跳回复,就断开连接。30和5可按需配置。

在Windows系统中修改ssh_config文件可能存在困难,可以使用将配置直接在命令行中输入的方式:

$ ssh -o TCPKeepAlive=yes -o ServerAliveInterval=30 \
-o ServerAliveCountMax=5 username@serverip -p port_number

ssh通信是加密的,而且加密算法可以自己设置,使用-c参数。查询支持的加密算法:ssh -Q cipher。查看ssh默认使用的加密算法序列,以及如何自己设置:man 5 ssh_config

SSH Tunnel(隧道)

SSH Tunnel也被称为SSH端口转发,其有分为本地端口转发(Local Port Forwarding),远端端口转发(Remote Port Forwarding)和动态转发(Dynamic Port Forwarding)。

SSH tunneling is a method of transporting arbitrary networking data over an encrypted SSH connection. It can be used to add encryption to legacy applications. It can also be used to implement VPNs (Virtual Private Networks) and access intranet services across firewalls.

SSH Tunnel技术是一个可以在加密的SSH连接中传输任意网络数据的方法。它可以用来给传统的那些没有加密功能的应用提供加密传输服务,也可以用来实现VPN,并且可以穿透内网

SSH隧道会自动加密和解密所有SSH客户端与服务端之间的网络数据。而且SSH还同时提供了一个非常有用的功能,这就是端口转发。它能够将其他TCP端口的网络数据通过SSH链接来转发,并且自动提供了加密及解密服务。这一过程有时也被叫做“隧道”(tunneling),这是因为SSH为其他TCP链接提供了一个安全的通道来进行传输而得名。例如,Telnet,SMTP,LDAP这些TCP应用均能够从中得益,避免了用户名,密码以及隐私信息的明文传输。与此同时,如果你工作环境中的防火墙限制了一些网络端口的使用,但是允许SSH连接,那么也能够通过将TCP端口转发来使用SSH进行通讯。

打开SSH端口转发功能:

AllowAgentForwarding yes

本地端口转发

顾名思义,本地端口转发,通过访问本地端口,实现访问另一个通过SSH隧道连接的远程端口。这个功能常用来正面穿透防火墙。

比如A和X在防火墙外,B和C在防火墙内,A和X不能直接访问B和C上的资源,幸运的是,A可以通过SSH连接B,此时,我们就可以通过SSH的本地端口转发功能,实现A任意访问B和C上的资源,也可以让X通过A实现同样的访问。如下图:

ssh_local_port_forwarding

A$ ssh -g -N -f -L 12345:remotehost:23456 username@B -p port

-L 参数是必须的,-g-f-N可选。

远端端口转发

如果说本地端口转发是正面穿透防火墙,那么远端端口转发就是反向穿透,从内向外打通一条隧道。这几乎是最简单的内网穿透的方案。

A和X在防火墙外,B和C在防火墙内部,A和X不能访问防火墙内部,不幸的是,A和X也不能通过SSH连接B和C。幸运的是,B可以在防火墙内部通过SSH连接A,此时可以在B上发起远端端口转发,实现A可以任意访问B和C的资源。

ssh_remote_port_forwarding

B$ ssh -N -f -R 12345:host:23456 username@A -p port

这样,A就可以访问B或C了!但是X还是不能访问,默认A只在loopback上监听12345端口。要实现X也能够像A一样访问B或C,可以

A$ ssh -g -N -L 12346:localhost:12345 localhost

X访问A的12346,相同于访问A的12345,也就相当于访问B或C。

这个方法最好,默认A上的端口只在localhost上监听,如果A在公网,这是必须的。X建立加密隧道到A,去访问A的localhost,整个链路都有加密。

GatewayPorts yes

这样默认remote port forwarding的端口,会在remote host的所有接口上监听。如果host在公网,相当于端口就直接暴露在公网了,慎用!

动态端口转发

不同于前两种端口转发,动态端口转发不限制访问的端口号,因此被称为动态。

$ ssh -D localhost:12345 username@remotehost -p port

在本地建立一个动态端口转发,访问本地的12345端口时,所有访问流量全部通过加密SSH隧道,转发到remotehost,然后再转发出去。

内网穿透

多说一点内网穿透。

我给公司写过一个简单的定制化的内网穿透方案,在测试的时候,遇到了问题。当一个内网业务端口被映射到公网后,有可能对这个端口的访问,有多个TCP连接,比如典型的HTTP 80端口。因此,推到重来了一次:

  1. 映射出去的端口,要支持多连接;
  2. 公网先连接,然后再在内网发起连接;

这一版的难点,是要在一路TCP连接中,区分多路TCP的数据。这需要设计一个私有协议,在IO multiplexing的时候,要非常小心。(私有协议参考:用一路TCP传输多路TCP数据

很快代码就运行成功了,但做完之后,我发现这个功能,跟SSH的Remote Port Forwarding是何其相似呀!因此我说,一般情况下,SSH的Remote Port Forwarding,就是最简单最好用的内网穿透方案!只要是本地在内网可以访问的业务端口,都可以一键穿透。

动态端口的内网穿透

按前述方法,内网穿透只能是单端口的,即只能将内网中的某一个端口映射出去,能干什么取决于这个端口的能力。是否能够实现,内网穿透之后的动态端口转发的能力呢?可以的。

方法01

在内网,将一台Linux主机的22号端口先Remote出去:

$ ssh -fNCR 10055:localhost:22 username@yourdomain -p port

在yourdomain上,设置动态端口转发,目标端口是localhost:10055:

$ ssh -fNCD 0.0.0.0:10056 name2@localhost -p 10055

name2是内网Linux主机的username。在外网,将10056的访问映射到10055,10055对应内网的22,因此10056就相当于一个穿透到内网的动态转发端口。测试OK。

方法1的优点:动态端口的建立在外网主机上,在外网可以方便灵活控制。

方法02

可以将方法1反过来,在内网设置一个动态点,然后将内网的动态点remote出去。

方法2的优点:

  1. 所有的ssh命令输入,都在内网的某一个主机上完成;
  2. Windows主机也可以,现在默认都支持ssh client;

缺点就是方法1的优点。有的时候,这种灵活控制非常重要,涉及安全问题。

SSH隧道使用技巧

以上对SSH隧道的总结,肯定有一些不全面的地方,以后在使用过程中,再慢慢积累。

技巧01:多条SSH隧道可以相互之间连接,形成一条由多段SSH隧道组成的更长的隧道。

技巧02:在网速慢而且不主要传视频的情况下,可以使用-C参数,开启压缩传输,来提高速度!比如git clone的时候,能够从十几K一下子提高到几百K!

SSH秘钥登录

使用秘钥登录的好处是免密,因此可以很顺畅的运行一些重要的脚本,还很安全!但需要知道,用SSH秘钥来实现免密,不是惟一的方法,也并不一定是最安全的方式!

被我长期忽视的SSH Client的配置文件:~/.ssh/config

ssh-keygen

在命令行直接运行ssh-keygen,回车几次,就可以在~/.ssh目录中生成两个以id_开头文件,一个私钥,一个公钥!

-t,选择加密方式,默认rsa

-b,秘钥长度,默认1024bit

-f,指定生成的文件名

-C,Comments,默认为hostname@username

ssh-keygen生产的public key文件格式,与openssl略有不同,前面有ssh-rsa前缀,后面有用-C生产的comments:

$ cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCtTajTMbZPSinFDPIvAMRkBdy03j52zXyLNUk9tsJaurS23eAW2Dr6qEiFRLEVfZ8Kh/KJwxdB8OnJlhWYSJWUQF0+8mxEZ6/tw7XHH3o5+WLcD5/W25tY8hEuAVqgUn4X0P3W4yuFhkEUawnKHQwFFvkOz0E7kJVsMCEeui0NKEKqEKftOUjdB1C/SVren9jmAr9ksqS3snzB5NKDGkk2OytrMQw3yqj054xyFQ81G+a1U7NHJiQf5RozvYxKWFwqIMoNZBOhf8EGZqKekoFvKtBJYFkMC8J8+G9SSx1su2+vsdZkbLAu5VnMgsDZxTaIpGO0PKkY+kUY8sZIRRu6MyGtk0EwTzZDH+E7pDgT/B/cOCKstVzUfg1gfWFKjssqNFm0U2qYq3Qnul5f5Qydgn1hBGP2qnFSqoPG5fH23JJmBXNlqchQzlbsgzLCCb/7JzXQM09mciF2jEgavZUh32ajw4n2qRZPs7s5ShwIUStX2gTpFXM66F+XM33R0I0= xinlin@K

指定具体哪个网站使用哪个private key,要修改~/.ssh/config

$ cat ~/.ssh/config
# jimin gitlab
Host 192.222.1.150
#HostName github.com
#User xinlin
IdentityFile ~/.ssh/id_rsa

ssh-copy-id

要把你的密钥复制到服务器上,使用 ssh-copy-id 命令。例如,如果我拥有一台名为 example.com 的服务器,那么我可以用这个命令把我的公钥复制到它上面:ssh-copy-id yourname@example.com,最后修改sshd配置文件,PasswordAuthentication no

ssh远程命令执行

在登录ssh的命令后面,跟上一组命令字符串,就实现了远程命令的执行。

$ ssh -l <name> <domain|ip> [-p <port>] <command>
$ ssh name@domain|ip [-p <port>] <command>
$ ssh -l <name> <domain|ip> [-p <port>] 'pwd; cd ~/repos;ls'

当需要远程执行需要交互命令的时候,比如top命令,需要使用-t参数,来显式地告诉ssh提供一个TTY:

$ ssh name@domain|ip [-p <port>] -t <command>

An often overlooked feature of ssh is the ability to run commands directly. ssh foobar@server ls will execute ls in the home folder of foobar. It works with pipes, so ssh foobar@server ls | grep PATTERN will grep locally the remote output of ls and ls | ssh foobar@server grep PATTERN will grep remotely the local output of ls.

本地有一个shell脚本,在远端的主机上执行:

$ ssh name@domain|ip [-p <port>] 'bash -s' < test.sh

-- EOF --

本文链接:https://cs.pynote.net/net/202109201/

donate

-- More --