6

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 中,即重置为空

上述步骤有几个缺点:

  1. 每次打开终端都需要新启动一个 agent 进程,或设置 SSH_AUTH_SOCK 和 SSH_AGENT_PID 为上次命令输出的内容,使当前终端可以使用新的 agent 或上次未关闭的 agent。
  2. 在第一步打开的终端中,需要重新添加本次登录需要使用的私钥文件到 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 回车即可。

上面说的比较多,是因为细节比较多,总结起来就是:

  1. 在客户端生成密钥对。
  2. 在客户端配置私钥文件。
  3. 在服务端配置公钥文件。
  4. 测试客户端免密登录服务端是否生效。

错误排查

错误排查手段:

  • 服务端(目标服务器)查看安全日志 sudo cat /var/log/secure
  • 客户端访问时开启日志,如:ssh root@144.90.100.144 -vssh root@144.90.100.144 -vvv

SSH 登录流程:

ssh_login_flow.png

如果按之前的步骤完成免密登录配置后,免密登录测试失败,仍然提示输入密码,可能是下面的一些原因。

服务端 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 中的缓存身份验证文件,具体表现为满足以下任一条件:

  1. 指定了私钥文件位置,但指定的私钥文件不存在。
  2. 指定了私钥文件位置,但指定的私钥文件内容错误。
  3. 未指定私钥文件位置,且私钥文件名不是默认文件名,如: id_rsa
  4. 未指定私钥文件位置,且私钥文件名是默认文件名,但内容错误。

在满足上述前提的条件下,出现下列情形将无法免密登录:

  • 未开启 ssh-agent
  • 开启了 ssh-agent,但没有通过 ssh-add 命令将私钥文件加载进 ssh-agent
  • 开启了 ssh-agent 且有对应的私钥文件,但在命令行或客户端配置中指定了 IdentitiesOnly yes
  • 在第一个 SSH 中开启了 ssh-agent 并加载了私钥,但在第二个 SSH 中执行命令。比如,在 Git Bash 中开启了 ssh-agent 并加载了私钥,但在 PowershellCMD 中执行 SSH 命令。
  • 使用 Git Bash 但没有进行全局配置。在 Git Bash 的一个终端窗口执行的 ssh-agentssh-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 配置项。
    • 命令中设置 IdentitiesOnlyssh -o IdentitiesOnly=yes aliyun
  • 本地文件过多导致的报错,指定私钥文件。

提示:IdentitiesOnly 参数取值只能是 yesno,默认是 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 的配置。

这种情况下,免密登录的解决方法:

  • 将别名 HostHostName 设置为相同的值。
  • 在命令中使用别名,如: 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

配置文件有哪些配置项,各项的含义:

  1. SSH Config File - ssh.com
  2. ssh_config(5) - Linux man page
  3. linux ssh_config和sshd_config配置文件学习

参考资料

  1. Windows 中的 OpenSSH
  2. 解决SSH免密登录配置成功后不生效问题
  3. Working with SSH key passphrases

千里云飞
31 声望1 粉丝

徒步向前。