Skip to content

Commit

Permalink
pass certs down to grpc
Browse files Browse the repository at this point in the history
  • Loading branch information
jackkav committed Sep 24, 2024
1 parent 01ce9e0 commit 602c4d4
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 26 deletions.
51 changes: 27 additions & 24 deletions packages/insomnia/src/main/ipc/grpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
ChannelCredentials,
type ClientDuplexStream,
type ClientReadableStream,
credentials,
makeGenericClientConstructor,
Metadata,
type ServiceError,
Expand All @@ -27,7 +26,6 @@ import type {
} from '@grpc/proto-loader';
import * as protoLoader from '@grpc/proto-loader';
import electron, { type IpcMainEvent } from 'electron';
import fs from 'fs';
import * as grpcReflection from 'grpc-reflection-js';

import { version } from '../../../package.json';
Expand All @@ -44,6 +42,9 @@ const grpcCalls = new Map<string, Call>();

export interface GrpcIpcRequestParams {
request: RenderedGrpcRequest;
clientCert?: string;
clientKey?: string;
caCertificate?: string;
}

export interface GrpcIpcMessageParams {
Expand Down Expand Up @@ -202,16 +203,19 @@ const getMethodsFromReflectionServer = async (
const getMethodsFromReflection = async (
host: string,
metadata: GrpcRequestHeader[],
reflectionApi: GrpcRequest['reflectionApi']
reflectionApi: GrpcRequest['reflectionApi'],
clientCert?: string,
clientKey?: string,
caCertificate?: string,
): Promise<MethodDefs[]> => {
if (reflectionApi.enabled) {
return getMethodsFromReflectionServer(reflectionApi);
}
try {
const { url, enableTls } = parseGrpcUrl(host);
const { url } = parseGrpcUrl(host);
const client = new grpcReflection.Client(
url,
enableTls ? credentials.createSsl() : credentials.createInsecure(),
getChannelCredentials({ url: host, caCertificate, clientCert, clientKey }),
grpcOptions,
filterDisabledMetaData(metadata)
);
Expand Down Expand Up @@ -265,12 +269,18 @@ export const loadMethodsFromReflection = async (options: {
url: string;
metadata: GrpcRequestHeader[];
reflectionApi: GrpcRequest['reflectionApi'];
clientCert?: string;
clientKey?: string;
caCertificate?: string;
}): Promise<GrpcMethodInfo[]> => {
invariant(options.url, 'gRPC request url not provided');
const methods = await getMethodsFromReflection(
options.url,
options.metadata,
options.reflectionApi
options.reflectionApi,
options.clientCert,
options.clientKey,
options.caCertificate
);
return methods.map(method => ({
type: getMethodType(method),
Expand Down Expand Up @@ -345,27 +355,22 @@ const isEnumDefinition = (definition: AnyDefinition): definition is EnumTypeDefi
return (definition as EnumTypeDefinition).format === 'Protocol Buffer 3 EnumDescriptorProto';
};

const getChannelCredentialsByWorkspaceId = async (workspaceId: string): ChannelCredentials => {
const clientCerts = await models.clientCertificate.findByParentId(workspaceId);
const caCert = await models.caCertificate.findByParentId(workspaceId);
const caCertficatePath = caCert?.path || null;
const rootCert = (caCertficatePath && fs.readFileSync(caCertficatePath));
// TODO: filter out disabled client certs and select approriate
const clientCert = clientCerts?.[0]?.cert;
const clientKey = clientCerts?.[0]?.key;

if (!rootCert) {
const getChannelCredentials = ({ url, clientCert, clientKey, caCertificate }: { url: string; clientCert?: string; clientKey?: string; caCertificate?: string }): ChannelCredentials => {
if (url.toLowerCase().startsWith('grpc:')) {
return ChannelCredentials.createInsecure();
}
if (rootCert && clientKey && clientCert) {
return ChannelCredentials.createSsl(rootCert, Buffer.from(clientKey, 'utf8'), Buffer.from(clientCert, 'utf8'));
if (caCertificate && clientKey && clientCert) {
return ChannelCredentials.createSsl(Buffer.from(caCertificate, 'utf8'), Buffer.from(clientKey, 'utf8'), Buffer.from(clientCert, 'utf8'));
}
if (caCertificate) {
return ChannelCredentials.createSsl(Buffer.from(caCertificate, 'utf8'),);
}
return ChannelCredentials.createSsl(rootCert);
return ChannelCredentials.createInsecure();
};

export const start = (
event: IpcMainEvent,
{ request }: GrpcIpcRequestParams,
{ request, clientCert, clientKey, caCertificate }: GrpcIpcRequestParams,
) => {
getSelectedMethod(request)?.then(method => {
if (!method) {
Expand All @@ -374,17 +379,15 @@ export const start = (
}
const methodType = getMethodType(method);
// Create client
const { url, enableTls } = parseGrpcUrl(request.url);
const workspaceId = request.parentId;
const { url } = parseGrpcUrl(request.url);

if (!url) {
event.reply('grpc.error', request._id, new Error('URL not specified'));
return undefined;
}
console.log(`[gRPC] connecting to url=${url} ${enableTls ? 'with' : 'without'} TLS`);
// @ts-expect-error -- TSCONVERSION second argument should be provided, send an empty string? Needs testing
const Client = makeGenericClientConstructor({});
const creds = enableTls ? getChannelCredentialsByWorkspaceId(workspaceId) : credentials.createInsecure();
const creds = getChannelCredentials({ url: request.url, clientCert, clientKey, caCertificate });
const client = new Client(url, creds);
if (!client) {
return;
Expand Down
24 changes: 22 additions & 2 deletions packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { readFile } from 'fs/promises';
import React, { type FunctionComponent, useRef, useState } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
import { useParams, useRouteLoaderData } from 'react-router-dom';
Expand All @@ -11,7 +12,9 @@ import type { GrpcMethodType } from '../../../main/ipc/grpc';
import * as models from '../../../models';
import type { GrpcRequestHeader } from '../../../models/grpc-request';
import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls';
import { urlMatchesCertHost } from '../../../network/url-matches-cert-host';
import { tryToInterpolateRequestOrShowRenderErrorModal } from '../../../utils/try-interpolate';
import { setDefaultProtocol } from '../../../utils/url/protocol';
import { useRequestPatcher } from '../../hooks/use-request';
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
import type { GrpcRequestState } from '../../routes/debug';
Expand Down Expand Up @@ -86,7 +89,15 @@ export const GrpcRequestPane: FunctionComponent<Props> = ({
purpose: 'send',
skipBody: canClientStream(methodType),
});
window.main.grpc.start({ request });
const workspaceClientCertificates = await models.clientCertificate.findByParentId(workspaceId);
const clientCertificate = workspaceClientCertificates.find(c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, 'grpc:'), request.url, false));
const caCertificatePath = (await models.caCertificate.findByParentId(workspaceId))?.path;
window.main.grpc.start({
request,
clientCert: clientCertificate?.cert || undefined,
clientKey: clientCertificate?.key || undefined,
caCertificate: caCertificatePath ? await readFile(caCertificatePath, 'utf8') : undefined,
});
setGrpcState({
...grpcState,
requestMessages: [],
Expand Down Expand Up @@ -177,7 +188,7 @@ export const GrpcRequestPane: FunctionComponent<Props> = ({
disabled={!activeRequest.url}
onClick={async () => {
try {
const rendered =
let rendered =
await tryToInterpolateRequestOrShowRenderErrorModal({
request: activeRequest,
environmentId,
Expand All @@ -187,6 +198,15 @@ export const GrpcRequestPane: FunctionComponent<Props> = ({
reflectionApi: activeRequest.reflectionApi,
},
});
const workspaceClientCertificates = await models.clientCertificate.findByParentId(workspaceId);
const clientCertificate = workspaceClientCertificates.find(c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, 'grpc:'), request.url, false));
const caCertificatePath = (await models.caCertificate.findByParentId(workspaceId))?.path;
rendered = {
...rendered,
clientCert: clientCertificate?.cert || undefined,
clientKey: clientCertificate?.key || undefined,
caCertificate: caCertificatePath ? await readFile(caCertificatePath, 'utf8') : undefined,
};
const methods = await window.main.grpc.loadMethodsFromReflection(rendered);
setGrpcState({ ...grpcState, methods });
patchRequest(requestId, { protoFileId: '', protoMethodName: '' });
Expand Down

0 comments on commit 602c4d4

Please sign in to comment.