详细记录一下关于 ssh 的内容,这部分内容比较繁杂。

基本使用(1)

ssh 最基本的用途就是登录远程服务器

1
ssh user@hostname

其中:

  • user 是登录用户名
  • hostname 是主机名,它可以是域名,也可以是某个具体的 IP 地址或局域网内部的主机名。

可以缺省用户名,此时将使用本地用户名作为远程服务器的登录用户名。

1
ssh hostname

用户名也可以通过 -l 参数指定,这样用户名和主机名就不用写在一起了,在脚本中可能更方便

1
ssh -l username hostname

ssh 会默认连接远程服务器的 22 端口,使用 -p 参数也可以指定其他端口(同时也要修改远程服务器的监听端口)

1
ssh -p 8821 foo.com

ssh 在连接到远程服务器后会进行验证:如果第一次通过 ssh 连接某一台服务器, 命令行会显示一段文字,表示不认识这台机器,提醒用户确认是否需要连接

1
2
3
The authenticity of host 'foo.com (192.168.121.111)' can't be established.
ECDSA key fingerprint is SHA256:Vybt22mVXuNuB5unE++yowF7lgA/9/2bLSiO3qmYWBY.
Are you sure you want to continue connecting (yes/no)?

这段文字告诉用户,foo.com 这台服务器的指纹是陌生的,用户需要确认是否要继续连接(输入 yes 或 no)。

所谓 “服务器指纹” 指的就是 ssh 服务器提供的公钥的哈希值,利用公钥的特性可以识别和确认服务器,下面的命令可以查看某个公钥所对应的指纹

1
2
ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key.pub
256 da:24:43:0b:2e:c1:3f:a1:84:13:92:01:52:b4:84:ff (ECDSA)

ssh 会自动将曾经连接过的所有服务器公钥的指纹,储存在 ~/.ssh/known_hosts 文件中,具体的内容格式对 Windows 和 Linux 不太一样。 每次连接服务器时,通过该文件判断是否为陌生主机(陌生公钥),对于陌生主机需要手动确认,对于已经存储在 ~/.ssh/known_hosts 文件中的则不再需要确认。

我们可以使用下面的命令检查指定的主机名是否在 known_hosts 中,或者将指定主机名从 known_hosts 中移除

1
2
3
4
5
# find
ssh-keygen -F example.com

# remove
ssh-keygen -R example.com

ssh 在验证了服务器指纹之后会要求用户输入所要登录账户的密码,用户输入并验证密码正确以后,就可以登录远程服务器的 Shell 了。 (如果采用下面的密钥登陆,则无需输入密码)

有时我们只需要临时登录并执行一条简单命令,可以把命令直接加在后面,在登录之后会自动执行这条命令并输出到本地,然后自动退出 ssh 登录,例如

1
ssh user@hostname cat /etc/hosts

密钥

非对称加密算法

ssh 支持很多种非对称加密算法,用于登陆时的认证签名:

  • RSA 算法,基于大数分解,兼容性最好,所有 ssh 客户端都支持此算法。
  • ECDSA 算法:基于椭圆曲线,需要在新的场景中使用更安全的密钥时,可以选择此类型。
  • ED25519 算法:基于 Edwards 曲线(也属于椭圆曲线),提供极高的安全性和性能,兼容性相对最差。

除此之外,还有两种已经被淘汰的算法:ssh 协议版本1的 RSA 算法(目前使用的是版本2的 RSA 算法)和 DSA 算法(基于离散对数),不建议在新场景中使用。

关于密钥的位数:

  • RSA 算法:必须加大密钥位数来提升安全性,建议密钥位数至少为 2048 位,推荐使用 3072 位或 4096 位以获得更高的安全性。
  • ECDSA 算法:只支持三种密钥大小:256,384 和 521 位,即使最低的 256 位也已经非常安全,384 位和 521 位提供额外的安全冗余。
  • ED25519 算法:密钥大小固定为 256 位,安全性在 RSA 2048 与 RSA 4096 之间,且性能数十倍于 RSA 算法。

