了不起的SSH

概述

SSH(Secure Shell Host,安全外壳主机,其实笔者觉得翻译成"安全主机外壳"好像更合适一点)是一种基于TCP/IP协议的网络安全协议、网络工具和应用体系,它通过信息加密和身份认证机制实现安全的远程网络访问和文件传输等业务。SSH是应用广泛的主流远程访问协议,在各个主要的操作系统和网络设备中都提供支持,也是Linux和MacOS系统原生默认的远程访问和操作机制,而且在较新的Windows操作系统中也内置了其支持或者作为选项提供。考虑到这几个系统在服务器和桌面系统市场的巨大数量,这使它成为现在互联网行业事实的技术标准。

使用Telnet协议进行来实现终端模拟和远程操作的做法由来已久,这实际是各种网络原生和服务器操作系统如Unix等的标准做法。但原来的Telnet协议使用明文传输信息,存在巨大的安全隐患,已经被逐步淘汰。另外,从功能和应用角度来讲,SSH也不仅仅是Telnet的简单升级和替代产品,除了远程终端之外,它甚至还结合了文件传输和通信隧道等功能,从而大大拓展了其应用的方式和场景。这些我们都会在后面深入探讨。

原理和安全机制

SSH协议通过在客户端和服务器之间创建安全的信息通道来进行工作。它本身是基于TCP/IP协议的,这个协议本身是不安全的,但SSH通过信息加密机制,对传输的IP数据信息进行了加密和封装,从而来保证通信过程的安全。这和HTTPS协议有点类似。

SSH的技术基础是使用公钥来协商加密,我们简单大概的说明一下这个过程。在服务端,SSH的服务程序通常作为守护进动,默认情况下它在启动后会监听网络的22号端口,等待客户端的连接;要建立SSH连接,客户端首先向服务器的IP地址和SSH端)发送请求来启动SSH协议;随后服务器将其公钥发送给客户端,客户端也将其公钥(临时或者固定)发送给服务器;两者通过己方私钥和对方公钥可以协商计算出一个通信密钥;然后都使用这个密钥对所需要传输的信息进行加密和解密,从而实现SSH的安全通信过程;随后,才开始标准的登录和认证过程,在身份认证成功之后,就进行后续的远程网络操作了。可以看到,这个程结合了对称和非对称加密算法,而且其选择的算法和配置能够保证传输数据的机密性、完整性和真实性。

当然,真实和实际的实现由于要考虑很多兼容、可靠和稳定的因素,比这个要复杂一些,但基本上就是这样,如下图所示:

前面提到,SSH的核心要素就是这个Secure-安全,为此它设计和应用了很多安全方面的技术和机制。我们在稍微深入一点,可以更好了理解为什么要这样做。

首先就是为什么要使用公钥,而不是一个共享密钥(像很多VPN程序那样)。其实,在完整的安全机制中,通常认为共享密钥是不安全的。因为共享的密钥如何传输和共享,是一个逻辑结构上的冲突。而公钥加密算法能够在不进行网络传输密钥的情况下,基于一个数学的算法,通过己方的私钥和对方的公钥,协商计算出一个密钥,这个算法能保证在两端是一样的,从而达到共享密钥的目的。

其次是为什么不直接使用公私钥进行加密和解密。这应该是工程上的考虑。公私钥加密最大的问题是其执行的效率,相对而言它的执行速度太慢,而且也不适合处理大量的数据,所以人们就巧妙的设计使用公钥来协商出一个对称加密用的密钥,然后对需要传输的信息进行对称加密,从而完美的平衡了安全和效率之间的冲突。

此外,动态协商密钥的机制,能够保证每次会话连接建立的时候,都使用不同的对称加密密钥和初始信息,进一步提高了网络连接的安全性。

还有,就是为了保证兼容性和可用性,在交互的过程中有一些配置协商机制,如版本协商、算法选择协商等等,这些机制保证了在不同平台和实现之间的可互操作性,使整个技术系统能够平稳向前发展。

