一、背景
最近ssh agent经常出问题,把agent工作原理和详细排查思路整理了一下。
如果要了解forward的原理,一定要先把本地agent的原理弄清楚。
文本部分基于CARL TASHIAN的博客(参考链接1)。
二、本地SSH Agent 工作原理
关于ssh-agent
ssh agent是一个运行在后台的进程,会将您解密后的密钥和证书保存在内存中,供ssh使用。运行后,它会在本地创建一个监听 socket,例如名为/tmp/ssh-d1X3vAYSmpkp/agent.8552
(linux) 或 /private/tmp/com.apple.launchd.L1AN4vL69w/Listeners
(由macOS系统启动) 或 /var/folders/19/17p_75b970dck7p16jwslf_r0000gn/T//ssh-hCUp3SQXCMjd/agent.65779
(由macOS用户启动)。
启动ssh-agent的过程
启动ssh-agent的方法是eval $(ssh-agent -s)
,这会执行以下指令。启动一个新的ssh agent进程,创建socket,同时设置SSH_AUTH_SOCK
环境变量指向该socket。
# start new ssh-agent process
SSH_AUTH_SOCK=/tmp/ssh-tgQtgD7sjimD/agent.10045; export SSH_AUTH_SOCK;
SSH_AGENT_PID=10046; export SSH_AGENT_PID;
echo Agent pid 10046;
在这之后,通常还会使用ssh-add /path/to/key
来添加key,或者省略文件参数来添加所有默认文件名的key。加入的key可以在后续ssh-add -l
的测试中列出来。
如果多次执行了eval $(ssh-agent -s)
会导致启动多个进程,产生多个socket!想要手动杀掉可以用pkill ssh-agent
!
关闭ssh-agent的过程
关闭ssh-agent的方法是eval $(ssh-agent -k)
,这会执行以下指令。杀死当前的ssh-agent,删除socket,清除SSH_AUTH_SOCK
变量。
# kill process with pid = $SSH_AGENT_PID;
unset SSH_AUTH_SOCK; unset SSH_AGENT_PID;
echo Agent pid 10046 killed;
谁来启动ssh-agent
在有些系统(如macOS)上,它会在首次启动ssh时启动,并自动在所有shell session设置环境变量并生效。另外一些系统上,则需要在每个shell session手动启动。
对于后一种情况,如果想要每次开启shell时自动启动/关联agent,常见的操作是在bashrc文件中启动ssh-agent,并通过trap exit或.bash_logout在退出时关闭这个ssh-agent进程,这样每个shell对应至多一个ssh-agent(参考链接3)。如果想要多个shell共用一个ssh-agent进程则比较复杂,大概思路就是扫描socket目录或者去其他shell继承环境变量(参考链接4)。
三、如何使用ssh-agent
列出ssh-agent中的key
一个最简单的用途,就是使用ssh-add -l
列出ssh-agent中的key。这也是测试ssh-agent的基本方法。
ssh-add -l
# 输出:
# 256 SHA256:Yldaz/TPb+LzFRJX6S7YDZ0aF5zqj4gpI+JvVNdWte8 raccoon@macbook.local (ED25519)
我们可以看到输出了之前ssh-add进去的一个key,说明这个key已经在这个ssh-agent里可用于认证了。
如果想要列出其他ssh-agent进程中的key,则需要找到其对应的socket文件(一般可以通过pid推测),然后带着环境变量(关键)运行ssh-add -l
:
SSH_AUTH_SOCK=/tmp/ssh-tgQtgD7sjimD/agent.10045 ssh-add -l
# 输出:
# 256 SHA256:Yldaz/TPb+LzFRJX6S7YDZ0aF5zqj4gpI+JvVNdWte8 raccoon@macbook.local (ED25519)
使用ssh-agent中的key进行ssh认证
接下来,我们开始(终于)逐渐进入正题了。假设现在环境变量SSH_AUTH_SOCK
已经设置,且指向的socket存在,且对应的ssh-agent进程中已经添加了一个有效的key。现在,我们可以使用这个key登录到远程机器(以GitHub为例)。
ssh -T git@github.com -vv
# 输出(macOS版本):
# ...
# debug1: Will attempt key: /path/to/my/key_file ED25519 SHA256:Yldaz/TPb+LzFRJX6S7YDZ0aF5zqj4gpI+JvVNdWte8 agent
# ...
# Hi XXX! You've successfully authenticated, .. (登录成功了)
# 输出(Linux版本):
# ...
# debug2: key: key_comment (0x55b97f874780), agent
# ...
# Hi XXX! You've successfully authenticated, .. (登录成功了)
如果你看到了上面带 agent 的输出,则说明本次认证已经在用agent中的key尝试认证了。如果想要使用特定ssh-agent中的key,与上一步类似,只需要为命令指定环境变量即可。
四、远程Forward Agent工作原理
为什么要Forward Agent
如果我们有一台堡垒机,我们想通过堡垒机连接到集群机器,或访问github,且希望使用本地电脑里的密钥登录,且不想把这个密钥发送到堡垒机。那么就可以使用Forward Agent。
当然,我们也可以在堡垒机上生成一个密钥,或者使用ProxyJump。这三种方式所适用的场景各有不同,这里不展开了。
通过一些特定操作,我们甚至可以实现将堡垒机上的密钥拿到本地计算机使用。
Forward Agent 如何工作
- 我们在ssh的config文件中为主机启用
ForwardAgent yes
,或者使用ssh -A
选项建立ssh连接到目标机(本例中是堡垒机)。 - ssh除了创建常规的terminal通道,现在还会创建另一个通道用来转发
SSH_AUTH_SOCK
指向的socket。 - 在目标机上创建一个socket文件(下称代理socket),
/tmp/ssh-xxx/agent.xxx
。所有发送到这个socket的认证请求实际上会被转发到本地主机的ssh-agent socket并由本地的ssh-agent进行处理。 - ssh为我们自动设定环境变量,将SSH_AUTH_SOCK指向在目标机上创建的代理socket。
- 在目标机上的所有后续指令,如
ssh-add -l
或ssh
等,都将正常读取SSH_AUTH_SOCK
并使用这个代理socket。 - 这些命令并不关心这个socket是一个代理socket还是本地socket,只要发送到这个socket的数据能够被一个ssh-agent响应即可。
在目标机上测试登录GitHub,我们会发现效果就像在本地一样:
ssh -T git@github.com -vv
# 输出(Linux版本):
# ...
# debug2: key: your_key_comment (0x55b97f874780), agent
# ...
# Hi XXX! You've successfully authenticated, .. (登录成功了)
(高级) 反向添加key
实际上,实现从目标机添加key到本机agent并不复杂。只要先开启forward agent连接上目标机,然后在目标机上执行一下ssh-add /path/to/key_file
,就会将相应的key添加到socket通向的agent(也就是本地主机上这个agent)。在断开ssh后,本地主机执行ssh-add -l
之后,就会发现key已经被添加到本机的agent里了。
(高级) 串联代理agent
如果我们要在集群节点上也需要主机上的key,只需要在堡垒机上继续使用ssh -A
登录即可。本机的这个ssh-agent是可以被层层代理的。理论上你只需要在本地机器上跑一个ssh-agent进程就够了,中间的机器上都不需要创建ssh-agent进程。
五、常见问题与排查思路
1. 本机没有ssh-agent进程在运行
ps -e | grep ssh-agent | grep -v grep
没有结果?那说明没有在运行的ssh-agent。可以参考上文启动ssh-agent相关内容。
如果是macOS系统:一般来说在首次使用ssh的时候(参考链接2)会由系统启动一个ssh-agent,pid为1,无法通过ps列出来,但可以用。要验证有没有,可以尝试
ls -l /private/tmp/com.apple.launchd.*/Listeners
如果有结果,则说明其实已经有一个ssh-agent在运行了,这个agent比较特殊,没必要再开一个。
2. SSH_AUTH_SOCK变量没有正确设置
echo $SSH_AUTH_SOCK
没有结果?那说明变量没有设置。
- 如果是本地机且确认ssh-agent在运行,则检查ssh-agent的启动方法。对于macOS用户来说,若使用的是系统ssh-agent,正常情况下所有shell会自动设置这个变量。如有问题怀疑是launchd和plist出现了问题(参考链接2)。
- 如果是目标机且开启了agent forward,则先检查发出ssh的shell是否有这个变量且文件有效(可以用上文提到的
ssh-add -l
等方法测试;或者在ssh -vvv日志里发现ssh_get_authentication_socket: No such file or directory
等错误)。 - 如果本机ssh-agent和变量都确认没问题,但是目标机上却怎么都没有这个变量,说明代理转发没有生效。这个时候要仔细看ssh日志,排查问题出在哪里。
- ssh_config 和服务端sshd_config 默认允许agent forwarding功能,可以确认一下是否在服务配置文件关闭了这个功能!
- 已知:当Host key有问题的时候,就算设置了
-o StrictHostKeyChecking=no
也会导致ssh强制禁用代理转发!可以清除known_hosts
或用-o UserKnownHostsFile=/dev/null
再试一次!若开启了Master,主连接的Host key有问题则所有的shared session也都会有问题!
3. 其他可能的问题
-
执行
ssh-add -l
:提示
The agent has no identities.
:
成功连接到agent,但是这个agent里没有key,用ssh-add
添加一下即可(参考上文启动ssh-agent相关)。提示
Could not open a connection to your authentication agent.
:
SSH_AUTH_SOCK
变量未设置或者socket有问题(如用tmux可能有此问题,可参考下文)提示
Error connecting to agent: No such file or directory
:
很少见,SSH_AUTH_SOCK
变量指向的文件不存在。
测试本机是否可以认证成功:
ssh -T git@github.com
若不能,则还轮不到目标机器的问题,先聚焦本机问题排查。目标机器上理论上不需要跑ssh-agent,如果有ssh-agent在运行,一定要确认没有干扰到当前shell session (根据上面的原理来分析):
ps -ef | grep 'ssh-agent' | grep -v grep
通过
ssh-add
添加的key重启agent进程(重启计算机)后会消失,这是正常的。ssh -vvv
可以看到ssh的认证和加载细节,在日志中搜索agent,socket可能会有帮助。ssh配合tmux使用时,因为shell存在的时间比ssh会话寿命长,下次重新attach时一定出问题! 变量还是指向旧的socket,而此时维护这个socket的ssh已经断开了。所以,如果重新连进来之后,需要手动更新一下这个变量,才能让forward功能生效!详细讨论及自动修复的方式见(参考链接5)。
可以执行这个命令解决:eval $(tmux show-env -s |grep '^SSH_')
。
也可以在bashrc定义一个命令,每次重连之后快捷修复一下:
fixssh() {
eval $(tmux show-env -s |grep '^SSH_')
}
六、补充说明
- 文章写的有点长,如有错误感谢更正。
- 使用AgentForwarding可能引起安全问题(被目标机上sudo用户盗用认证),若密钥权限很高请了解后再使用AgentForwarding(参考链接1)!可以考虑在本地机器上采用支持指纹认证或弹窗确认的优化版ssh-agent(github上有这种开源项目),避免转发的agent被目标机上的root用户盗用!
七、参考链接:
- https://smallstep.com/blog/ssh-agent-explained/
- https://www.packetmischief.ca/2016/09/06/ssh-agent-on-os-x/
- https://unix.stackexchange.com/questions/90853/how-can-i-run-ssh-add-automatically-without-a-password-prompt/90869
- https://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions
- https://blog.testdouble.com/posts/2016-11-18-reconciling-tmux-and-ssh-agent-forwarding/