有的比较激进的组织出于安全性的原因,已经考虑禁止使用RSA密钥,建议使用后两种算法,并且对于 ECDSA 算法建议使用最长的 512 位。

密钥文件

密钥主要是用来签名验证自身的身份,因此对于一个固定机器上的一个用户,通常只需要在本地维护一个密钥对, 用户必须保证安全地保存密钥,并且需要将公钥手动传递给希望远程登陆的服务器,见下文。 密钥通常存放在 ~/.ssh 目录下,下面是这些文件的默认名称

  • id_rsaid_rsa.pub:用于 SSH 协议版本2 的 RSA 私钥和公钥。
  • id_ecdsaid_ecdsa.pub:ECDSA 私钥和公钥。
  • id_ed25519id_ed25519.pub:ED25519 私钥和公钥。
  • identityidentity.pub:用于 SSH 协议版本1 的 RSA 私钥和公钥。(通常没有,除非在很老的机器中)

在 Linux 系统中,ssh 要求用户的私钥文件不能被其它用户直接访问到,否则会拒绝使用这个不安全的私钥,可以使用 chmod 命令将私钥文件的权限设置为 600 或 400,对于公钥文件则没什么要求,例如设置为 644 即可。

私钥文件和公钥文件都是文本文件,私钥例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-----BEGIN OPENSSH PRIVATE KEY-----
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
yf/PzW634X+YF3AAAACWxpc2h1QFRYSgE=
-----END OPENSSH PRIVATE KEY-----

RSA 算法需要足够长的私钥来保证安全性,如果采用新的算法(ECDSA 或 ED25519)则私钥长度可以短很多。

公钥例如

1
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCmP1rbqoMBBO9rBNSfRYZyyRUlWeF1pldu3bW8MMfXf/oR78CEN4MT643ZV994qlukZJHjja3cE9X23mUley78DMUaSCIDF+KuBlZYNv1Iaok6MMbUMNfXXESms5bjAT76kpx35IGvbSyOnpgmXMeMcxGbRtgbe448mZCESh8/+mUciznqZiHjhC8oZWJOrvJyx/TYR/wfoNz1MxfafBf0HEUSShxU6HjfQHlt5+rA++mzjOtlTEcqxWIXKDvDy3iVn885kwpUQazQ8D2rJZBmycggRI4TGw6iZYyp5YZ9akXnW2qmT71GNxI6Xt0SUOQIlHHD/9U1yZhebAbkCaPzHbiG1XII78PVxOUcSyo7cp5fVmINwgjEuq0oCbwSdfGPISl32NKeBzGDSLz/qwb9YQXGZRv5FDWP7TZkO78r8+RGKDDWHP8F7XuE3sPrObkCTkMim1jxWdIdXBs/JbG/MJQaplseUKGTKlmeJ5x9SvsmlaE09i2ST7dIAnlrtK0= your_email@domain.com

公钥内容只有一行,比私钥长度小很多,在公钥文件的开头会表明当前算法的类型,在文件末尾的空格之后会有一个注释,标记公钥所对应的用户名和主机名,这个注释并不是密钥的一部分,只是便于区分不同的公钥,我们可以直接修改注释,这不会对密钥对产生影响。

密钥生成

ssh 提供的配套工具 ssh-keygen 可以用于生成密钥(默认使用 RSA 算法),当然我们也可以通过其它工具生成密钥对,因为算法都是公开的

1
ssh-keygen

ssh-keygen 需要我们确认或修改几个信息:

  1. 保存的路径和文件名,使用默认的路径和文件名即可,还可以通过 -f 选项指定,见下文
  2. 密钥对的保护密码(passphrase),客户端在每次使用这个密钥对时需要用户输入保护密码,通常使用空密码即可,还可以使用 -N 选项指定保护密码,见下文

最后 ssh-keygen 会生成一对密钥对,将其存储到指定位置,然后显示公钥的指纹和一个基于公钥生成的图像, 用图像来判断比使用字符串形式的指纹对人类更加友好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
The key fingerprint is:
SHA256:MaA3rGf/h3Jh5jVehAw6QerUG0TMOTJN/n93P2zwMXs xxx@yyy
The key's randomart image is:
+--[ED25519 256]--+
| .B=. |
| oo*B . |
| . *o*+ o . |
| = .o* o . |
| . + S.. . |
| o . +.o..o |
| .+ =.o= *|
| ..+ o. BE|
| o.. . +|
+----[SHA256]-----+

