Skip to content

Commit 418b0d6

Browse files
committed
test(mbe): add more config tests for master express
Ticket: WP-5339
1 parent eefd2c6 commit 418b0d6

File tree

6 files changed

+84
-110
lines changed

6 files changed

+84
-110
lines changed

src/__tests__/config.test.ts

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ describe('Configuration', () => {
1212
beforeEach(() => {
1313
// Reset to original environment and clear all relevant variables
1414
process.env = { ...originalEnv };
15+
delete process.env.APP_MODE;
16+
delete process.env.BITGO_APP_MODE;
1517
delete process.env.KMS_URL;
1618
delete process.env.ENCLAVED_EXPRESS_URL;
1719
delete process.env.ENCLAVED_EXPRESS_CERT;
1820
delete process.env.TLS_MODE;
1921
delete process.env.TLS_KEY;
2022
delete process.env.TLS_CERT;
21-
delete process.env.MTLS_REQUEST_CERT;
2223
delete process.env.MTLS_REJECT_UNAUTHORIZED;
2324
delete process.env.MTLS_ALLOWED_CLIENT_FINGERPRINTS;
2425
delete process.env.ALLOW_SELF_SIGNED;
@@ -140,14 +141,12 @@ describe('Configuration', () => {
140141
process.env.KMS_URL = 'http://localhost:3000';
141142
process.env.TLS_KEY = mockTlsKey;
142143
process.env.TLS_CERT = mockTlsCert;
143-
process.env.MTLS_REQUEST_CERT = 'true';
144144
process.env.MTLS_REJECT_UNAUTHORIZED = 'true';
145145
process.env.MTLS_ALLOWED_CLIENT_FINGERPRINTS = 'ABC123,DEF456';
146146

147147
const cfg = initConfig();
148148
isEnclavedConfig(cfg).should.be.true();
149149
if (isEnclavedConfig(cfg)) {
150-
cfg.mtlsRequestCert!.should.be.true();
151150
cfg.mtlsAllowedClientFingerprints!.should.deepEqual(['ABC123', 'DEF456']);
152151
cfg.kmsUrl.should.equal('http://localhost:3000');
153152
cfg.tlsKey!.should.equal(mockTlsKey);
@@ -261,36 +260,13 @@ describe('Configuration', () => {
261260
}
262261
});
263262

264-
it('should read mTLS settings from environment variables', () => {
265-
process.env.MTLS_REQUEST_CERT = 'true';
266-
process.env.ALLOW_SELF_SIGNED = 'true';
267-
process.env.MTLS_ALLOWED_CLIENT_FINGERPRINTS = 'ABC123,DEF456';
268-
269-
const cfg = initConfig();
270-
isMasterExpressConfig(cfg).should.be.true();
271-
if (isMasterExpressConfig(cfg)) {
272-
cfg.mtlsRequestCert!.should.be.true();
273-
cfg.allowSelfSigned!.should.be.true();
274-
cfg.mtlsAllowedClientFingerprints!.should.deepEqual(['ABC123', 'DEF456']);
275-
cfg.enclavedExpressUrl.should.equal('https://localhost:3080');
276-
}
277-
});
278-
279263
it('should throw error when ENCLAVED_EXPRESS_URL is not set', () => {
280264
delete process.env.ENCLAVED_EXPRESS_URL;
281265
(() => initConfig()).should.throw(
282266
'ENCLAVED_EXPRESS_URL environment variable is required and cannot be empty',
283267
);
284268
});
285269

286-
it('should suceed if mTLS mode is enabled but mTLS_REQUEST_CERT is false', () => {
287-
process.env.MTLS_REQUEST_CERT = 'false';
288-
const cfg = initConfig();
289-
isMasterExpressConfig(cfg).should.be.true();
290-
if (isMasterExpressConfig(cfg)) {
291-
cfg.mtlsRequestCert!.should.be.false();
292-
}
293-
294270
it('should throw error when ENCLAVED_EXPRESS_URL is empty', () => {
295271
process.env.ENCLAVED_EXPRESS_URL = '';
296272
(() => initConfig()).should.throw(
@@ -327,8 +303,6 @@ describe('Configuration', () => {
327303
});
328304

329305
it('should handle URL protocol conversion correctly', () => {
330-
process.env.ENCLAVED_EXPRESS_CERT = mockSecuredExpressCert;
331-
332306
// Test with URL that already has protocol
333307
process.env.ENCLAVED_EXPRESS_URL = 'https://enclaved.example.com:3080';
334308
let cfg = initConfig();
@@ -356,8 +330,6 @@ describe('Configuration', () => {
356330
});
357331

358332
it('should handle custom BitGo root URI protocol conversion', () => {
359-
process.env.ENCLAVED_EXPRESS_URL = 'http://localhost:3080';
360-
process.env.ENCLAVED_EXPRESS_CERT = mockSecuredExpressCert;
361333
process.env.BITGO_CUSTOM_ROOT_URI = 'bitgo.example.com';
362334
const cfg = initConfig();
363335
isMasterExpressConfig(cfg).should.be.true();

src/enclavedApp.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,15 @@ import logger from './logger';
2222
* Create a startup function which will be run upon server initialization
2323
*/
2424
export function startup(config: EnclavedConfig, baseUri: string): () => void {
25-
return function () {
26-
logger.info('BitGo Enclaved Express running');
25+
return () => {
26+
logger.info('Enclaved Express server starting...');
2727
logger.info(`Base URI: ${baseUri}`);
28-
logger.info(`TLS Mode: ${config.tlsMode}`);
29-
logger.info(`mTLS Enabled: ${config.tlsMode === TlsMode.MTLS}`);
30-
logger.info(`Request Client Cert: ${config.mtlsRequestCert}`);
31-
logger.info(`Allow Self-Signed: ${config.allowSelfSigned}`);
28+
logger.info(`mTLS Mode: ${config.tlsMode}`);
29+
logger.info(`Allow Self-Signed Certificates: ${config.allowSelfSigned}`);
30+
logger.info(`Port: ${config.port}`);
31+
logger.info(`Bind: ${config.bind}`);
3232
logger.info(`KMS URL: ${config.kmsUrl}`);
33-
if (config.mtlsAllowedClientFingerprints?.length) {
34-
logger.info(
35-
`Allowed Client Fingerprints: ${config.mtlsAllowedClientFingerprints.length} configured`,
36-
);
37-
}
33+
logger.info('Enclaved Express server started successfully');
3834
};
3935
}
4036

@@ -48,7 +44,7 @@ async function createHttpsServer(
4844
app: express.Application,
4945
config: EnclavedConfig,
5046
): Promise<https.Server> {
51-
const { tlsKey, tlsCert, tlsMode, mtlsRequestCert } = config;
47+
const { tlsKey, tlsCert, tlsMode } = config;
5248

5349
if (!tlsKey || !tlsCert) {
5450
throw new Error('TLS key and certificate must be provided for HTTPS server');
@@ -58,9 +54,8 @@ async function createHttpsServer(
5854
secureOptions: SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1,
5955
key: tlsKey,
6056
cert: tlsCert,
61-
// Only request cert if mTLS is enabled AND we want to request certs
62-
// This prevents TLS handshake failures when no cert is provided
63-
requestCert: tlsMode === TlsMode.MTLS && mtlsRequestCert,
57+
// Always request cert if mTLS is enabled
58+
requestCert: tlsMode === TlsMode.MTLS,
6459
rejectUnauthorized: false, // Handle authorization in middleware
6560
};
6661

src/initConfig.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ const defaultEnclavedConfig: EnclavedConfig = {
5050
bind: 'localhost',
5151
timeout: 305 * 1000,
5252
logFile: '',
53+
debugNamespace: [],
5354
kmsUrl: '', // Will be overridden by environment variable
5455
tlsMode: TlsMode.MTLS,
55-
mtlsRequestCert: true,
5656
allowSelfSigned: false,
5757
};
5858

@@ -101,7 +101,6 @@ function enclavedEnvConfig(): Partial<EnclavedConfig> {
101101
tlsKey: readEnvVar('TLS_KEY'),
102102
tlsCert: readEnvVar('TLS_CERT'),
103103
tlsMode: determineTlsMode(),
104-
mtlsRequestCert: readEnvVar('MTLS_REQUEST_CERT')?.toLowerCase() !== 'false',
105104
mtlsAllowedClientFingerprints: readEnvVar('MTLS_ALLOWED_CLIENT_FINGERPRINTS')?.split(','),
106105
allowSelfSigned: readEnvVar('ALLOW_SELF_SIGNED') === 'true',
107106
};
@@ -132,7 +131,6 @@ function mergeEnclavedConfigs(...configs: Partial<EnclavedConfig>[]): EnclavedCo
132131
tlsKey: get('tlsKey'),
133132
tlsCert: get('tlsCert'),
134133
tlsMode: get('tlsMode'),
135-
mtlsRequestCert: get('mtlsRequestCert'),
136134
mtlsAllowedClientFingerprints: get('mtlsAllowedClientFingerprints'),
137135
allowSelfSigned: get('allowSelfSigned'),
138136
};
@@ -186,13 +184,13 @@ const defaultMasterExpressConfig: MasterExpressConfig = {
186184
bind: 'localhost',
187185
timeout: 305 * 1000,
188186
logFile: '',
187+
debugNamespace: [],
189188
env: 'test',
190189
disableEnvCheck: true,
191190
authVersion: 2,
192191
enclavedExpressUrl: '', // Will be overridden by environment variable
193192
enclavedExpressCert: '', // Will be overridden by environment variable
194193
tlsMode: TlsMode.MTLS,
195-
mtlsRequestCert: true,
196194
allowSelfSigned: false,
197195
};
198196

@@ -219,9 +217,7 @@ function masterExpressEnvConfig(): Partial<MasterExpressConfig> {
219217
}
220218

221219
// Debug mTLS environment variables
222-
const mtlsRequestCertRaw = readEnvVar('MTLS_REQUEST_CERT');
223220
const allowSelfSignedRaw = readEnvVar('ALLOW_SELF_SIGNED');
224-
const mtlsRequestCert = mtlsRequestCertRaw?.toLowerCase() !== 'false';
225221
const allowSelfSigned = allowSelfSignedRaw === 'true';
226222

227223
return {
@@ -248,7 +244,6 @@ function masterExpressEnvConfig(): Partial<MasterExpressConfig> {
248244
tlsKey: readEnvVar('TLS_KEY'),
249245
tlsCert: readEnvVar('TLS_CERT'),
250246
tlsMode,
251-
mtlsRequestCert,
252247
mtlsAllowedClientFingerprints: readEnvVar('MTLS_ALLOWED_CLIENT_FINGERPRINTS')?.split(','),
253248
allowSelfSigned,
254249
};
@@ -287,7 +282,6 @@ function mergeMasterExpressConfigs(
287282
tlsKey: get('tlsKey'),
288283
tlsCert: get('tlsCert'),
289284
tlsMode: get('tlsMode'),
290-
mtlsRequestCert: get('mtlsRequestCert'),
291285
mtlsAllowedClientFingerprints: get('mtlsAllowedClientFingerprints'),
292286
allowSelfSigned: get('allowSelfSigned'),
293287
};

src/masterExpressApp.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,14 @@ import { setupRoutes } from './routes/master';
2121
* Create a startup function which will be run upon server initialization
2222
*/
2323
export function startup(config: MasterExpressConfig, baseUri: string): () => void {
24-
return function () {
25-
logger.info('BitGo Master Express running');
24+
return () => {
25+
logger.info('Master Express server starting...');
2626
logger.info(`Base URI: ${baseUri}`);
27-
logger.info(`Environment: ${config.env}`);
2827
logger.info(`TLS Mode: ${config.tlsMode}`);
29-
logger.info(`mTLS Enabled: ${config.tlsMode === TlsMode.MTLS}`);
30-
logger.info(`Request Client Cert: ${config.mtlsRequestCert}`);
31-
logger.info(`Allow Self-Signed: ${config.allowSelfSigned}`);
32-
if (config.mtlsAllowedClientFingerprints?.length) {
33-
logger.info(
34-
`Allowed Client Fingerprints: ${config.mtlsAllowedClientFingerprints.length} configured`,
35-
);
36-
}
28+
logger.info(`Port: ${config.port}`);
29+
logger.info(`Bind: ${config.bind}`);
30+
logger.info(`Enclaved Express URL: ${config.enclavedExpressUrl}`);
31+
logger.info('Master Express server started successfully');
3732
};
3833
}
3934

@@ -47,7 +42,7 @@ async function createHttpsServer(
4742
app: express.Application,
4843
config: MasterExpressConfig,
4944
): Promise<https.Server> {
50-
const { tlsKey, tlsCert, tlsMode, mtlsRequestCert } = config;
45+
const { tlsKey, tlsCert, tlsMode } = config;
5146

5247
if (!tlsKey || !tlsCert) {
5348
throw new Error('TLS key and certificate must be provided for HTTPS server');
@@ -57,9 +52,8 @@ async function createHttpsServer(
5752
secureOptions: SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1,
5853
key: tlsKey,
5954
cert: tlsCert,
60-
// Only request cert if mTLS is enabled AND we want to request certs
61-
// This prevents TLS handshake failures when no cert is provided
62-
requestCert: tlsMode === TlsMode.MTLS && mtlsRequestCert,
55+
// Always request cert if mTLS is enabled
56+
requestCert: tlsMode === TlsMode.MTLS,
6357
rejectUnauthorized: false, // Handle authorization in middleware
6458
};
6559

src/shared/appUtils.ts

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import bodyParser from 'body-parser';
99
import pjson from '../../package.json';
1010
import logger from '../logger';
1111

12-
import { Config, TlsMode } from '../shared/types';
12+
import { Config, TlsMode, MasterExpressConfig } from '../shared/types';
1313

1414
/**
1515
* Set up the logging middleware provided by morgan
@@ -121,7 +121,6 @@ export async function prepareIpc(ipcSocketFilePath: string): Promise<void> {
121121
*/
122122
export function createMtlsMiddleware(config: {
123123
tlsMode: TlsMode;
124-
mtlsRequestCert: boolean;
125124
allowSelfSigned?: boolean;
126125
mtlsAllowedClientFingerprints?: string[];
127126
}): express.RequestHandler {
@@ -132,8 +131,8 @@ export function createMtlsMiddleware(config: {
132131
const hasValidClientCert =
133132
clientCert && Object.keys(clientCert).length > 0 && clientCert.subject;
134133

135-
// If client cert is required but not provided
136-
if (config.mtlsRequestCert && !hasValidClientCert) {
134+
// If mTLS is enabled, client certificate is always required
135+
if (config.tlsMode === TlsMode.MTLS && !hasValidClientCert) {
137136
return res.status(403).json({
138137
error: 'mTLS Authentication Failed',
139138
message: 'Client certificate is required for this endpoint',
@@ -176,30 +175,53 @@ export function createMtlsMiddleware(config: {
176175
/**
177176
* Validate that TLS certificates are properly loaded when TLS is enabled
178177
*/
179-
export function validateTlsCertificates(config: {
180-
tlsMode: TlsMode;
181-
tlsKey?: string;
182-
tlsCert?: string;
183-
}): void {
184-
if (config.tlsMode !== TlsMode.DISABLED) {
185-
if (!config.tlsKey || !config.tlsCert) {
186-
throw new Error('TLS is enabled but certificates are not properly loaded');
178+
export function validateTlsCertificates(config: Config): void {
179+
if (config.tlsMode === TlsMode.DISABLED) {
180+
return;
181+
}
182+
183+
if (!config.tlsKey || !config.tlsCert) {
184+
throw new Error('TLS key and certificate must be provided when TLS is enabled');
185+
}
186+
187+
// Validate certificate format
188+
try {
189+
// Basic PEM format validation
190+
if (
191+
!config.tlsKey.includes('-----BEGIN PRIVATE KEY-----') &&
192+
!config.tlsKey.includes('-----BEGIN RSA PRIVATE KEY-----')
193+
) {
194+
throw new Error('Invalid TLS private key format');
187195
}
196+
if (!config.tlsCert.includes('-----BEGIN CERTIFICATE-----')) {
197+
throw new Error('Invalid TLS certificate format');
198+
}
199+
} catch (error) {
200+
throw new Error(
201+
`TLS certificate validation failed: ${
202+
error instanceof Error ? error.message : String(error)
203+
}`,
204+
);
188205
}
189206
}
190207

191208
/**
192209
* Validate Master Express configuration
193210
*/
194-
export function validateMasterExpressConfig(config: {
195-
tlsMode: TlsMode;
196-
enclavedExpressUrl: string;
197-
enclavedExpressCert: string;
198-
}): void {
199-
if (!config.enclavedExpressUrl) {
200-
throw new Error('ENCLAVED_EXPRESS_URL is required for Master Express mode');
211+
export function validateMasterExpressConfig(config: MasterExpressConfig): void {
212+
// Validate that we have the required enclaved express certificate for mTLS
213+
if (config.tlsMode === TlsMode.MTLS && !config.enclavedExpressCert) {
214+
throw new Error('Enclaved Express certificate is required for mTLS mode');
201215
}
202-
if (config.tlsMode === 'mtls' && !config.enclavedExpressCert) {
203-
throw new Error('ENCLAVED_EXPRESS_CERT is required for Master Express mode');
216+
217+
// Validate client certificate if mTLS is enabled
218+
if (config.tlsMode === TlsMode.MTLS) {
219+
const hasValidClientCert =
220+
config.enclavedExpressCert &&
221+
config.enclavedExpressCert.includes('-----BEGIN CERTIFICATE-----');
222+
223+
if (!hasValidClientCert) {
224+
throw new Error('Valid client certificate is required for mTLS mode');
225+
}
204226
}
205227
}

0 commit comments

Comments
 (0)