Skip to content
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
20 changes: 13 additions & 7 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,17 +196,19 @@ const defaultMasterExpressConfig: MasterExpressConfig = {
allowSelfSigned: false,
};

function forceSecureUrl(url: string): string {
function determineProtocol(url: string, tlsMode: TlsMode, isBitGo = false): string {
const regex = new RegExp(/(^\w+:|^)\/\//);
const protocol = isBitGo ? 'https' : 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<MasterExpressConfig> {
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');
Expand Down Expand Up @@ -245,7 +247,7 @@ function masterExpressEnvConfig(): Partial<MasterExpressConfig> {
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,
Expand Down Expand Up @@ -295,13 +297,17 @@ 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<MasterExpressConfig> = {};
if (config.customRootUri) {
updates.customRootUri = forceSecureUrl(config.customRootUri);
updates.customRootUri = determineProtocol(config.customRootUri, config.tlsMode, true);
}
if (config.enclavedExpressUrl) {
updates.enclavedExpressUrl = forceSecureUrl(config.enclavedExpressUrl);
updates.enclavedExpressUrl = determineProtocol(
config.enclavedExpressUrl,
config.tlsMode,
false,
);
}
config = { ...config, ...updates };

Expand Down
2 changes: 1 addition & 1 deletion src/enclavedApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 : ''}`;
}
Expand Down
40 changes: 30 additions & 10 deletions src/masterBitgoExpress/enclavedExpressClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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');
}

Expand All @@ -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,
Expand All @@ -59,7 +65,12 @@ export class EnclavedExpressClient {
async ping(): Promise<void> {
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);
Expand All @@ -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);
Expand Down
34 changes: 20 additions & 14 deletions src/masterExpressApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down