SSH公钥认证登录

SSH PubkeyAuthentication

“互信”只是公钥认证的一个特例。 2台服务器相互使用公钥认证登录,才叫互信。

本文前提条件是使用 OpenSSH,假设服务器 A(192.168.1.1) 的用户 user1 需要使用公钥认证登录服务器 B(192.168.1.2)的用户 user2。

由谁操作

原则上,谁拥有 user1、user2 的密码权限,谁负责操作,不应该使用 root 来建立普通用户之间的公钥认证。

管理公钥认证就如管理密码一样,需要绝对清楚各个系统之间的交互关系。

生成密钥对

.pub 结尾的是公钥,可以任意公开。不带 .pub 结尾的是私钥,可设置私钥的密码,私钥不能泄露
服务器之间的公钥认证,如果设置私钥密码,当服务器系统重启后需要人工处理才能正常使用。
主机密钥对与用户密钥对生成方法一样,只是文件名、保存位置、用户属组不同。

RSA密钥对

登录服务器 A 的用户 user1,生成非对称加密 RSA 密钥对,建议使用 2048 或 4096 位:

1
ssh-keygen -t rsa -b 2048

按照提示一直按回车,将会在 ~/.ssh 目录下生成 2 个文件:

1
2
~/.ssh/id_rsa
~/.ssh/id_rsa.pub

ed25519密钥对

OpenSSH 6.5 之后的版本支持新的 ed25519 加密算法,可代替 RSA 算法。

登录服务器 A 的用户 user1,生成非对称加密 ed25519 密钥对,无需指定密钥长度:

1
ssh-keygen -t ed25519

按照提示一直按回车,将会在 ~/.ssh 目录下生成 2 个文件:

1
2
~/.ssh/id_ed25519
~/.ssh/id_ed25519.pub

授权公钥

通俗的解释:一个手指就是一个私钥,对应的指纹特征就是公钥,系统就相当于门禁。授权公钥的过程就相当于录入指纹!可以使用不同的手指打开一个门,也可以同一个手指打开多个门。

把 user1@A 的公钥添加到 user2@B 的 ~/.ssh/authorized_keys 授权文件里,user1@A 即可免密码登录 user2@B。

使用ssh-copy-id

登录服务器 A 的 user1 用户操作。

Linux/Unix

Linux 服务器默认有 ssh-copy-id 命令,AIX 服务器的 ssh 版本低,没有该命令。

-i 参数后指定的是用户公钥
用户公钥和用户私钥需在同一目录。

1
ssh-copy-id -i ~/.ssh/id_rsa.pub user2@192.168.1.2

端口不是22时需要用以下这种写法:

1
ssh-copy-id -i ~/.ssh/id_rsa.pub 'user2@192.168.1.2 -p 22'

MacOSX

Mac OSX 系统没有带 ssh-copy-id 命令,可以用以下方法安装。

1
2
curl https://raw.github.com/beautifulcode/ssh-copy-id-for-OSX/master/ssh-copy-id.sh > /usr/local/bin/ssh-copy-id
chmod +x /usr/local/bin/ssh-copy-id

or

1
brew install ssh-copy-id

手动编辑(不推荐)

登录服务器 B 的 user2 用户操作。

首先把 user1@A 的公钥传输到 user2@B 的某一个目录下。

如果没有 ~/.ssh 目录则需新建。

1
2
3
4
5
[ ! -d ~/.ssh ] && mkdir ~/.ssh
chmod 700 ~/.ssh
cat ~/yourname.pub >> ~/.ssh/authorized_keys
rm ~/yourname.pub
chmod 600 ~/.ssh/authorized_keys

管道操作

登录服务器 A 的 user1 用户操作。

1
2
cat ~/.ssh/id_rsa.pub | \
ssh user2@192.168.1.2 "umask 077; test -d ~/.ssh || mkdir ~/.ssh ; cat >> ~/.ssh/authorized_keys"

或者

1
2
ssh user2@192.168.1.2 '(umask 077; test -d ~/.ssh || mkdir ~/.ssh) && \
cat >> ~/.ssh/authorized_keys' < ~/.ssh/id_rsa.pub

