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 | ~/.ssh/id_rsa |
ed25519密钥对
OpenSSH 6.5 之后的版本支持新的 ed25519 加密算法,可代替 RSA 算法。
登录服务器 A 的用户 user1,生成非对称加密 ed25519 密钥对,无需指定密钥长度:
1 | ssh-keygen -t ed25519 |
按照提示一直按回车,将会在 ~/.ssh 目录下生成 2 个文件:
1 | ~/.ssh/id_ed25519 |
授权公钥
通俗的解释:一个手指就是一个私钥,对应的指纹特征就是公钥,系统就相当于门禁。授权公钥的过程就相当于录入指纹!可以使用不同的手指打开一个门,也可以同一个手指打开多个门。
把 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 | curl https://raw.github.com/beautifulcode/ssh-copy-id-for-OSX/master/ssh-copy-id.sh > /usr/local/bin/ssh-copy-id |
or
1 | brew install ssh-copy-id |
手动编辑(不推荐)
登录服务器 B 的 user2 用户操作。
首先把 user1@A 的公钥传输到 user2@B 的某一个目录下。
如果没有 ~/.ssh 目录则需新建。
1 | [ ! -d ~/.ssh ] && mkdir ~/.ssh |
管道操作
登录服务器 A 的 user1 用户操作。
1 | cat ~/.ssh/id_rsa.pub | \ |
或者
1 | ssh user2@192.168.1.2 '(umask 077; test -d ~/.ssh || mkdir ~/.ssh) && \ |
检查权限
登录端
服务器 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 | Host * |
或者在 ssh/sftp/scp 命令行里使用参数:
1 | ssh -o StrictHostKeyChecking=no \ |
先删除后再默认添加服务器主机公钥的缺点是登录时会延迟。
UserKnownHostsFile 是可选的,使用 /dev/null“黑洞文件”保存主机公钥。
获取集群主机公钥
利用 ssh-keyscan 获取集群机器 SSH 公钥指纹。
首先准备好需要获取公钥指纹的 IP 或 HOSTNAME 列表文件 hostlist.txt
1 | 127.0.0.1 |
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 | ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key |
1 | -rw-r-----. 1 root ssh_keys 227 Aug 29 2017 ssh_host_ecdsa_key |
主备客户端
如果在主备 2 台服务器部署类似 Ansible 的管理工具,可以使用相同的一对用户公钥 id_rsa.pub、私钥 id_rsa,这样只需在所有的目标服务器做一次授权。
小权用户
建立小权用户查看别的用户的目录时,最常见的低级错误是 chown -R 和 chmod 更改 /home、$HOME 的属组、权限,导致公钥认证失效。
不能对组、其他用户赋予 $HOME 的写入权限!正确的权限配置:
1 | #chmod og+x /home |
# 开头的行代表用 root 权限执行,$ 代表用户权限执行。
密码有效期
- 用户密码设置有效期,到期后也无法用公钥认证登录。
- 非 root 用户公钥认证登录后无法更改用户密码,拥有密码即拥有当前用户的最高权限。
- 公钥认证登录时,用私钥密码代替用户密码,私钥密码可随时修改。
- 更改用户密码、私钥密码都不影响别的公钥认证登录。
禁止密码认证
root 用户编辑 /etc/ssh/sshd_config 文件,禁止所有用户(包括 root)使用密码认证方式登录:
1 | PasswordAuthentication no # 密码认证,默认 yes |
使用 Match User 可让某些选项只对该用户生效,比如除了 abc 用户外,其他用户都可以用密码登录:
1 | PasswordAuthentication yes |
限制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 | #Subsystem sftp /usr/libexec/openssh/sftp-server |
CA扩展
OpenSSH 5.4 以上版本支持 CA 扩展,客户端、服务器需要同时支持才可用。
大量的服务器需要使用公钥认证登录 user1@A 时,可使用 CA 证书认证。
用户认证的基本流程是:用一台服务器 C(也可以是 A 本身)的一个私钥,对客户端的用户公钥签名,user1@A 的授权文件里只需保存 C 的一个签名公钥。
CA 扩展更方便的控制用户证书里使用的登录用户名、X11 转发、SSH 代理转发、端口转发、来源 IP 地址、PTY 等。