<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Agent · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/agent/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/agent/index.xml" rel="self" type="application/rss+xml"/><item><title>SSH 代理转发的权限边界</title><link>https://www.xbcatm.com/zh/posts/linux-ssh-agent-forwarding/</link><pubDate>Wed, 09 Feb 2028 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/linux-ssh-agent-forwarding/</guid><description>Agent 转发把私钥材料留在本地，却允许远端主机请求用于认证的签名。</description><content:encoded>&lt;p&gt;agent 转发并不等于 agent 本身。本地 agent 可以替客户端完成认证操作，而不把私钥交给服务器；转发则进一步在远端开放一个套接字，让那边的程序也能请求这项服务。因此，「私钥材料留在本地」和「身份认证能力只留在本地」是两个不同的承诺。&lt;/p&gt;
&lt;p&gt;选择堡垒机访问方式时，这个区别尤其重要。如果只是要登录内网机器，通常用 &lt;code&gt;ProxyJump&lt;/code&gt; 并关闭 agent 转发就够了。只有程序确实需要在远端发起认证，例如一条经过明确授权的后续 SSH 连接，才需要考虑把 agent 的使用能力交过去。不要把网络可达性和身份委托混成一个开关。&lt;/p&gt;
&lt;h2 id="远端拿到的是能力"&gt;远端拿到的是能力&lt;/h2&gt;
&lt;p&gt;转发出来的套接字通常通过远端的 &lt;code&gt;SSH_AUTH_SOCK&lt;/code&gt; 暴露。能访问它的进程，包括拥有足够权限的本机攻击者，都可以向原始 agent 请求操作。这不意味着服务器上的任何普通账户都有权限，更不意味着私钥文件已经被复制过去；风险来自可以调用身份，而不是必须先偷到密钥。&lt;/p&gt;
&lt;p&gt;一把没有额外约束的已加载密钥，在转发通道仍然可用时，可能被拿去完成你没有打算批准的登录。密钥的存活期、逐次确认、目标限制，以及目标服务器是否接受这个身份，都会改变请求能否成功。把它说成「完整拥有私钥」不够准确，把它说成「只是一只无害套接字」同样会误导操作判断。&lt;/p&gt;
&lt;p&gt;旧版本曾把一枚没有解释来源的八十八字节签名当作证据。单看长度，既不能证明实验实现了 OpenSSH agent 协议，也不能证明一次经过转发的认证成功。下面改用真正的 SSH 客户端，验证一个范围明确、读者可以复现的性质：最终生效的转发选项。这个检查不冒充真实网络集成测试。&lt;/p&gt;
&lt;h2 id="转发由生效配置决定"&gt;转发由生效配置决定&lt;/h2&gt;
&lt;p&gt;命令里没有 &lt;code&gt;-A&lt;/code&gt;，并不代表关闭了转发。匹配的 &lt;code&gt;Host&lt;/code&gt; 段、被包含的配置文件，或者系统配置，都可能已经把它打开。反过来，即使文件写着 &lt;code&gt;ForwardAgent no&lt;/code&gt;，命令行上的 &lt;code&gt;-A&lt;/code&gt; 仍会显式启用转发。需要关闭时，应当使用 &lt;code&gt;-a&lt;/code&gt;，或者没有相互矛盾选项的 &lt;code&gt;-o ForwardAgent=no&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ProxyJump&lt;/code&gt; 提供到目标的 TCP 路径，并不会顺便重置目标连接的转发选项。目标 SSH 连接及其认证仍从本地发起，所以单纯借道跳板机不需要把 agent 暴露过去。目标和跳板机的配置也不一定一样，应分别检查相应别名，不要把某一条 &lt;code&gt;ssh -G&lt;/code&gt; 的输出理解成整条路径的所有设置。&lt;/p&gt;
&lt;p&gt;如果已经进入远端 shell，再运行一次 SSH，那台机器上的客户端会读取它自己的配置。判断哪一跳继续转发，要看每次调用的有效设置，不能数历史命令里出现过几次 &lt;code&gt;-A&lt;/code&gt;。排查已有连接时还要留意连接复用：打印当前配置描述的是这次拟议调用，不能反推所有既有主连接的运行状态。&lt;/p&gt;
&lt;h2 id="缩小委托范围"&gt;缩小委托范围&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ssh-add -c&lt;/code&gt; 通过 askpass 程序请求使用确认，不能只因为请求方没有终端，就断言确认必然失败。&lt;code&gt;ssh-add -t&lt;/code&gt; 可以限制身份留在 agent 中的时间。自 OpenSSH 8.9 起提供的目标约束依据已知主机密钥识别主机，经过转发路径使用时还需要客户端和服务端配合支持。所有必要的跳转都要被允许，孤立的一条来源到目标规则不构成完整部署方案。&lt;/p&gt;
&lt;p&gt;服务端设置 &lt;code&gt;AllowAgentForwarding no&lt;/code&gt;，可以关闭 OpenSSH 自带的转发设施，但不能阻止拥有不受限制 shell 的用户另行搭建转发器。这个限制有配置卫生价值，却不能单独隔离一个恶意 shell 用户。仍然应该把服务器信任关系和可执行程序的权限纳入判断。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;堡垒机被攻陷后，攻击者可能在转发通道和相应身份仍可用的窗口里滥用 agent。在磁盘上换掉私钥文件，并不会自动卸载 agent 内存里的旧身份。处置时应终止转发连接、移除相关的已加载身份；如果风险需要，再撤销服务器接受该身份的授权。已经建立的远程会话不会因此自动消失，需要另外处理。&lt;/p&gt;
&lt;p&gt;另一个常见问题是把 &lt;code&gt;ForwardAgent yes&lt;/code&gt; 放进 &lt;code&gt;Host *&lt;/code&gt; 后忘记它。最好只为确实需要的主机设置例外，并让用于转发的 agent 只包含必要身份。给某次登录指定 &lt;code&gt;IdentityFile&lt;/code&gt;，并不会把整个转发 agent 过滤成只有那一把钥匙。登录时选什么身份，和远端能向 agent 请求什么，是两个不同层次的问题。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;下面使用一次性配置文件，不建立网络连接，也不读取私钥；示例域名仅作为配置匹配对象：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Host *\n ForwardAgent no\n&amp;#39;&lt;/span&gt; &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -G -F &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; target.invalid &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s1"&gt;&amp;#39;^forwardagent &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -G -F &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -A target.invalid &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s1"&gt;&amp;#39;^forwardagent &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Host *\n ForwardAgent yes\n&amp;#39;&lt;/span&gt; &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -G -F &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -J bastion.invalid target.invalid &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s1"&gt;&amp;#39;^forwardagent &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -G -F &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -a -J bastion.invalid target.invalid &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s1"&gt;&amp;#39;^forwardagent &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在 OpenSSH 10.0p2 上实际观察到的顺序是 no、yes、yes、no。这验证了选项处理，不验证套接字权限、签名操作、目标约束，也没有建立真实的多跳连接。检查自己的配置时，可以先查看 &lt;code&gt;ssh -G destination&lt;/code&gt; 和各跳板别名的结果，再决定是否连接。每一个 yes 都应该能回答两个问题：为什么需要转发，以及接收方为什么值得信任。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man.openbsd.org/ssh.1"&gt;OpenBSD：ssh&lt;/a&gt;，命令行转发开关、跳板连接与配置检查，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man.openbsd.org/ssh_config.5"&gt;OpenBSD：ssh_config&lt;/a&gt;，ForwardAgent、ProxyJump 与配置优先级，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man.openbsd.org/ssh-agent.1"&gt;OpenBSD：ssh-agent&lt;/a&gt;，agent 套接字与无需传输私钥的认证方式，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man.openbsd.org/ssh-add.1"&gt;OpenBSD：ssh-add&lt;/a&gt;，askpass 确认、存活期与目标约束的支持条件，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man.openbsd.org/sshd_config.5"&gt;OpenBSD：sshd_config&lt;/a&gt;，AllowAgentForwarding 与 shell 访问下的限制，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>