From 5e3857e632ae73818fb66d41041ad76abd8c2b8a Mon Sep 17 00:00:00 2001 From: Pranav Jain Date: Thu, 5 Jun 2025 13:22:57 -0400 Subject: [PATCH 1/2] fix: both tls modes working with http/https Ticket: WP-4676 --- src/config.ts | 16 ++++---- src/enclavedApp.ts | 2 +- .../enclavedExpressClient.ts | 40 ++++++++++++++----- src/masterExpressApp.ts | 34 +++++++++------- 4 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/config.ts b/src/config.ts index 5d0958cf..eaa18aa1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -196,17 +196,19 @@ const defaultMasterExpressConfig: MasterExpressConfig = { allowSelfSigned: false, }; -function forceSecureUrl(url: string): string { +function forceSecureUrl(url: string, tlsMode: TlsMode): string { const regex = new RegExp(/(^\w+:|^)\/\//); + const protocol = tlsMode === TlsMode.DISABLED ? 'http' : 'https'; if (regex.test(url)) { - return url.replace(/(^\w+:|^)\/\//, 'https://'); + return url.replace(/(^\w+:|^)\/\//, `${protocol}://`); } - return `https://${url}`; + return `${protocol}://${url}`; } function masterExpressEnvConfig(): Partial { const enclavedExpressUrl = readEnvVar('ENCLAVED_EXPRESS_URL'); const enclavedExpressCert = readEnvVar('ENCLAVED_EXPRESS_CERT'); + const tlsMode = determineTlsMode(); if (!enclavedExpressUrl) { throw new Error('ENCLAVED_EXPRESS_URL environment variable is required and cannot be empty'); @@ -245,7 +247,7 @@ function masterExpressEnvConfig(): Partial { crtPath: readEnvVar('TLS_CERT_PATH'), tlsKey: readEnvVar('TLS_KEY'), tlsCert: readEnvVar('TLS_CERT'), - tlsMode: determineTlsMode(), + tlsMode, mtlsRequestCert, mtlsAllowedClientFingerprints: readEnvVar('MTLS_ALLOWED_CLIENT_FINGERPRINTS')?.split(','), allowSelfSigned, @@ -295,13 +297,13 @@ export function configureMasterExpressMode(): MasterExpressConfig { const env = masterExpressEnvConfig(); let config = mergeMasterExpressConfigs(env); - // Post-process URLs to ensure they use HTTPS + // Post-process URLs to ensure they use the correct protocol based on TLS mode const updates: Partial = {}; if (config.customRootUri) { - updates.customRootUri = forceSecureUrl(config.customRootUri); + updates.customRootUri = forceSecureUrl(config.customRootUri, config.tlsMode); } if (config.enclavedExpressUrl) { - updates.enclavedExpressUrl = forceSecureUrl(config.enclavedExpressUrl); + updates.enclavedExpressUrl = forceSecureUrl(config.enclavedExpressUrl, config.tlsMode); } config = { ...config, ...updates }; diff --git a/src/enclavedApp.ts b/src/enclavedApp.ts index f4ce2a1e..315710e2 100644 --- a/src/enclavedApp.ts +++ b/src/enclavedApp.ts @@ -79,7 +79,7 @@ export async function createServer( export function createBaseUri(config: EnclavedConfig): string { const { bind, port } = config; - const tls = isTLS(config); + const tls = config.tlsMode === TlsMode.MTLS; const isStandardPort = (port === 80 && !tls) || (port === 443 && tls); return `http${tls ? 's' : ''}://${bind}${!isStandardPort ? ':' + port : ''}`; } diff --git a/src/masterBitgoExpress/enclavedExpressClient.ts b/src/masterBitgoExpress/enclavedExpressClient.ts index 21b475b9..94426e37 100644 --- a/src/masterBitgoExpress/enclavedExpressClient.ts +++ b/src/masterBitgoExpress/enclavedExpressClient.ts @@ -2,6 +2,7 @@ import superagent from 'superagent'; import https from 'https'; import debug from 'debug'; import { MasterExpressConfig } from '../types'; +import { TlsMode } from '../types'; const debugLogger = debug('bitgo:express:enclavedExpressClient'); @@ -24,16 +25,17 @@ export interface IndependentKeychainResponse { export class EnclavedExpressClient { private readonly baseUrl: string; private readonly enclavedExpressCert: string; - private readonly tlsKey: string; - private readonly tlsCert: string; + private readonly tlsKey?: string; + private readonly tlsCert?: string; private readonly allowSelfSigned: boolean; private readonly coin?: string; + private readonly tlsMode: TlsMode; constructor(cfg: MasterExpressConfig, coin?: string) { if (!cfg.enclavedExpressUrl || !cfg.enclavedExpressCert) { throw new Error('enclavedExpressUrl and enclavedExpressCert are required'); } - if (!cfg.tlsKey || !cfg.tlsCert) { + if (cfg.tlsMode === TlsMode.MTLS && (!cfg.tlsKey || !cfg.tlsCert)) { throw new Error('tlsKey and tlsCert are required for mTLS communication'); } @@ -43,10 +45,14 @@ export class EnclavedExpressClient { this.tlsCert = cfg.tlsCert; this.allowSelfSigned = cfg.allowSelfSigned ?? false; this.coin = coin; + this.tlsMode = cfg.tlsMode; debugLogger('EnclavedExpressClient initialized with URL: %s', this.baseUrl); } private createHttpsAgent(): https.Agent { + if (!this.tlsKey || !this.tlsCert) { + throw new Error('TLS key and certificate are required for HTTPS agent'); + } return new https.Agent({ rejectUnauthorized: !this.allowSelfSigned, ca: this.enclavedExpressCert, @@ -59,7 +65,12 @@ export class EnclavedExpressClient { async ping(): Promise { try { debugLogger('Pinging enclaved express at %s', this.baseUrl); - await superagent.get(`${this.baseUrl}/ping`).agent(this.createHttpsAgent()).send(); + if (this.tlsMode === TlsMode.MTLS) { + await superagent.get(`${this.baseUrl}/ping`).agent(this.createHttpsAgent()).send(); + } else { + // When TLS is disabled, use plain HTTP without any TLS configuration + await superagent.get(`${this.baseUrl}/ping`).send(); + } } catch (error) { const err = error as Error; debugLogger('Failed to ping enclaved express: %s', err.message); @@ -79,13 +90,22 @@ export class EnclavedExpressClient { try { debugLogger('Creating independent keychain for coin: %s', this.coin); - const { body: keychain } = await superagent - .post(`${this.baseUrl}/api/${this.coin}/key/independent`) - .agent(this.createHttpsAgent()) - .type('json') - .send(params); + let response; + if (this.tlsMode === TlsMode.MTLS) { + response = await superagent + .post(`${this.baseUrl}/api/${this.coin}/key/independent`) + .agent(this.createHttpsAgent()) + .type('json') + .send(params); + } else { + // When TLS is disabled, use plain HTTP without any TLS configuration + response = await superagent + .post(`${this.baseUrl}/api/${this.coin}/key/independent`) + .type('json') + .send(params); + } - return keychain; + return response.body; } catch (error) { const err = error as Error; debugLogger('Failed to create independent keychain: %s', err.message); diff --git a/src/masterExpressApp.ts b/src/masterExpressApp.ts index 96552555..38f73cb3 100644 --- a/src/masterExpressApp.ts +++ b/src/masterExpressApp.ts @@ -157,20 +157,26 @@ function setupMasterExpressRoutes(app: express.Application, cfg: MasterExpressCo try { logger.debug('Pinging enclaved express'); - // Use Master Express's own certificate as client cert when connecting to Enclaved Express - const httpsAgent = new https.Agent({ - rejectUnauthorized: cfg.tlsMode === TlsMode.MTLS && !cfg.allowSelfSigned, - ca: cfg.enclavedExpressCert, - // Provide client certificate for mTLS - key: cfg.tlsKey, - cert: cfg.tlsCert, - }); - - const response = await superagent - .post(`${cfg.enclavedExpressUrl}/ping`) - .ca(cfg.enclavedExpressCert) - .agent(httpsAgent) - .send(); + let response; + if (cfg.tlsMode === TlsMode.MTLS) { + // Use Master Express's own certificate as client cert when connecting to Enclaved Express + const httpsAgent = new https.Agent({ + rejectUnauthorized: !cfg.allowSelfSigned, + ca: cfg.enclavedExpressCert, + // Provide client certificate for mTLS + key: cfg.tlsKey, + cert: cfg.tlsCert, + }); + + response = await superagent + .post(`${cfg.enclavedExpressUrl}/ping`) + .ca(cfg.enclavedExpressCert) + .agent(httpsAgent) + .send(); + } else { + // When TLS is disabled, use plain HTTP without any TLS configuration + response = await superagent.post(`${cfg.enclavedExpressUrl}/ping`).send(); + } res.json({ status: 'Successfully pinged enclaved express', From a03fea9117ff655a28c4ac8067b87b7df431e1da Mon Sep 17 00:00:00 2001 From: Pranav Jain Date: Thu, 5 Jun 2025 13:30:19 -0400 Subject: [PATCH 2/2] chore: always use https for bitgo api calls Ticket: WP-4676 --- src/config.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/config.ts b/src/config.ts index eaa18aa1..9fe13749 100644 --- a/src/config.ts +++ b/src/config.ts @@ -196,9 +196,9 @@ const defaultMasterExpressConfig: MasterExpressConfig = { allowSelfSigned: false, }; -function forceSecureUrl(url: string, tlsMode: TlsMode): string { +function determineProtocol(url: string, tlsMode: TlsMode, isBitGo = false): string { const regex = new RegExp(/(^\w+:|^)\/\//); - const protocol = tlsMode === TlsMode.DISABLED ? 'http' : 'https'; + const protocol = isBitGo ? 'https' : tlsMode === TlsMode.DISABLED ? 'http' : 'https'; if (regex.test(url)) { return url.replace(/(^\w+:|^)\/\//, `${protocol}://`); } @@ -300,10 +300,14 @@ export function configureMasterExpressMode(): MasterExpressConfig { // Post-process URLs to ensure they use the correct protocol based on TLS mode const updates: Partial = {}; if (config.customRootUri) { - updates.customRootUri = forceSecureUrl(config.customRootUri, config.tlsMode); + updates.customRootUri = determineProtocol(config.customRootUri, config.tlsMode, true); } if (config.enclavedExpressUrl) { - updates.enclavedExpressUrl = forceSecureUrl(config.enclavedExpressUrl, config.tlsMode); + updates.enclavedExpressUrl = determineProtocol( + config.enclavedExpressUrl, + config.tlsMode, + false, + ); } config = { ...config, ...updates };