其他的机制,可能还包括在其选择的算法和配置当中。如可以使用SHA算法来验证和保证信息的完整性;AES算法实现对称加密保证信息的机密;RSA非对称加密算法来实现密钥协商,并使用签名来保证信息的不可抵赖性等等。这些都是标准主流的密码学算法,它们综合起来,构成了SSH信息安全的密码学理论基础。

从功能和系统架构上来看,SSH实际上也是分为几个层次和模块的:

  • 传输层(Transport): 建立连接和提供底层安全机制

  • 认证层(Authentication): 用于对客户端进行认证

  • 连接层(Connection): 用于维护数据传输的通道

这些都帮助我们理解其构成和流程之间的逻辑关联。所以,SSH不仅仅是一个网络安全协议,也是一套网络安全和应用体系。

下面,我们可以使用ssh -vv 尝试连接一个服务器,来实际看看这个过程发生了什么,其实里面还是挺有趣的,有很多细节,比看上面哪个流程图好多了。如果没有兴趣可以直接看后面的解读信息。

shell 复制代码
PS C:\Users\yanjh> ssh -vv 192.168.9.36
OpenSSH_for_Windows_8.1p1, LibreSSL 3.0.2
debug2: resolve_canonicalize: hostname 192.168.9.36 is address
debug2: ssh_connect_direct
debug1: Connecting to 192.168.9.36 [192.168.9.36] port 22.
debug1: Connection established.
debug1: identity file C:\\Users\\yanjh/.ssh/id_rsa type -1
debug1: identity file C:\\Users\\yanjh/.ssh/id_rsa-cert type -1
debug1: identity file C:\\Users\\yanjh/.ssh/id_dsa type -1
debug1: identity file C:\\Users\\yanjh/.ssh/id_dsa-cert type -1
debug1: identity file C:\\Users\\yanjh/.ssh/id_ecdsa type -1
debug1: identity file C:\\Users\\yanjh/.ssh/id_ecdsa-cert type -1
debug1: identity file C:\\Users\\yanjh/.ssh/id_ed25519 type -1
debug1: identity file C:\\Users\\yanjh/.ssh/id_ed25519-cert type -1
debug1: identity file C:\\Users\\yanjh/.ssh/id_xmss type -1
debug1: identity file C:\\Users\\yanjh/.ssh/id_xmss-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_for_Windows_8.1
debug1: Remote protocol version 2.0, remote software version OpenSSH_8.4p1 Debian-5+deb11u1
debug1: match: OpenSSH_8.4p1 Debian-5+deb11u1 pat OpenSSH* compat 0x04000000
debug2: fd 3 setting O_NONBLOCK
debug1: Authenticating to 192.168.9.36:22 as 'yanjh'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug2: local client KEXINIT proposal
debug2: KEX algorithms: curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,ext-info-c
debug2: host key algorithms: ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
debug2: ciphers ctos: chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
debug2: ciphers stoc: chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
debug2: MACs ctos: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: MACs stoc: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: compression ctos: none,zlib@openssh.com,zlib
debug2: compression stoc: none,zlib@openssh.com,zlib
debug2: languages ctos:
debug2: languages stoc:
debug2: first_kex_follows 0
debug2: reserved 0
debug2: peer server KEXINIT proposal
debug2: KEX algorithms: curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
debug2: host key algorithms: rsa-sha2-512,rsa-sha2-256,ssh-rsa,ecdsa-sha2-nistp256,ssh-ed25519
debug2: ciphers ctos: chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
debug2: ciphers stoc: chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
debug2: MACs ctos: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: MACs stoc: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: compression ctos: none,zlib@openssh.com
debug2: compression stoc: none,zlib@openssh.com
debug2: languages ctos:
debug2: languages stoc:
debug2: first_kex_follows 0
debug2: reserved 0
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ecdsa-sha2-nistp256 SHA256:7kvTz2GZcoWhEXqIwt0Sz/mDfur+oAk7YB6xszg01XI
debug1: read_passphrase: can't open /dev/tty: No such file or directory
The authenticity of host '192.168.9.36 (192.168.9.36)' can't be established.
ECDSA key fingerprint is SHA256:7kvTz2GZcoWhEXqIwt0Sz/mDfur+oAk7YB6xszg01XI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.9.36' (ECDSA) to the list of known hosts.
debug2: set_newkeys: mode 1
debug1: rekey out after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug2: set_newkeys: mode 0
debug1: rekey in after 134217728 blocks
debug1: pubkey_prepare: ssh_get_authentication_socket: No such file or directory
debug1: Will attempt key: C:\\Users\\yanjh/.ssh/id_rsa
debug1: Will attempt key: C:\\Users\\yanjh/.ssh/id_dsa
debug1: Will attempt key: C:\\Users\\yanjh/.ssh/id_ecdsa
debug1: Will attempt key: C:\\Users\\yanjh/.ssh/id_ed25519
debug1: Will attempt key: C:\\Users\\yanjh/.ssh/id_xmss
debug2: pubkey_prepare: done
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=<ssh-ed25519,sk-ssh-ed25519@openssh.com,ssh-rsa,rsa-sha2-256,rsa-sha2-512,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ecdsa-sha2-nistp256@openssh.com,webauthn-sk-ecdsa-sha2-nistp256@openssh.com>
debug2: service_accept: ssh-userauth
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Trying private key: C:\\Users\\yanjh/.ssh/id_rsa
debug2: we sent a publickey packet, wait for reply
debug1: Authentications that can continue: publickey,password
debug1: Trying private key: C:\\Users\\yanjh/.ssh/id_dsa
debug1: Trying private key: C:\\Users\\yanjh/.ssh/id_ecdsa
debug1: Trying private key: C:\\Users\\yanjh/.ssh/id_ed25519
debug1: Trying private key: C:\\Users\\yanjh/.ssh/id_xmss
debug2: we did not send a packet, disable method
debug1: Next authentication method: password
debug1: read_passphrase: can't open /dev/tty: No such file or directory
yanjh@192.168.9.36's password:
debug2: we sent a password packet, wait for reply
debug1: Authentication succeeded (password).
Authenticated to 192.168.9.36 ([192.168.9.36]:22).
debug1: channel 0: new [client-session]
debug2: channel 0: send open
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: network
debug1: ENABLE_VIRTUAL_TERMINAL_INPUT is supported. Reading the VTSequence from console
debug1: ENABLE_VIRTUAL_TERMINAL_PROCESSING is supported. Console supports the ansi parsing
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug2: channel_input_open_confirmation: channel 0: callback start
debug2: fd 3 setting TCP_NODELAY
debug2: client_session2_setup: id 0
debug2: channel 0: request pty-req confirm 1
debug2: channel 0: request shell confirm 1
debug2: channel_input_open_confirmation: channel 0: callback done
debug2: channel 0: open confirm rwindow 0 rmax 32768
debug2: channel_input_status_confirm: type 99 id 0
debug2: PTY allocation request accepted on channel 0
debug2: channel 0: rcvd adjust 2097152
debug2: channel_input_status_confirm: type 99 id 0
debug2: shell request accepted on channel 0
Linux db11-minio 5.10.0-23-amd64 #1 SMP Debian 5.10.179-3 (2023-07-27) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Oct  8 14:34:54 2023 from 192.168.9.209
  • 我用的是Windows Powershell启动的ssh,它会执行一个Windows的OpenSSH客户端软件,版本8.1
  • 简单判断一下是使用IP地址还是主机名
  • 尝试连接对端服务器的22端口
  • 连接建立
  • 加载本地的标识文件,位于用户文件夹和.ssh
  • 看到对方的版本是OpenSSH 8.4,使用SSH版本v2
  • 协商和交换签名、加密、压缩等算法
  • 协商的结果是:curve25519-sha256, ecdsa-sha2-nistp256,chacha20-poly1305@openssh.com,无压缩
  • 客户端获取服务器公钥,格式为ecdsa-sha2-nistp256
  • 由于是首次连接,客户没有保存这个公钥,会提示用户进行保存
  • 客户端将公钥存入 known-host列表中
  • 客户端准备己方的密钥对,包括公钥
  • 客户端尝试使用公钥登录,这个尝试失败()
  • 客户端尝试使用密码登录,认证成功
  • 创建并进入新的客户端会话
  • 配置会话控制台
  • 状态确认,服务器向客户端发送登录欢迎信息和相关系统信息
  • 终端控制台就绪

