SSH Agent Forwarding 工作原理及问题排查方法

一、背景

最近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用户启动)。

agent-forwarding-in-action(原文见参考链接1)

启动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 如何工作

  1. 我们在ssh的config文件中为主机启用ForwardAgent yes,或者使用ssh -A选项建立ssh连接到目标机(本例中是堡垒机)。
  2. ssh除了创建常规的terminal通道,现在还会创建另一个通道用来转发SSH_AUTH_SOCK指向的socket。
  3. 在目标机上创建一个socket文件(下称代理socket),/tmp/ssh-xxx/agent.xxx。所有发送到这个socket的认证请求实际上会被转发到本地主机的ssh-agent socket并由本地的ssh-agent进行处理。
  4. ssh为我们自动设定环境变量,将SSH_AUTH_SOCK指向在目标机上创建的代理socket。
  5. 在目标机上的所有后续指令,如ssh-add -lssh等,都将正常读取SSH_AUTH_SOCK并使用这个代理socket。
  6. 这些命令并不关心这个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. 其他可能的问题

  1. 执行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变量指向的文件不存在。

  2. 测试本机是否可以认证成功:
    ssh -T git@github.com
    若不能,则还轮不到目标机器的问题,先聚焦本机问题排查。

  3. 目标机器上理论上不需要跑ssh-agent,如果有ssh-agent在运行,一定要确认没有干扰到当前shell session (根据上面的原理来分析):
    ps -ef | grep 'ssh-agent' | grep -v grep

  4. 通过ssh-add添加的key重启agent进程(重启计算机)后会消失,这是正常的。

  5. ssh -vvv 可以看到ssh的认证和加载细节,在日志中搜索agent,socket可能会有帮助。

  6. 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_')
}

六、补充说明

  1. 文章写的有点长,如有错误感谢更正。
  2. 使用AgentForwarding可能引起安全问题(被目标机上sudo用户盗用认证),若密钥权限很高请了解后再使用AgentForwarding(参考链接1)!可以考虑在本地机器上采用支持指纹认证或弹窗确认的优化版ssh-agent(github上有这种开源项目),避免转发的agent被目标机上的root用户盗用!

七、参考链接:

  1. https://smallstep.com/blog/ssh-agent-explained/
  2. https://www.packetmischief.ca/2016/09/06/ssh-agent-on-os-x/
  3. https://unix.stackexchange.com/questions/90853/how-can-i-run-ssh-add-automatically-without-a-password-prompt/90869
  4. https://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions
  5. https://blog.testdouble.com/posts/2016-11-18-reconciling-tmux-and-ssh-agent-forwarding/
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342