ssh-keygen 支持很多选项,下面介绍几个最常用的选项。

-t 选项可以指定加密算法,默认是RSA算法

1
2
ssh-keygen -t rsa
ssh-keygen -t ed25519

对于位数可变的算法,-b 选项可以指定密钥的二进制位数

1
ssh-keygen -t rsa -b 4096

对于位数可变的算法,我们必须使用合法的位数,RSA算法默认采用 2048 位,ECDSA 算法默认采用 256 位,位数都是足够的;对于位数固定的 ED25519 算法,这个选项会被直接忽略。

-C 选项可以为公钥文件指定注释,格式为 username@host,默认是当前用户名和当前主机名

1
ssh-keygen -C "your_email@domain.com"

-f 参数可以指定生成的私钥文件(否则使用默认名称,并存放在 ~/.ssh/ 目录下)

1
ssh-keygen -f mykey

这个命令会在当前目录下生成私钥文件 mykey 和公钥文件 mykey.pub

-N 参数可以指定私钥的保护密码(passphrase)

1
2
3
ssh-keygen -N secretword
# empty
ssh-keygen -N ''

使用下面的命令可以利用现有的私钥生成对应的公钥

1
2
ssh-keygen -y -f key_file_name # 输出到标准输出流
ssh-keygen -y -f key_file_name > key_file_name.pub

完整的生成密钥对的命令如下

1
ssh-keygen -t rsa -C "your_email@domain.com" -f key_file_name -N ''

基本使用(2)

密码登陆过于繁琐且不够安全,更建议的做法是使用密钥登陆,大致流程如下:

  • 预备步骤,客户端通过 ssh-keygen 生成自己的公钥和私钥,手动将客户端的公钥放入远程服务器中的指定位置。
  • 第一步,客户端向服务器发起 ssh 登录的请求。
  • 第二步,服务器收到用户 ssh 登录的请求,发送一些随机数据给用户,要求用户使用私钥加密以证明自己的身份。
  • 第三步,客户端收到服务器发来的数据,使用私钥对数据进行加密,然后再发还给服务器。(使用私钥加密数据的过程通常被称为签名)
  • 第四步,服务器收到客户端发来的加密签名后,使用对应的公钥解密检查,如果数据一致就允许用户登录。

密钥对的生成在上面已经介绍,下面关注将公钥上传到服务器中。

用户公钥保存在服务器的 ~/.ssh/authorized_keys 文件中,把公钥添加到这个文件之中就是把公钥上传到服务器了。 authorized_keys 是纯文本文件,其中可以包括多个公钥,每个公钥占据一行,把最新的公钥粘贴到文件尾部即可。 如果 authorized_keys 文件不存在,也可以直接手动创建。

和私钥文件类似,ssh 对 authorized_keys 文件的权限也有要求,建议设置为 644,权限不对可能导致 ssh 使用失败。

用户可以使用密码登陆到远程服务器,然后手动编辑该文件,把公钥粘贴进去,也可以直接在本机计算机上,执行下面的命令

1
cat ~/.ssh/id_rsa.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

注意将这里的文件替换为需要上传的密钥文件,需要确保服务器上的 authorized_keys 没有格式错误,并且文件权限正确。

在配置了密钥对之后,我们就可以使用 -i 选项指定密钥而非使用密码进行远程登陆

1
ssh -i /path/to/private_key user@hostname

如果缺省 -i 选项,ssh 客户端会依次尝试使用下面的几个私钥文件(如果存在的话)

  • ~/.ssh/id_rsa
  • ~/.ssh/id_dsa
  • ~/.ssh/id_ecdsa
  • ~/.ssh/id_ed25519

我们还可以使用 ~/.ssh/config 文件进行更详细的配置,见下文。

配置

客户端配置