技术实现和工具

SSH也是标准的以客户端/服务器模式工作的网络协议,所以其具体的技术实现也包括服务器和客户端两个方面。

Unix系统(包括以FreeBSD为基础的MacOS)和Linux内置了SSH服务器和客户端软件,基本上可以做到安装完成即可使用。在Linux系统上,默认的SSH服务端实现是OpenSSH这个软件包,其核心程序文件为sshd(SSH Deamon,SSH守护程序)。而客户端程序主要就是ssh这个命令行工具。

图为OpenSSH的官方slogan? 很有九十年代的风格。这里呼吁一下,这么好的软件,也不设计个好点的vi,也许是他们觉得不需要吧。

Windows系统的情况复杂一点,早期的系统是需要作为第三方软件安装的,但在比较新的Windows版本中,SSH服务器软件(OpenSSH on Winodws)是系统的可选组件;而SSH客户端软件可以直接使用。具体如何,要看具体的时间版本和功能版本。这一点还是要夸赞一下微软,原来的微软,对于Unix和Linux体系基本上是对抗和遏制的态度,现在随着互联网技术的发展,积极的拥抱和融合,反而越发展越好。

虽然Windows内置了SSH客户端,但很多用户还是喜欢使用一些第三方SSH工具,可能是由于传统和系统,也可能是看重它们提供的丰富的功能和方便性。原来的Windows SSH客户端软件选择不多,只有老牌的putty,secretCRT等少数几个,但近来随着Linux的广泛应用和Web开发工具的快速,近来出现了很多好用的软件和工具。如笔者常用的Termius,它可以将SSH连接记录称为配置项目,打开和管理SSH连接就非常方便,不用每次都输入连接指令,另外还提供了简单的SFTP功能、隧道连接和脚本管理等小工具,使用起来比纯指令方式直观方便多了。