检查权限

登录端

服务器 A 的 user1 用户,只对私钥文件的权限敏感,检查私钥的权限是否为600:

1
ls -l ~/.ssh/id_rsa

被登录端

登录服务器 B 的 user2 用户操作。

检查目录权限是否为700:

1
ls -ld ~/.ssh

检查授权文件的权限是否为600:

1
ls -l ~/.ssh/authorized_keys

检查授权文件的内容,每个公钥应该只占 1 行:

1
cat -n ~/.ssh/authorized_keys

检查用户主目录的权限是否为700/750/755:

1
ls -ld $HOME

还要检查目录及文件的属主。

SSH原理

默认配置文件

与服务端相关的目录、文件:

目录 文件名 用户属组 权限 备注
/etc/ssh - root 755
::: sshd_config root 644 服务器全局设置
::: ssh_host_ecdsa_key root 600 主机ecdsa私钥
::: ssh_host_ecdsa_key.pub root 644 主机ecdsa公钥
::: ssh_host_ed25519_key root 600 主机ed25519私钥,可与rsa同时启用,可选
::: ssh_host_ed25519_key.pub root 644 主机ed25519公钥,可选
::: ssh_host_rsa_key root 600 主机rsa私钥
::: ssh_host_rsa_key.pub root 644 主机rsa公钥
::: ssh_host_dsa_key root 600 主机dsa私钥,安全原因,OpenSSH 7.0弃用
::: ssh_host_dsa_key.pub root 644 主机dsa公钥,弃用
::: ssh_host_key root 600 主机rsa私钥,SSH1协议使用
::: ssh_host_key.pub root 644 主机rsa公钥,SSH1协议使用
~/.ssh - 对应用户 700
::: authorized_keys 对应用户 600 授权文件,保存其他服务器的用户公钥
::: id_dsa - - 无效文件
::: id_dsa.pub - - 无效文件
/home - root 755
$HOME - 对应用户 700/750/755 Linux系统,除 root 用户

与客户端相关的目录、文件:

目录 文件名 用户属组 权限 备注
/etc/ssh - root 755
::: ssh_config root 644 客户端全局设置
::: ssh_known_hosts root 644 全局授权主机列表,可选
~/.ssh - 对应用户 700
::: config 对应用户 600 用户配置
::: id_rsa 对应用户 600 用户rsa私钥
::: id_rsa.pub 对应用户 644 用户rsa公钥
::: id_ed25519 对应用户 600 用户ed25519私钥,可选
::: id_ed25519.pub 对应用户 644 用户ed25519公钥,可选
::: known_hosts 对应用户 644 授权主机列表,保存其他服务器的主机公钥

公钥文件

每个公钥只有一行,常用4个字段,空格分隔,详见 sshd(8) manpage 帮助的 AUTHORIZED_KEYS FILE FORMAT 章节。

格式:

1
options keytype base64-encoded-key comment
  • 如 from 指定来源 ip,command 指定允许的命令(可选,需要禁止密码登录)
  • 加密类型,如 ssh-rsa
  • 公钥内容
  • 备注

公钥文件扩展名常用 .pub
公钥内容的加密类型要用小写,比如 ssh-rsa、ssh-ed25519
备注常用个人邮箱、用户名@主机名、用户名@IP

示例:

1
from="10.0.0.?,*.example.com,192.168.0.0/24",no-X11-forwarding ssh-rsa AB3Nz...EN8w== user@example.com

授权文件

authorized_keys 文件用于 SSH 服务端!推荐使用权限 600,不严谨使用 644 也行。

服务器一般不保留用户公钥 .pub 文件,需要用额外方式登记各系统之间的授权关系。

授权登录服务器的用户公钥都全部记录到 authorized_keys 文件里,每个用户公钥一行。

从服务器上每个受影响的 authorized_keys 文件移除对应的用户公钥即可撤销登录权限。

ssh 服务器使用 OpenSSH 时,支持限制来源 IP,增强安全性。Dropbear 不支持该功能。

只允许 10.0.0.2 主机的任意用户使用公钥认证登录到本用户的 authorized_keys 示例:

