Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,40 @@ test('defers pairing finalization when reconnect does not complete in time', asy
await manager.close();
});

test('bounds an in-flight pairing finalization and preserves its unknown outcome', async () => {
const local = candidateHarness({ hostId: 'host-a' });
const remote = candidateHarness({
hostId: 'a'.repeat(64),
finalizeFailures: [
new RuntimeHostRequestInterruptedError(
'access.credential.finalize',
'command',
'dispatched',
'timeout',
),
],
});
let starts = 0;
const manager = await startRuntimeHostDesktopManager(
{} as DesktopRuntimeHostCandidateStartInput,
{
startCandidate: async () => ready(starts++ === 0 ? local.candidate : remote.candidate),
pairingFinalizationTimeoutMs: 25,
},
);
await manager.enable(remoteTarget('office'));

await assert.rejects(
() => manager.finalizePairing('office'),
RuntimeHostPairingFinalizationInterruptedError,
);

assert.equal(remote.finalizeCalls, 1);
assert.equal(remote.finalizeTimeouts.length, 1);
assert.ok(remote.finalizeTimeouts[0]! > 0 && remote.finalizeTimeouts[0]! <= 25);
await manager.close();
});

test('coalesces concurrent enable requests for one remote profile', async () => {
const local = candidateHarness({ hostId: 'host-a' });
const remote = candidateHarness({ hostId: 'host-b', lifecycleMode: 'remote' });
Expand Down Expand Up @@ -777,6 +811,7 @@ function candidateHarness(
let lifecycleState: 'ready' | 'unavailable' = 'ready';
let prepareUpgradeCalls = 0;
let finalizeCalls = 0;
const finalizeTimeouts: number[] = [];
const prepareUpgradeAuthorities: boolean[] = [];
const candidate = {
closed,
Expand All @@ -802,8 +837,9 @@ function candidateHarness(
}
return { kind: 'prepared' as const, pid: 42 };
},
async finalizeAccessCredential() {
async finalizeAccessCredential(timeoutMs?: number) {
finalizeCalls += 1;
if (timeoutMs !== undefined) finalizeTimeouts.push(timeoutMs);
const failure = options.finalizeFailures?.shift();
if (failure) {
if (options.disconnectOnFinalizeFailure) {
Expand Down Expand Up @@ -854,6 +890,7 @@ function candidateHarness(
get finalizeCalls() {
return finalizeCalls;
},
finalizeTimeouts,
};
}

Expand Down
204 changes: 201 additions & 3 deletions apps/desktop/src/main/__tests__/runtime-host-management.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,171 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { runtimeHostAccessCredentialFingerprint } from '@maka/runtime-host/operator';
import { createDesktopRuntimeHostManagement } from '../runtime-host-management.js';
import type {
DesktopRuntimeHostSshAccessInput,
DesktopRuntimeHostSshCleanupInput,
DesktopRuntimeHostSshManagementInput,
} from '../runtime-host-ssh-terminal.js';

test('identifies, rotates, and revokes managed credentials without exposing secrets', async () => {
const handlers = new Map<string, (...args: unknown[]) => unknown>();
const profile = {
id: 'office',
name: 'Office',
kind: 'remote' as const,
rootId: 'a'.repeat(64),
transport: {
kind: 'ssh' as const,
destination: 'operator@example.com',
remotePort: 7443,
websocketPath: '/runtime-host',
},
};
const service = {
id: 'b'.repeat(64),
rootPath: '/srv/maka',
operatorPath: '/home/operator/.local/share/maka/operator',
};
const principalId = 'desktop:original-installation';
const replacement = 'maka_rh_replacement-secret';
let profileEnabled = true;
let prepareCalls = 0;
let currentFingerprint = runtimeHostAccessCredentialFingerprint('maka_rh_current-secret');
let credentials = [
accessCredential('current', principalId, currentFingerprint),
accessCredential(
'obsolete',
principalId,
runtimeHostAccessCredentialFingerprint('maka_rh_obsolete-secret'),
),
];

createDesktopRuntimeHostManagement({
ipcMain: {
handle: (channel, handler) => handlers.set(channel, handler as (...args: unknown[]) => unknown),
removeHandler: (channel) => handlers.delete(channel),
},
profiles: {
resolveManagedService: async () => ({ profile, service, state: 'active' as const }),
resolveManagedAccess: async () => ({
profile,
service,
state: 'active' as const,
credentialFingerprint: currentFingerprint,
enabled: profileEnabled,
}),
rotateManagedCredential: async (_profileId, credential) => {
assert.equal(credential, replacement);
currentFingerprint = runtimeHostAccessCredentialFingerprint(credential);
credentials = [accessCredential('replacement', principalId, currentFingerprint)];
},
markManagedServiceUninstalling: async (binding) => binding,
clearManagedServiceBinding: async () => undefined,
},
runServiceManagement: async () => assert.fail('service management is not expected'),
runAccessManagement: async (input: DesktopRuntimeHostSshAccessInput) => {
if (input.action === 'list') {
return { schemaVersion: 1, kind: 'result', action: 'list', credentials };
}
if (input.action === 'prepare') {
prepareCalls += 1;
assert.equal(input.currentCredentialFingerprint, currentFingerprint);
const pending = {
...accessCredential(
'replacement',
principalId,
runtimeHostAccessCredentialFingerprint(replacement),
),
status: 'pending' as const,
expiresAt: '2026-08-21T01:15:00.000Z',
};
return {
schemaVersion: 1,
kind: 'result',
action: 'prepare',
credential: replacement,
credentials: [...credentials, pending],
};
}
assert.equal(input.currentCredentialFingerprint, currentFingerprint);
const target = credentials.find(
(credential) => credential.credentialId === input.credentialId,
);
if (target?.credentialFingerprint === input.currentCredentialFingerprint) {
return {
schemaVersion: 1,
kind: 'error',
action: 'revoke',
error: {
code: 'credential_protected',
message: 'Rotate this Desktop credential instead of revoking it',
},
};
}
credentials = credentials.filter(
(credential) => credential.credentialId !== input.credentialId,
);
return {
schemaVersion: 1,
kind: 'result',
action: 'revoke',
credentialId: input.credentialId!,
revoked: true,
credentials,
};
},
cleanupManagedDeployment: async () => assert.fail('cleanup is not expected'),
});

const list = handlers.get('runtime-host-management:list-credentials');
const rotate = handlers.get('runtime-host-management:rotate-credential');
const revoke = handlers.get('runtime-host-management:revoke-credential');
assert.ok(list && rotate && revoke);
const initial = await list({}, profile.id);
assert.equal((initial as { canRotate: boolean }).canRotate, true);
assert.deepEqual(
(initial as { credentials: { credentialId: string; isCurrentDesktop: boolean }[] }).credentials
.map(({ credentialId, isCurrentDesktop }) => ({ credentialId, isCurrentDesktop })),
[
{ credentialId: 'current', isCurrentDesktop: true },
{ credentialId: 'obsolete', isCurrentDesktop: false },
],
);
await assert.rejects(
revoke({}, profile.id, 'current') as Promise<unknown>,
/Rotate this Desktop credential/u,
);
const revoked = await revoke({}, profile.id, 'obsolete');
assert.equal(JSON.stringify(revoked).includes('obsolete-secret'), false);
const rotated = await rotate({}, profile.id);
assert.equal(JSON.stringify(rotated).includes(replacement), false);
assert.deepEqual(
(rotated as { credentials: { credentialId: string; isCurrentDesktop: boolean }[] }).credentials,
[{
credentialId: 'replacement',
principalKind: 'remote_owner',
principalId,
status: 'active',
createdAt: '2026-08-21T01:00:00.000Z',
isCurrentDesktop: true,
}],
);
profileEnabled = false;
assert.equal((await list({}, profile.id) as { canRotate: boolean }).canRotate, false);
await assert.rejects(
rotate({}, profile.id) as Promise<unknown>,
/Enable this Runtime Host before rotating/u,
);
assert.equal(prepareCalls, 1);
});

test('manages only the service identity bound by Desktop onboarding', async () => {
const handlers = new Map<string, (...args: unknown[]) => unknown>();
const managementInputs: DesktopRuntimeHostSshManagementInput[] = [];
const cleanupInputs: DesktopRuntimeHostSshCleanupInput[] = [];
const uninstallOrder: string[] = [];
let operatorAccess = false;
let cleared = 0;
const managedProfile = {
id: 'office',
Expand Down Expand Up @@ -39,6 +194,7 @@ test('manages only the service identity bound by Desktop onboarding', async () =
profileId === managedProfile.id
? { profile: managedProfile, service: managedService, state: 'active' as const }
: undefined,
resolveManagedAccess: async () => undefined,
markManagedServiceUninstalling: async (binding) => {
uninstallOrder.push('mark-uninstalling');
return { ...binding, state: 'uninstalling' as const };
Expand All @@ -47,14 +203,16 @@ test('manages only the service identity bound by Desktop onboarding', async () =
cleared += 1;
uninstallOrder.push('clear-binding');
},
rotateManagedCredential: async () => assert.fail('credential rotation is not expected'),
},
runServiceManagement: async (input) => {
managementInputs.push(input);
if (input.action === 'uninstall') {
uninstallOrder.push('uninstall-service');
}
return serviceResult(input.action);
return serviceResult(input.action, operatorAccess);
},
runAccessManagement: async () => assert.fail('access management is not expected'),
cleanupManagedDeployment: async (input) => {
cleanupInputs.push(input);
uninstallOrder.push('cleanup-deployment');
Expand All @@ -67,7 +225,17 @@ test('manages only the service identity bound by Desktop onboarding', async () =
run({}, 'manual', 'uninstall') as Promise<unknown>,
/not bound to a managed service/u,
);
await run({}, 'office', 'status');
const legacyStatus = await run({}, 'office', 'status');
assert.equal(
(legacyStatus as { accessManagementAvailable: boolean }).accessManagementAvailable,
false,
);
operatorAccess = true;
const currentStatus = await run({}, 'office', 'status');
assert.equal(
(currentStatus as { accessManagementAvailable: boolean }).accessManagementAvailable,
true,
);
const managementInput = managementInputs.at(-1);
assert.deepEqual(managementInput && {
destination: managementInput.destination,
Expand Down Expand Up @@ -143,6 +311,7 @@ test('resumes deployment cleanup without repeating the committed service uninsta
},
profiles: {
resolveManagedService: async () => ({ profile, service, state }),
resolveManagedAccess: async () => undefined,
markManagedServiceUninstalling: async (binding) => {
state = 'uninstalling';
return { ...binding, state };
Expand All @@ -151,11 +320,13 @@ test('resumes deployment cleanup without repeating the committed service uninsta
clearAttempts += 1;
if (clearAttempts === 1) throw new Error('local metadata is unavailable');
},
rotateManagedCredential: async () => assert.fail('credential rotation is not expected'),
},
runServiceManagement: async (input) => {
calls.push(input);
return serviceResult(input.action);
},
runAccessManagement: async () => assert.fail('access management is not expected'),
cleanupManagedDeployment: async () => {
cleanups += 1;
},
Expand Down Expand Up @@ -206,11 +377,13 @@ test('does not commit uninstall until the remote service confirms it is removed'
},
state: 'active' as const,
}),
resolveManagedAccess: async () => undefined,
markManagedServiceUninstalling: async (binding) => {
marked = true;
return { ...binding, state: 'uninstalling' as const };
},
clearManagedServiceBinding: async () => assert.fail('uninstall was not committed'),
rotateManagedCredential: async () => assert.fail('credential rotation is not expected'),
},
runServiceManagement: async () => {
const result = serviceResult('uninstall');
Expand All @@ -219,6 +392,7 @@ test('does not commit uninstall until the remote service confirms it is removed'
service: { ...result.service, state: 'running' as const, pid: 42 },
};
},
runAccessManagement: async () => assert.fail('access management is not expected'),
cleanupManagedDeployment: async () => assert.fail('cleanup must not start'),
});

Expand All @@ -231,11 +405,17 @@ test('does not commit uninstall until the remote service confirms it is removed'
assert.equal(marked, false);
});

function serviceResult(action: DesktopRuntimeHostSshManagementInput['action']) {
function serviceResult(
action: DesktopRuntimeHostSshManagementInput['action'],
operatorAccess = false,
) {
return {
schemaVersion: 1 as const,
kind: 'result' as const,
action,
...(operatorAccess
? { operatorCapabilities: ['access-management-v1' as const] }
: {}),
service: {
platform: 'linux',
arch: 'x64',
Expand All @@ -248,3 +428,21 @@ function serviceResult(action: DesktopRuntimeHostSshManagementInput['action']) {
},
};
}

function accessCredential(
credentialId: string,
principalId: string,
credentialFingerprint: string,
) {
return {
credentialId,
credentialFingerprint,
principalKind: 'remote_owner' as const,
principalId,
status: 'active' as const,
operationGrants: ['host.status', 'turn.start'],
canPublishClientCapabilities: true,
canUseHostPaths: false,
createdAt: '2026-08-21T01:00:00.000Z',
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -503,8 +503,13 @@ test("preserves a staged pairing when finalization is interrupted", async () =>
setDefault: () => undefined,
finalizePairing: async () => undefined,
});
await assert.rejects(
recovered.resolveManagedAccess(MANAGED_PROFILE.id),
/unfinished pairing/u,
);
await recovered.startEnabledProfiles();

assert.ok(await recovered.resolveManagedAccess(MANAGED_PROFILE.id));
assert.equal(
(await recovered.getSnapshot()).entries.find(
(entry) => entry.profile.id === MANAGED_PROFILE.id,
Expand Down
Loading