SSH 主机密钥验证(STA-4319)¶
在安全与迁移评审后修订。初稿中错误的地方,修正内容保持可见,而不是悄悄删改——对于之后修改此文档的人来说,推理过程很重要。
缺陷¶
src/main/ssh/ssh-connection.ts:1184 安装了一个 hostVerifier,它记录 SHA-256 指纹,然后 return true。每个 ssh2 连接都会接受任意主机密钥。src/main/ssh/ 中没有任何 known_hosts 查询、信任记录或变更检测。ssh2 Client 构造点只有一个,因此修复只有一个关键瓶颈。
范围是按连接,而不是按功能:每个目标一个 SshConnection,服务于 exec、SFTP、端口转发、文件系统监视器和 relay 部署。
威胁模型(修正)¶
流量仍然加密,因此被动观察者什么都得不到。暴露面是能够重定向连接的主动攻击者——ARP/DNS 欺骗、恶意 Wi-Fi、被劫持的内部名称。
对初稿的三处修正:
- 跳板主机并不是最坏情况;它们已经安全。
shouldUseSystemSshTransport(ssh-transport-selection.ts:71-91) 在resolveEffectiveProxy(ssh-proxy-command.ts:17-38) 返回代理的完全相同条件下返回 true——两者基于相同输入、按相同顺序分支——并且attemptConnect在系统探测后无条件返回 (ssh-connection.ts:670-673)。因此 ProxyJump/ProxyCommand 通过 OpenSSH 处理,并且已经经过验证。:697处的 ssh2 代理启动实际上不可达。这对迁移是好消息,初稿的动机示例本身就是错的。 - Agent 转发被夸大了。
agentForward受用户的ForwardAgent yes(ssh-connection-utils.ts:203-205) 控制。config.agent总是被设置,但那是 agent auth,其签名绑定会话 ID,无法继续重放。风险适用于选择启用ForwardAgent的用户,而不是所有人。 - 凭据窃取被低估了,并且 relay 的说法反了。
isAgentFallbackError将 任何 认证错误都视为 agent 回退 (ssh-connection-utils.ts:59-61),因此拒绝 publickey 的 MITM 会把用户引导到密码提示 (ssh-connection.ts:844) 和私钥口令提示 (:834),并且cachedPassword在每次重连时都会无提示地重放 (:709)。与此同时,relay 上传的重要性不如假设的——攻击者已经拥有他们的机器。真正的客户端影响在返回方向:攻击者成为我们工作区信任的主机,驱动 relay 协议帧,将 SFTP 内容落到本地 worktrees,并注入 agent-hook 载荷。
决策¶
D1. 读取用户的 known_hosts;仅写入我们自己的存储¶
将用户真实的 known_hosts 作为信任来源查询——大多数开发者已经通过 ssh 和 git 在那里有了他们的主机,这就是整个迁移故事。不要写入它:该文件与机器上的每个其他 SSH 工具共享,追加会带来换行符、权限、并发写入者,以及远超我们范围的损坏影响范围。
需要主动承担而不是事后发现的两个后果:
- 吊销不会传播到我们的存储。
ssh-keygen -R host会清除known_hosts,但不会清除我们的记录。对于普通轮换,这是可承受的,因为known_hosts匹配现在先于我们存储的不匹配被判定——运行我们打印的补救措施并重连即可工作,而当存储先被查询时它并不工作。它无法治愈的是我们只自己知道、从未写入known_hosts的主机:对那一个没有ssh-keygen -R,因此拒绝会直接命名存储文件。“forget” 操作(D5)将其替换为一个按钮;在此之前,其辅助函数故意缺失,因为一个没有任何调用方能够触达的导出 API 在生产环境中未经验证。不匹配消息必须继续指明哪个来源不一致。 ssh -G在 HOME 不一致的-F路径上会抑制/etc/ssh/ssh_config(ssh-g-config-resolution.ts:44-52),隐藏站点范围的StrictHostKeyChecking yes和GlobalKnownHostsFile。在该路径上,我们必须以严格方式失败,绝不能比ssh更宽松。
没有仅靠 ssh 的出路:-F /dev/null 不会反转排除,它报告内置默认值,因此基于它的探测在每台机器上看起来都是宽松的。已针对 OpenSSH 10.2p1 验证。因此直接读取该文件,回答一个刻意较弱的问题——站点配置_是否可能_在限制主机密钥——其中任何模糊情况(不可读、无法解析的
Include、指令是否存在)都保持拒绝。只有明确表明对主机密钥没有任何说法的站点配置才能清除它,这正是阻止该规则惩罚每个 devcontainer、
su shell 和 Nix shell 的地方。
D2. 询问 ssh -G,不要重新实现配置解析¶
ssh -G 报告 userknownhostsfile、globalknownhostsfile、stricthostkeychecking、
checkhostip、hostkeyalgorithms、fingerprinthash、hashknownhosts、updatehostkeys 和
hostkeyalias——并且已经应用了 Match 和 Include。resolveWithSshG 存在,只是
尚未读取它们。
userknownhostsfile 是一行中以空格分隔的列表,可能包含 ~,并且可能包含带空格的
双引号路径。当 ssh -G 不可用(没有 ssh、非零退出、>5s 超时)时,
回退到 ~/.ssh/known_hosts + known_hosts2——绝不回退到接受。
必须遵守 HostKeyAlias:通过 localhost:port 隧道化堡垒机的用户依赖它,否则会遇到虚假的不匹配。它目前未出现在 src/main/ssh/ 中的任何地方。
查找键。 配置解析使用 configHost || label (ssh-connection.ts:660),而 ssh2
拨号 effectiveHost (ssh-connection-utils.ts:188)。known_hosts 查找必须使用
HostKeyAlias(如果已设置),否则使用解析后的主机名——以 Orca 标签作为键会错过所有
现有条目。
两次有序的查找遍历,而不是一个候选集。 针对 OpenSSH 10.2p1 验证:非默认端口会先查找 [host]:port,如果没找到,再重试裸主机。关键在于,在第二次遍历中,错误的密钥会被降级为 unknown,而不是被报告为已更改。因此遍历是 [['[host]:port'], ['host']],回退遍历只能产生 match 或 unknown。把它们合并成一个集合,会给任何拥有裸行并通过非默认端口连接的人一个虚假的首次接触提示;把回退视为权威则会引发虚假的密钥变更警报。
进入第二次遍历的条件才是会咬人的部分。 ssh 只在带端口限定的查找没有匹配到任何密钥类型的普通条目时才运行它——而不是“没有匹配”。如果以“没有匹配且没有同类型不匹配”作为门控,当存在另一个类型的非默认端口条目时,就会到达裸行,并在 ssh 打印 IDENTIFICATION HAS CHANGED 的地方返回 match:这是一条接受已更改密钥的路径,已在实际中复现。并且每次遍历的观察结果不得泄漏到另一次遍历,否则仅在回退中找到的条目会拒绝 ssh 作为首次接触接受的主机。
HostKeyAlias 会完全抑制端口。 ssh 以裸形式查找别名,并且从不给它加括号,因此无论端口如何,别名都只经过一次遍历。结合上述规则,一条过期的 [alias]:port 行否则会阻止 ssh 实际执行的裸查找——这将把该功能引用 HostKeyAlias 的堡垒机场景变成硬性失败。
哈希条目对候选形式进行哈希,而不是对裸主机 —— 对于带括号的条目,被 HMAC 的是 [example.com]:2222,因此每个候选项都必须单独哈希。
多个文件取并集。 任何文件中的任何精确命中都会胜出;另一个文件中的不一致条目不会使其成为不匹配。已在两种顺序下实际确认。
密钥等于所呈现的普通主机密钥的 @cert-authority 行不算匹配 —— CA 行只验证证书。与其并存的普通行仍然会做出决定。但 ssh 对由 CA 覆盖的主机呈现普通密钥的判定是 HOST_NEW,而不是失败:它会连接。参见 D4。
D3. 六种结果,且类型限定只有在算法排序下才安全¶
match | mismatch | revoked | ca-only | unknown-type-known-host | unknown.
不匹配限定于同一密钥类型:一个只有 RSA 条目却呈现 ed25519 的主机不算“已更改”。如果没有限定,我们几乎会在每个 RSA 时代用户首次升级连接时误报,训练他们忽略唯一重要的警告。
针对实际客户端进行了更正。 上述前提关于 OpenSSH 是错误的,尽管结论仍然成立。
check_key_in_hostkeys完全没有按类型限定:对于该主机,任何非标记条目只要不是字节相等,就会产生HOST_CHANGED。已在 127.0.0.1:2223 上验证——known_hosts只保存ssh-rsa,而服务器只有 ed25519 时,会打印IDENTIFICATION HAS CHANGED并拒绝。因此 ssh 并不是通过限定来避免误报;它通过order_hostkeyalgs避免该_情况_,并且当情况出现时仍然会硬性失败。我们将结果拆分为mismatch和unknown-type-known-host,因此只是选择措辞——两者都拒绝,这正是 ssh 的行为。下面排序为我们带来的,正是它为 ssh 带来的:这种情况大多不会出现。
但仅有限定是一个降级向量,而这是最改变设计的更正。 OpenSSH 在此处安全,只是因为 order_hostkeyalgs() 会重新排序客户端提议的主机密钥算法,把 known_hosts 中已有的类型放在前面,并且 RFC 4253 赋予_客户端_顺序优先权——因此服务器无法选择客户端已降低优先级的类型。无论如何,ssh2 都会先协商 ed25519。一个无法伪造已存档 RSA 密钥的攻击者只需呈现 ed25519,就会收到友好的首次接触提示,而不是硬性失败。
因此:将 ssh2 的 algorithms.serverHostKey 设置为以该主机已知密钥类型开头。 没有算法排序的类型限定不是安全设计。
而当所呈现的类型未知,_同时该主机已知其他类型_时,那就是 unknown-type-known-host —— 绝不是一次普通的 TOFU 提示。它必须说明我们已经持有该主机的另一个密钥。
D4. 结果¶
- match → 静默连接。
- unknown → 首次使用即信任(见下文分阶段说明,判断它是静默还是提示)。
- mismatch → 硬性失败,失败界面中没有覆盖选项。
- revoked → 始终硬性失败。
- ca-only → ~~硬性失败~~ 已反转:视为首次接触。 见下文。
- unknown-type-known-host → 视为可疑,而不是首次接触。
StrictHostKeyChecking 会被遵守:no/off 接受未知,但从不持久化,并且仍然对已更改和已吊销项硬性失败;accept-new 静默持久化;yes 拒绝未知。
ssh -G不会报告用户所写的拼写。 StrictHostKeyChecking 通过fmt_multistate_int渲染,它打印multistate_strict_hostkey的第一个条目,而该表在 yes/no 之前列出 true/false。因此yes到达时是true,no和off都是false;只有ask和accept-new原样通过。匹配yes/no/off不会匹配任何真实配置能产生的值。UpdateHostKeys具有相同形状(true,而不是yes)。
ca-only,评审后反转。 拒绝比 ssh 更严格,并且影响范围被低估了。一个 SSH CA 用户只持有一行——非常常见的是 @cert-authority *——它匹配每个候选项,因此每个目标都失败,而不仅仅是 CA 签名的目标,包括按需运行时 VM,并且 StrictHostKeyChecking=no 没有帮助。ORCA_SSH_FORCE_SYSTEM_TRANSPORT=1 从进程环境中读取,而从 Dock 或开始菜单启动的 Electron 应用没有该环境,因此文档中记载的逃生路径对最需要它的人完全不可达。并且 OpenSSH 自己对由 CA 覆盖的主机呈现普通密钥的判定是 HOST_NEW:它会连接。ssh2 根本无法验证证书,因此拒绝并没有让 ssh 做出它尚未做出的让步。
残余风险被接受,而非解决:对于受 CA 保护的主机,我们接受一个无法关联到 CA 的普通密钥。证书支持属于第二阶段工作。ca-only 结果仍会生成并贯穿决策,以便日志显示涉及了 CA 行。
无法读取的 known_hosts 会连接但不记录任何内容。 一个存在但无法打开的文件是证据缺失,常见触发因素并不罕见——离线时 Windows OneDrive 已知文件夹迁移占位符会以云文件错误失败,而不是 ENOENT。在那里拒绝会破坏普通企业笔记本,同时归咎于一个配置正常的文件,并且与我们自己的存储不对称:我们的存储会降级为“无可信项”并连接。ssh 会警告并将主机视为未知;我们也这样做——但我们不写入记录,因此我们无法检查的首次接触永远不会成为持久信任。文件缺失不属于这种情况:那是全新配置文件的正常状态,真正意味着没有任何已知信息。
D5. 恢复操作不得放在失败对话框中¶
不匹配对话框_中_的“忘记此主机密钥”按钮是 D4 已拒绝的“仍然信任”多一次点击。恢复操作位于目标设置中:一个独立且刻意的界面,无自动重试,并显示已存储的指纹,让用户知情选择。
仅当我们的存储是分歧方时提供;当 known_hosts 分歧时,忘记我们的记录无法解除连接阻塞。消息,措辞避免提及内部实现:
我们的记录不一致 — “
build-01的主机密钥自你上次从 Orca 连接以来已更改。如果你重建或重新配置了这台机器,这是预期行为。” → 忘记已保存的密钥 / 取消
known_hosts不一致 — “build-01的主机密钥与~/.ssh/known_hosts中的条目不匹配。ssh和git也会拒绝此主机。请运行ssh-keygen -R build-01。” → 无按钮,因为按钮无济于事。
D6. 后台重连时绝不弹出提示¶
只有当人类发起连接时,提示才有意义。目前 userInitiated 不存在于连接路径中,必须贯穿 connect → attemptConnect → doSsh2Connect,默认 false。
两个陷阱:useAutomationDispatchEvents.ts:203 和 pty-connection.ts:857 在没有人类点击的情况下到达 ssh:connect——自动化必须传递 false,但终端窗格焦点重连必须算作用户发起,否则终端会静默死亡。并且拒绝字符串必须避免 “认证失败”/“权限被拒绝”,否则 isAgentFallbackError/isAuthError(ssh-connection-utils.ts:46-61)会错误分类,重连阶梯会重试一个永远不会改变的决策。
D7. 失败关闭——三种已知的失败开放形态¶
- 现有位于
:1185的 generation/disposed 守卫目前具有失败开放形态:跳过记录,仍然return true。修复后该分支必须拒绝。 - 验证器内部的同步抛出可能不会被 ssh2 捕获——需包装并
verify(false)。 - 任何非
undefined返回值都会立即接受(见陷阱)。
另外:未注册提示通道 → 拒绝(关键默认值位于 doSsh2Connect,而非 IPC,因此忘记接线的调用方不会意外接受);无窗口 → 拒绝;超时 → 拒绝;对话框被取消 → 拒绝。
D8. 存储结构与范围¶
已接受的密钥按 主机 + 端口 + 密钥类型 限定范围,而不是 target id——别名指向不同机器,两个目标可以命名同一台机器,重新创建的目标不得丢失信任。
存储是一个专用文件,而不是主持久化 blob(persistence.ts:7088):设置恢复或回滚不得静默重置信任。接受事件和不匹配事件会被记录。
hostKeyFingerprint 现在既与安全相关,也与线路相关——它是发送到主机的隔离命名空间(ssh-relay-session.ts:1298、managed-hook-owner-identity.ts:187)。在系统传输上它是 undefined,因此任何信任逻辑都不得以它作为键,并且其格式不得更改(见陷阱)。
分阶段——先交付防御再交付对话框¶
评审认为,此次变更风险最高的部分不是安全模型,而是模态框。启动恢复会为_所有_先前处于活动状态的目标并行触发急切连接(App.tsx:1041),超时为 15 秒,而提示会持续 120 秒——N 个未知主机意味着 N 个堆叠的对话框,其存活时间超过已经推迟它们的超时。运行时拥有的临时 VM(ephemeral-vm-runtime-ssh.ts:31)每次启动都会连接到一个全新配置的主机,并使用全新密钥。配对 Web 连接运行在_主机桌面_上(runtime/rpc/methods/ssh.ts:32),因此对话框会在别人的屏幕上打开,而 Web 用户只能看着加载动画。
第一阶段——无新模态框。 查询 known_hosts + 我们的存储。match 连接。unknown 以 accept-new 语义静默持久化,并附带一个被动通知,指明主机和指纹。mismatch(同类型)和 revoked 硬性失败。这就是完整的 MITM 防御,零提示、零启动风暴、零 Web 挂起。
第二阶段——TOFU 对话框、StrictHostKeyChecking 遵循、ca-only、userInitiated 管道,以及 D5 设置界面。
第一阶段发布前所需的例外:
- 运行时拥有的临时目标豁免持久化——每次启动出现新密钥是预期行为,而非可疑行为,记录它会导致每次启动累积一行,最终看起来像虚假变更。通过
target.owner?.type === 'on-demand-runtime'实现。 - RPC 发起的连接:第一阶段不需要,第二阶段必需。 评审要求这些连接快速失败,而不是让配对 Web 用户盯着加载动画等待 120 秒提示超时。该挂起只有在提示存在时才可达,而第一阶段没有提示——决策函数由一个断言其永不返回
prompt的测试固定。因此 RPC 连接的行为与本地连接完全一致:首次接触时接受并记录,或立即以主机密钥原因失败。现在添加快速失败路径会为一种不可能发生的挂起引入失败模式。一旦对话框落地,它就成了关键路径,并列入第二阶段。
需要注意 Phase 2 的一点:runtime/rpc/methods/ssh.ts 已经吞掉了具体错误,并重新抛出 getPublicSshError(status),因此 Web 客户端看到的是通用失败信息,而不是主机密钥原因。这是既有行为,但意味着如果不改那里,Phase 2 的消息将无法到达 Web 用户。
陷阱¶
以下每一项都会让修复悄悄失效。全部在我们的代码树中确认过。
async验证器会使其完全失效。 ssh2 执行const ret = hashCb(key, verify); if (ret !== undefined) verify(ret)。async 函数返回的是 Promise——不是undefined,且为真值——所以 ssh2 在我们的回调完成之前就接受了。- 不要设置 ssh2 的
hostHash。 它会向回调传递十六进制摘要,并丢弃我们必须比较的原始 blob——而且它会改变hostKeyFingerprint的格式,这是跨版本状态破坏,不是本地重构。 - 现有测试 mock 以单参数调用
hostVerifier(key)并忽略返回值(ssh-connection.test.ts:86-91)。在 async 验证器下,那里的每个连接测试都会失败。mock 必须更改——有意标注,而非悄悄重写。 - 验证 blob:内嵌算法名称必须与该行的密钥类型字段匹配;拒绝空解码、空盐,以及哈希不是 20 字节的散列条目。
ssh-relay-live-connect.test.ts:59构造了一个没有凭据回调的连接——无提示通道的无头模式必须拒绝,而不是挂起。
范围¶
在范围内,已修正: IPv6 字面量和 [host]:port 方括号解析。评审是对的,这是一个_解析器_需求,而不是范围决策——搞错了就意味着 ssh 认得的主机返回 unknown,这正是 D3 存在以规避的提示训练危害。
超出范围,但后果已说明:
CheckHostIP— OpenSSH 默认关闭;我们仅从主机名构建候选。- WSL —
src/main/ssh/没有 WSL 感知;发行版的known_hosts不可达,因此 WSL 用户对他们已验证过的主机会遇到首次接触处理。 UpdateHostKeys— 我们读取它但未使用,因此永远学不到轮换密钥,这使得 D5 成为密钥轮换的常规路径而非例外。- 将 SFTP 迁移到系统传输层 — 方向正确,但需单独变更。
测试计划¶
解析器(针对文件格式,而非我们代码的形态):普通行、host,host2 列表、仅当端口 ≠ 22 时使用 [host]:port、IPv6 字面量、带真实可计算向量的散列 |1|salt|hash、@revoked、@cert-authority、*/? 通配符、! 否定否决整行、无法识别的 @marker 跳过该行、格式错误的行跳过而非致命错误、每主机多密钥、CRLF、空行、注释、用户文件和全局文件不一致。
决策函数:全部六种结果;类型作用域;无论行顺序如何,撤销在匹配之前解决;每一个 StrictHostKeyChecking 值;no/off 永不持久化。
算法排序:algorithms.serverHostKey 以文件中的类型为先——这个测试使 D3 安全而不只是有范围。
接线:未知项持久化(Phase 1)且无提示;匹配永不通知;不匹配失败且无接受路径;撤销失败;后台重连拒绝;中止的连接将待定的 verify 结算为 false;无提示通道拒绝;运行时拥有的目标豁免;拒绝字符串不匹配 isAuthError;以及——抓住最严重的回归——验证器不返回任何内容,因此重构为 async 会使测试变红,而非波及用户。
对照真实客户端验证,而不仅仅是文件格式。 设计依赖的两个假设通过运行 OpenSSH 10.2p1 客户端对 127.0.0.1:2222 上的真实 sshd 并记录其判定来验证:
- 裸主机回退传递从不报告变更。 使用
StrictHostKeyChecking=accept-new时,持有_不同_密钥的裸行在非默认端口上拨号,使 ssh 连接并追加新的[127.0.0.1]:2222行——首次接触,没有IDENTIFICATION HAS CHANGED。在此传递上报告mismatch会拒绝 ssh 乐于连接的主机,而且看起来像是谨慎的选择。 unknown-type-known-host是 ssh 自身的行为。 known_hosts 持有ssh-rsa而服务器提供 ed25519 时,ssh 会打印IDENTIFICATION HAS CHANGED并拒绝。因此该拒绝既不比 ssh 更严也不比 ssh 更松——而将其视为首次接触(朴素的类型作用域查找会这样做)才是更松的错误。这也意味着ssh-keygen -R是在那里命名的正确补救措施。
Phase 1 交付了什么,评审又改变了什么¶
上述设计在实现中幸存。之后发现的每个缺陷都在接线中,这个模式值得记录,因为它会重复:每一个都让我们要么失明,要么不可用,从不微妙地出错。
评审后修复:
- 我们自己的存储可以被类型降级。 内联查找先按密钥类型过滤,只能回答匹配/不匹配/未知,因此_不同_类型的记录读作
unknown。D3 的降级,应用于我们自己创建的记录。存储的类型现在也参与算法排序——没有这个,守卫只存在一半。 - 我们以 Orca 标签为键。 当没有 Host 块匹配时,
ssh -G会将其自身参数回显为hostname,因此对于手动目标,resolved.hostname_就是_标签——即 D2 禁止的那个名字。我们完全没有查阅任何条目。 - 被拒绝的密钥仍然走完凭据阶梯。 ssh2 将拒绝报告为通用认证失败,因此我们继续提示输入口令并将其交给刚刚拒绝过的主机。拒绝现在是一个类型化错误,在任何回退之前被识别。
- 故障关闭几乎变成了永远故障。 "无
known_hosts可读"将_缺失_文件与不可读文件等同对待,因此从未连接过的配置文件——每个人的首次运行——会被拒绝,而套件通过只是因为开发机器有known_hosts。 - 临时运行时因无法满足的策略而被拒绝。 豁免位于不完整来源检查之下,因此 HOME 不一致的环境完全关闭了按需运行时。
第二轮评审针对运行中的 OpenSSH 客户端和 sshd 进行,而不是针对源代码,又发现了五个问题——并且模式一致:最重要的两个都是我们拒绝了 ssh 所连接的主机,而最严重的单个缺陷是 StrictHostKeyChecking 从未被正确读取,因此配置中写 yes 会被静默接受并持久化。参见上文 D2/D3/D4 的更正。值得记住的教训:这些问题对单元测试都不可见,因为单元测试向代码提供的是人类写入的值,而不是工具输出的值。
行动项(STA-4319)¶
消息实际显示的位置。 已端到端追踪,因为用户无法读取的拒绝是一种只交付了一半的功能。本分支已修复:设置卡片将其截断为一行且没有工具提示,终端重连覆盖层则完全没有请求它。仍未解决:
- “远程主机”状态栏只显示
Error。SshTargetStatusRow没有接收到该错误,因此状态栏在用户最可能注意到失败的地方成了死胡同。 - “再次连接”对于一个永远不会改变的决定来说是错误建议。 终端覆盖层现在会在下方打印原因,但其行动号召仍然邀请一个不可能成功的操作。要在渲染器中区分永久拒绝和临时故障,需要在传输层使用带类型的理由,而不是字符串——这是一个远程传输兼容性决策,因此被有意推迟。
- Toast 带有 Electron 的
Error invoking remote method 'ssh:connect':前缀。 仓库中正好有用于去除该前缀的工具;没有任何 SSH 调用点使用它。另外值得注意的是,sonner 会在 4 秒后自动消失,对于以一条用户需要复制的命令结尾的消息来说,这个时间太短。
第二阶段之前:
UpdateHostKeys(上述范围之外,现在是最有价值的缺口)。 我们读取了它但没有使用任何内容,因此轮换后的密钥会成为用户必须手动解决的硬性失败。与 D5 结合,这是密钥轮换的常规路径,也将是合法用户遇到拒绝的最常见方式。需要决定第二阶段是否遵循它,还是由 D5 的恢复界面吸收它。- Web 用户永远看不到原因。
runtime/rpc/methods/ssh.ts在全部三条路径上重新抛出getPublicSshError(status),并且推送事件通过getPublicSshState进行脱敏,因此配对 Web 客户端始终只看到SSH connection unavailable。这是既有问题,但它使得第二阶段对话框消息在没有改动的情况下无法在那里触达。注意脱敏并非仅限 Web:任何由配对运行时环境拥有的目标都会被脱敏,因此查看由远程 Orca 服务器拥有的主机的 桌面 用户也会得到同样的通用字符串。 - RPC 快速失败 在对话框存在的那一刻就成为关键依赖(参见分阶段)。
第一阶段接受的已知缺口,列出来是为了让它们是选择而不是意外:
- WSL — 发行版的
known_hosts无法访问,因此 WSL 用户对于已在发行版内通过ssh验证过的主机,会得到首次接触的处理。 CheckHostIP— 候选项仅从主机名生成。- 证书验证 仍然缺失——由 CA 覆盖的主机现在在首次接触时会被接受而不是拒绝(D4),因此这些用户可以连接,但 CA 本身并没有为我们验证任何东西。
DEFAULT_SERVER_HOST_KEY_ALGORITHMS是 ssh2 内部值的手工副本。一个测试将其固定,因此改变它的 ssh2 升级会导致 CI 失败而不是发布——但这个固定必须被遵守,而不是删除,因为 ssh2 会抛出Unsupported algorithm,并且每个目标都会停止连接。
发布: 首个包含此功能的版本是 Orca 第一次能够拒绝 SSH 连接。值得进行分阶段发布或设置紧急开关:我们无法发现的故障模式,根据上述五个问题的形态,更可能是“合法主机被拒绝”,而不是“坏密钥被接受”。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 orca48/orca