1
from="10.0.0.2" ssh-rsa AB3Nz...EN8w== user@example.com

授权主机列表

known_hosts 文件用于 SSH 客户端!

当前用户登录过的服务器的主机公钥全部记录到 known_hosts 文件,每个主机公钥一行。

当下次访问相同计算机时,OpenSSH 会核对公钥,如果公钥不同,OpenSSH 会发出警告或拒绝登录,避免你受到 DNS劫持、中间人攻击。

格式与授权文件有差异,不建议手工编辑。

删除授权的主机公钥

删除 known_hosts 文件里指定服务器(比如192.168.0.100)的主机公钥的命令,可用于自动化脚本:

1
ssh-keygen -R 192.168.0.100 2>/dev/null

不管是否命中,都会自动重新备份 known_hosts.old
known_hosts 文件里有格式错误的行时,删除失败,不会备份。

默认自动保存主机公钥

在 ~/.ssh/config 里增加 StrictHostKeyChecking 选项:

1
2
3
Host *
StrictHostKeyChecking no
UserKnownHostsFile /dev/null

或者在 ssh/sftp/scp 命令行里使用参数:

1
2
3
ssh -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
user@example.com

先删除后再默认添加服务器主机公钥的缺点是登录时会延迟。
UserKnownHostsFile 是可选的,使用 /dev/null“黑洞文件”保存主机公钥。

获取集群主机公钥

利用 ssh-keyscan 获取集群机器 SSH 公钥指纹。

首先准备好需要获取公钥指纹的 IP 或 HOSTNAME 列表文件 hostlist.txt

1
2
127.0.0.1
127.0.0.2
1
ssh-keyscan -f hostlist.txt 1>>~/.ssh/known_hosts 2>/dev/null

而后将 ~/.ssh/known_hosts 用 scp 拷贝到 hostlist.txt 中的所有机器上即可。

常见问题

访问关系

user1@A 要登录 user2@B、user3@C、user4@D,那么 user1 的用户公钥需要全部存放到 B、C、D 对应的用户的授权文件里。

user2@B、user3@C、user4@D 都要登录 user1@A,那么 user2、user3、user4 的用户公钥都需要存放到 user1 的授权文件里。

多节点服务器

多台服务器,使用 DMZ / NAT / HA 等技术对外使用同一个 IP 或者域名提供服务,当服务器切换后,由于目标服务器的主机公钥变化,构造人为的中间人攻击模型,客户端验证 known_hosts 失败并拒绝登录。

解决办法有以下几个,任选其一:

  • 服务器复用主机密钥对,即 /etc/ssh/ 目录里的配置保存一致。
  • 客户端每次都删除 known_hosts 里的主机公钥,登录时重新记录。
  • 客户端不使用浮动的 IP 访问服务器,而是使用各台服务器私有的 IP 访问。需要通过其他方式判断服务器集群活动的主节点,故不推荐该法。

系统克隆

克隆虚拟机会有安全问题,比如 user1@A 公钥认证登录 user2@B,B 系统克隆得到 C,那么 user1@A 可以公钥认证登录 user2@C。

  • 无密钥复用的特殊要求时候,一般建议克隆虚拟机、系统磁盘后重新生成主机密钥对。
  • 删除所有用户的 .ssh 目录。

参考命令及文件属组:

1
2
3
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key
ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
ssh-keygen -t rsa -b 2048 -f /etc/ssh/ssh_host_rsa_key
1
2
3
4
5
6
-rw-r-----. 1 root ssh_keys    227 Aug 29  2017 ssh_host_ecdsa_key
-rw-r--r--. 1 root root 162 Aug 29 2017 ssh_host_ecdsa_key.pub
-rw-r-----. 1 root ssh_keys 387 Aug 29 2017 ssh_host_ed25519_key
-rw-r--r--. 1 root root 82 Aug 29 2017 ssh_host_ed25519_key.pub
-rw-r-----. 1 root ssh_keys 1675 Aug 29 2017 ssh_host_rsa_key
-rw-r--r--. 1 root root 382 Aug 29 2017 ssh_host_rsa_key.pub

