diff --git a/README.md b/README.md index dca8a9c..af941ef 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,7 @@ Both modes use the same TLS configuration variables: ### Logging and Debug -- `LOGFILE` - Path to log file (optional) -- `DEBUG_NAMESPACE` - Debug namespaces to enable (e.g., 'enclaved:\*') +- `HTTP_LOGFILE` - Path to HTTP request log file (optional, used by Morgan for HTTP access logs) ## Quick Start @@ -232,6 +231,7 @@ curl -k -X POST https://localhost:3081/ping/enclavedExpress ``` Notes: + - `host.containers.internal` is a special DNS name that resolves to the host machine from inside containers - The `:Z` option in volume mounts is specific to SELinux-enabled systems and ensures proper volume labeling - The logs directory will be created with appropriate permissions if it doesn't exist diff --git a/src/__tests__/api/enclaved/postIndependentKey.test.ts b/src/__tests__/api/enclaved/postIndependentKey.test.ts index 3b94087..cb2a75d 100644 --- a/src/__tests__/api/enclaved/postIndependentKey.test.ts +++ b/src/__tests__/api/enclaved/postIndependentKey.test.ts @@ -33,10 +33,9 @@ describe('postIndependentKey', () => { port: 0, // Let OS assign a free port bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', kmsUrl: kmsUrl, tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/enclaved/postMpcV2Key.test.ts b/src/__tests__/api/enclaved/postMpcV2Key.test.ts index 8a8af68..e8edb69 100644 --- a/src/__tests__/api/enclaved/postMpcV2Key.test.ts +++ b/src/__tests__/api/enclaved/postMpcV2Key.test.ts @@ -35,10 +35,9 @@ describe('postMpcV2Key', () => { port: 0, // Let OS assign a free port bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', kmsUrl: kmsUrl, tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/enclaved/recoveryMultisigTransaction.test.ts b/src/__tests__/api/enclaved/recoveryMultisigTransaction.test.ts index 5c63b1e..601d0a0 100644 --- a/src/__tests__/api/enclaved/recoveryMultisigTransaction.test.ts +++ b/src/__tests__/api/enclaved/recoveryMultisigTransaction.test.ts @@ -19,9 +19,8 @@ describe('UTXO recovery', () => { port: 0, bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, kmsUrl: 'kms.example.com', }; diff --git a/src/__tests__/api/enclaved/recoveryMusigEth.test.ts b/src/__tests__/api/enclaved/recoveryMusigEth.test.ts index d21bea3..b06e4a6 100644 --- a/src/__tests__/api/enclaved/recoveryMusigEth.test.ts +++ b/src/__tests__/api/enclaved/recoveryMusigEth.test.ts @@ -36,10 +36,9 @@ describe('recoveryMultisigTransaction', () => { port: 0, // Let OS assign a free port bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', kmsUrl: kmsUrl, tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/enclaved/signMpcRecoveryTransaction.test.ts b/src/__tests__/api/enclaved/signMpcRecoveryTransaction.test.ts index e7a79ae..41413a9 100644 --- a/src/__tests__/api/enclaved/signMpcRecoveryTransaction.test.ts +++ b/src/__tests__/api/enclaved/signMpcRecoveryTransaction.test.ts @@ -10,15 +10,14 @@ import { AppMode, EnclavedConfig, TlsMode } from '../../../shared/types'; describe('EdDSA Recovery Signing', () => { let agent: supertest.SuperTest; const config: EnclavedConfig = { + kmsUrl: 'http://localhost:3000', appMode: AppMode.ENCLAVED, port: 0, bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, - kmsUrl: 'kms.example.com', }; const commonKeychain = diff --git a/src/__tests__/api/enclaved/signMpcTransaction.test.ts b/src/__tests__/api/enclaved/signMpcTransaction.test.ts index 1d57b2d..8327b29 100644 --- a/src/__tests__/api/enclaved/signMpcTransaction.test.ts +++ b/src/__tests__/api/enclaved/signMpcTransaction.test.ts @@ -40,10 +40,9 @@ describe('signMpcTransaction', () => { port: 0, // Let OS assign a free port bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', kmsUrl: kmsUrl, tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/enclaved/signMultisigTransaction.test.ts b/src/__tests__/api/enclaved/signMultisigTransaction.test.ts index 241775c..df97430 100644 --- a/src/__tests__/api/enclaved/signMultisigTransaction.test.ts +++ b/src/__tests__/api/enclaved/signMultisigTransaction.test.ts @@ -33,10 +33,9 @@ describe('signMultisigTransaction', () => { port: 0, // Let OS assign a free port bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', kmsUrl: kmsUrl, tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/master/accelerate.test.ts b/src/__tests__/api/master/accelerate.test.ts index a2ddebc..11c5fb2 100644 --- a/src/__tests__/api/master/accelerate.test.ts +++ b/src/__tests__/api/master/accelerate.test.ts @@ -23,14 +23,13 @@ describe('POST /api/:coin/wallet/:walletId/accelerate', () => { port: 0, // Let OS assign a free port bind: 'localhost', timeout: 30000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl: enclavedExpressUrl, enclavedExpressCert: 'test-cert', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/master/consolidate.test.ts b/src/__tests__/api/master/consolidate.test.ts index 2e983b7..6dea2b1 100644 --- a/src/__tests__/api/master/consolidate.test.ts +++ b/src/__tests__/api/master/consolidate.test.ts @@ -26,14 +26,13 @@ describe('POST /api/:coin/wallet/:walletId/consolidateunspents', () => { port: 0, bind: 'localhost', timeout: 30000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl: enclavedExpressUrl, enclavedExpressCert: 'test-cert', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/master/consolidateUnspents.test.ts b/src/__tests__/api/master/consolidateUnspents.test.ts index 310437a..3facc47 100644 --- a/src/__tests__/api/master/consolidateUnspents.test.ts +++ b/src/__tests__/api/master/consolidateUnspents.test.ts @@ -23,14 +23,13 @@ describe('POST /api/:coin/wallet/:walletId/consolidateunspents', () => { port: 0, bind: 'localhost', timeout: 30000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl: enclavedExpressUrl, enclavedExpressCert: 'test-cert', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/master/generateWallet.test.ts b/src/__tests__/api/master/generateWallet.test.ts index 5137627..3b1efc1 100644 --- a/src/__tests__/api/master/generateWallet.test.ts +++ b/src/__tests__/api/master/generateWallet.test.ts @@ -25,14 +25,13 @@ describe('POST /api/:coin/wallet/generate', () => { port: 0, // Let OS assign a free port bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl: enclavedExpressUrl, enclavedExpressCert: 'dummy-cert', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; @@ -1122,12 +1121,11 @@ describe('POST /api/:coin/wallet/generate', () => { port: 0, bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/master/musigRecovery.test.ts b/src/__tests__/api/master/musigRecovery.test.ts index 86123ee..1b09c9f 100644 --- a/src/__tests__/api/master/musigRecovery.test.ts +++ b/src/__tests__/api/master/musigRecovery.test.ts @@ -23,14 +23,13 @@ describe('POST /api/:coin/wallet/recovery', () => { port: 0, // Let OS assign a free port bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl: enclavedExpressUrl, enclavedExpressCert: 'dummy-cert', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/master/recoveryConsolidationsWallet.test.ts b/src/__tests__/api/master/recoveryConsolidationsWallet.test.ts index 0d37a71..9ca4bff 100644 --- a/src/__tests__/api/master/recoveryConsolidationsWallet.test.ts +++ b/src/__tests__/api/master/recoveryConsolidationsWallet.test.ts @@ -22,14 +22,13 @@ describe('POST /api/:coin/wallet/recoveryconsolidations', () => { port: 0, bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl, enclavedExpressCert: 'dummy-cert', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; const app = expressApp(config); diff --git a/src/__tests__/api/master/recoveryWallet.test.ts b/src/__tests__/api/master/recoveryWallet.test.ts index 6d60416..60c1477 100644 --- a/src/__tests__/api/master/recoveryWallet.test.ts +++ b/src/__tests__/api/master/recoveryWallet.test.ts @@ -22,14 +22,13 @@ describe('Recovery Tests', () => { port: 0, bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl: enclavedExpressUrl, enclavedExpressCert: 'dummy-cert', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/master/sendMany.test.ts b/src/__tests__/api/master/sendMany.test.ts index d66a559..d5a583a 100644 --- a/src/__tests__/api/master/sendMany.test.ts +++ b/src/__tests__/api/master/sendMany.test.ts @@ -26,14 +26,13 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { port: 0, // Let OS assign a free port bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl: enclavedExpressUrl, enclavedExpressCert: 'dummy-cert', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; @@ -580,12 +579,11 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => { port: 0, bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/master/signAndSendTxRequest.test.ts b/src/__tests__/api/master/signAndSendTxRequest.test.ts index 0351850..b635427 100644 --- a/src/__tests__/api/master/signAndSendTxRequest.test.ts +++ b/src/__tests__/api/master/signAndSendTxRequest.test.ts @@ -44,14 +44,13 @@ describe('POST /api/:coin/wallet/:walletId/txrequest/:txRequestId/signAndSend', port: 0, // Let OS assign a free port bind: 'localhost', timeout: 60000, - logFile: '', + httpLoggerFile: '', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl: enclavedExpressUrl, enclavedExpressCert: 'dummy-cert', tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, allowSelfSigned: true, }; diff --git a/src/__tests__/api/secured/postIndependentKey.test.ts b/src/__tests__/api/secured/postIndependentKey.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts index a090fdf..d058791 100644 --- a/src/__tests__/config.test.ts +++ b/src/__tests__/config.test.ts @@ -1,5 +1,6 @@ import 'should'; -import { initConfig, isEnclavedConfig, TlsMode } from '../initConfig'; +import { initConfig, isEnclavedConfig, isMasterExpressConfig, TlsMode } from '../initConfig'; +import path from 'path'; describe('Configuration', () => { const originalEnv = process.env; @@ -7,9 +8,32 @@ describe('Configuration', () => { const mockTlsCert = '-----BEGIN CERTIFICATE-----\nMOCK_CERT\n-----END CERTIFICATE-----'; beforeEach(() => { + // Reset to original environment and clear all relevant variables process.env = { ...originalEnv }; - // Clear TLS-related environment variables + delete process.env.APP_MODE; + delete process.env.BITGO_APP_MODE; + delete process.env.KMS_URL; + delete process.env.ENCLAVED_EXPRESS_URL; + delete process.env.ENCLAVED_EXPRESS_CERT; delete process.env.TLS_MODE; + delete process.env.TLS_KEY; + delete process.env.TLS_CERT; + delete process.env.MTLS_ALLOWED_CLIENT_FINGERPRINTS; + delete process.env.ALLOW_SELF_SIGNED; + delete process.env.ENCLAVED_EXPRESS_PORT; + delete process.env.MASTER_EXPRESS_PORT; + delete process.env.BIND; + delete process.env.IPC; + delete process.env.HTTP_LOGFILE; + delete process.env.KEEP_ALIVE_TIMEOUT; + delete process.env.HEADERS_TIMEOUT; + delete process.env.BITGO_ENV; + delete process.env.BITGO_CUSTOM_ROOT_URI; + delete process.env.BITGO_DISABLE_ENV_CHECK; + delete process.env.BITGO_AUTH_VERSION; + delete process.env.BITGO_CUSTOM_BITCOIN_NETWORK; + delete process.env.TLS_KEY_PATH; + delete process.env.TLS_CERT_PATH; }); after(() => { @@ -32,13 +56,12 @@ describe('Configuration', () => { describe('Enclaved Mode', () => { beforeEach(() => { process.env.APP_MODE = 'enclaved'; + }); + + it('should use default configuration when minimal environment variables are set', () => { process.env.KMS_URL = 'http://localhost:3000'; - // Set default TLS certificates process.env.TLS_KEY = mockTlsKey; process.env.TLS_CERT = mockTlsCert; - }); - - it('should use default configuration when no environment variables are set', () => { const cfg = initConfig(); isEnclavedConfig(cfg).should.be.true(); if (isEnclavedConfig(cfg)) { @@ -54,6 +77,9 @@ describe('Configuration', () => { it('should read port from environment variable', () => { process.env.ENCLAVED_EXPRESS_PORT = '4000'; + process.env.KMS_URL = 'http://localhost:3000'; + process.env.TLS_KEY = mockTlsKey; + process.env.TLS_CERT = mockTlsCert; const cfg = initConfig(); isEnclavedConfig(cfg).should.be.true(); if (isEnclavedConfig(cfg)) { @@ -65,6 +91,10 @@ describe('Configuration', () => { }); it('should read TLS mode from environment variables', () => { + process.env.KMS_URL = 'http://localhost:3000'; + process.env.TLS_KEY = mockTlsKey; + process.env.TLS_CERT = mockTlsCert; + // Test with TLS disabled process.env.TLS_MODE = 'disabled'; let cfg = initConfig(); @@ -104,19 +134,224 @@ describe('Configuration', () => { }); it('should read mTLS settings from environment variables', () => { - process.env.MTLS_REQUEST_CERT = 'true'; - process.env.MTLS_REJECT_UNAUTHORIZED = 'true'; + process.env.KMS_URL = 'http://localhost:3000'; + process.env.TLS_KEY = mockTlsKey; + process.env.TLS_CERT = mockTlsCert; process.env.MTLS_ALLOWED_CLIENT_FINGERPRINTS = 'ABC123,DEF456'; const cfg = initConfig(); isEnclavedConfig(cfg).should.be.true(); if (isEnclavedConfig(cfg)) { - cfg.mtlsRequestCert!.should.be.true(); cfg.mtlsAllowedClientFingerprints!.should.deepEqual(['ABC123', 'DEF456']); cfg.kmsUrl.should.equal('http://localhost:3000'); cfg.tlsKey!.should.equal(mockTlsKey); cfg.tlsCert!.should.equal(mockTlsCert); } }); + + it('should throw error when KMS_URL is not set', () => { + delete process.env.KMS_URL; + (() => initConfig()).should.throw( + 'KMS_URL environment variable is required and cannot be empty', + ); + }); + + it('should throw error when KMS_URL is empty', () => { + process.env.KMS_URL = ''; + (() => initConfig()).should.throw( + 'KMS_URL environment variable is required and cannot be empty', + ); + }); + + it('should succeed when TLS certificates are not set for disabled TLS mode', () => { + process.env.KMS_URL = 'http://localhost:3000'; + process.env.TLS_MODE = 'disabled'; + delete process.env.TLS_KEY; + delete process.env.TLS_CERT; + const cfg = initConfig(); + isEnclavedConfig(cfg).should.be.true(); + if (isEnclavedConfig(cfg)) { + cfg.tlsMode.should.equal(TlsMode.DISABLED); + cfg.kmsUrl.should.equal('http://localhost:3000'); + } + }); + + it('should throw error when TLS certificates are not set for MTLS mode', () => { + process.env.KMS_URL = 'http://localhost:3000'; + process.env.TLS_MODE = 'mtls'; + delete process.env.TLS_KEY; + delete process.env.TLS_CERT; + (() => initConfig()).should.throw(); + }); + + it('should read HTTP_LOGFILE into httpLoggerFile in Enclaved mode', () => { + process.env.KMS_URL = 'http://localhost:3000'; + process.env.TLS_KEY = mockTlsKey; + process.env.TLS_CERT = mockTlsCert; + process.env.HTTP_LOGFILE = '/tmp/test-http-access.log'; + const cfg = initConfig(); + isEnclavedConfig(cfg).should.be.true(); + if (isEnclavedConfig(cfg)) { + cfg.httpLoggerFile.should.equal('/tmp/test-http-access.log'); + } + }); + }); + + describe('Master Express Mode', () => { + beforeEach(() => { + process.env.APP_MODE = 'master-express'; + process.env.ENCLAVED_EXPRESS_URL = 'http://localhost:3080'; + process.env.ENCLAVED_EXPRESS_CERT = path.resolve( + __dirname, + 'mocks/certs/enclaved-express-cert.pem', + ); + process.env.TLS_CERT_PATH = path.resolve(__dirname, 'mocks/certs/test-ssl-cert.pem'); + process.env.TLS_KEY_PATH = path.resolve(__dirname, 'mocks/certs/test-ssl-key.pem'); + }); + + it('should use default configuration when minimal environment variables are set', () => { + const cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.port.should.equal(3081); + cfg.bind.should.equal('localhost'); + cfg.tlsMode.should.equal(TlsMode.MTLS); + cfg.timeout.should.equal(305 * 1000); + cfg.enclavedExpressUrl.should.equal('https://localhost:3080'); + cfg.env.should.equal('test'); + } + }); + + it('should read port from environment variable', () => { + process.env.MASTER_EXPRESS_PORT = '4001'; + const cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.port.should.equal(4001); + cfg.enclavedExpressUrl.should.equal('https://localhost:3080'); + } + }); + + it('should read BitGo environment settings', () => { + process.env.BITGO_ENV = 'prod'; + process.env.BITGO_CUSTOM_ROOT_URI = 'https://custom.bitgo.com'; + process.env.BITGO_DISABLE_ENV_CHECK = 'false'; + process.env.BITGO_AUTH_VERSION = '3'; + process.env.BITGO_CUSTOM_BITCOIN_NETWORK = 'testnet'; + + const cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.env.should.equal('prod'); + cfg.customRootUri!.should.equal('https://custom.bitgo.com'); + cfg.disableEnvCheck!.should.be.false(); + cfg.authVersion!.should.equal(3); + cfg.customBitcoinNetwork!.should.equal('testnet'); + } + }); + + it('should handle TLS mode disabled configuration', () => { + // Test with TLS disabled + process.env.TLS_MODE = 'disabled'; + delete process.env.ENCLAVED_EXPRESS_CERT; + delete process.env.TLS_KEY_PATH; + delete process.env.TLS_CERT_PATH; + delete process.env.TLS_KEY; + delete process.env.TLS_CERT; + + const cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.tlsMode.should.equal(TlsMode.DISABLED); + cfg.enclavedExpressUrl.should.equal('http://localhost:3080'); + } + }); + + it('should throw error when ENCLAVED_EXPRESS_URL is not set', () => { + delete process.env.ENCLAVED_EXPRESS_URL; + (() => initConfig()).should.throw( + 'ENCLAVED_EXPRESS_URL environment variable is required and cannot be empty', + ); + }); + + it('should throw error when ENCLAVED_EXPRESS_URL is empty', () => { + process.env.ENCLAVED_EXPRESS_URL = ''; + (() => initConfig()).should.throw( + 'ENCLAVED_EXPRESS_URL environment variable is required and cannot be empty', + ); + }); + + it('should throw error when ENCLAVED_EXPRESS_CERT is not set for MTLS mode', () => { + process.env.TLS_MODE = 'mtls'; + delete process.env.ENCLAVED_EXPRESS_CERT; + (() => initConfig()).should.throw( + 'ENCLAVED_EXPRESS_CERT environment variable is required for MTLS mode.', + ); + }); + + it('should succeed when ENCLAVED_EXPRESS_CERT is not set for disabled TLS mode', () => { + process.env.ENCLAVED_EXPRESS_URL = 'http://localhost:3080'; + process.env.TLS_MODE = 'disabled'; + delete process.env.ENCLAVED_EXPRESS_CERT; + const cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.tlsMode.should.equal(TlsMode.DISABLED); + cfg.enclavedExpressUrl.should.equal('http://localhost:3080'); + cfg.enclavedExpressCert!.should.equal(''); + } + }); + + it('should throw error when ENCLAVED_EXPRESS_CERT is not set for default MTLS mode', () => { + delete process.env.ENCLAVED_EXPRESS_CERT; + (() => initConfig()).should.throw( + 'ENCLAVED_EXPRESS_CERT environment variable is required for MTLS mode.', + ); + }); + + it('should handle URL protocol conversion correctly', () => { + // Test with URL that already has protocol + process.env.ENCLAVED_EXPRESS_URL = 'https://enclaved.example.com:3080'; + let cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.enclavedExpressUrl.should.equal('https://enclaved.example.com:3080'); + } + + // Test with URL without protocol (should add https for MTLS) + process.env.ENCLAVED_EXPRESS_URL = 'enclaved.example.com:3080'; + cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.enclavedExpressUrl.should.equal('https://enclaved.example.com:3080'); + } + + // Test with URL without protocol and disabled TLS (should add http) + process.env.ENCLAVED_EXPRESS_URL = 'enclaved.example.com:3080'; + process.env.TLS_MODE = 'disabled'; + cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.enclavedExpressUrl.should.equal('http://enclaved.example.com:3080'); + } + }); + + it('should handle custom BitGo root URI protocol conversion', () => { + process.env.BITGO_CUSTOM_ROOT_URI = 'bitgo.example.com'; + const cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.customRootUri!.should.equal('https://bitgo.example.com'); + } + }); + + it('should read HTTP_LOGFILE into httpLoggerFile in Master Express mode', () => { + process.env.HTTP_LOGFILE = '/tmp/test-http-access.log'; + const cfg = initConfig(); + isMasterExpressConfig(cfg).should.be.true(); + if (isMasterExpressConfig(cfg)) { + cfg.httpLoggerFile.should.equal('/tmp/test-http-access.log'); + } + }); }); }); diff --git a/src/__tests__/mocks/certs/enclaved-express-cert.pem b/src/__tests__/mocks/certs/enclaved-express-cert.pem new file mode 100644 index 0000000..6076da4 --- /dev/null +++ b/src/__tests__/mocks/certs/enclaved-express-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUbE+vqSu9IgPoLJncJqX5aiXh2GkwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDUzMDIwMDgxNVoXDTI2MDUz +MDIwMDgxNVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAsK1g8ts/QRdEHVnmiZSijvtKl08yf13JWY0yksJW0O6x +mrt2uotvONxMNKhGtS+hPjcJ2OC7fCyift8oaCDs7PfIXjVNcN2zRKPci8ihNWvQ +XrYGLTvL9EVHpH7CdlJU43BTaeFusH+k/qv2pW5WQnz13ULdq7yvnDFvJAeahm9X +ptvr9RX9f8Aki0Y82Zi04PCiaHdqBPPl1OfHi+brf4xl7pQUq7Pub94/IDywe+QK +lGFPQ0exSVm5X/7hWv/AxqEFa/Bqb6Uw0qatVqhrgLEHlLUYVXs9NDNXm+865+aT +kvW2dnBpTVRZjnXO+N+BwSj+PfI28RqMXsmIhraN4QIDAQABo1MwUTAdBgNVHQ4E +FgQUnsZxpWiuxqDq/1kV12rMos4NN/cwHwYDVR0jBBgwFoAUnsZxpWiuxqDq/1kV +12rMos4NN/cwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAsAX4 +CCEsIVrKQKJKluEDqOFiuKg0SSe4xVqlSW9vy9z3UYLfOpw14EtB6Lzbtgw7z47w +AnZZS99Zzn3tbYd06/X+b3jThF5TU1gqBcYDCC9HCd9xpmQEC7Ss1Xa88ubjuh+U +E/7xN5xRt85S07VihJWscfY7JCUAELBo3gDCZLfgHjw8xMfPRceE36rkc5B2p60b +WEmmOBWjrSboMOfocasBTUVUMDvGgmxEGEmKgTYshr5lWKcIteisbZi7+OZlkflp +PUZNu5DUyQyjftr2EShndaceZrjgXt6ezoyQBVgPRA+N+NAJn+uBr3B3nZZb/mft +n3XsbtsAAoU49kEVOg== +-----END CERTIFICATE----- diff --git a/src/__tests__/mocks/certs/test-ssl-cert.pem b/src/__tests__/mocks/certs/test-ssl-cert.pem new file mode 100644 index 0000000..773b5a0 --- /dev/null +++ b/src/__tests__/mocks/certs/test-ssl-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUYN0EUBLwq7uoLwDuTx7gDW0HS2UwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDUxNDIxMTUzMVoXDTI2MDUx +NDIxMTUzMVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAmxdCF7xCJcE6yyG+wrdhHMwRiQxbhDLW/hiKQcO/nn7z +QMM9LAV04+WG5LCmm0ygocbfXXhfWn5D6SSSESKTb8dbSj6AJbLSmYjA5vDSzh9R +rO9GzNTDCDne+epo+rN4BOjGh7K83naLei/bEfmklMp7x+TyoBC8Ps/3Eq4HOJfd +UzgV2L3oC/4dCbAnkgK2zanL8KEaH6aM0HytIaqMFYLBs2t8s7HHHcSEadHfjlJu +GwTmTS0nVhJBWYJvF6Pv/SwLFuSo93TJybaMMUSF3oJK35NEYXg6EtibJLUC9RvX +FVLytRA5z+x7FBnGBdi4ctMseecokV4u15ePCB3MXwIDAQABo1MwUTAdBgNVHQ4E +FgQUxHrQZFFBfTfuXeOOoXmHZQ8E6rswHwYDVR0jBBgwFoAUxHrQZFFBfTfuXeOO +oXmHZQ8E6rswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAj0hy +bwWh2B26cR0ADhuDC0MtGPBH0BrHypJEZ96nUPTY4oxRrjZusgdq60ooqsNyIW6k +cZHkajC/O0V6hnV4yMhLE8ZwA+31iyQakonpT5N1OON4Ddv9Bfvx8xOn75x/+RP+ +GlAa31XxivryIi/5y7MEI0PwU34T/6bbMWdBaFRQtbuIXJ/90AZ0fBwIV0vJWjaO +1DriZAJe7hl63ZUw6CsfutpoyKkanF5GQB2CpolR3t1oeHwuDbZ550p1g2XFB6UI +9W+zlggQFeAnthzMoi3erO4sQ3j2b15QLZbk1HXHZcn3+89QcvdUcpG0u51bZdFW +SJ+bnT3TZ9H+szoa1w== +-----END CERTIFICATE----- diff --git a/src/__tests__/mocks/certs/test-ssl-key.pem b/src/__tests__/mocks/certs/test-ssl-key.pem new file mode 100644 index 0000000..cf4f9ef --- /dev/null +++ b/src/__tests__/mocks/certs/test-ssl-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbF0IXvEIlwTrL +Ib7Ct2EczBGJDFuEMtb+GIpBw7+efvNAwz0sBXTj5YbksKabTKChxt9deF9afkPp +JJIRIpNvx1tKPoAlstKZiMDm8NLOH1Gs70bM1MMIOd756mj6s3gE6MaHsrzedot6 +L9sR+aSUynvH5PKgELw+z/cSrgc4l91TOBXYvegL/h0JsCeSArbNqcvwoRofpozQ +fK0hqowVgsGza3yzsccdxIRp0d+OUm4bBOZNLSdWEkFZgm8Xo+/9LAsW5Kj3dMnJ +towxRIXegkrfk0RheDoS2JsktQL1G9cVUvK1EDnP7HsUGcYF2Lhy0yx55yiRXi7X +l48IHcxfAgMBAAECggEAAft4dHXgi+ZTX7iiXTobe1MUakxbzcMZ7QzX6jfxTA+o +APeTNuvURFFxDvKUaT8VJ9wzNgOi3F+UHffCB4a0nGTPmDxXm6O/KLKPHKSOso8Y +ln2cdGPHy7l0TdIe3g113EI0FL9GcLrSf5D7Bi4gWhKDJdlETKLKH9dn+2IkD3zE +VM+7pwqYV6XZu2GuZ1om1JE+Hx2D5YLIuRCON0RKpzCLikmM7VErA7sjy7LsCSh9 +ty5n1s9GoNtF+YuOD9WeMWGDonMyJdYWTsmFYoT+LF7W+GHoEwvYm9595QuNnxVx +KQ4P5oKm/EfcSiBhCC8BdCIGch9tQPT7c/syVhHG4QKBgQDTdUVW7rJNnLtHgWF2 +ubjh9b3ZfxPuTEu6ueKON5XXvSgfsMBNgCxwkemefGJ6xIjDu+swud+2H33Tqj23 +GMMTZ1JEzNYINO1m/laSAK+DcL81q4sLLlJTbBhYeE6FBeEO1hAmU2IiYrgU6zbO +eyo4ysXtFJdnHSR9PHjZpt/cMQKBgQC7wmxw2UNPlmwTzl6l7z5DqYxizIfezb9l +pIYrmcD92asxZKPi1soz8PcOn30gGmjtZn/7FkXFHSRsNknUmqJOEbZrvNCcKndz +O+RbKGs8FAKlyog8k3CTToAng4PutsYrAuK/kx84P9FPCgTxdhejRMkfkSebCJQH +fmXRnlRdjwKBgQCm2Drz0rcBIg9q5hz+zp+gOoOnnusc9TozhQPLbvReGzQTfSTe +gamO0LJiiIYzk+rNdfKmqaJoUwS3A/ZaB8G0B6wT+QNPymMfBsNLxBq4PTfBoy68 +jboLdJjpBVP/BZqEWEa51sTxmK7iYo0F8oxn7yaoX7zucUIfRp2cLl0noQKBgB+K +RG8cgAshiJw3IX0cWEhDdfquwvAxfcJURdmTJXE/HFvavREA5cyd4NKLBhjbdt7S +RhNmpWe8Qn8PC430P+l/XjZw7FYfaBtqZyzM+F6KOfuhrwsF9XY5TJvWotX5zAYz +oOVvkGIBjmaJl1T8cnIRvvtXheCsKzmrCO2SfDePAoGAM1ToKwXSeZEm2Kyf/PV9 +74lvBKYP5LP+pSmrcTq9jUbPQy3KmlhBi+kyhVK+2Awmh0J9tzu83C8lWR25L4mc +/Uwjhv2KwmvJKyZ4/5t/oMZ+BsZERSHj39juLNW+UL82M2heM5tv5/MI779SAmzl +VJMN4N1x+L7408dEGu0j2ds= +-----END PRIVATE KEY----- diff --git a/src/__tests__/routes.test.ts b/src/__tests__/routes.test.ts index 2df84f4..41dd6e1 100644 --- a/src/__tests__/routes.test.ts +++ b/src/__tests__/routes.test.ts @@ -12,8 +12,9 @@ describe('Routes', () => { app = express(); setupRoutes(app, { appMode: AppMode.ENCLAVED, + httpLoggerFile: '', + allowSelfSigned: true, tlsMode: TlsMode.DISABLED, - mtlsRequestCert: false, kmsUrl: 'http://localhost:3000/kms', timeout: 5000, port: 3000, diff --git a/src/enclavedApp.ts b/src/enclavedApp.ts index 17e8306..840cedf 100644 --- a/src/enclavedApp.ts +++ b/src/enclavedApp.ts @@ -22,19 +22,15 @@ import logger from './logger'; * Create a startup function which will be run upon server initialization */ export function startup(config: EnclavedConfig, baseUri: string): () => void { - return function () { - logger.info('BitGo Enclaved Express running'); + return () => { + logger.info('Enclaved Express server starting...'); logger.info(`Base URI: ${baseUri}`); - logger.info(`TLS Mode: ${config.tlsMode}`); - logger.info(`mTLS Enabled: ${config.tlsMode === TlsMode.MTLS}`); - logger.info(`Request Client Cert: ${config.mtlsRequestCert}`); - logger.info(`Allow Self-Signed: ${config.allowSelfSigned}`); + logger.info(`mTLS Mode: ${config.tlsMode}`); + logger.info(`Allow Self-Signed Certificates: ${config.allowSelfSigned}`); + logger.info(`Port: ${config.port}`); + logger.info(`Bind: ${config.bind}`); logger.info(`KMS URL: ${config.kmsUrl}`); - if (config.mtlsAllowedClientFingerprints?.length) { - logger.info( - `Allowed Client Fingerprints: ${config.mtlsAllowedClientFingerprints.length} configured`, - ); - } + logger.info('Enclaved Express server started successfully'); }; } @@ -48,7 +44,7 @@ async function createHttpsServer( app: express.Application, config: EnclavedConfig, ): Promise { - const { tlsKey, tlsCert, tlsMode, mtlsRequestCert } = config; + const { tlsKey, tlsCert, tlsMode } = config; if (!tlsKey || !tlsCert) { throw new Error('TLS key and certificate must be provided for HTTPS server'); @@ -58,9 +54,8 @@ async function createHttpsServer( secureOptions: SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1, key: tlsKey, cert: tlsCert, - // Only request cert if mTLS is enabled AND we want to request certs - // This prevents TLS handshake failures when no cert is provided - requestCert: tlsMode === TlsMode.MTLS && mtlsRequestCert, + // Always request cert if mTLS is enabled + requestCert: tlsMode === TlsMode.MTLS, rejectUnauthorized: false, // Handle authorization in middleware }; diff --git a/src/initConfig.ts b/src/initConfig.ts index cc4f5c3..e042697 100644 --- a/src/initConfig.ts +++ b/src/initConfig.ts @@ -49,10 +49,9 @@ const defaultEnclavedConfig: EnclavedConfig = { port: 3080, bind: 'localhost', timeout: 305 * 1000, - logFile: '', + httpLoggerFile: 'logs/http-access.log', kmsUrl: '', // Will be overridden by environment variable tlsMode: TlsMode.MTLS, - mtlsRequestCert: true, allowSelfSigned: false, }; @@ -88,8 +87,7 @@ function enclavedEnvConfig(): Partial { port: Number(readEnvVar('ENCLAVED_EXPRESS_PORT')), bind: readEnvVar('BIND'), ipc: readEnvVar('IPC'), - debugNamespace: (readEnvVar('DEBUG_NAMESPACE') || '').split(',').filter(Boolean), - logFile: readEnvVar('LOGFILE'), + httpLoggerFile: readEnvVar('HTTP_LOGFILE'), timeout: Number(readEnvVar('TIMEOUT')), keepAliveTimeout: Number(readEnvVar('KEEP_ALIVE_TIMEOUT')), headersTimeout: Number(readEnvVar('HEADERS_TIMEOUT')), @@ -101,7 +99,6 @@ function enclavedEnvConfig(): Partial { tlsKey: readEnvVar('TLS_KEY'), tlsCert: readEnvVar('TLS_CERT'), tlsMode: determineTlsMode(), - mtlsRequestCert: readEnvVar('MTLS_REQUEST_CERT')?.toLowerCase() !== 'false', mtlsAllowedClientFingerprints: readEnvVar('MTLS_ALLOWED_CLIENT_FINGERPRINTS')?.split(','), allowSelfSigned: readEnvVar('ALLOW_SELF_SIGNED') === 'true', }; @@ -121,8 +118,7 @@ function mergeEnclavedConfigs(...configs: Partial[]): EnclavedCo port: get('port'), bind: get('bind'), ipc: get('ipc'), - debugNamespace: get('debugNamespace'), - logFile: get('logFile'), + httpLoggerFile: get('httpLoggerFile'), timeout: get('timeout'), keepAliveTimeout: get('keepAliveTimeout'), headersTimeout: get('headersTimeout'), @@ -132,7 +128,6 @@ function mergeEnclavedConfigs(...configs: Partial[]): EnclavedCo tlsKey: get('tlsKey'), tlsCert: get('tlsCert'), tlsMode: get('tlsMode'), - mtlsRequestCert: get('mtlsRequestCert'), mtlsAllowedClientFingerprints: get('mtlsAllowedClientFingerprints'), allowSelfSigned: get('allowSelfSigned'), }; @@ -185,14 +180,13 @@ const defaultMasterExpressConfig: MasterExpressConfig = { port: 3081, bind: 'localhost', timeout: 305 * 1000, - logFile: '', + httpLoggerFile: 'logs/http-access.log', env: 'test', disableEnvCheck: true, authVersion: 2, enclavedExpressUrl: '', // Will be overridden by environment variable enclavedExpressCert: '', // Will be overridden by environment variable tlsMode: TlsMode.MTLS, - mtlsRequestCert: true, allowSelfSigned: false, }; @@ -219,9 +213,7 @@ function masterExpressEnvConfig(): Partial { } // Debug mTLS environment variables - const mtlsRequestCertRaw = readEnvVar('MTLS_REQUEST_CERT'); const allowSelfSignedRaw = readEnvVar('ALLOW_SELF_SIGNED'); - const mtlsRequestCert = mtlsRequestCertRaw?.toLowerCase() !== 'false'; const allowSelfSigned = allowSelfSignedRaw === 'true'; return { @@ -229,8 +221,7 @@ function masterExpressEnvConfig(): Partial { port: Number(readEnvVar('MASTER_EXPRESS_PORT')), bind: readEnvVar('BIND'), ipc: readEnvVar('IPC'), - debugNamespace: (readEnvVar('DEBUG_NAMESPACE') || '').split(',').filter(Boolean), - logFile: readEnvVar('LOGFILE'), + httpLoggerFile: readEnvVar('HTTP_LOGFILE') || 'logs/http-access.log', timeout: Number(readEnvVar('TIMEOUT')), keepAliveTimeout: Number(readEnvVar('KEEP_ALIVE_TIMEOUT')), headersTimeout: Number(readEnvVar('HEADERS_TIMEOUT')), @@ -248,7 +239,6 @@ function masterExpressEnvConfig(): Partial { tlsKey: readEnvVar('TLS_KEY'), tlsCert: readEnvVar('TLS_CERT'), tlsMode, - mtlsRequestCert, mtlsAllowedClientFingerprints: readEnvVar('MTLS_ALLOWED_CLIENT_FINGERPRINTS')?.split(','), allowSelfSigned, }; @@ -270,8 +260,7 @@ function mergeMasterExpressConfigs( port: get('port'), bind: get('bind'), ipc: get('ipc'), - debugNamespace: get('debugNamespace'), - logFile: get('logFile'), + httpLoggerFile: get('httpLoggerFile'), timeout: get('timeout'), keepAliveTimeout: get('keepAliveTimeout'), headersTimeout: get('headersTimeout'), @@ -287,7 +276,6 @@ function mergeMasterExpressConfigs( tlsKey: get('tlsKey'), tlsCert: get('tlsCert'), tlsMode: get('tlsMode'), - mtlsRequestCert: get('mtlsRequestCert'), mtlsAllowedClientFingerprints: get('mtlsAllowedClientFingerprints'), allowSelfSigned: get('allowSelfSigned'), }; diff --git a/src/masterExpressApp.ts b/src/masterExpressApp.ts index 25fd9f0..12b6eae 100644 --- a/src/masterExpressApp.ts +++ b/src/masterExpressApp.ts @@ -21,19 +21,14 @@ import { setupRoutes } from './routes/master'; * Create a startup function which will be run upon server initialization */ export function startup(config: MasterExpressConfig, baseUri: string): () => void { - return function () { - logger.info('BitGo Master Express running'); + return () => { + logger.info('Master Express server starting...'); logger.info(`Base URI: ${baseUri}`); - logger.info(`Environment: ${config.env}`); logger.info(`TLS Mode: ${config.tlsMode}`); - logger.info(`mTLS Enabled: ${config.tlsMode === TlsMode.MTLS}`); - logger.info(`Request Client Cert: ${config.mtlsRequestCert}`); - logger.info(`Allow Self-Signed: ${config.allowSelfSigned}`); - if (config.mtlsAllowedClientFingerprints?.length) { - logger.info( - `Allowed Client Fingerprints: ${config.mtlsAllowedClientFingerprints.length} configured`, - ); - } + logger.info(`Port: ${config.port}`); + logger.info(`Bind: ${config.bind}`); + logger.info(`Enclaved Express URL: ${config.enclavedExpressUrl}`); + logger.info('Master Express server started successfully'); }; } @@ -47,7 +42,7 @@ async function createHttpsServer( app: express.Application, config: MasterExpressConfig, ): Promise { - const { tlsKey, tlsCert, tlsMode, mtlsRequestCert } = config; + const { tlsKey, tlsCert, tlsMode } = config; if (!tlsKey || !tlsCert) { throw new Error('TLS key and certificate must be provided for HTTPS server'); @@ -57,9 +52,8 @@ async function createHttpsServer( secureOptions: SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1, key: tlsKey, cert: tlsCert, - // Only request cert if mTLS is enabled AND we want to request certs - // This prevents TLS handshake failures when no cert is provided - requestCert: tlsMode === TlsMode.MTLS && mtlsRequestCert, + // Always request cert if mTLS is enabled + requestCert: tlsMode === TlsMode.MTLS, rejectUnauthorized: false, // Handle authorization in middleware }; diff --git a/src/shared/appUtils.ts b/src/shared/appUtils.ts index 1af51e6..a1e13db 100644 --- a/src/shared/appUtils.ts +++ b/src/shared/appUtils.ts @@ -9,7 +9,7 @@ import bodyParser from 'body-parser'; import pjson from '../../package.json'; import logger from '../logger'; -import { Config, TlsMode } from '../shared/types'; +import { Config, TlsMode, MasterExpressConfig } from '../shared/types'; /** * Set up the logging middleware provided by morgan @@ -17,9 +17,9 @@ import { Config, TlsMode } from '../shared/types'; export function setupLogging(app: express.Application, config: Config): void { // Set up morgan for logging, with optional logging into a file let middleware; - if (config.logFile) { + if (config.httpLoggerFile) { // create a write stream (in append mode) - const accessLogPath = path.resolve(config.logFile); + const accessLogPath = path.resolve(config.httpLoggerFile); const accessLogStream = fs.createWriteStream(accessLogPath, { flags: 'a' }); logger.info(`Log location: ${accessLogPath}`); // setup the logger @@ -121,7 +121,6 @@ export async function prepareIpc(ipcSocketFilePath: string): Promise { */ export function createMtlsMiddleware(config: { tlsMode: TlsMode; - mtlsRequestCert: boolean; allowSelfSigned?: boolean; mtlsAllowedClientFingerprints?: string[]; }): express.RequestHandler { @@ -132,8 +131,8 @@ export function createMtlsMiddleware(config: { const hasValidClientCert = clientCert && Object.keys(clientCert).length > 0 && clientCert.subject; - // If client cert is required but not provided - if (config.mtlsRequestCert && !hasValidClientCert) { + // If mTLS is enabled, client certificate is always required + if (config.tlsMode === TlsMode.MTLS && !hasValidClientCert) { return res.status(403).json({ error: 'mTLS Authentication Failed', message: 'Client certificate is required for this endpoint', @@ -176,29 +175,53 @@ export function createMtlsMiddleware(config: { /** * Validate that TLS certificates are properly loaded when TLS is enabled */ -export function validateTlsCertificates(config: { - tlsMode: TlsMode; - tlsKey?: string; - tlsCert?: string; -}): void { - if (config.tlsMode !== TlsMode.DISABLED) { - if (!config.tlsKey || !config.tlsCert) { - throw new Error('TLS is enabled but certificates are not properly loaded'); +export function validateTlsCertificates(config: Config): void { + if (config.tlsMode === TlsMode.DISABLED) { + return; + } + + if (!config.tlsKey || !config.tlsCert) { + throw new Error('TLS key and certificate must be provided when TLS is enabled'); + } + + // Validate certificate format + try { + // Basic PEM format validation + if ( + !config.tlsKey.includes('-----BEGIN PRIVATE KEY-----') && + !config.tlsKey.includes('-----BEGIN RSA PRIVATE KEY-----') + ) { + throw new Error('Invalid TLS private key format'); } + if (!config.tlsCert.includes('-----BEGIN CERTIFICATE-----')) { + throw new Error('Invalid TLS certificate format'); + } + } catch (error) { + throw new Error( + `TLS certificate validation failed: ${ + error instanceof Error ? error.message : String(error) + }`, + ); } } /** * Validate Master Express configuration */ -export function validateMasterExpressConfig(config: { - enclavedExpressUrl: string; - enclavedExpressCert: string; -}): void { - if (!config.enclavedExpressUrl) { - throw new Error('ENCLAVED_EXPRESS_URL is required for Master Express mode'); +export function validateMasterExpressConfig(config: MasterExpressConfig): void { + // Validate that we have the required enclaved express certificate for mTLS + if (config.tlsMode === TlsMode.MTLS && !config.enclavedExpressCert) { + throw new Error('Enclaved Express certificate is required for mTLS mode'); } - if (!config.enclavedExpressCert) { - throw new Error('ENCLAVED_EXPRESS_CERT is required for Master Express mode'); + + // Validate client certificate if mTLS is enabled + if (config.tlsMode === TlsMode.MTLS) { + const hasValidClientCert = + config.enclavedExpressCert && + config.enclavedExpressCert.includes('-----BEGIN CERTIFICATE-----'); + + if (!hasValidClientCert) { + throw new Error('Valid client certificate is required for mTLS mode'); + } } } diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts index 57164c8..05f7c65 100644 --- a/src/shared/types/index.ts +++ b/src/shared/types/index.ts @@ -11,16 +11,15 @@ export enum AppMode { export type EnvironmentName = 'prod' | 'test' | 'staging' | 'dev' | 'local'; // Common base configuration shared by both modes -interface BaseConfig { +export interface BaseConfig { appMode: AppMode; port: number; bind: string; ipc?: string; - debugNamespace?: string[]; - logFile?: string; timeout: number; keepAliveTimeout?: number; headersTimeout?: number; + httpLoggerFile: string; } // Enclaved mode specific configuration @@ -34,7 +33,6 @@ export interface EnclavedConfig extends BaseConfig { tlsKey?: string; tlsCert?: string; tlsMode: TlsMode; - mtlsRequestCert: boolean; mtlsAllowedClientFingerprints?: string[]; allowSelfSigned?: boolean; } @@ -56,7 +54,6 @@ export interface MasterExpressConfig extends BaseConfig { tlsKey?: string; tlsCert?: string; tlsMode: TlsMode; - mtlsRequestCert: boolean; mtlsAllowedClientFingerprints?: string[]; allowSelfSigned?: boolean; }