(图为笔者使用的Termius软件软件界面,美观大气)

应用

ssh的应用,主要指使用ssh客户端连接服务器之后的相关操作和使用。

认证

建立SSH和使用ssh通道进行登录,在逻辑上应该是两个事情。登录和认证是终端程序和系统提供的功能,ssh通道能够保证这一过程和后续会话过程的安全。

ssh认证有两种模式,用户名密码模式和客户端公钥模式。

用户名密码模式很容易理解,用户使用用户名连接SSH服务器,建立连接后提示输入密码,认证成功,进入终端。这里有一个不起眼的小设定,就是连接发起时就需要提供用户名,服务器响应后再提示输入密码;而非像HTTP一样先建立连接,然后统一提供用户名和密码,这个可以留给读者思考。

除了这种方式之外,SSH还支持使用公钥进行认证。在正确配置完成之后,用户只需要在SSH客户端发起连接,整个连接过程可以顺畅完成,不需要再次等待用户输入密码,也就是没有用户交互的过程。这个方式实现的关键就是其原理和配置方式,其过程和原理大致如下:

1 用户使用当前的账号,执行生成客户端密钥对,包括一个私钥文件和一个公钥文件 2 密钥文件通常存储在用户目录 .ssh文件夹中,公钥文件通常为id_rsa(RSA格式的公钥文件和标识) 3 用户使用 ssh-copy-id 命令,可以连接目标ssh服务器,并将生成的公钥文件,传输给SSH服务器 4 ssh服务器收到当前登录的用户信息和公钥,并将其保存在用户 .ssh/authorized_keys 认证密钥列表文件中 5 下次用户登录,服务器收到登录请求后,尝试基于登录用户搜索匹配的公钥信息,如果找到,则使用此公钥来和客户端进行协商 6 客户端在.ssh文件夹中搜索,如果找到密钥对,则使用密钥完成协商过程 7 双方的公私钥都能够匹配,并且协商成功后,就算完成认证,并建立SSH会话

这里的逻辑和原理其实不复杂,客户端向服务器提供公钥,服务器保存客户端公钥,登录时服务器要求客户端证明此公钥是属于客户端的,客户端通过私钥来进行证明他持有该公钥,能够完成证明,就可以通过验证。