主备客户端

如果在主备 2 台服务器部署类似 Ansible 的管理工具,可以使用相同的一对用户公钥 id_rsa.pub、私钥 id_rsa,这样只需在所有的目标服务器做一次授权。

小权用户

建立小权用户查看别的用户的目录时,最常见的低级错误是 chown -R 和 chmod 更改 /home、$HOME 的属组、权限,导致公钥认证失效。

不能对组、其他用户赋予 $HOME 的写入权限!正确的权限配置:

1
2
#chmod og+x /home
$chmod og-w $HOME

# 开头的行代表用 root 权限执行,$ 代表用户权限执行。

密码有效期

  • 用户密码设置有效期,到期后也无法用公钥认证登录。
  • 非 root 用户公钥认证登录后无法更改用户密码,拥有密码即拥有当前用户的最高权限。
  • 公钥认证登录时,用私钥密码代替用户密码,私钥密码可随时修改。
  • 更改用户密码、私钥密码都不影响别的公钥认证登录。

禁止密码认证

root 用户编辑 /etc/ssh/sshd_config 文件,禁止所有用户(包括 root)使用密码认证方式登录:

1
2
PasswordAuthentication no    # 密码认证,默认 yes
PermitEmptyPasswords no # 空密码登录,默认 no

使用 Match User 可让某些选项只对该用户生效,比如除了 abc 用户外,其他用户都可以用密码登录:

1
2
3
4
PasswordAuthentication yes

Match User abc
PasswordAuthentication no

限制root用户登录

/etc/ssh/sshd_config 的可选项 PermitRootLogin 可以限定 root 用户通过 ssh 的登录方式。

参数类别 是否允许ssh登陆 登录方式 交互shell
yes 允许 没有限制 没有限制
without-password 允许 除密码以外 没有限制
forced-commands-only 允许 仅允许使用密钥 仅允许已授权的命令
no 不允许 N/A N/A
prohibit-password 允许 除密码以外 没有限制

OpenSSH 7.0 开始调整默认选项从 yes 到 prohibit-password(without-password 的同义且语义改良参数)。
PasswordAuthentication 的优先级比 PermitRootLogin 高。

禁止用户登录

/etc/ssh/sshd_config 的可选项 AllowUsers 和 Denyusers 可设置用户白名单、黑名单。

除了可以禁止某个用户登录,还可以针对固定的 IP 进行禁止登录。/etc/hosts.allow 和 /etc/hosts.deny 可设置允许访问 sshd 进程 IP 列表。

在 /etc/passwd 里设置 /sbin/nologin 禁止某用户登录,即使用 root 用户 su 命令切换用户也无法登陆。

被禁止的用户或 IP 地址,密码认证、公钥认证都无法登录。

只允许sftp登录

建议 sftp 只用于维护操作,不当文件服务器使用,因为有最大连接数限制。

authorized_keys方式

在 authorized_keys 授权文件的公钥开头写上允许使用的命令,只适用于公钥认证。

用户可自己修改授权文件时,权限管控不严格。禁用密码认证并采用 CA 扩展,效果好一点。

1
command="internal-sftp" ssh-rsa AAAAB3NzaC1yc2EAAAADA...

sshd_config方式

严格的权限管控需要由 root 用户更改 sshd_config 文件,用公钥认证或密码认证都无法 ssh 登录。

1
2
3
4
5
6
#Subsystem       sftp    /usr/libexec/openssh/sftp-server
Subsystem sftp internal-sftp

Match User user1
ChrootDirectory /home
ForceCommand internal-sftp

CA扩展

OpenSSH 5.4 以上版本支持 CA 扩展,客户端、服务器需要同时支持才可用。

大量的服务器需要使用公钥认证登录 user1@A 时,可使用 CA 证书认证。

用户认证的基本流程是:用一台服务器 C(也可以是 A 本身)的一个私钥,对客户端的用户公钥签名,user1@A 的授权文件里只需保存 C 的一个签名公钥。

CA 扩展更方便的控制用户证书里使用的登录用户名、X11 转发、SSH 代理转发、端口转发、来源 IP 地址、PTY 等。