OpenSSH
Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。SSH 基于客户端-服务器体系结构,用户在其中工作的系统是客户端,所管理的远程系统是服务器。 SSH 最常见的用途是远程登录系统,人们通常利用 SSH 来传输命令行界面和远程执行命令。
OpenSSH 是 SSH 工具的开源版本,并且应用广泛。
OpenSSH 包含一系列组件和工具,用于提供一种安全且简单的远程系统管理方法,其中包括:
- sshd.exe,它是远程所管理的系统上必须运行的 SSH 服务器组件
- ssh.exe,它是在用户的本地系统上运行的 SSH 客户端组件
- ssh-keygen.exe,为 SSH 生成、管理和转换身份验证密钥
- ssh-agent.exe,存储用于公钥身份验证的私钥
- ssh-add.exe,将私钥添加到服务器允许的列表中,即添加到 ssh-agent 中
- ssh-keyscan.exe,帮助从许多主机收集公用 SSH 主机密钥
- sftp.exe,这是提供安全文件传输协议的服务,通过 SSH 运行
- scp.exe 是在 SSH 上运行的文件复制实用工具
以上内容来自 Windows 中的 OpenSSH。
SSH 免密登录
从 A 免密登录到 B,【A】表示在 A 上操作,【B】表示在 B 上操作。
- A:笔记本电脑
- B:阿里云机器,IP 为 144.90.100.144
- 终端:Powershell、Command Prompt 或 Git Bash,建议使用管理员身份打开终端,省去某些步骤会要求权限的麻烦。
免密登录设置
1. 【A】打开终端,生成密钥对。
PS C:\Users\Think> cd .\.ssh\
PS C:\Users\Think\.ssh> ssh-keygen -t rsa
-
提示输入文件名时:
- 直接回车将使用默认文件名
id_rsa
。 - 如果不止一个密钥对,请输入一个其他文件名,如:
id_rsa_aliyun
。
- 直接回车将使用默认文件名
-
提示输入密码时:
- 直接回车,不使用密码(我的选择)。
- 输入密码再回车。
2. 【A】将私钥添加到 ssh-agent
第 2 步和第 3 步都是可选步骤,但至少要选择一个。
- 如果私钥文件过多或者嫌麻烦,建议选择第 3 步。
- 如果私钥文件不多,可以选择第 2 步或第 3 步,或者两步都选择。
ssh-agent
是 OpenSSH 身份认证代理,存储用于公钥身份验证的私钥。每当从客户端进行身份验证需要使用私钥时,ssh-agent
都会自动检索代理存储的本地私钥,并将其传递到你的 SSH 客户端。
ssh-add
将私钥加载进 ssh-agent
。
私钥加载到ssh-agent
后可以删除本地的私钥文件,但要注意无法从代理中检索私钥,另外Git Bash
在每次ssh-add
时也将无法使用私钥文件(因为被删除了)。如果删除本地私钥文件后,失去了对私钥的访问权限或误删了 agent 中的私钥,则必须在 A 上创建一个新的密钥对并在所有的 B 上更新公钥。删除本地私钥文件可以防止私钥泄露,从而使登录更安全,但也带来了一些麻烦,请根据需要作出选择。
这一步建议使用管理员身份打开终端。
注意区分不同的 SSH,不同 SSH 的 ssh-agent
是相互独立的,这意味着如果你在 Windows SSH 的 ssh-agent 中添加了你登录 GitHub 的私钥,在 Git Bash
中登录 GitHub 时仍然可能无法免密登录。
但不同 SSH 共用客户端配置文件 ~/.ssh/config
:
- Windows 操作系统的 SSH(Powershell 和 CMD 使用该 SSH):
C:\Windows\System32\OpenSSH
- Windows 上 Git 自带的 SSH(Git Bash 使用该 SSH):
D:\Program\Git\usr\bin
启动 ssh-agent
后,在 任务管理器 - 详细信息
中可以看到进程名为 ssh-agent.exe
的进程。
Windows SSH
Powershell 或 CMD 终端,将私钥添加进 Windows SSH 的 ssh-agent
。
Windows SSH 以服务的形式管理,所以 ssh-agent
是全局的,不需要每次设置。
# 查看 ssh-agent 服务状态,这里输出:Stopped
Get-Service ssh-agent
# 查看 ssh-agent 服务的启动类型,这里输出:Disabled
Get-Service ssh-agent | Select StartType
# 将 ssh-agent 服务的启动类型修改为 Manual(手动启动)。
Get-Service -Name ssh-agent | Set-Service -StartupType Manual
# 手动启动 ssh-agent 服务
Start-Service ssh-agent
# 将私钥加载进 ssh-agent
ssh-add C:\Users\Think\.ssh\id_rsa_aliyun
# 显示 ssh-agent 中的公钥,验证私钥是否添加成功
ssh-add -L
# 服务重启后,添加的密钥仍在 agent 中
如果不想使用命令管理服务,可在 Powershell 或 CMD 终端中输入命令 services.msc
打开服务管理窗口,进行可视化操作。
Git 自带 SSH
Git Bash
终端,将私钥添加进 Git 自带 SSH 的 ssh-agent
。 Git Bash
中启动的 ssh-agent
,作用域为当前终端,需要每次设置。
Git Bash
中一次设置的步骤(不用操作,仅是为了讲解设置过程):
# 启动一个 ssh-agent,作用域为当前终端,并将该 ssh-agent 作为当前终端的 ssh-agent。
eval $(ssh-agent) # 启动一个 ssh-agent 进程,并执行 ssh-agent 命令的输出内容(一段脚本)
# 【可以使用】ssh-agent bash # 作用同上
# 【不推荐使用】eval ssh-agent 或 ssh-agent,这两种写法不会执行命令输出,所以不会将 PID 写入变量 SSH_AGENT_PID,导致当前终端无法使用 ssh-agent -k 关闭 ssh-agent 进程。
# ssh-agent 命令输出内容:
# SSH_AUTH_SOCK=/tmp/ssh-T77bnnY1OwEu/agent.22080; export SSH_AUTH_SOCK;
# SSH_AGENT_PID=24132; export SSH_AGENT_PID;
# echo Agent pid 24132;
# 输出当前终端中 SSH_AUTH_SOCK 的值:ssh 和 scp 用来同ssh-agent 建立对话的 UNIX 域套接字的路径。
echo $SSH_AUTH_SOCK
# 输出当前终端中 ssh-agent 的进程 ID
echo $SSH_AGENT_PID
# 将私钥加载进 ssh-agent
ssh-add ~/.ssh/id_rsa_aliyun
# 显示 ssh-agent 中的公钥,验证私钥是否添加成功
ssh-add -L
# 测试免密登录
ssh -t root@144.90.100.144
# 关闭 ssh-agent 进程
ssh-agent -k
# 如果上述命令不生效(PID 缺失),尝试下面的方法:
# 1. taskkill /IM ssh-agent.exe /F /T
# 2. 任务管理器找到 ssh-agent.exe 进程,并右击结束进程
# ssh-agent 重启后,添加的密钥将不在 agent 中,即重置为空
上述步骤有几个缺点:
- 每次打开终端都需要新启动一个 agent 进程,或设置 SSH_AUTH_SOCK 和 SSH_AGENT_PID 为上次命令输出的内容,使当前终端可以使用新的 agent 或上次未关闭的 agent。
- 在第一步打开的终端中,需要重新添加本次登录需要使用的私钥文件到 agent。
为了解决上面的问题,以期达到只需一次配置的效果,可以将下面的脚本添加到 /etc/bash.bashrc
末尾(根目录 / 对应 Windows 哪个目录,可以使用 df -h
命令查看):
# 要添加到 agent 的私钥文件列表,多个用空格隔开
keys="$HOME/.ssh/id_rsa_aliyun $HOME/.ssh/id_rsa_tencent"
env=~/.ssh/agent.env
agent_load_env () { test -f "$env" && . "$env" >| /dev/null ; }
agent_start () {
(umask 077; ssh-agent >| "$env")
. "$env" >| /dev/null ; }
agent_load_env
# agent_run_state: 0=agent running w/ key; 1=agent w/o key; 2= agent not running
agent_run_state=$(ssh-add -l >| /dev/null 2>&1; echo $?)
if [ ! "$SSH_AUTH_SOCK" ] || [ $agent_run_state = 2 ]; then
agent_start
ssh-add $keys
elif [ "$SSH_AUTH_SOCK" ] && [ $agent_run_state = 0 ]; then
ssh-add -D
ssh-add $keys
fi
unset env
上面的脚本参考自文章:Working with SSH key passphrases。
3. 【A】修改客户端配置
除了 ssh-agent
外,修改客户端配置也可以实现免密登录。
客户端配置文件位置:
- Windows 上是
~/.ssh/config
- Linux 上是
/etc/ssh/ssh_config
# 别名(Host):Host 和 HostName 的值可以相同
# 如 ssh aliyun,在这里等于 ssh -i C:\Users\Think\.ssh\id_rsa_aliyun root@144.90.100.144
# 用别名登录会使用别名下的配置,不用别名登录(如IP)不会使用别名下的配置
Host aliyun
User root
HostName 144.90.100.144
# 私钥文件位置
IdentityFile "~/.ssh/id_rsa_aliyun"
Host tencent
User root
HostName 100.28.144.47
IdentityFile "~/.ssh/id_rsa_tencent"
4. 【A/B】服务端配置公钥
【A】【推荐】在 A 机器的 Git Bash
中执行下列命令,该命令将公钥拷贝到服务端 B 的 ~/.ssh/authorized_keys
,并设置文件权限。
ssh-copy-id -i ~/.ssh/id_rsa_aliyun.pub root@144.90.100.144
或者
【B】在 A 上复制 ~/.ssh/id_rsa_aliyun.pub
中的内容,并将其手动写入(vim 编辑或 echo 追加)服务端 B 的 ~/.ssh/authorized_keys
文件末尾。然后在服务端执行下列命令设置文件权限:
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
# 或者
chmod g-w /root/.ssh
chmod g-w /root/.ssh/authorized_keys
如果是 GitHub 或 GitLab 等平台,服务端公钥配置一般在个人设置的 SSH Keys 中,增加一个新的公钥,并将公钥文件内容粘贴进去。
5. 免密登录测试
# 使用别名测试登录
ssh aliyun -v
# 使用 IP 测试登录
ssh root@144.90.100.144 -v
# 第一次免密登录需要确认服务端指纹,输入 yes 回车即可。
上面说的比较多,是因为细节比较多,总结起来就是:
- 在客户端生成密钥对。
- 在客户端配置私钥文件。
- 在服务端配置公钥文件。
- 测试客户端免密登录服务端是否生效。
错误排查
错误排查手段:
- 服务端(目标服务器)查看安全日志
sudo cat /var/log/secure
- 客户端访问时开启日志,如:
ssh root@144.90.100.144 -v
或ssh root@144.90.100.144 -vvv
SSH 登录流程:
如果按之前的步骤完成免密登录配置后,免密登录测试失败,仍然提示输入密码,可能是下面的一些原因。
服务端 SSH 未开启
sshd 管理命令:
# 查看 ssh 服务进程
ps -ef|grep ssh
# 查看服务状态
service sshd status
# 启动服务
service sshd start
# 停止服务
service sshd stop
# 重启服务
service sshd restart
服务端配置问题
正常情况下,因为服务端默认开启免密登录认证,所以不会有这个问题。如果手动关闭了免密登录认证,只要再次开启就好。
# 允许密钥认证,在 SSH1.0 中使用,默认开启
#RSAAuthentication yes
# 允许密钥认证,在 SSH2.0 中使用,默认开启
#PubkeyAuthentication yes
# 公钥存放位置
AuthorizedKeysFile .ssh/authorized_keys
服务端权限问题
在目标服务器上查看日志 sudo cat /var/log/secure
,其中 sshd
表示 ssh 的日志,发现如下报错信息:
Jun 27 17:08:22 izj6c66dst6ergsqe2wynxz sshd[17719]: Authentication refused: bad ownership or modes for file /root/.ssh/authorized_keys
提示:身份验证被拒绝,文件 /root/.ssh/authorized_keys
所有人或模式设置错误。原因是 SSH 不希望 home
目录和 ~/.ssh
目录对组有写权限。
修改文件夹和文件权限,排除组的写权限:
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
# 或者
chmod g-w /root/.ssh
chmod g-w /root/.ssh/authorized_keys
服务端开启了 SELINUX
# 查看 SELINUX 状态
getenforce
# 禁用 SELINUX
vim /etc/selinux/config
# SELINUX=disabled
# 重启服务器生效
reboot
ssh-agent 问题
本小节的讨论前提(对于要登录的服务端)是 ssh 无法正确使用本地身份验证文件,即只能使用 ssh-agent
中的缓存身份验证文件,具体表现为满足以下任一条件:
- 指定了私钥文件位置,但指定的私钥文件不存在。
- 指定了私钥文件位置,但指定的私钥文件内容错误。
- 未指定私钥文件位置,且私钥文件名不是默认文件名,如:
id_rsa
。 - 未指定私钥文件位置,且私钥文件名是默认文件名,但内容错误。
在满足上述前提的条件下,出现下列情形将无法免密登录:
- 未开启
ssh-agent
。 - 开启了
ssh-agent
,但没有通过ssh-add
命令将私钥文件加载进ssh-agent
。 - 开启了
ssh-agent
且有对应的私钥文件,但在命令行或客户端配置中指定了IdentitiesOnly yes
。 - 在第一个 SSH 中开启了
ssh-agent
并加载了私钥,但在第二个 SSH 中执行命令。比如,在Git Bash
中开启了ssh-agent
并加载了私钥,但在Powershell
或CMD
中执行 SSH 命令。 - 使用
Git Bash
但没有进行全局配置。在Git Bash
的一个终端窗口执行的ssh-agent
和ssh-add
命令只在当前终端窗口有效,在新打开的其他Git Bash
终端窗口中无效。
客户端配置问题
本小节场景一和场景二的讨论前提是无法使用 ssh-agent
中缓存的身份验证文件进行登录(参考上一小节)。
场景一
私钥文件不是默认文件名,且没有指定私钥文件。
解决方法:可以通过下面两种方式指定私钥文件。
示例(客户端配置):
Host aliyun
User root
HostName 144.90.100.144
IdentityFile "~/.ssh/id_rsa_aliyun"
示例(命令的 -i 参数):
ssh -i C:\Users\Think\.ssh\id_rsa_aliyun root@144.90.100.144
场景二
指定的私钥文件不存在或内容错误。
场景三
SSH 解析客户端配置和命令后,会有个待验证的身份验证文件列表,并按照文件列表顺序逐个尝试。该文件列表的顺序是:缓存文件列表、本地文件列表。
如果缓存文件或本地文件过多,且多次登录失败,将引发以下报错信息:
Received disconnect from ... Too many authentication failures
本地文件过多的配置示例:
Host *
IdentityFile "~/.ssh/id_rsa_1"
IdentityFile "~/.ssh/id_rsa_2"
IdentityFile "~/.ssh/id_rsa_3"
...
IdentityFile "~/.ssh/id_rsa_n"
针对多次登录失败的解决方法:
-
缓存文件过多导致的报错,禁用 agent 中的缓存文件。
- 关闭
ssh-agent
。 - 在客户端配置文件,对所有 Host 或指定 Host 增加
IdentitiesOnly yes
配置项。 - 命令中设置
IdentitiesOnly
:ssh -o IdentitiesOnly=yes aliyun
。
- 关闭
- 本地文件过多导致的报错,指定私钥文件。
提示:IdentitiesOnly
参数取值只能是 yes
或 no
,默认是 no
。
# 对所有 Host 增加 `IdentitiesOnly yes` 配置项
Host *
IdentitiesOnly yes
# 对指定 Host 增加 `IdentitiesOnly yes` 配置项
Host aliyun
User root
HostName 144.90.100.144
IdentitiesOnly yes
IdentityFile "~/.ssh/id_rsa_aliyun"
没有使用别名
不使用别名的情况下,客户端配置文件中的配置不会被使用。
比如,ssh root@144.90.100.144 -v
不会使用 Host aliyun
的配置。
这种情况下,免密登录的解决方法:
- 将别名
Host
和HostName
设置为相同的值。 - 在命令中使用别名,如:
ssh aliyun -v
,这样就会使用配置文件中的配置。 - 在命令中指定配置(私钥):
ssh -i C:\Users\Think\.ssh\id_rsa_aliyun root@144.90.100.144 -v
。
扩展阅读
客户端配置
客户端配置文件位置:
- Windows 上是
~/.ssh/config
- Linux 上是
/etc/ssh/ssh_config
配置文件有哪些配置项,各项的含义:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。