Last Updated: 2022-08-14 14:42:39 Sunday
-- TOC --
使用Linux环境的同学,一定离不开SSH(Secure Shell)这个重要工具!
历史上,网络主机之间的通信是明文通信,不加密。这使得通信很不安全,一个典型的例子就是远程服务器的登录。登录远程服务器,需要用户输入的账户密码,并传给服务器,如果这个过程是明文通信,就意味着传递过程中,线路经过的所有中间设备都能看到账号密码,这是很可怕的。
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(隧道)和端口转发,为其它自身不带安全加密的业务提供一条安全的通道。
$ 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
,按提示打开启动即可。
man 5 sshd_config
SSH服务器的配置文件如下:
$ sudo vim /etc/ssh/sshd_config
修改配置后,需要重启sshd进程:
$ sudo systemctl restart 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一段时间不操作后断开连接,常常是因为长时间没有数据传输,这条连接被中间的路由器掐断了。有了心跳就可以解决这个问题。SSH Tunnel的Keep alive,也依赖这个机制。这个机制可以在ssh client上配置,效果一样。
关于保活,还有个TCPKeepAlive配置项,打开也无妨,只是它们工作在不同层面,TCPKeepAlive保活的效果可能并不好,据说防火墙可能丢弃保活报文,似乎很少见到真正依赖TCP层保活机制的实现?!
有了X11Forwarding的加持,我们就可以通过SSH客户端直接运行对端主机上的GUI程序。这个功能在调试GUI程序的时候,异常的方便。
X11Forwarding yes
AllowTcpForwarding 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。
OS系统一般都自带SSH客户端,Windows10系统除了可以在cmd窗口里直接使用ssh之外,还有很多独立的可以用来做SSH客户端的软件,比如我比较喜欢的MobaXterm。Putty也是一个非常著名的SSH客户端软件。
man 5 ssh_config
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连接某台主机的时候,如果加上-v参数,会打印出非常多的debug信息,用以辅助调试链接方面的问题。可以使用多个v
,显示出来的信息更多,最多3个。
$ ssh -l pi 192.168.2.107 -p 22
$ ssh pi@192.168.2.107:22
-l(小写L)跟户名,-p跟端口号;上面两种写法等效!如果不指定端口号,端口号默认使用22。如果不指定用户名,默认使用登录当前系统的用户名(Windows系统下基本上就是Administrator)。
如果你无法配置服务器,而正好这台服务器没有配置心跳,你可以配置客户端发起心跳。客户端配置文件如下:
$ sudo vim /etc/ssh/ssh_config # different with sshd_config
# configurate items
TCPKeepAlive yes
ServerAliveInterval 30
ServerAliveCountMax 5
保持TCP连接,客户端每30秒发一次心跳给服务器,如果连续5次都收不到服务器的心跳回复,就断开连接。30和5可按需配置。
当sshd配置了Client保活心跳,同时又在Client配置了Server保活心跳,测试发现,这种情况下,只有配置间隔较小的心跳起作用。
在Windows系统中修改ssh_config文件可能存在困难,可以使用将配置直接在命令行中输入的方式:
$ ssh -o ServerAliveInterval=15 \
-o ServerAliveCountMax=5 username@serverip -p port_number
如果sshd端没有配置ClientAlive,ssh客户端就可以在命令行使用ServerAlive配置项,他们的功能和效果是一样的。不推荐使用TcpKeepAlive,它在TCP层发挥作用,发送空的TCP ACK报文,这样的报文有可能会被防火墙丢弃。(TCP的KeepAlive机制似乎很少人使用)
ssh tunnel的保活,也是使用ClientAlive或ServerAlive配置项。
使用-o可以配置任意本需要在配置文件中配置的参数。
ssh通信是加密的,而且加密算法按什么优先级选择,可以自己设置,使用-c
参数。
-c cipher_spec
Selects the cipher specification for encrypting the session.
cipher_spec is a comma-separated list of ciphers listed in order of preference.
See the Ciphers keyword in ssh_config(5) for more information.
我理解,如果-c参数后面只有一个加密算法,相当于指定了用此算法。用-o
参数指定Ciphers配置的效果是一样的。
$ ssh -Q cipher
SSH Port Forwarding,SSH端口转发,也称为Tcp Forwarding,其又分为本地端口转发(Local Port Forwarding),远端端口转发(Remote Port Forwarding)和动态转发(Dynamic Port Forwarding)。
SSH有Tunnel技术,这是个3层概念,可以用来实现SSH-Based V@P$N,而Port Forwarding是4层概念。
SSH提供了一个非常有用的端口转发功能。它能够将其他TCP端口的网络数据通过SSH链接来转发,并且自动提供了加密及解密服务。
打开SSH端口转发功能:
AllowTcpForwarding yes
顾名思义,本地端口转发,通过访问本地端口,实现访问另一个通过SSH连接的远程端口。这个功能常用来正面穿透防火墙。
比如A和X在防火墙外,B和C在防火墙内,A和X不能直接访问B和C上的资源,幸运的是,A可以通过SSH连接B,此时,我们就可以通过SSH的本地端口转发功能,实现A任意访问B和C上的资源,也可以让X通过A实现同样的访问。如下图:
A$ ssh -g -N -f -L 12345:remotehost:23456 username@B -p port
-L
,Local Port Forwarding12345:remotehost:23456
,本地端口为12345,实际访问的端口是remotehost:23456username@B -p port
,SSH连接的主机,它一定是B可以访问remotehost的主机,比如主机C,如果是访问B自己,命令行参数就应该写成localhost(B的视角)-N
,表示SSH连接之后,不打开shell-f
,表示此连接进程后台运行,当前shell还可以干别的,但是关闭shell窗口会导致此进程也被关闭-g
,gateway,表示A在局域网内开放自己的12345端口,此时X可以通过访问A的12345端口,实现访问remotehost:23456(相当于在A的所有接口上监听12345端口,而不仅仅是在loopback接口上监听,-g
参数只对local port forwarding有效,因为(1)remote的监听地址由sshd的配置确定;(2)dynamic的监听地址可直接配置。)-L
参数是必须的,-g
,-f
和-N
可选。
如果说本地端口转发是正面穿透防火墙,那么远端端口转发就是反向穿透,从内向外打通一条TCP链路。这几乎是最简单的内网穿透的方案。
A和X在防火墙外,B和C在防火墙内部,A和X不能访问防火墙内部,不幸的是,A和X也不能通过SSH连接B和C。幸运的是,B可以在防火墙内部通过SSH连接A,此时可以在B上发起远端端口转发,实现A可以任意访问B和C的资源。
B$ ssh -N -f -R 12345:host:23456 username@A -p port
-R
,Remote Port Forwardingusername@A -p port
,远端连接主机,对B而言12345:host:23456
,12345是远端连接主机的端口,表示在远端主机上访问此端口,相当于通过SSH的端口转发,访问了host:23456,host:23456是B可以访问的资源,如果是B自己,可以写localhost,如果是C,就写C的ip地址这样,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在公网,相当于端口就直接暴露在公网了,慎用!
最后,推荐自研开源小工具report:轻量级的remote port forwarding工具。
不同于前两种端口转发,动态端口转发不限制访问的端口号,因此被称为动态。
$ ssh -D localhost:12345 username@remotehost -p port
-D
,Dynamic Port Forwarding在本地建立一个动态端口转发,访问本地的12345端口时,所有访问流量全部通过加密TCP链接,转发到remotehost,然后再转发出去。
多说一点内网穿透(Intranet Penetration)。
我给公司写过一个简单的定制化的内网穿透方案,在测试的时候,遇到了问题。当一个内网业务端口被映射到公网后,有可能对这个端口的访问,有多个TCP连接,比如典型的HTTP 80端口。因此,推到重来了一次:
这一版的难点,是要在一路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 Port Forwarding的总结,肯定有一些不全面的地方,以后在使用过程中,再慢慢积累。
技巧01:多条SSH端口转发路径可以相互之间连接,组合形成一条更长的转发链路。
技巧02:在网速慢而且不主要传视频的情况下,可以使用-C
参数,开启压缩传输,来提高速度!比如git clone的时候,能够从十几K一下子提高到几百K!
技巧03:SSH不管是Server还是Client,都可以配置链路保活心跳(间前面配置部分的说明),这个心跳配置对于端口转发也适用(抓包验证)。因此,不用太担心长时间没有数据的TCP链接的保活的问题。不过,还是有可能因为各种原因导致中间的TCP连接被中断,此时,可以试试autopass。
使用秘钥登录的好处是免密,因此可以很顺畅的运行一些重要的脚本,还很安全!但需要知道,用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
identityfile,不是identifyfile....:)
ssh-copy-id
要把你的密钥复制到服务器上,使用 ssh-copy-id 命令。例如,如果我拥有一台名为 example.com 的服务器,那么我可以用这个命令把我的公钥复制到它上面:ssh-copy-id yourname@example.com
,最后修改sshd配置文件,PasswordAuthentication no
在登录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, sossh foobar@server ls | grep PATTERN
will grep locally the remote output of ls andls | ssh foobar@server grep PATTERN
will grep remotely the local output of ls.
本地有一个shell脚本,在远端的主机上执行:
$ ssh name@domain|ip [-p <port>] 'bash -s' < test.sh
这是我个人的开源项目,纯Python开发,专注于实现自动输入ssh的密码,以及sudo时的密码自动输入。具体请参考这里:autopass:自动输入ssh,scp,sudo的密码
-- EOF --
本文链接:https://cs.pynote.net/net/202109201/
-- More --