具体的命令和操作如下,能够让我们直观清晰的理解这一过程:

js 复制代码
// 生成密钥对 
# ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/home/yanjh/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/yanjh/.ssh/id_rsa.
Your public key has been saved in /home/yanjh/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:5/z6Af1j5xYELsv2PmhXLLbdKiJ2hETtKain+MeuWDs yanjh@WK-YANJH-AMD
The key's randomart image is:
+---[RSA 2048]----+
|         .       |
|        . .  .   |
|       o . .. .  |
|      . o oo . . |
|     . .So+ + o  |
|    . . .+.= + + |
|   ..+   .+ = O +|
|  .oE.o o o+.B =o|
|  ..+=.. oo+=ooo.|
+----[SHA256]-----+


// 上传公钥
# ssh-copy-id -i 192.168.9.36
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/yanjh/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
yanjh@192.168.9.36's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh '192.168.9.36'"
and check to make sure that only the key(s) you wanted were added.

// 连接认证
# ssh 192.168.9.36

// 登录后检查公钥
# cat ~/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCC6shXM6KRHqRCxPJv0HoD70W2F/oSP7m4KHriADvGW8Mu+EDYhSMGoorkb4C0pdKTJ8q6a9SDxV/KQAJdhGDBBNn4DiChQCIsGUdQnNu63GH/uqzXa8cJafMEpX1g6hd2ZtxLGPjRLf7o7sJ0Ixg2KNmQIFWiGdh3z8vUpq3DfpU1MMoiOo9AyPFRO+LqhsMKsASmDWwjzPoxDDtlreMD8o6TwS7QpAbIhaf325vGqYCl3yscZPCO/40b/Nv/ahbvBP61c3bUC/UDzZ7b9LdE0PHyP5hHThoekmw2oUhRXyIUhA9b9Y1z9lQPQCBwq8UxBDbHyayrGdIcjoRJfaR yanjh@WK-YANJH-AMD

使用公钥进行认证,就可以省略用户的键盘输入交互过程,可以实现无人值守连接SSH,这不仅仅是减少了用户的操作,更重要的是这对于使用脚本进行批量自动处理以至于实现运维自动化,提供了重要的技术基础。

使用密码或者公钥登录,在SSH服务端是可以配置的,以提供更高的安全性或者灵活性。

远程终端

远程终端是最常见的SSH使用场景。它可以通过网络来模拟和操作远程主机的终端,就像直接登录到远程主机上进行操作一样,并保证这一过程的安全。

使用ssh连接远程主机非常简单,在命令行中,使用以下的指令就可以启动:

ssh 复制代码
ssh user@host -p port
或者:
ssh host

这里host是需要访问的ssh主机的地址,可以使用主机名或者IP地址,但主机名应当能被正确的解析成IP地址;port是ssh端口,默认是22,如果忽略,客户端会尝试使用此端口连接服务器,注意这里的格式,port和host之间并没有常见的表示端口的冒号,而使用-p参数;用户名是在服务器上的账号,也可以忽略,客户端会使用当前在客户端系统中的账号名称进行登录尝试。

整个连接过程,我们其实已经在前面的原理部分有详细阐述,这里就不再重复了。

远程执行

ssh提供了一种方式,可以不进入远程终端的shell环境,就可以执行,再配合公钥登录,可以实现自动化的脚本远程执行。

shell 复制代码
ssh 192.168.9.36 df -h 
ssh 192.168.9.36 'cd /opt; ls'
ssh 192.168.9.36 'echo "passwd" | sudo -S -k  ntpdate pool.ntp.org'

这里要注意几点:

  • 如果有多个命令,可以使用逗号分隔
  • 简单命令可以不使用字符串
  • 要注意命令执行的权限问题
  • 如果实在需要sudo权限,可以搭配 -S -k 参数,这里 -S 表示使用标准输入,-k表示不记录在命令缓存中
  • 如果有多个比较复杂的指令需要执行,也可以考虑将其编写成脚本文件,并放在SSH服务器上。

