ssh技巧

SSH的各种玩法(隧道 端口转发 多路复用 跳板)

  1. 使用公钥登陆服务器
    使用公钥登陆,服务器端sshd_config需要配置如下:
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no

客户端使用ssh-keygen生成一对公私钥,然后将公钥存到登陆用户家目录的.ssh/authorized_keys
这样客户端就能用公私钥登陆了,使用ssh客户端的-i选项可以指定私钥路径。

ssh -i /path/to/file.pem user@moneyslow.com
如果没有指定文件路径,默认会找客户端用户目录下的~/.ssh/id_dsa或~/.ssh/id_rsa。

需要注意的是,私钥的权限应该是600(当前用户可读可写)

使用ssh-keygen创建私钥时可能设置了密码,可以使用ssh-agent管理这些密钥
将密钥交给ssh-agent管理

ssh-add ~/.ssh/id_rsa
如果设置了密码,这个命令会让你输入密钥的密码,之后使用ssh登陆就不用输入密码了。

  1. 换服务器端口
    由于默认的22号端口众所周知,很容易收到网上的黑客流量的暴力攻击,可以在sshd_config中换掉端口:

Port 2222 #这个端口默认是22,改成不容易猜的
这时候客户端需要加上-p选项

ssh -p 2222 user@moneyslow.com
也可以使用ssh URI方式来指定端口

ssh ssh://user@moneyslow.com:2222
和http://xxx的80端口类似,ssh://xxx不指定端口默认就是22号端口

  1. 远程执行命令
    有时候,我们并不想进入远程服务器的终端中执行命令(比如在shell脚本中)。这时候我们可以在ssh最后跟上需要执行的命令,让远程服务器执行

ssh user@moneyslow.com “ps aux”
除此之外,我们还可以将命令的输出与客户端的命令进行交互

ssh user@moneyslow.com “ps aux” | less
甚至可以从远端拷贝文件夹到客户端

ssh user@moneyslow.com ‘tar cz /usr/share/nginx/html/’ | tar xzv
这里压缩是为了加快传输速度

ssh user@moneyslow.com ‘tar -C /usr/share/nginx/html/ zcf – 404.html 50x.html’ | tar zxf –
同样,我们可以使用ssh自己实现一个带压缩的scp的功能

tar czv src | ssh user@moneyslow.com ‘tar xz’
所以前面提到在.ssh/authorized_keys中添加公钥,也可以用ssh远程执行

ssh user@moneyslow.com ‘mkdir -p .ssh && cat >> .ssh/authorized_keys’ < ~/.ssh/id_rsa.pub 也可以用ssh-copy-id @实现上面的功能,当然如果是用其他端口或其他公钥,可以另外用参数指定:
ssh-copy-id -i ~/.ssh/otherkey -p 2222 username@host

还可以远程执行一段脚本,无需拷贝脚本到远程服务器

ssh user@moneyslow.com bash < /path/to/local/script.sh
远程执行交互式命令

ssh user@moneyslow.com python
如果是执行python命令,这个命令不会立即返回,所以会直接阻塞,这个时候我们要加一个-t选项,让远程主机创建一个tty伪终端连接。

ssh user@moneyslow.com -t python
这个远程执行命令的功能,让ssh有了更多的想象空间

  1. SSH Tunnel端口转发
    有时候我们无法直接从host1连上host2,但是如果有一个host3在中间作为桥梁,host1能通过ssh连上host3,host3再将请求转发给host2,这时我们就在host1和host2建立了SSH Tunnel。

本地端口转发
将host2的5000端口映射到本地的8080端口

ssh -L host1:8080:host2:5000 user@host3 -N
其中host1就是本地主机网卡的ip地址,如果没有指定host1地址,默认会监听所有网卡。

ssh -L 8080:host2:5000 user@host3 -N
远程端口转发

上面的本地端口转发是针对host1的。相反,远程端口转换就是将host3的端口开放给host1这样的客户端,让host1的请求能转发到host2。

ssh -R 0.0.0.0:8080:host2:5000 user@host1 -N
这条命令执行在host3上,把host2的5000端口映射到host1的8080端口。

这个场景和前面本地端口转发的区别在于:前面的本地端口转发host1能连上host3,但如果host3是内网,host1无法访问,而host3能访问host1,这时就应该让host3来建立与host1的通道。

畅想一下这个功能的作用:

host3是部署在国外的http代理,host1因为被GFW墙了无法访问host2和host3,这时可以让国外的代理主动与host1建立通道,之后就可以顺畅的访问host2和host3了。
你在本机开发了一个Web应用,给别人测试,但是你现在在内网,外网无法访问你的Web应用,一种方式是找台公网ip主机把Web应用部署上去。这样很麻烦,但是使用ssh远程转发,可以让在内网建立与外网的通道,外网就可以访问了。

注意:因为host3需要连接host1,所以host1应该要有sshd服务应用。然后host3的sshd要将AllowTcpForwarding选项打开。

动态端口转发

本地端口转发、远程端口转发都需要固定单一的端口。动态转发更牛逼的一点就是无需指定被访问目标主机的端口号。这个端口号需要在本地通过协议指定,该协议就是简单、安全、实用的 SOCKS 协议。

