Skip to content
This repository was archived by the owner on Feb 8, 2024. It is now read-only.
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
7 changes: 6 additions & 1 deletion packages/teleterm/src/mainProcess/resolveNetworkAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ const TCP_PORT_MATCH = /\{CONNECT_GRPC_PORT:\s(\d+)}/;
// transport method.
const UDS_MATCH = /\{CONNECT_GRPC_PORT:/;

/**
* Waits for the process to start a gRPC server and log the address used by the gRPC server.
*
* @return {Promise<string>} The address used by the gRPC server started from the process.
*/
export async function resolveNetworkAddress(
requestedAddress: string,
process: ChildProcess,
timeoutMs = 10_000 // 10s
timeoutMs = 15_000 // 15s; needs to be larger than other timeouts in the processes.
): Promise<string> {
const protocol = new URL(requestedAddress).protocol;

Expand Down
58 changes: 49 additions & 9 deletions packages/teleterm/src/preload.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { contextBridge } from 'electron';
import { ChannelCredentials } from '@grpc/grpc-js';

import createTshClient from 'teleterm/services/tshd/createClient';
import createMainProcessClient from 'teleterm/mainProcess/mainProcessClient';
import createLoggerService from 'teleterm/services/logger';
import PreloadLogger from 'teleterm/logger';

import Logger from 'teleterm/logger';
import { createPtyService } from 'teleterm/services/pty/ptyService';
import { getClientCredentials } from 'teleterm/services/grpcCredentials';

import { ElectronGlobals } from './types';
import {
GrpcCertName,
createClientCredentials,
createInsecureClientCredentials,
generateAndSaveGrpcCert,
readGrpcCert,
shouldEncryptConnection,
} from 'teleterm/services/grpcCredentials';
import { ElectronGlobals, RuntimeSettings } from 'teleterm/types';

const mainProcessClient = createMainProcessClient();
const runtimeSettings = mainProcessClient.getRuntimeSettings();
Expand All @@ -18,16 +24,18 @@ const loggerService = createLoggerService({
name: 'renderer',
});

PreloadLogger.init(loggerService);
Logger.init(loggerService);

contextBridge.exposeInMainWorld('loggerService', loggerService);

contextBridge.exposeInMainWorld('electron', getElectronGlobals());

async function getElectronGlobals(): Promise<ElectronGlobals> {
const [addresses, credentials] = await Promise.all([
mainProcessClient.getResolvedChildProcessAddresses(),
getClientCredentials(runtimeSettings),
createGrpcCredentials(runtimeSettings),
]);
const tshClient = createTshClient(addresses.tsh, credentials.tsh);
const tshClient = createTshClient(addresses.tsh, credentials.tshd);
const ptyServiceClient = createPtyService(
addresses.shared,
credentials.shared,
Expand All @@ -38,6 +46,38 @@ async function getElectronGlobals(): Promise<ElectronGlobals> {
mainProcessClient,
tshClient,
ptyServiceClient,
loggerService,
};
}

/**
* For TCP transport, createGrpcCredentials generates the renderer key pair and reads the public key
* for tshd and the shared process from disk. This lets us set up gRPC clients in the renderer
* process that connect to the gRPC servers of tshd and the shared process.
*/
async function createGrpcCredentials(
runtimeSettings: RuntimeSettings
): Promise<{
// Credentials for talking to the tshd process.
tshd: ChannelCredentials;
// Credentials for talking to the shared process.
shared: ChannelCredentials;
}> {
if (!shouldEncryptConnection(runtimeSettings)) {
return {
tshd: createInsecureClientCredentials(),
shared: createInsecureClientCredentials(),
};
}

const { certsDir } = runtimeSettings;
const [rendererKeyPair, tshdCert, sharedCert] = await Promise.all([
generateAndSaveGrpcCert(certsDir, GrpcCertName.Renderer),
readGrpcCert(certsDir, GrpcCertName.Tshd),
readGrpcCert(certsDir, GrpcCertName.Shared),
]);

return {
tshd: createClientCredentials(rendererKeyPair, tshdCert),
shared: createClientCredentials(rendererKeyPair, sharedCert),
};
}

This file was deleted.

56 changes: 56 additions & 0 deletions packages/teleterm/src/services/grpcCredentials/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
ChannelCredentials,
credentials,
ServerCredentials,
} from '@grpc/grpc-js';

import { RuntimeSettings } from 'teleterm/mainProcess/types';

export function createClientCredentials(
clientKeyPair: { cert: Buffer; key: Buffer },
serverCert: Buffer
): ChannelCredentials {
return credentials.createSsl(
serverCert,
clientKeyPair.key,
clientKeyPair.cert
);
}

export function createServerCredentials(
serverKeyPair: { cert: Buffer; key: Buffer },
clientCert: Buffer
): ServerCredentials {
return ServerCredentials.createSsl(
clientCert,
[
{
cert_chain: serverKeyPair.cert,
private_key: serverKeyPair.key,
},
],
true
);
}

export function createInsecureClientCredentials(): ChannelCredentials {
return credentials.createInsecure();
}

export function createInsecureServerCredentials(): ServerCredentials {
return ServerCredentials.createInsecure();
}

/**
* Checks if the gRPC connection should be encrypted.
* The only source of truth is the type of tshd protocol.
* Any protocol other than `unix` should be encrypted.
* The same check is performed on the tshd side.
*/
export function shouldEncryptConnection(
runtimeSettings: RuntimeSettings
): boolean {
return (
new URL(runtimeSettings.tshd.requestedNetworkAddress).protocol !== 'unix:'
);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import path from 'path';

import { watch } from 'fs';
import { readFile, writeFile, stat, rename } from 'fs/promises';

import { RuntimeSettings } from 'teleterm/mainProcess/types';

import { makeCert } from './makeCert';

/**
Expand Down Expand Up @@ -83,17 +80,3 @@ export async function readGrpcCert(
abortController.abort();
}
}

/**
* Checks if the gRPC connection should be encrypted.
* The only source of truth is the type of tshd protocol.
* Any other protocol than `unix` should be encrypted.
* The same check is performed on the tshd side.
*/
export function shouldEncryptConnection(
runtimeSettings: RuntimeSettings
): boolean {
return (
new URL(runtimeSettings.tshd.requestedNetworkAddress).protocol !== 'unix:'
);
}
5 changes: 3 additions & 2 deletions packages/teleterm/src/services/grpcCredentials/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './clientCredentials';
export * from './serverCredentials';
export * from './types';
export * from './credentials';
export * from './files';
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { makeCert } from './makeCert';

test('create CA cert', async () => {
const ca = await makeCert({
commonName: 'Test CA',
commonName: 'test-ca',
validityDays: 365,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ interface GeneratedCert {
cert: string;
}

/**
* Creates a self-signed cert. commonName should be a valid domain name.
*/
export async function makeCert({
commonName,
validityDays,
Expand All @@ -69,6 +72,15 @@ export async function makeCert({
digitalSignature: true,
keyEncipherment: true,
},
{
name: 'subjectAltName',
altNames: [
{
type: 2, // DNS type
value: commonName,
},
],
},
];

return await generateRawCert({
Expand Down

This file was deleted.

11 changes: 7 additions & 4 deletions packages/teleterm/src/services/grpcCredentials/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// `Client` and `TshServer` file names are also used on the tshd side
// Each process creates its own key pair. The public key is saved to disk under the specified
// filename, the private key stays in the memory.
//
// `Renderer` and `Tshd` file names are also used on the tshd side.
export enum GrpcCertName {
Client = 'client.crt',
TshServer = 'tsh_server.crt',
SharedServer = 'shared_server.crt',
Renderer = 'renderer.crt',
Tshd = 'tshd.crt',
Shared = 'shared.crt',
}
Loading