如果你有很多服务器需要管理,而且有一些日常的管理工作都可以脚本化的话,这时一个非常高效的功能,你可以统一编写相关的系统运维指令,集中在一台服务器上启动和管理多个远程命令,可以大大提高操作效率。

会话共享

SSH会话共享(SSH session sharing)指的是多个SSH客户端共享同一个SSH会话的技术。这个功能需要在比较新的服务器端和客户端上提供支持,如OpenSSH 7.3 版本开始支持该特性。这个会话共享,本质上是对SSH连接通道的重用,显然有很多好处。首先当然是对计算和网络资源的节省,和更高的连接效率;在功能上也有比较有趣的应用场景,如切换设备但不断开原有会话,或者在多个设备上共享同一个命令行界面等等。

经过对claude的咨询,了解到其基本的工作原理是:

  • 第一个客户端建立 SSH 连接,启动一个 shell 会话
  • 如果有相关的配置,则该会话不会退出,而是保持在后台运行
  • 当第二个客户端连接时,检测到存在可共享的会话,就可以加入该会话,而非启动新的会话
  • 多个客户端可以共享同一个 shell 会话和屏幕 I/O。

笔者觉得现在这个功能的易用性好像还不是很好,有一定的门槛,比如一定的版本要求等等。而且通常用户使用SSH都是进行系统管理的工作,并发和性能都不是一个重要的考量因素。要实现这个功能,需要一系列在客户端和服务器端的配置工作。这个功能笔者平常工作中用的也比较少,就不再深入,有兴趣的读者可以自己发掘一下。

文件传输

在Telnet时代,是没有直接支持文件传输的,通常使用独立的FTP协议和软件来实现。鉴于这个功能需求非常重要和常见,SSH内置了文件传输的功能,也被称为SFTP(Secure File Transform Protocol, 安全文件传输协议)。

在命令行模式下,需要使用一个单独的scp或者sftp命令来进行操作。scp命令使用非常简单,可以用于传输文件:

shell 复制代码
// 将本地的zip文件复制到远程主机登录用户的文件夹中
scp *.zip user@host:~/.

