Skip to content
Merged
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
36 changes: 3 additions & 33 deletions apps/desktop/renderer-architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@
"src/renderer/settings/relay-profile-draft.ts",
"src/renderer/settings/relay-thinking-bulk.ts",
"src/renderer/settings/request-customization-editor.tsx",
"src/renderer/settings/runtime-host-connection-code-dialog.tsx",
"src/renderer/settings/runtime-host-interaction-boundary.tsx",
"src/renderer/settings/runtime-host-management-dialog.tsx",
"src/renderer/settings/runtime-host-onboarding-dialog.tsx",
Expand Down Expand Up @@ -3723,30 +3722,6 @@
"@maka/ui/icons": 1
}
},
"src/renderer/settings/runtime-host-connection-code-dialog.tsx": {
"bridgePaths": {
"window.maka.runtimeHostProfiles.importConnectionCode": 1
},
"environmentCapabilities": {
"navigator.clipboard.writeText": 1
},
"hookCalls": {
"useState": 2,
"useToast": 1,
"useUiLocale": 1
},
"lifecycleMethods": {},
"unresolvedDependencies": 0,
"actionFactories": [],
"dependencyPaths": {
"../locales/settings-projects-copy.js": 1,
"./settings-error-copy.js": 1,
"@astryxdesign/core/Dialog": 1,
"@astryxdesign/core/Layout": 1,
"@maka/ui": 1,
"react": 1
}
},
"src/renderer/settings/runtime-host-interaction-boundary.tsx": {
"bridgePaths": {},
"environmentCapabilities": {},
Expand Down Expand Up @@ -3788,13 +3763,12 @@
"dependencyPaths": {
"../../preload/bridge-contract.js": 1,
"../../shared/runtime-host-project-directory-policy.js": 1,
"../features/runtime-host-management": 1,
"../locales/settings-projects-copy.js": 1,
"./runtime-host-project-directory-editor.js": 1,
"./settings-error-copy.js": 1,
"@astryxdesign/core/Dialog": 1,
"@astryxdesign/core/Layout": 1,
"@astryxdesign/core/Switch": 1,
"@astryxdesign/core/Text": 1,
"@astryxdesign/core/Tooltip": 1,
"@maka/core/ui-locale": 1,
"@maka/ui": 1,
Expand All @@ -3811,13 +3785,11 @@
"window.maka.runtimeHostOnboarding.start": 1,
"window.maka.runtimeHostOnboarding.subscribe": 1
},
"environmentCapabilities": {
"navigator.userAgent.includes": 2
},
"environmentCapabilities": {},
"hookCalls": {
"useEffect": 2,
"useRef": 2,
"useState": 8,
"useState": 7,
"useUiLocale": 1
},
"lifecycleMethods": {},
Expand All @@ -3828,7 +3800,6 @@
"../../shared/runtime-host-project-directory-policy.js": 1,
"../locales/settings-projects-copy.js": 1,
"./runtime-host-project-directory-editor.js": 1,
"@astryxdesign/core": 1,
"@astryxdesign/core/Dialog": 1,
"@astryxdesign/core/Layout": 1,
"@astryxdesign/core/Text": 1,
Expand Down Expand Up @@ -3870,7 +3841,6 @@
"../locales/session-collaboration-copy.js": 1,
"../locales/settings-projects-copy.js": 1,
"./password-input.js": 1,
"./runtime-host-connection-code-dialog.js": 1,
"./runtime-host-management-dialog.js": 1,
"./runtime-host-onboarding-dialog.js": 1,
"./settings-error-copy.js": 1,
Expand Down
69 changes: 67 additions & 2 deletions apps/desktop/src/main/__tests__/runtime-host-management.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { RuntimeHostOperationError } from '@maka/runtime-host/client';
import {
encodeRuntimeHostOwnerConnectionCode,
RuntimeHostOperationError,
} from '@maka/runtime-host/client';
import {
RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
RUNTIME_HOST_OPERATOR_PEER_WEBRTC_STUN_CAPABILITY,
Expand Down Expand Up @@ -299,6 +302,17 @@ test('identifies, rotates, and revokes managed credentials without exposing secr
const replacement = 'maka_rh_replacement-secret';
let profileEnabled = true;
let prepareCalls = 0;
let issuedConnectionCode = encodeRuntimeHostOwnerConnectionCode({
name: profile.name,
rootId: profile.rootId,
transport: {
kind: 'libp2p-direct',
peerId: '12D3KooWoffice',
routeHints: ['/ip4/192.0.2.8/udp/44001/quic-v1'],
coordinationRelays: [],
},
credential: 'pending-credential',
});
const currentFingerprint = runtimeHostAccessCredentialFingerprint('maka_rh_current-secret');
const credentials = [
accessCredential('current', principalId, currentFingerprint),
Expand All @@ -317,6 +331,11 @@ test('identifies, rotates, and revokes managed credentials without exposing secr
},
profiles: {
...unusedDirectPeerProfileDependencies(),
resolveManagedDirectPeerProfile: async () => ({
exists: true,
enabled: false,
peerId: '12D3KooWoffice',
}),
resolveManagedService: async () => managedBinding(profile, service, 'active'),
resolveManagedAccess: async () => ({
...managedBinding(profile, service, 'active'),
Expand Down Expand Up @@ -346,6 +365,15 @@ test('identifies, rotates, and revokes managed credentials without exposing secr
if (input.action === 'list') {
return { schemaVersion: 1, kind: 'result', action: 'list', credentials };
}
if (input.action === 'connection-code') {
assert.equal(input.name, profile.name);
return {
schemaVersion: 1,
kind: 'result',
action: 'connection-code',
connectionCode: issuedConnectionCode,
};
}
if (input.action === 'prepare') {
prepareCalls += 1;
assert.equal(input.currentCredentialFingerprint, currentFingerprint);
Expand Down Expand Up @@ -391,9 +419,46 @@ test('identifies, rotates, and revokes managed credentials without exposing secr
});

const list = handlers.get('runtime-host-management:list-credentials');
const connectionCode = handlers.get('runtime-host-management:create-connection-code');
const rotate = handlers.get('runtime-host-management:rotate-credential');
const revoke = handlers.get('runtime-host-management:revoke-credential');
assert.ok(list && rotate && revoke);
assert.ok(list && connectionCode && rotate && revoke);
assert.equal(await connectionCode({}, profile.id), issuedConnectionCode);
issuedConnectionCode = encodeRuntimeHostOwnerConnectionCode({
name: profile.name,
rootId: 'c'.repeat(64),
transport: {
kind: 'libp2p-direct',
peerId: '12D3KooWoffice',
routeHints: ['/ip4/192.0.2.8/udp/44001/quic-v1'],
coordinationRelays: [],
},
credential: 'pending-credential',
});
await assert.rejects(
connectionCode({}, profile.id) as Promise<unknown>,
/different Host/u,
);
issuedConnectionCode = encodeRuntimeHostOwnerConnectionCode({
name: profile.name,
rootId: profile.rootId,
transport: {
kind: 'libp2p-direct',
peerId: '12D3KooWunexpected',
routeHints: ['/ip4/192.0.2.8/udp/44001/quic-v1'],
coordinationRelays: [],
},
credential: 'pending-credential',
});
await assert.rejects(
connectionCode({}, profile.id) as Promise<unknown>,
/different Direct peer/u,
);
issuedConnectionCode = 'not-a-connection-code';
await assert.rejects(
connectionCode({}, profile.id) as Promise<unknown>,
/connection code is invalid/u,
);
const initial = await list({}, profile.id);
assert.equal((initial as { canRotate: boolean }).canRotate, true);
assert.deepEqual(
Expand Down
35 changes: 35 additions & 0 deletions apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,41 @@ test('keeps a prepared access credential out of the SSH terminal projection', as
await harness.terminal.close();
});

test('creates an owner connection code through the framed SSH operator channel', async () => {
const harness = createHarness('pending');
const connectionCode = 'maka-runtime-host:connect:v1:secret-code';
const management = harness.terminal.runAccessManagement({
destination: 'operator@example.com',
operatorPath: '/home/operator/.local/share/maka/operator',
rootPath: '/srv/maka',
expectedRootId: 'a'.repeat(64),
action: 'connection-code',
name: "Owner's Linux",
});
await waitFor(() => harness.pty.hasDataListener());
harness.pty.emitData(encodeRuntimeHostAccessManagementFrame({
schemaVersion: 1,
kind: 'result',
action: 'connection-code',
connectionCode,
}));
harness.pty.exit(0);

const result = await management;
assert.equal(
result.kind === 'result' && result.action === 'connection-code'
? result.connectionCode
: undefined,
connectionCode,
);
const command = harness.launchArgs.at(-1)?.at(-1) ?? '';
assert.match(command, /access.*connection-code/u);
assert.match(command, /--name/u);
assert.match(command, /Owner/u);
assert.doesNotMatch(JSON.stringify(harness.events), /secret-code|MAKA_RUNTIME/u);
await harness.terminal.close();
});

test('requests adaptive-connectivity status only on the peer-management frame', async () => {
const harness = createHarness('pending');
const management = harness.terminal.runPeerManagement({
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/main/runtime-host-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ export class DesktopRuntimeHostClient {
}

get hostId(): string {
return this.rootId;
}

get rootId(): string {
return this.connection.rootId;
}

Expand Down
33 changes: 11 additions & 22 deletions apps/desktop/src/main/runtime-host-local-remote-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import { hostname } from 'node:os';
import { dirname, isAbsolute, join } from 'node:path';
import type { IpcMain } from 'electron';
import {
consumeAccessCredentialDelivery,
encodeRuntimeHostOwnerConnectionCode,
issueRuntimeHostOwnerConnectionCode,
} from '@maka/runtime-host/client';
import { resolveRuntimeHostManagedDeploymentAuthority } from '@maka/runtime-host/operator';
import { REMOTE_OWNER_OPERATION_GRANTS, type HostRegistration } from '@maka/runtime-host/protocol';
import type { HostRegistration } from '@maka/runtime-host/protocol';
import type {
DesktopLocalRuntimeHostRemoteAccessEnableResult,
DesktopLocalRuntimeHostRemoteAccessSnapshot,
Expand Down Expand Up @@ -328,7 +328,7 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
changed.response.restarted ? previousHostEpoch : undefined,
);
return enabledResult(
await issueConnectionCode(input.rootPath, desired.rootId, peer, localClient(input.manager)),
await issueConnectionCode(input.rootPath, peer, localClient(input.manager)),
);
}

Expand Down Expand Up @@ -511,7 +511,7 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
);
const peer = await readPeer(input.operator, managed);
if (!peer) throw new Error('Remote access is not enabled on this computer');
return issueConnectionCode(input.rootPath, managed.rootId, peer, localClient(input.manager));
return issueConnectionCode(input.rootPath, peer, localClient(input.manager));
});

const createCollaborationConnectionTarget = () =>
Expand Down Expand Up @@ -978,29 +978,15 @@ function enabledResult(

async function issueConnectionCode(
rootPath: string,
rootId: string,
peer: LocalPeerDescriptor,
client: DesktopRuntimeHostClient,
): Promise<string> {
const livePeer = await readLivePeer(client, peer);
const prepared = await client.request('access.credential.prepare', {
principalKind: 'remote_owner',
principalId: LOCAL_REMOTE_ACCESS_PRINCIPAL_ID,
operationGrants: REMOTE_OWNER_OPERATION_GRANTS,
canPublishClientCapabilities: true,
canUseHostPaths: false,
bindClientInstance: true,
});
const credential = await consumeAccessCredentialDelivery(
return issueRuntimeHostOwnerConnectionCode({
rootPath,
prepared.deliveryId,
prepared.credentialId,
);
return encodeRuntimeHostOwnerConnectionCode({
name: hostName(),
rootId,
transport: { kind: 'libp2p-direct', ...livePeer },
credential,
principalId: LOCAL_REMOTE_ACCESS_PRINCIPAL_ID,
expectedPeerId: peer.peerId,
client,
});
}

Expand Down Expand Up @@ -1032,6 +1018,9 @@ async function hasSharedAccess(
target,
});
if (response.kind === 'error') throw new Error(response.error.message);
if (response.action !== 'list') {
throw new Error('Runtime Host operator returned an unrelated access result');
}
return response.credentials.some(
(credential) =>
credential.principalKind === 'remote_owner' &&
Expand Down
28 changes: 28 additions & 0 deletions apps/desktop/src/main/runtime-host-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import type { IpcMain } from 'electron';
import { decodeRuntimeHostOwnerConnectionCode } from '@maka/runtime-host/client';
import {
RUNTIME_HOST_OPERATOR_ACCESS_MANAGEMENT_CAPABILITY,
RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
Expand Down Expand Up @@ -900,6 +901,30 @@ export function createDesktopRuntimeHostManagement(input: {
);
};

const createConnectionCode = async (profileId: unknown): Promise<string> => {
const access = await resolveAccess(profileId);
const response = await input.runAccessManagement({
...access.target,
action: 'connection-code',
name: access.managed.profile.name,
});
if (response.kind === 'error') throw new Error(response.error.message);
if (response.action !== 'connection-code') {
throw new Error('Remote Runtime Host did not return a connection code');
}
const decoded = decodeRuntimeHostOwnerConnectionCode(response.connectionCode);
if (decoded.rootId !== access.managed.profile.rootId) {
throw new Error('Remote Runtime Host returned a connection code for a different Host');
}
const peerProfile = await input.profiles.resolveManagedDirectPeerProfile(
access.managed.profile.id,
);
if (peerProfile.peerId && decoded.transport.peerId !== peerProfile.peerId) {
throw new Error('Remote Runtime Host returned a connection code for a different Direct peer');
}
return response.connectionCode;
};

const rotateCredential = async (
profileId: unknown,
): Promise<DesktopRuntimeHostAccessSnapshot> => {
Expand Down Expand Up @@ -980,6 +1005,7 @@ export function createDesktopRuntimeHostManagement(input: {
run: 'runtime-host-management:run',
update: 'runtime-host-management:update',
configureProjectDirectories: 'runtime-host-management:configure-project-directories',
createConnectionCode: 'runtime-host-management:create-connection-code',
listCredentials: 'runtime-host-management:list-credentials',
rotateCredential: 'runtime-host-management:rotate-credential',
revokeCredential: 'runtime-host-management:revoke-credential',
Expand Down Expand Up @@ -1019,6 +1045,8 @@ export function createDesktopRuntimeHostManagement(input: {
allowInterruptActiveTasks,
),
);
input.ipcMain.handle(channels.createConnectionCode, (_event, profileId: unknown) =>
createConnectionCode(profileId));
input.ipcMain.handle(channels.listCredentials, (_event, profileId: unknown) =>
listCredentials(profileId));
input.ipcMain.handle(channels.rotateCredential, (_event, profileId: unknown) =>
Expand Down
Loading
Loading