Skip to content

Commit c9671af

Browse files
committed
Add/fix support for various TLS passthrough options for Websockets
Previously, clientCertificateHostMap was explicitly not supported, while extraCACertificates was quietly ignored by remote clients, and ignoreHostHttpsErrors ignored only certificate issues, not other TLS configuration problems. With this change, the full set of passthrough connection options should be handled the same way in both cases.
1 parent f4a9898 commit c9671af

File tree

4 files changed

+42
-14
lines changed

4 files changed

+42
-14
lines changed

src/rules/requests/request-handler-definitions.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -771,8 +771,8 @@ export class PassThroughHandlerDefinition extends Serializable implements Reques
771771
this.proxyConfig = options.proxyConfig;
772772
this.simulateConnectionErrors = !!options.simulateConnectionErrors;
773773

774-
this.clientCertificateHostMap = options.clientCertificateHostMap || {};
775774
this.extraCACertificates = options.trustAdditionalCAs || [];
775+
this.clientCertificateHostMap = options.clientCertificateHostMap || {};
776776

777777
if (options.beforeRequest && options.transformRequest && !_.isEmpty(options.transformRequest)) {
778778
throw new Error("BeforeRequest and transformRequest options are mutually exclusive");
@@ -874,8 +874,9 @@ export class PassThroughHandlerDefinition extends Serializable implements Reques
874874
return {
875875
type: this.type,
876876
...this.forwarding ? {
877-
forwardToLocation: this.forwarding.targetHost,
878-
forwarding: this.forwarding
877+
forwarding: this.forwarding,
878+
// Backward compat:
879+
forwardToLocation: this.forwarding.targetHost
879880
} : {},
880881
proxyConfig: serializeProxyConfig(this.proxyConfig, channel),
881882
lookupOptions: this.lookupOptions,

src/rules/requests/request-handlers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,9 +746,11 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
746746
lookup: getDnsLookupFunction(this.lookupOptions) as typeof dns.lookup,
747747
// ^ Cast required to handle __promisify__ type hack in the official Node types
748748
agent,
749+
749750
// TLS options:
750751
...UPSTREAM_TLS_OPTIONS,
751-
minVersion: strictHttpsChecks ? tls.DEFAULT_MIN_VERSION : 'TLSv1', // Allow TLSv1, if !strict
752+
// Allow TLSv1, if !strict
753+
minVersion: strictHttpsChecks ? tls.DEFAULT_MIN_VERSION : 'TLSv1',
752754
rejectUnauthorized: strictHttpsChecks,
753755
...clientCert,
754756
...caConfig

src/rules/websockets/websocket-handler-definitions.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
ClientServerChannel,
77
Serializable,
88
SerializedProxyConfig,
9-
serializeProxyConfig
9+
serializeProxyConfig,
10+
serializeBuffer
1011
} from "../../serialization/serialization";
1112

1213
import { Explainable, Headers } from "../../types";
@@ -52,6 +53,7 @@ export interface SerializedPassThroughWebSocketData {
5253
proxyConfig?: SerializedProxyConfig;
5354
ignoreHostCertificateErrors?: string[] | boolean; // Doesn't match option name, backward compat
5455
extraCACertificates?: Array<{ cert: string } | { certPath: string }>;
56+
clientCertificateHostMap?: { [host: string]: { pfx: string, passphrase?: string } };
5557
}
5658

5759
export class PassThroughWebSocketHandlerDefinition extends Serializable implements WebSocketHandlerDefinition {
@@ -63,17 +65,15 @@ export class PassThroughWebSocketHandlerDefinition extends Serializable implemen
6365

6466
public readonly forwarding?: ForwardingOptions;
6567
public readonly ignoreHostHttpsErrors: string[] | boolean = [];
68+
public readonly clientCertificateHostMap: {
69+
[host: string]: { pfx: Buffer, passphrase?: string }
70+
};
6671

6772
public readonly extraCACertificates: Array<{ cert: string | Buffer } | { certPath: string }> = [];
6873

6974
constructor(options: PassThroughWebSocketHandlerOptions = {}) {
7075
super();
7176

72-
this.ignoreHostHttpsErrors = options.ignoreHostHttpsErrors || [];
73-
if (!Array.isArray(this.ignoreHostHttpsErrors) && typeof this.ignoreHostHttpsErrors !== 'boolean') {
74-
throw new Error("ignoreHostHttpsErrors must be an array or a boolean");
75-
}
76-
7777
// If a location is provided, and it's not a bare hostname, it must be parseable
7878
const { forwarding } = options;
7979
if (forwarding && forwarding.targetHost.includes('/')) {
@@ -87,10 +87,19 @@ export class PassThroughWebSocketHandlerDefinition extends Serializable implemen
8787
`);
8888
}
8989
}
90+
9091
this.forwarding = options.forwarding;
9192

93+
this.ignoreHostHttpsErrors = options.ignoreHostHttpsErrors || [];
94+
if (!Array.isArray(this.ignoreHostHttpsErrors) && typeof this.ignoreHostHttpsErrors !== 'boolean') {
95+
throw new Error("ignoreHostHttpsErrors must be an array or a boolean");
96+
}
97+
9298
this.lookupOptions = options.lookupOptions;
9399
this.proxyConfig = options.proxyConfig;
100+
101+
this.extraCACertificates = options.trustAdditionalCAs || [];
102+
this.clientCertificateHostMap = options.clientCertificateHostMap || {};
94103
}
95104

96105
explain() {
@@ -120,6 +129,9 @@ export class PassThroughWebSocketHandlerDefinition extends Serializable implemen
120129
return certObject;
121130
}
122131
}),
132+
clientCertificateHostMap: _.mapValues(this.clientCertificateHostMap,
133+
({ pfx, passphrase }) => ({ pfx: serializeBuffer(pfx), passphrase })
134+
)
123135
};
124136
}
125137
}

src/rules/websockets/websocket-handlers.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as WebSocket from 'ws';
88

99
import {
1010
ClientServerChannel,
11+
deserializeBuffer,
1112
deserializeProxyConfig
1213
} from "../../serialization/serialization";
1314

@@ -296,12 +297,18 @@ export class PassThroughWebSocketHandler extends PassThroughWebSocketHandlerDefi
296297

297298
const effectivePort = getEffectivePort(parsedUrl);
298299

299-
const checkServerCertificate = shouldUseStrictHttps(
300+
const strictHttpsChecks = shouldUseStrictHttps(
300301
parsedUrl.hostname!,
301302
effectivePort,
302303
this.ignoreHostHttpsErrors
303304
);
304305

306+
// Use a client cert if it's listed for the host+port or whole hostname
307+
const hostWithPort = `${parsedUrl.hostname}:${effectivePort}`;
308+
const clientCert = this.clientCertificateHostMap[hostWithPort] ||
309+
this.clientCertificateHostMap[parsedUrl.hostname!] ||
310+
{};
311+
305312
const trustedCerts = await this.trustedCACertificates();
306313
const caConfig = trustedCerts
307314
? { ca: trustedCerts }
@@ -334,7 +341,10 @@ export class PassThroughWebSocketHandler extends PassThroughWebSocketHandlerDefi
334341

335342
// TLS options:
336343
...UPSTREAM_TLS_OPTIONS,
337-
rejectUnauthorized: checkServerCertificate,
344+
// Allow TLSv1, if !strict:
345+
minVersion: strictHttpsChecks ? tls.DEFAULT_MIN_VERSION : 'TLSv1',
346+
rejectUnauthorized: strictHttpsChecks,
347+
...clientCert,
338348
...caConfig
339349
} as WebSocket.ClientOptions & { lookup: any, maxPayload: number });
340350

@@ -387,9 +397,12 @@ export class PassThroughWebSocketHandler extends PassThroughWebSocketHandlerDefi
387397
// By default, we assume we just need to assign the right prototype
388398
return _.create(this.prototype, {
389399
...data,
390-
extraCACertificates: data.extraCACertificates || [],
391400
proxyConfig: deserializeProxyConfig(data.proxyConfig, channel, ruleParams),
392-
ignoreHostHttpsErrors: data.ignoreHostCertificateErrors
401+
extraCACertificates: data.extraCACertificates || [],
402+
ignoreHostHttpsErrors: data.ignoreHostCertificateErrors,
403+
clientCertificateHostMap: _.mapValues(data.clientCertificateHostMap,
404+
({ pfx, passphrase }) => ({ pfx: deserializeBuffer(pfx), passphrase })
405+
),
393406
});
394407
}
395408
}

0 commit comments

Comments
 (0)