fix(wsl): keep the desktop pairing across host restarts (#29042)

* fix(wsl): retain the paired desktop identity across host restarts

* refactor(wsl): resolve enrolled pairing in the identity module

* test(wsl): supply the required host readiness state

* fix(wsl): resolve registered pairings through the saved authority
This commit is contained in:
Brennan Benson
2026-10-19 18:27:48 -07:00
committed by GitHub
parent af6fe23623
commit 65d671ecc9
8 changed files with 125 additions and 30 deletions
@@ -14,6 +14,13 @@ import { OrcaRuntimeService } from '../../runtime/orca-runtime'
import { OrcaRuntimeRpcServer } from '../../runtime/runtime-rpc'
import { verifyRuntimePairingIdentity } from '../../runtime/runtime-environment-identity-verification'
import { verifyManagedOrcadTunnelIdentity } from './orcad-managed-tunnel-identity'
import { getPreferredPairingOffer } from '../../../shared/runtime-environments'
import {
addWslHostEnvironment,
refreshWslHostPairing
} from '../../../shared/runtime-environment-wsl-host-store'
import { buildServePairingReadiness } from '../../server/serve-pairing-readiness'
import { WslOrcadConnectionManager } from '../../wsl/wsl-orcad-connection'
vi.mock('../../git/worktree', () => ({
listWorktrees: vi.fn().mockResolvedValue([]),
@@ -84,6 +91,61 @@ function reachedThrough(pairing: PairingOffer, server: OrcaRuntimeRpcServer): Pa
}
describe('reopening a managed tunnel after orcad restarted elsewhere', () => {
it('keeps the WSL caller across host and desktop restarts, until explicit re-pairing', async () => {
const hostProfile = mkdtempSync(join(tmpdir(), 'orcad-wsl-host-'))
const desktopProfile = mkdtempSync(join(tmpdir(), 'orcad-wsl-client-'))
let server = await startOrcad(hostProfile)
const connect = async () => {
const manager = new WslOrcadConnectionManager({
getPairingOffer: () => {
const [saved] = listEnvironments(desktopProfile)
return saved ? getPreferredPairingOffer(saved) : null
},
deployAndLaunch: async () => ({
outcome: 'ready', distro: 'Ubuntu', distroId: null, fullVersion: '1', pid: null,
holder: 'adopted', port: Number(new URL(server.getWebSocketEndpoint()!).port),
readiness: { runtimeId: server['runtime'].getRuntimeId(),
managedWslCliReconciliation: 'settled',
...await buildServePairingReadiness({ pairingAddress: '127.0.0.1' }, server) }
}),
listen: async (_transport, { remotePort }) => ({
entry: { id: 'bridge', connectionId: 'wsl:ubuntu', localPort: remotePort,
remoteHost: '127.0.0.1', remotePort },
close: async () => {}, dispose: () => {}
})
})
const dispatch = vi.spyOn(server['dispatcher'], 'dispatchStreaming')
try {
const connection = await manager.ensure('Ubuntu')
return { connection, caller: dispatch.mock.calls.at(-1)?.[2]?.authenticatedCallerFingerprint }
} finally {
dispatch.mockRestore()
await manager.dispose()
}
}
const first = await connect()
expect(first.caller).toBeTruthy()
addWslHostEnvironment(desktopProfile, {
id: 'wsl:ubuntu', name: 'Ubuntu', pairingCode: first.connection.pairingOffer,
record: { kind: 'wsl', distro: 'Ubuntu',
localPort: first.connection.localPort, remotePort: first.connection.remotePort }
})
await server.stop()
server = await startOrcad(hostProfile)
const restarted = await connect()
expect(restarted.caller).toBe(first.caller)
const paired = parsePairingCode(first.connection.pairingOffer)!
server.getDeviceRegistry()!.removeDevice(paired.pairedDeviceId!)
await expect(connect()).rejects.toThrow('orcad_identity_mismatch')
refreshWslHostPairing(desktopProfile, 'wsl:ubuntu', {
pairingCode: encodePairingOffer(pairingOffer(server)),
localPort: restarted.connection.localPort, remotePort: restarted.connection.remotePort
})
const rePaired = await connect()
expect(rePaired.caller).toBeTruthy()
expect(rePaired.caller).not.toBe(first.caller)
})
it('accepts the same host under a new runtime id, and its status recovers', async () => {
const hostProfile = mkdtempSync(join(tmpdir(), 'orcad-host-'))
const first = await startOrcad(hostProfile)
+10 -4
View File
@@ -1,14 +1,20 @@
import { encodePairingOffer, parsePairingCode } from '../../shared/pairing'
import { encodePairingOffer, parsePairingCode, type PairingOffer } from '../../shared/pairing'
import { classifyRemotePairingHostname } from '../../shared/remote-pairing-address'
import type { ServeReadiness } from '../server/serve-readiness'
export function tunneledOrcadPairingCode(readiness: ServeReadiness, localPort: number): string {
if (!readiness.pairing.available) {
export function tunneledOrcadPairingCode(
readiness: ServeReadiness,
localPort: number,
pairedOffer?: PairingOffer | null
): string {
if (!pairedOffer && !readiness.pairing.available) {
throw new Error(
`The managed Orca server did not publish a pairing offer: ${readiness.pairing.guidance}`
)
}
const offer = parsePairingCode(readiness.pairing.url)
// An enrolled client keeps its grant; startup offers are for new clients.
const offer =
pairedOffer ?? (readiness.pairing.available ? parsePairingCode(readiness.pairing.url) : null)
if (!offer) {
throw new Error('The managed Orca server published an invalid pairing offer.')
}
+1 -1
View File
@@ -5,7 +5,7 @@ import type { WslHostStatus } from '../../shared/wsl-host-types'
import type { WslOrcadConnection } from './wsl-orcad-connection'
import type { WslOrcadConnectionStatus } from './wsl-orcad-connection-status'
/** Saves a relaunch's new device token or a moved bridge; true when the pairing changed. */
/** Saves an explicit re-pair or a moved bridge; true when the pairing changed. */
export function persistWslHostConnection(
userDataPath: string,
record: WslHostDeploymentRecord,
+6
View File
@@ -1,5 +1,7 @@
/** The production WSL side of `WslHostService`: one holder, connection manager and supervisor. */
import { isWslDistroRunning } from '../wsl-background-distro-gate'
import { resolveEnvironmentPairingOffer } from '../../shared/runtime-environment-store'
import { findWslHostDeployment } from '../../shared/runtime-environment-wsl-host-store'
import { createWslDistroHolder, type WslDistroHolder } from './wsl-distro-holder'
import { listWslHostDistros } from './wsl-host-distro-list'
import type { WslHostConnector } from './wsl-host-service'
@@ -54,6 +56,10 @@ export function createWslHostConnector(options: {
}
const manager = new WslOrcadConnectionManager({
holder,
getPairingOffer: (environmentId) =>
findWslHostDeployment(options.userDataPath, environmentId)
? resolveEnvironmentPairingOffer(options.userDataPath, environmentId)
: null,
onChange: options.onChange,
deployAndLaunch: (distro, deployOptions) =>
options.isKnownDistro(distro)
+3 -3
View File
@@ -180,14 +180,14 @@ describe('WslOrcadConnectionManager', () => {
expect(onChange).toHaveBeenCalledTimes(2)
})
it('reports a changed pairing offer without rebuilding the bridge', async () => {
it('keeps its client grant when a relaunch advertises a new pairing offer', async () => {
const { manager, launches, onChange } = setup()
const connection = await manager.ensure('Ubuntu')
launches.push(ready(first.port, 'rotated-token'))
const refreshed = await manager.ensure('Ubuntu')
expect(refreshed.localPort).toBe(connection.localPort)
expect(parsePairingCode(refreshed.pairingOffer)?.deviceToken).toBe('rotated-token')
expect(onChange).toHaveBeenLastCalledWith('Ubuntu', refreshed, { state: 'ready', census: null })
expect(refreshed.pairingOffer).toBe(connection.pairingOffer)
expect(onChange).toHaveBeenCalledTimes(2)
})
it('moves the bridge to the new port after a relaunch, on the same local port', async () => {
+19 -19
View File
@@ -5,13 +5,10 @@
* deployment record (C7) persists what `onChange` reports.
*/
import { errorMessage } from '../../shared/error-message'
import { parsePairingCode, type PairingOffer } from '../../shared/pairing'
import type { ServeReadiness } from '../server/serve-readiness'
import type { OrcadHostTransport } from '../ssh/orcad-host-transport'
import {
OrcadManagedIdentityError,
type OrcadTunnelIdentity
} from '../managed-server/lifecycle/orcad-managed-tunnel-identity'
import { tunneledOrcadPairingCode } from '../ssh/orcad-tunneled-pairing'
import type { OrcadTunnelIdentity } from '../managed-server/lifecycle/orcad-managed-tunnel-identity'
import type { StartedPortForward } from '../ssh/port-forwarding/ssh-port-forward-provider'
import type { WslDistroHolder } from './wsl-distro-holder'
import type { WslOrcadConnectionStatus } from './wsl-orcad-connection-status'
@@ -25,7 +22,7 @@ import {
} from './wsl-orcad-deploy'
import type { WslOrcadHostTarget } from './wsl-orcad-host-target'
import { createWslOrcadHostTransport } from './wsl-orcad-host-transport'
import { verifyWslOrcadIdentity } from './wsl-orcad-pairing-identity'
import { resolveWslOrcadPairing } from './wsl-orcad-pairing-identity'
import { trackWslOrcadHostEnvironment } from './wsl-orcad-host-environments'
import {
listenWslOrcadStdioBridge,
@@ -84,6 +81,7 @@ const READY: WslOrcadConnectionStatus = { state: 'ready', census: null }
export type WslOrcadConnectionDependencies = {
holder?: WslDistroHolder
getPairingOffer?: (environmentId: string) => PairingOffer | null
deployAndLaunch?: (distro: string, options: WslOrcadDeployOptions) => Promise<WslOrcadLaunch>
createTransport?: (target: WslOrcadHostTarget) => OrcadHostTransport
listen?: (
@@ -225,12 +223,23 @@ export class WslOrcadConnectionManager {
throw new WslOrcadConnectionSupersededError(distro)
}
const current = this.active.get(key)
const paired =
this.dependencies.getPairingOffer?.(environmentId) ??
(current?.connection.environmentId === environmentId
? parsePairingCode(current.connection.pairingOffer)
: null)
if (
current &&
current.connection.remotePort === launch.port &&
current.connection.environmentId === environmentId
) {
return this.refreshOffer(key, current, launch, options.status ? status.ready : null)
const pairingOffer = await resolveWslOrcadPairing(
launch, current.connection.localPort, paired, this.dependencies.verifyIdentity
)
if (this.generation(key) !== generation) {
throw new WslOrcadConnectionSupersededError(distro)
}
return this.refreshOffer(key, current, launch, pairingOffer, options.status ? status.ready : null)
}
// orcad moved ports (or this is the first connect): the old bridge dials a dead port.
const preferredLocalPort = current?.connection.localPort ?? options.localPort ?? 0
@@ -250,18 +259,9 @@ export class WslOrcadConnectionManager {
if (this.generation(key) !== generation) {
throw new WslOrcadConnectionSupersededError(distro)
}
const pairingOffer = tunneledOrcadPairingCode(launch.readiness, forward.entry.localPort)
const identity = await (this.dependencies.verifyIdentity ?? verifyWslOrcadIdentity)(
pairingOffer,
launch.readiness
const pairingOffer = await resolveWslOrcadPairing(
launch, forward.entry.localPort, paired, this.dependencies.verifyIdentity
)
if (identity.verdict === 'foreign') {
throw new OrcadManagedIdentityError(launch.port, identity.detail)
}
if (identity.verdict === 'unreachable') {
// Why not fail: a server that is still starting is not a wrong server.
console.warn(`[wsl] Orca server in ${distro} did not answer its bridge: ${identity.detail}`)
}
if (this.generation(key) !== generation) {
throw new WslOrcadConnectionSupersededError(distro)
}
@@ -293,10 +293,10 @@ export class WslOrcadConnectionManager {
key: string,
entry: ActiveWslOrcadConnection,
launch: Extract<WslOrcadLaunch, { outcome: 'ready' }>,
pairingOffer: string,
/** Reported even when nothing changed; null stays quiet then. */
announce: WslOrcadConnectionStatus | null
): WslOrcadConnection {
const pairingOffer = tunneledOrcadPairingCode(launch.readiness, entry.connection.localPort)
const deferredUpdate = launch.deferredUpdate ?? null
const changed =
pairingOffer !== entry.connection.pairingOffer ||
+22 -1
View File
@@ -1,10 +1,31 @@
import { pairedRuntimeConnectionFromOffer } from '../../shared/paired-runtime-connection'
import { parsePairingCode } from '../../shared/pairing'
import { parsePairingCode, type PairingOffer } from '../../shared/pairing'
import type { ServeReadiness } from '../server/serve-readiness'
import {
OrcadManagedIdentityError,
verifyOrcadPairingIdentity,
type OrcadTunnelIdentity
} from '../managed-server/lifecycle/orcad-managed-tunnel-identity'
import { tunneledOrcadPairingCode } from '../ssh/orcad-tunneled-pairing'
import type { WslOrcadLaunch } from './wsl-orcad-deploy'
/** Reconnect with the enrolled grant and host key; startup offers only enroll new clients. */
export async function resolveWslOrcadPairing(
launch: Extract<WslOrcadLaunch, { outcome: 'ready' }>,
localPort: number,
paired: PairingOffer | null,
verifyIdentity = verifyWslOrcadIdentity
): Promise<string> {
const pairingOffer = tunneledOrcadPairingCode(launch.readiness, localPort, paired)
const identity = await verifyIdentity(pairingOffer, launch.readiness)
if (identity.verdict === 'foreign') {
throw new OrcadManagedIdentityError(launch.port, identity.detail)
}
if (identity.verdict === 'unreachable') {
console.warn(`[wsl] Orca server in ${launch.distro} did not answer its bridge: ${identity.detail}`)
}
return pairingOffer
}
/** Whether the server behind a WSL bridge is the orcad that published `readiness`. */
export async function verifyWslOrcadIdentity(
@@ -106,8 +106,8 @@ export function addWslHostEnvironment(
}
/**
* Keeps a WSL host's pairing and ports current after orcad relaunched (a new device token) or
* the bridge moved. Returns whether the pairing changed, so callers retire stale transports.
* Saves an explicit re-pair or moved bridge. Server restarts retain the paired client grant.
* Returns whether the pairing changed, so callers retire stale transports.
*/
export function refreshWslHostPairing(
userDataPath: string,