ssh 客户端通常需要关注如下两个配置文件:

  • ~/.ssh/known_hosts:记录登陆过的服务器的公钥指纹,还有一个 known_hosts.old 是在主机指纹发生更改时,客户端进行的自动备份;
  • ~/.ssh/config:用户级配置文件,与之相对的是全局配置文件 /etc/ssh/ssh_config,用户级的优先级更高。

config 配置文件由一系列 Host 块组成,每个块包含一组针对特定主机或一组主机的配置选项。 配置冲突时,后面的块优先级比前面的更高,通常会先将通用的放在前面,具体的放在后面,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 通配符配置
Host *
ServerAliveInterval 60

Host *.example.com
User commonuser
Port 2222

# 具体主机配置,优先级更高

Host myserver1.example.com
User user1
Port 2200

Host myserver2
HostName myserver2.example.com
User user2
Port 2222

Host myserver3
HostName myserver3.example.com
User user3
Port 2200
IdentityFile ~/.ssh/id_rsa3

解释一下这里的选项:

  • Host <pattern>:指定主机模式,可以是 ip 或具体主机名,也可以包含通配符
  • HostName:实际要连接的主机名
  • User:登录用户名
  • Port:端口号(默认是22)。
  • IdentityFile:用于身份验证的私钥文件路径,如果缺省则会依次尝试 id_rsaid_dsaid_ecdsaid_ed25519 这些默认文件。
  • ServerAliveInterval:发送到服务器的间隔秒数,以保持连接活跃
  • ServerAliveCountMax:在放弃连接之前,服务器没有响应的最大次数

服务端配置

ssh 服务端通常需要关注如下两个配置文件:

  • ~/.ssh/authorized_keys:记录允许从远程登陆的用户公钥;
  • /etc/ssh/sshd_config:ssh 服务端(sshd 服务)的全局配置文件。

修改服务器上的/etc/ssh/sshd_config可以控制用户的 ssh 登陆行为,例如

1
2
3
4
5
6
7
8
9
10
11
# 设置ssh的端口(还需要修改防火墙等)
# 默认:22
Port 2222

# 不允许使用密码登陆
# 默认:yes
PasswordAuthentication no

# 不允许root用户从远程登陆(建议先用普通用户登陆,然后切换为root用户)
# 默认:不允许root用户远程密码登陆 prohibit-password
PermitRootLogin no

修改需要重启 sshd 服务才能生效。

除了配置文件,ssh 服务端在 /etc/ssh/ 目录中还存储了所有的服务端密钥对,例如 /etc/ssh/ssh_host_ecdsa_key, 这些服务端密钥对被用于生成服务器指纹,重装 ssh 服务端可能会导致这些密钥对重新生成,之前的指纹记录就全部失效,我们必须在重装之前备份这些密钥。

git 是一个特殊的用户,我们需要避免使用 git 用户登陆到远程服务器的 shell,通常的做法是修改 /etc/passwd 文件中 git 所在的行,使用定制的 git-shell 替代默认的 bash

1
git:x:1003:1003:,,,:/home/git:/usr/bin/git-shell

当然我们也可以直接在 /etc/ssh/sshd_config 中通过白名单的形式禁止 git 用户的远程登陆, 但这会导致我们无法通过 ssh 访问在服务器中的 git 仓库。

文件权限小结

整理一下关于 Linux 系统中 .ssh/ 目录下的权限要求:

  • 私钥:自动生成的私钥为 600,改成 400 也可以,如果对其它用户可读就会报错
  • 其它所有的文件都建议使用 644 权限,包括:
    • 公钥
    • authorized_keys
    • config
    • known_hosts

进阶使用

端口常识

下面是不同端口号段的含义:

  • 0-1023: 知名端口,预留给常见的服务和协议。
  • 1024-49151: 注册端口,一般分配给用户进程或特定服务。
  • 49152-65535: 动态端口或私有端口,通常用于临时目的,如客户端程序向服务器发起连接。

知名端口例如:

  • HTTP:80
  • HTTPS:443
  • FTP:21
  • SSH:22
  • DNS:53