// 将远程主机用户文件夹中的zip文件复制到本地
scp user@host:~/*.zip .

sftp命令稍微复杂一点,应该是ftp指令的复刻版本,会进入一个ftp子系统模式,来执行更多样复杂的操作,如列表文件夹、上传下载文件、删除文件等等。这里面的内容较多,但实际上用的并不多,这里就不赘述了。

对于图形界面,很多FTP软件,都提供了SFTP协议的支持,可以像使用标准的FTP一样使用SFTP,没有什么差异。这样就极大的方便了对远程文件进行的管理。常见的SFTP客户端软件包括WinSCP,FlashFXP,FileZilla(跨平台软件)等等。笔者觉得差异不大,大家都可以按照自己的喜好选择。

(图为winscp软件界面,请留意其使用的sftp协议和22号端口)

端口转发

SSH可以实现所谓的"端口转发"的功能。意思是在SSH客户端主机和服务器主机和端口之间,可以建立某种映射关系,从而实现一些应用场景。这种技术在很多场景中也被形象的成为"隧道"。

SSH提供三种端口转发模式:

  • Local Port Forwarding (本地端口转发)

本地端口转发的意思是,在SSH客户端主机上,可以使用一个端口;所有发送到这个端口上的网络请求,都会被转发到某个确定主机的确定端口之上,但是这个确定的主机和端口是SSH服务器主机可以访问的。(如图,图片来源于网络)

本地端口转发在实际中最典型的应用是管理用的远程数据库连接。由于安全策略的限制(比如限制访问网段和主机),不能从常规网络上直接访问数据库系统,就可以先连接到数据库系统所在网段相同的主机(所谓的管理主机)上,再连接到真正的数据库系统上。但对于数据库管理程序而言,只需要访问SSH客户端主机(也可能是本地主机)的端口,就像数据库就是安装在本地一样。

  • Remote Port Forwarding (远程端口转发)

远程端口转发的意思是,可以将SSH服务器主机上的一个端口,映射到SSH客户端主机的一个端口上。

这种模式可以用于发布在客户端系统上的网络服务如HTTP服务。或者理解成为SSH服务器可以实现类似于nginx一样的反向代理。所有访问远程端口(SSH服务器的端口)的请求,都被转发到SSH客户端所在的主机和端口上。在那些(应用)客户端系统上看来,服务是由服务器提供的,但实际是由SSH客户端主机提供的。

  • Dynamic Port Forwarding (动态端口转发)

这个模式将会在客户端上使用一个端口,所有发送到这个端口上的网络请求,都被转发到SSH服务器上,SSH服务器将作为一个代理程序,进一步将请求发送到目标服务器上。在使用这种模式建立连接之后,这个SSH客户端-服务器系统,可以被看成一种代理服务器系统。所以这里的动态的意思是,目标主机和端口都可以是动态的,不限定在SSH客户端和服务器之间。

可以看到,本地端口转发实际上是动态端口转发的特例,只不过网络请求处理被限定在特定的目标主机和端口而已。这样可以更安全。

这三种模式,都是通过ssh执行时使用不同的参数来实现的,它们的语法和用法如下:

js 复制代码
// 本地端口转发, 本地端口->远程主机和端口,使用SSH服务器
ssh -L <local port>:<remote ip address>:<remote port> <ssh server ip address>

// 远程端口转发,远程端口->本地地址和端口,使用SSH服务器
ssh -R <remote port>:<local ip address>:<local port> <ssh server ip address>

// 动态端口转发,设置SSH客户端端口,使用SSH服务器
ssh --D <local port> <ssh server ip address>

看起来复杂,其实也很好理解:

  • 主程序都是ssh,然后L、R、和D都是转发模式的首字母大写
  • 参数中,最后一个参数都是要使用的SSH服务器和用户
  • 第一个参数是请求接收的地址和端口,就是来源
  • 然后是转发目的的地址和端口
  • 动态模式显然没有目标地址和端口

其实笔者觉得,对大多数系统管理的任务而言,命令行和脚本化的管理方式效率应该是更高一点的。比如在准备本文材料的时候,看到其他人的博文中,使用putty配置端口转发要进行的操作,大概是这样的,大家可以体会一下:

在技术上来看,这样的表达和沟通成本就太高了。所以,作为专业人士,还是应该习惯命令行的工作方式。

SSH File System

在局域网络上,除了FTP之外,我们还会经常使用如NFS和SMB来进行文件的共享,其他的更底层的网络存储技术还包括如iSCSI和SAN等等。从使用角度而言,FTP还是一种"文件传输"的机制,就是如果要使用文件,还需要一个显式的将文件从远程传输到本地的过程;而如SMB可以直接将远程主机的文件映射称为本地文件,让它的使用和本地磁盘无异。这样对于用户或者应用程序而言就可以做到完全的透明,提供了极大的方便和兼容能力。

上面提到的一些网络文件系统,和一般的网络应用一样,都是客户端/服务器的工作模式。对于一般的Linux主机而言,如果要使用SMB或者NFS,必须要就需要在需要提供文件服务的服务器上安装相关的服务器软件,就是NFS或者SMB;而在客户端上,也需要安装和配置相关的客户端软件。

理论上来说,任何网络传输协议,都可以被升级和改造成为网络文件访问协议甚至网络文件系统。因为文件系统从抽象的逻辑角度主要就是两个东西:文件操作指令和文件数据块传输,这其实和文件是存储在磁盘中还是网络中是没有本质的区别的,差异主要是在性能和错误信息的处理,网络的可靠性毕竟不如磁盘那么高,而且即使跨越网络也需要访问磁盘,复杂程度更高一些。

这样,就有人在SSH协议的基础上,开发了SSHFS!这个系统最妙的是,要使用这个系统,不需要安装服务器软件,因为任何一个SSH服务器都可以作为文件服务器。但现在除了标准的SSH客户端之外,还需要安装相应的文件系统客户端软件。比如这个 SSHFS-Win,它还基于另外一个软件叫做 WinSFP(Windows Filesystem Proxy),它们结合,可以将SSH服务器和文件操作指令,映射成为Windows文件系统。安装完成之后,我们可以使用 sshfs作为协议,将一个SSH服务器的路径,挂载到Windows本地网络文件夹中。然后,就可以像操作本地文件一样,操作在SSH服务器上的文件了。(图为在Windows系统中连接SSH服务器网络文件夹)。

在Linux系统上也有类似的技术,就叫做sshfs。但遗憾的是,可能是由于需求不旺盛或者比较冷门,这个技术的发展并不是很快。笔者使用的SSHFS-Win,在复制和传输海量文件的时候,偶尔会出现卡死的问题,而且显然是客户端的问题,因为可以关闭重连也是可以的,说明SSH服务器是异常稳定的。

另外,由于不是原生网络文件系统,再加上加密的传输协议,SSHFS肯定有一些性能损失,但经过实际体验和测试,没有到数量级的差异。考虑到现在千兆到桌面的高速局域网络环境,普通的文件操作的性能是完全可以接受,因为如果操作和管理大量文件,可比WinSCP方便太多了。

Everything Over SSH

基于SSH强大的网络功能和性能,衍生出很多有趣和有用的网络应用模式和场景。我们下面例举几个:

  • Database: 很多数据库管理工具都提供了所谓的SSH隧道连接选项,本质上就是利用了SSH的端口转发

  • VNC: VNC本身不是加密的协议,所以很多VNC软件推荐的方式,就是先建立本地转发端口隧道,然后在本地进行连接

  • X11:Unix系统的桌面系统架构,也是C/S模式的,所以ssh专门提供了-X参数,和相关的配置,可以来支持X windows协议的通信转发,从而实现了安全的"远程显示器"

  • GIT: 是的,Git的默认网络协议,就是ssh,当你使用 git:// 的时候,实际上应该就是ssh

  • rsync: 文件传输工具,使用ssh作为其默认的网络传输协议

从上面的一些应用方式我们也可以感觉到,理论上而言,如果基于安全性的考虑,任何网络化的应用,都可以架构在SSH之上,成为"Everything Over SSH"。

故障排查

这里列举一些在SSH使用中经常遇到的问题和排查方案。

  • 服务器无响应

如果熟悉和了解SSH的工作原理,故障排查就可以非常简单,可以轻松的构建以下的检查清单:

_ SSHD服务是否启动, ps aux | grep sshd

_ 侦听端口和地址, netstat -lpnt

_ 防火墙是否打开,是否阻挡了22号端口或者自定义的SSH端口

_ 网络策略是否禁止客户端访问服务器(包括服务器和网络设备策略)

_ 如果使用了自定义端口,确定客户端使用 -p 参数进行连接

_ 如果使用域名,确认得到了正确的解析

笔者的一次惨痛的经验就是不知何故,要连接的主机域名解析到了127.0.0.1这个地址,然后始终无法连接...

  • 无法连接

这里有一个比较常见的情况是,如果服务器地址发生了变化,而又有一台新的服务器使用了原有的IP地址,当客户端使用这个IP地址连接的时候,会报"公钥不匹配的错误"。这时需要在 .ssh/known_hosts文件中,找到并删除原有的公钥条目,然后重新连接,会按照新服务器的方式进行处理。

  • 账号无法登录

相应的检查清单包括:

_ 账号是否存在, cat /etc/passwd

_ 账号是否允许登录

_ 账号密码是否正确

_ SSH服务器策略,是否允许root账号登录

总结

作为一个Linux用户,你用SSH越多,就会感觉到这项技术的精妙,同时也可以保持简洁,还有其多样性和适应性。还有竟然发现SSH好像没有与其直接竞争的技术和体系,更说明行业用户对其的推崇和认可。这也是笔者长时间的一个感想和本文的主题:

"了不起的SSH"

也希望读者能够在本文的内容之后,能够体会和理解这样的感受,发掘这样的逻辑和技术之美。

相关推荐
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
XMYX-04 小时前
使用 SSH 蜜罐提升安全性和记录攻击活动
linux·ssh
Chrikk4 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*4 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue4 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man4 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
customer086 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
石牌桥网管6 小时前
OpenSSL 生成根证书、中间证书和网站证书
网络协议·https·openssl