grpc-js: Connect with https scheme when using TLS

This commit is contained in:
Michael Lumish 2025-01-30 14:03:06 -08:00
parent 023b0862f0
commit 9569f20fc6
3 changed files with 31 additions and 16 deletions

View File

@ -63,8 +63,13 @@ export interface VerifyOptions {
rejectUnauthorized?: boolean; rejectUnauthorized?: boolean;
} }
export interface SecureConnectResult {
socket: Socket;
secure: boolean;
}
export interface SecureConnector { export interface SecureConnector {
connect(socket: Socket): Promise<Socket>; connect(socket: Socket): Promise<SecureConnectResult>;
getCallCredentials(): CallCredentials; getCallCredentials(): CallCredentials;
destroy(): void; destroy(): void;
} }
@ -178,7 +183,10 @@ class InsecureChannelCredentialsImpl extends ChannelCredentials {
_createSecureConnector(channelTarget: GrpcUri, options: ChannelOptions, callCredentials?: CallCredentials): SecureConnector { _createSecureConnector(channelTarget: GrpcUri, options: ChannelOptions, callCredentials?: CallCredentials): SecureConnector {
return { return {
connect(socket) { connect(socket) {
return Promise.resolve(socket); return Promise.resolve({
socket,
secure: false
});
}, },
getCallCredentials: () => { getCallCredentials: () => {
return callCredentials ?? CallCredentials.createEmpty(); return callCredentials ?? CallCredentials.createEmpty();
@ -247,14 +255,17 @@ function getConnectionOptions(secureContext: SecureContext, verifyOptions: Verif
class SecureConnectorImpl implements SecureConnector { class SecureConnectorImpl implements SecureConnector {
constructor(private connectionOptions: ConnectionOptions, private callCredentials: CallCredentials) { constructor(private connectionOptions: ConnectionOptions, private callCredentials: CallCredentials) {
} }
connect(socket: Socket): Promise<Socket> { connect(socket: Socket): Promise<SecureConnectResult> {
const tlsConnectOptions: ConnectionOptions = { const tlsConnectOptions: ConnectionOptions = {
socket: socket, socket: socket,
...this.connectionOptions ...this.connectionOptions
}; };
return new Promise<Socket>((resolve, reject) => { return new Promise<SecureConnectResult>((resolve, reject) => {
const tlsSocket = tlsConnect(tlsConnectOptions, () => { const tlsSocket = tlsConnect(tlsConnectOptions, () => {
resolve(tlsSocket) resolve({
socket: tlsSocket,
secure: true
})
}); });
tlsSocket.on('error', (error: Error) => { tlsSocket.on('error', (error: Error) => {
reject(error); reject(error);
@ -316,7 +327,7 @@ class CertificateProviderChannelCredentialsImpl extends ChannelCredentials {
private static SecureConnectorImpl = class implements SecureConnector { private static SecureConnectorImpl = class implements SecureConnector {
constructor(private parent: CertificateProviderChannelCredentialsImpl, private channelTarget: GrpcUri, private options: ChannelOptions, private callCredentials: CallCredentials) {} constructor(private parent: CertificateProviderChannelCredentialsImpl, private channelTarget: GrpcUri, private options: ChannelOptions, private callCredentials: CallCredentials) {}
connect(socket: Socket): Promise<Socket> { connect(socket: Socket): Promise<SecureConnectResult> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const secureContext = await this.parent.getSecureContext(); const secureContext = await this.parent.getSecureContext();
if (!secureContext) { if (!secureContext) {
@ -329,7 +340,10 @@ class CertificateProviderChannelCredentialsImpl extends ChannelCredentials {
...connnectionOptions ...connnectionOptions
} }
const tlsSocket = tlsConnect(tlsConnectOptions, () => { const tlsSocket = tlsConnect(tlsConnectOptions, () => {
resolve(tlsSocket) resolve({
socket: tlsSocket,
secure: true
});
}); });
tlsSocket.on('error', (error: Error) => { tlsSocket.on('error', (error: Error) => {
reject(error); reject(error);

View File

@ -63,5 +63,5 @@ export {
FileWatcherCertificateProvider, FileWatcherCertificateProvider,
FileWatcherCertificateProviderConfig FileWatcherCertificateProviderConfig
} from './certificate-provider'; } from './certificate-provider';
export { createCertificateProviderChannelCredentials, SecureConnector } from './channel-credentials'; export { createCertificateProviderChannelCredentials, SecureConnector, SecureConnectResult } from './channel-credentials';
export { SUBCHANNEL_ARGS_EXCLUDE_KEY_PREFIX } from './internal-channel'; export { SUBCHANNEL_ARGS_EXCLUDE_KEY_PREFIX } from './internal-channel';

View File

@ -21,7 +21,7 @@ import {
TLSSocket, TLSSocket,
} from 'tls'; } from 'tls';
import { PartialStatusObject } from './call-interface'; import { PartialStatusObject } from './call-interface';
import { SecureConnector } from './channel-credentials'; import { SecureConnector, SecureConnectResult } from './channel-credentials';
import { ChannelOptions } from './channel-options'; import { ChannelOptions } from './channel-options';
import { import {
ChannelzCallTracker, ChannelzCallTracker,
@ -651,7 +651,7 @@ export class Http2SubchannelConnector implements SubchannelConnector {
} }
private createSession( private createSession(
underlyingConnection: Socket, secureConnectResult: SecureConnectResult,
address: SubchannelAddress, address: SubchannelAddress,
options: ChannelOptions options: ChannelOptions
): Promise<Http2Transport> { ): Promise<Http2Transport> {
@ -669,10 +669,11 @@ export class Http2SubchannelConnector implements SubchannelConnector {
remoteName = uriToString(parsedTarget); remoteName = uriToString(parsedTarget);
} }
} }
const scheme = secureConnectResult.secure ? 'https' : 'http';
const targetPath = getDefaultAuthority(realTarget); const targetPath = getDefaultAuthority(realTarget);
const session = http2.connect(`http://${targetPath}`, { const session = http2.connect(`${scheme}://${targetPath}`, {
createConnection: (authority, option) => { createConnection: (authority, option) => {
return underlyingConnection; return secureConnectResult.socket;
}, },
settings: { settings: {
initialWindowSize: initialWindowSize:
@ -736,14 +737,14 @@ export class Http2SubchannelConnector implements SubchannelConnector {
return Promise.reject(); return Promise.reject();
} }
let tcpConnection: net.Socket | null = null; let tcpConnection: net.Socket | null = null;
let secureConnection: net.Socket | null = null; let secureConnectResult: SecureConnectResult | null = null;
try { try {
tcpConnection = await this.tcpConnect(address, options); tcpConnection = await this.tcpConnect(address, options);
secureConnection = await secureConnector.connect(tcpConnection); secureConnectResult = await secureConnector.connect(tcpConnection);
return this.createSession(secureConnection, address, options); return this.createSession(secureConnectResult, address, options);
} catch (e) { } catch (e) {
tcpConnection?.destroy(); tcpConnection?.destroy();
secureConnection?.destroy(); secureConnectResult?.socket.destroy();
throw e; throw e;
} }
} }