Repository navigation
feat(remote-ssh): support none authentication for pre-authenticated tunnels - #3659
Conversation
…unnels Coder workspace agents accept only the SSH `none` method: `coder ssh --stdio` authenticates the tunnel with the CLI token, so the agent disconnects any client that sends publickey or password. OpenSSH only connects because its first `none` probe succeeds before a key is offered, which also means "any key works" is an illusion. OpenBitFun had no way to select `none`, so these endpoints could not be connected at all. - add SSHAuthMethod::None and SavedAuthType::None with the authenticate_none branch, vault handling, and saved-profile round trip - mirror the variant in SavedAuthTypeRecord so migrated profiles survive - add a "No authentication" option with an explanatory hint to the SSH connection dialog and keep it on the auto-reconnect path - cover it with a Rust round-trip test and two Web UI tests
bobleer
left a comment
There was a problem hiding this comment.
这项功能有实际用途,但当前持久化和跨版本边界需要先补齐,暂不合入。
- P1:新增
SavedAuthType::None会让旧版本整份ssh_connections.json反序列化失败。 旧读取器一次读取Vec<SavedConnection>,写入器则用内存列表覆盖整个文件。因此“读取失败时不改文件”不足以保证安全:旧版本以空列表启动后,用户新增或保存一个连接就可能覆盖原来的全部 profiles。请提供兼容旧读取器的存储/迁移策略,并覆盖混合旧 profile + 新认证方式、旧端读取后保存、再次升级的 round-trip;不能仅增加新代码自读自写的测试,也不能仅靠本版本的 tolerant reader 修复已发布旧版本。 - P1:Peer 模式还缺目标能力协商。 新 UI 会直接把
{auth:{type:"None"}}发给旧 Host,而旧 Host 不认识该枚举。请在目标端广告 typed capability,控制端在连接、保存和自动恢复前检查,并给出明确 unsupported 状态,保留已有记录。
依据:本 PR 的 Reviewer Notes 已确认旧端整表解析失败;当前 load_saved_connections / save_connections 的整表读取和覆盖写入路径也支持上述风险。认证行为本身不应回退为密码或 Agent,以免违背这个特性的安全意图。
… peers Store None profiles in ssh_connections_extended.json so released builds keep parsing and rewriting ssh_connections.json without losing anything, gate None requests on a negotiated ssh_none_auth_v1 peer capability, and stop rejected None profiles from falling back to a credentials prompt.
|
Pushed the changes needed for the two P1s from the earlier review (plus a merge of Upgrade compatibility. Peer negotiation. Added the Fallback bug found in review. A rejected Docs updated: Remaining limits: an older controller talking to a newer host cannot be gated and will show a Exercised: unit and component tests for the local path and for peer gating at the adapter. Not exercised live: Remote Workspace to a real |
…e-auth Keep SshProxyCommandV1 and SshNoneAuthV1 and regenerate the capability artifacts.
Auto-reconnect and profile edits rebuilt the config without proxyCommand, so a Coder profile would dial the alias directly. Cover the full shape with a none-only server reached only through a ProxyCommand process.
|
Merged ProxyCommand plus Fixed one gap: auto-reconnect and profile edits rebuilt the config without Still open: the Web UI has no way to create a ProxyCommand profile. The dialog and |
…e-auth Desktop capabilities are now an explicit list; add SshNoneAuthV1 and the missing SshProxyCommandV1 so Desktop keeps advertising both.
…e-auth Keep SshNoneAuthV1 next to SshPortScopedIdentityV1 in the Desktop list and both peer gates in the transport adapter.
…ofiles Loading now shadows an extended profile by an equal id or a legacy :port alias of the same endpoint, matching save_connection. Cover port-scoped ids for None profiles across both stores.
|
Merged |
bobleer
left a comment
There was a problem hiding this comment.
Both P1s are covered. (1) None profiles are stored in ssh_connections_extended.json, so ssh_connections.json stays readable by released builds; tests cover a mixed profile, an old-side save, a re-upgrade with a frozen copy of the released parser, and dedup with the new #port ids and legacy :port aliases. (2) The Desktop-only ssh_none_auth_v1 capability is advertised and checked by the controller before connect, save and auto-reconnect, with an explicit unsupported state, and a rejected None profile never falls back to a password or agent.
Regenerate the remote surface registry artifacts after GCWing#3659.
Summary
Add a
noneSSH authentication method so endpoints that only accept the SSHnonemethod can be connected.The motivating endpoint is a Coder workspace:
coder ssh --stdioauthenticates the tunnel with the CLI token, so the workspace agent's sshd trusts the transport and advertises onlynone. It disconnects any client that sendspublickeyorpasswordinstead. OpenSSH appears to work only because its firstnoneprobe succeeds before a key is ever offered — which is also why "every key works" against such an agent is an illusion. OpenBitFun had no way to choosenone, so those workspaces failed during authentication with an opaqueearly eof.Fixes: no tracking issue; behaviour was reproduced from a live workspace (see Verification).
Type and Areas
Type: Feature
Areas: Rust core (
services-integrationsremote SSH + persistence records), Web UI (remote SSH connection dialog)Motivation / Impact
SSHAuthMethod::None/SavedAuthType::Noneare wired through the authenticate branch (authenticate_none), password-vault handling, saved-profile save and restore, and auto-reconnect.SavedAuthTypeRecord::Nonekeeps the legacy/migration record schema in step with the runtime enum.en-US,zh-CN, andzh-TW.noneprofiles do not create or keep a password vault entry, and never prompt for credentials.User impact: Coder-style workspaces (and any other connection-switched tunnel) become connectable without a local relay or a separate sshd.
Verification
cargo check -p openbitfun-services-integrations --features remote-ssh— passedcargo test -p openbitfun-services-integrations --features remote-ssh-concrete— passed (151 lib tests + 15 contract tests, including the newsaved_none_auth_profile_round_trips_without_credentials)pnpm --dir src/web-ui exec vitest run src/features/ssh-remote src/infrastructure/i18n— 14 files / 158 tests passed (includes the new dialog test and the new provider reconnect test)pnpm --dir src/web-ui exec eslint <changed web-ui files>— passedpnpm run i18n:audit— passed, 0 warningspnpm run fmt:rs— applied to the changed Rust filespnpm --dir src/web-ui exec tsc --noEmit— 17 errors, all in files this PR does not touch. This checkout runs from a worktree that reuses another worktree's prebuilt design-system packages, so errors reference props that exist in this branch's design-system source but not in the older prebuilt dist (for exampleSelect.showGroupLabels,chart-spline). Nossh-remotefile reports an error.pnpm run check:web— blocked before type-check by generated-artifact staleness unrelated to this change (miniapp-chat-styles,miniapp-chrome-theme,data-tools-design-system). This is the same local blocker recorded for previous work in this checkout; the audit suites that do run (typography, appearance contract) pass.Endpoint behaviour was verified independently of this build: with a plain TCP bridge to
coder ssh --stdio,paramiko.Transport.auth_none(...)succeeds,ssh -o PubkeyAuthentication=no -o PreferredAuthentications=noneestablishes a full session (exec, sftp, PTY), while both paramiko and russhpublickeyattempts are dropped by the agent right after key exchange. The new OpenBitFun code path itself was not exercised against a live Coder agent from a local build.Reviewer Notes
{"type":"None"}inssh_connections.json, so it logs a parse failure and starts with no saved connections. The file is not rewritten or deleted on that path, so re-upgrading restores every profile. This matches howKeyboardInteractivewas introduced; say if you would rather make per-entry parsing tolerant first.ProxyJumphops are unchanged: when the target usesnone, a bastion without its ownIdentityFilestill falls back to the existing agent/key path, because the hop's own transport is a normal SSH endpoint.ssh-connection-dialog__hintclass.Checklist
Generated with OpenBitFun