可以使用下面的命令查看当前是否监听了某个端口

1
2
# Windows
netstat -ano | findstr :22022

如果想要关闭监听,可以在任务管理器搜索进程号,然后结束任务即可。如果要终止 ssh 对端口的监听,也可以直接搜索 ssh 任务,然后关闭。

端口转发

ssh 除了登录服务器,还有一大用途:作为加密通信的中介,充当服务器之间的通信加密跳板。这个功能称为端口转发(port forwarding),又称 ssh 隧道(tunnel)。端口转发有两个主要目的:

  • 将不加密的数据放在 ssh 安全连接里面传输,使得原本不安全的网络服务增加安全性。
  • 作为数据通信的加密跳板,绕过网络防火墙。

ssh 提供三种形式的端口转发:

  • 本地转发(-L 选项)
  • 远程转发(-R 选项)
  • 动态转发(-D 选项)

在下文中还可以加上:

  • -N 选项,它表示这个 ssh 连接只用于端口转发,不会登录远程 Shell 并执行远程命令,只能充当隧道。
  • -f 选项,它表示 ssh 连接在后台进行。

这些选项的位置建议放在最后,至少要在转发的选项和参数之后。实践发现:

  • 如果两个选项都不加,那么端口转发会打开一个远程 shell,并且 shell 退出后隧道自动关闭;
  • 如果加上 -N 选项,那么不会打开远程 shell,但是在当前 shell 被关闭后,隧道也会自动关闭;
  • 如果加上 -N -f 选项,那么不会打开远程 shell,并且在当前 shell 退出后,隧道仍然存在,需要找到后台进程并手动关闭;
  • 只加上 -f 选项会报错。

在下面的各种转发命令中都省略了关于这些选项的讨论。

如果需要经常进行端口转发,可以把转发直接写入配置文件中,这里不作讨论。

本地转发

本地转发是在本地计算机建立的转发规则,具体含义为: 创建一个本地端口,将发往该端口的所有通信都通过 ssh 服务器,转发到指定的远程服务器的端口。 这种情况下,ssh 服务器只是一个作为跳板的中介,用于连接本地计算机无法直接连接的远程服务器。

1
2
3
4
5
6
7
场景:
A->C,某服务不可直连,C 处于内网中被保护,A 处于外网,无法连接到 C
A->B,ssh 可直连,B 也在内网中,但是可以被外网的 A 访问到
B->C,某服务可直连,B 和 C 同在内网中,相互可以正常通讯、

实现:(本地转发,在 A 上执行命令)
A->B->C,利用 B 作为跳板,实现 A 对 C 的访问

本地转发的命令格式为(在 A 上执行命令)

1
ssh -L A-tunnel-port:C-host:C-port B-host

其中:

  • A-tunnel-port:主机 A 上的端口,作为隧道入口
  • C-host:主机 C,可以是 C 的内网 ip,只要 B 可以访问即可
  • C-port:主机 C 的目标端口,如果需要使用 ssh 访问主机 C,那么最好使用 22 号端口
  • B-host:跳板机 B

例如(在 A 上执行命令)

1
ssh -L 22022:C-host:22 user@B-host

在建立了 ssh 隧道之后,主机 A 想要登陆到主机 C,访问 localhost:22022 即可(在 A 上执行命令)

1
ssh -p 22022 user@localhost

本地转发的一个更常见的应用情景是:远程服务器 C-host 的某个服务在监听端口 C-port, 本地主机 A-host 可以通过 ssh 连接到 C-host,但是由于防火墙等原因无法直接访问端口 C-port,此时可以使用本地转发功能(B-host=C-host),在本地的 A-tunnel-port 和远程服务器的 C-port 之间建立隧道。

1
ssh -N -L A-tunnel-port:localhost:C-port user@C-host

这里 -N 是不登陆,仅建立隧道。

远程转发

远程转发指的是在远程 ssh 服务器建立的转发规则,含义跟本地转发正好反过来:建立本地计算机到远程 ssh 服务器的隧道以后,本地转发是通过本地计算机访问远程 ssh 服务器,而远程转发则是通过远程 ssh 服务器访问本地计算机。