动态转发通过参数 -D 指定,格式:-D [本地主机:]本地主机端口。相对于前两个来说,动态转发无需再指定远程主机及其端口。它们由通过 SOCKS协议 连接到本地主机端口的那个主机。

举例:在host1上执行ssh -D 50000 user@host3 -N。这条命令创建了一个SOCKS代理,所以通过该SOCKS代理发出的数据包将经过host3转发出去。

怎么使用?

用firefox浏览器,在浏览器里设置使用socks5代理127.0.0.1:50000,然后浏览器就可以访问host3所在网络内的任何IP了。

如果是普通命令行应用,使用proxychains-ng,参考命令如下:

brew install proxychains-ng
vim /usr/local/etc/proxychains.conf # 在ProxyList配置段下添加配置 "socks5 127.0.0.1 50000"
proxychains-ng wget http://host2 # 在其它命令行前添加proxychains-ng即可


如果是ssh命令,则用以下命令使用socks5代理:

ssh -o ProxyCommand=’/usr/bin/nc -X 5 -x 127.0.0.1:5000 %h %p’ user@host2
这个功能想象空间更大:grin:

  1. ssh客户端配置文件
    前面有提到过sshd_config服务端配置文件,同样地,还有一个ssh_config客户端配置文件

用户的ssh客户端配置文件默认在~/.ssh/config路径下。

比如原来用ssh -i /path/to/private_key -p 2222 user@moneyslow.com远程登录的命令,我们只需要在~/.ssh/config文件中添加下面的配置就行

Host moneyslow.com
User user
Hostname moneyslow.com
# Non standard port
Port 2222
IdentityFile /path/to/private_key

以后就可以直接用ssh moneyslow.com命令登录了,这样可以方便的管理多个密钥对,多个Host配置

而且Host可以设置通配符:

Host *
User user
IdentityFile ~/.ssh/id_rsa
ServerAliveInterval 15
ServerAliveCountMax 3
# 可以添加其他的配置


更多的配置选项可以通过man ssh_config命令查看手册

  1. 穿越跳板机

跳板机在企业网络安全方面经常使用,为了避免多次ssh跳转多次,可以使用ssh的跳板机功能。

ssh -J
或者
ssh -J user@
如果跳板机有多层,可以直接用逗号分隔

ssh -J ,
-J选项提供了灵活性,当然我们也可以直接在.ssh/config文件中进行配置

The Bastion Host
Host bastion-host-nickname
HostName bastion-hostname

The Remote Host
Host remote-host-nickname
HostName remote-hostname
ProxyJump bastion-host-nickname


这样就可以直接使用ssh remote-host-nickname命令穿越跳板机登录远程主机了

  1. 连接保活
    TCP连接有超时时间,防火墙也可以配置空闲连接的超时关闭。所以经常看到我们ssh连上服务器,放着几分钟后,连接就断开了,这个时候就需要我们配置一下连接保活
Host *
# 是否开启TCP保活,开启后会自动发送TCP保活消息
# 默认yes
TCPKeepAlive yes
# 间隔多少秒发送一次保活消息
# 默认为0,不发送
ServerAliveInterval 60
# 最多发送多少次保活消息
# 默认为3,超过3次还是会断开连接
ServerAliveCountMax 10
  1. ssh多路复用
    通常来说,多路复用(Multiplexing)就是在单个连接上处理多个请求的功能。这和Java中的NIO解决的问题一样。

SSH Multiplexing也是在单个TCP连接上处理多个SSH会话。

通过SSH远程执行命令,比如说下面的两个命令

ssh user@moneyslow.com "ps aux"
ssh user@moneyslow.com "ping baidu.com"


如果没有多路复用,每次执行ssh连接到远程主机都会重新创建一个tcp连接,很明显创建TCP连接是很耗时的。
Ansible的基础就是通过SSH远程执行命令,来实现自动化管理集群的

我们在~/.ssh/config文件中添加如下配置:

Host moneyslow.com
User user
Hostname moneyslow.com
ControlMaster auto
ControlPath ~/.ssh/socket/cm-%r@%h:%p
ControlPersist 10m


上面就是一个标准的多路复用配置,里面有三个重要的配置参数:

ControlMaster:用来打开多路复用,设置成auto,SSH客户端会尝试使用已存在的连接,如果不存在就创建一个。
ControlPath:用来指定复用连接的套接字存储位置,%r表示登录的用户名,%h表示登录的主机名,%p表示远程端口号
ControlPersist:表示这个socket连接保持多久

$ ssh moneyslow.com
Last login: Tue Feb 23 13:19:09 2021 from 122.224.245.226
user@moneyslow.com$ exit

Shared connection to moneyslow.com closed.
$ ll .ssh/socket
srw——- 1 holmofy staff 0 2 23 13:22 -user@moneyslow.com:22
从登录以及推出的提示可以看到Connection to moneyslow.com closed.变成了Shared connection to moneyslow.com closed.。.ssh/socket`目录下也多了一个socket文件。

socket文件就是unix哲学:一切皆文件。

另外还有命令可以检查和关闭socket连接

ssh -O check moneyslow.com # 检查socket有没有建立
ssh -O exit moneyslow.com # 强制关闭socket连接