1
2
3
4
5
6
7
场景:
A->C,某服务不可直连,C 处于内网中被保护,A 处于外网,无法连接到 C
B->C,某服务可直连,B 和 C 同在内网中,相互之间可以正常通讯
B->A,ssh 可直连,B 虽然处于内网中,但是可以和外网的 A 正常连接

实现:(远程转发,在 B 上执行命令)
A->B->C,利用 B 作为跳板,实现 A 对 C 的访问

远程转发的命令格式为(在跳板 B 上执行命令)

1
ssh -R A-tunnel-port:C-host:C-port A-host

其中:

  • A-tunnel-port:主机 A 上的端口,作为隧道入口
  • C-host:主机 C,可以是 C 的内网 ip,只要 B 可以访问即可
  • C-port:主机 C 的目标端口,如果需要 ssh 访问主机 C,那么使用 22 号端口
  • A-host:主机 A

例如(在跳板 B 上执行命令)

1
ssh -R 22022:C-host:22 user@A-host

在建立了 ssh 隧道之后,主机 A 想要登陆到主机 C,访问 localhost:22022 即可(在 A 上执行命令)

1
ssh -p 22022 user@localhost

动态转发

动态转发指的是本机与 ssh 服务器之间创建了一个加密连接,本机内部针对某个端口的通信都通过这个加密连接转发,由 ssh 服务器代劳。 动态转发只是把本地端口绑定到 ssh 服务器,至于 ssh 服务器要去访问哪一个网站,完全取决于通信的要求,所以叫做动态转发。

一个常见的使用场景是:对所有外部网站的访问都转发到 ssh 服务器,实际上是用 ssh 服务器去访问。

这里有一个问题:之前使用固定的端口转发时,应用的访问请求都是明确指向被转发的端口 X,但现在应用的访问请求必须指向目标,以指定动态端口转发的目标,此时如何让数据全部从端口 X 进入 ssh 隧道?我们必须在系统或应用(浏览器等)中手动设置一个使用 SOCKS5 协议、服务器为 localhost、端口为X的代理,利用代理使这些网络请求全部走端口 X。

应用的网络请求从端口 X 进入 ssh 隧道,抵达 ssh 服务器后将其中的目标信息解析出来,ssh 服务器访问目标后再将结果通过隧道返回。

动态转发的命令格式为

1
ssh -D local-port tunnel-host

例如使用本地的2121端口

1
ssh -D 2121 tunnel-host

这里会自动登陆到 tunnel-host,但是无所谓,也可以使用 -N 不登陆,此时会处于阻塞状态。(关闭这个会话则转发随之停止)

curl 进行网络访问时需要加上 -x 选项设置 SOCKS5 代理

1
curl -x socks5://localhost:2121 www.baidu.com

注意 wget 不支持 socks5 代理,只支持 http 代理和 https 代理。

代理转发

-W选项要求 openssh 版本高于 7.3,更低版本可以使用 netcat 工具进行替代。

这部分内容不作详细讨论,只是提供一个典型的例子:内网中有主机 A 和主机 B,主机 A 无法通过 ssh 连通 example.com,但是主机 B 可以,那么我们可以在主机 A 中添加如下配置

1
2
3
4
5
6
7
8
Host B
...

Host example.com
User git
HostName example.com
ProxyCommand ssh -q -W %h:%p B
ForwardAgent yes

主机 A 就会通过主机 B 来连接 example.com,注意此时我们仍然使用的是主机 A 的密钥对,需要保证公钥已经被添加到 example.com 中,密钥不需要存储在跳板机 B 中。 (测试发现 ForwardAgent yes 删掉也可以正常工作)

跳板选项

-J选项要求 openssh 版本高于 7.3。

-J 选项可以简化基于跳板机登陆的步骤,更加简单易读,在本地主机 A 上执行如下命令

1
ssh -J userB@B userC@C

其中 B 是跳板机,C 是目标主机。

原本的端口转发命令也可以直接加上跳板机,例如

1
ssh -J userB@B -N -L 8989:localhost:8888 userC@C