Skip to content

Commit e354b00

Browse files
committed
feat(ebe): use KMS tls cert if mtls mode is enabled
Ticket: WP-4353
1 parent b42c8c7 commit e354b00

File tree

5 files changed

+52
-11
lines changed

5 files changed

+52
-11
lines changed

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,29 @@ Both modes use the same TLS configuration variables:
5959

6060
**Option 1: Certificate Files**
6161

62-
- `TLS_KEY_PATH` - Path to private key file
63-
- `TLS_CERT_PATH` - Path to certificate file
62+
- `TLS_KEY_PATH` - Path to private key file (used for both inbound mTLS server and outbound mTLS client to KMS)
63+
- `TLS_CERT_PATH` - Path to certificate file (used for both inbound mTLS server and outbound mTLS client to KMS)
6464

6565
**Option 2: Environment Variables**
6666

67-
- `TLS_KEY` - Private key content (PEM format)
68-
- `TLS_CERT` - Certificate content (PEM format)
67+
- `TLS_KEY` - Private key content (PEM format, used for both inbound and outbound)
68+
- `TLS_CERT` - Certificate content (PEM format, used for both inbound and outbound)
6969

7070
#### mTLS Settings (when TLS_MODE=mtls)
7171

7272
- `MTLS_REQUEST_CERT` - Request client certificates (default: true)
7373
- `ALLOW_SELF_SIGNED` - Allow self-signed certificates (default: false)
7474
- `MTLS_ALLOWED_CLIENT_FINGERPRINTS` - Comma-separated list of allowed client certificate fingerprints (optional)
7575

76+
#### Outbound mTLS to KMS
77+
78+
- When `TLS_MODE=mtls`, outbound mTLS to KMS is enabled by default.
79+
- The same `TLS_CERT` and `TLS_KEY` are used as the client certificate and key for outbound mTLS requests to KMS.
80+
- `KMS_TLS_CERT_PATH` - Path to the CA certificate to verify the KMS server (required when outbound mTLS is enabled).
81+
- If `TLS_MODE=disabled`, outbound mTLS to KMS is also disabled by default.
82+
83+
> **Note:** If you want to use a different client certificate for KMS, you will need to extend the configuration. By default, the same cert/key is used for both inbound and outbound mTLS.
84+
7685
### Logging and Debug
7786

7887
- `HTTP_LOGFILE` - Path to HTTP request log file (optional, used by Morgan for HTTP access logs)

src/__tests__/config.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ describe('Configuration', () => {
6262
process.env.KMS_URL = 'http://localhost:3000';
6363
process.env.TLS_KEY = mockTlsKey;
6464
process.env.TLS_CERT = mockTlsCert;
65+
process.env.KMS_TLS_CERT_PATH = path.resolve(__dirname, 'mocks/certs/test-ssl-cert.pem');
6566
const cfg = initConfig();
6667
isEnclavedConfig(cfg).should.be.true();
6768
if (isEnclavedConfig(cfg)) {
@@ -80,6 +81,7 @@ describe('Configuration', () => {
8081
process.env.KMS_URL = 'http://localhost:3000';
8182
process.env.TLS_KEY = mockTlsKey;
8283
process.env.TLS_CERT = mockTlsCert;
84+
process.env.KMS_TLS_CERT_PATH = path.resolve(__dirname, 'mocks/certs/test-ssl-cert.pem');
8385
const cfg = initConfig();
8486
isEnclavedConfig(cfg).should.be.true();
8587
if (isEnclavedConfig(cfg)) {
@@ -95,6 +97,7 @@ describe('Configuration', () => {
9597
process.env.TLS_KEY = mockTlsKey;
9698
process.env.TLS_CERT = mockTlsCert;
9799
process.env.RECOVERY_MODE = 'true';
100+
process.env.KMS_TLS_CERT_PATH = path.resolve(__dirname, 'mocks/certs/test-ssl-cert.pem');
98101
const cfg = initConfig();
99102
cfg.recoveryMode!.should.be.true();
100103
});
@@ -103,6 +106,7 @@ describe('Configuration', () => {
103106
process.env.KMS_URL = 'http://localhost:3000';
104107
process.env.TLS_KEY = mockTlsKey;
105108
process.env.TLS_CERT = mockTlsCert;
109+
process.env.KMS_TLS_CERT_PATH = path.resolve(__dirname, 'mocks/certs/test-ssl-cert.pem');
106110

107111
// Test with TLS disabled
108112
process.env.TLS_MODE = 'disabled';
@@ -147,6 +151,7 @@ describe('Configuration', () => {
147151
process.env.TLS_KEY = mockTlsKey;
148152
process.env.TLS_CERT = mockTlsCert;
149153
process.env.MTLS_ALLOWED_CLIENT_FINGERPRINTS = 'ABC123,DEF456';
154+
process.env.KMS_TLS_CERT_PATH = path.resolve(__dirname, 'mocks/certs/test-ssl-cert.pem');
150155

151156
const cfg = initConfig();
152157
isEnclavedConfig(cfg).should.be.true();
@@ -155,6 +160,7 @@ describe('Configuration', () => {
155160
cfg.kmsUrl.should.equal('http://localhost:3000');
156161
cfg.tlsKey!.should.equal(mockTlsKey);
157162
cfg.tlsCert!.should.equal(mockTlsCert);
163+
cfg.kmsTlsCertPath!.should.equal(path.resolve(__dirname, 'mocks/certs/test-ssl-cert.pem'));
158164
}
159165
});
160166

@@ -177,6 +183,7 @@ describe('Configuration', () => {
177183
process.env.TLS_MODE = 'disabled';
178184
delete process.env.TLS_KEY;
179185
delete process.env.TLS_CERT;
186+
delete process.env.KMS_TLS_CERT_PATH;
180187
const cfg = initConfig();
181188
isEnclavedConfig(cfg).should.be.true();
182189
if (isEnclavedConfig(cfg)) {
@@ -198,12 +205,20 @@ describe('Configuration', () => {
198205
process.env.TLS_KEY = mockTlsKey;
199206
process.env.TLS_CERT = mockTlsCert;
200207
process.env.HTTP_LOGFILE = '/tmp/test-http-access.log';
208+
process.env.KMS_TLS_CERT_PATH = path.resolve(__dirname, 'mocks/certs/test-ssl-cert.pem');
201209
const cfg = initConfig();
202210
isEnclavedConfig(cfg).should.be.true();
203211
if (isEnclavedConfig(cfg)) {
204212
cfg.httpLoggerFile.should.equal('/tmp/test-http-access.log');
205213
}
206214
});
215+
216+
it('should throw error when KMS_TLS_CERT_PATH is not set for MTLS mode', () => {
217+
process.env.KMS_URL = 'http://localhost:3000';
218+
process.env.TLS_MODE = 'mtls';
219+
delete process.env.KMS_TLS_CERT_PATH;
220+
(() => initConfig()).should.throw('KMS_TLS_CERT is required when TLS mode is MTLS');
221+
});
207222
});
208223

209224
describe('Master Express Mode', () => {

src/initConfig.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,13 @@ function enclavedEnvConfig(): Partial<EnclavedConfig> {
8787
port: Number(readEnvVar('ENCLAVED_EXPRESS_PORT')),
8888
bind: readEnvVar('BIND'),
8989
ipc: readEnvVar('IPC'),
90-
httpLoggerFile: readEnvVar('HTTP_LOGFILE'),
90+
httpLoggerFile: readEnvVar('HTTP_LOGFILE') || 'logs/http-access.log',
9191
timeout: Number(readEnvVar('TIMEOUT')),
9292
keepAliveTimeout: Number(readEnvVar('KEEP_ALIVE_TIMEOUT')),
9393
headersTimeout: Number(readEnvVar('HEADERS_TIMEOUT')),
9494
// KMS settings
9595
kmsUrl,
96+
kmsTlsCertPath: readEnvVar('KMS_TLS_CERT_PATH'),
9697
// mTLS settings
9798
keyPath: readEnvVar('TLS_KEY_PATH'),
9899
crtPath: readEnvVar('TLS_CERT_PATH'),
@@ -124,6 +125,7 @@ function mergeEnclavedConfigs(...configs: Partial<EnclavedConfig>[]): EnclavedCo
124125
keepAliveTimeout: get('keepAliveTimeout'),
125126
headersTimeout: get('headersTimeout'),
126127
kmsUrl: get('kmsUrl'),
128+
kmsTlsCertPath: get('kmsTlsCertPath'),
127129
keyPath: get('keyPath'),
128130
crtPath: get('crtPath'),
129131
tlsKey: get('tlsKey'),
@@ -166,6 +168,19 @@ function configureEnclavedMode(): EnclavedConfig {
166168
logger.info('Using TLS certificate from environment variable');
167169
}
168170

171+
if (!config.kmsTlsCertPath) {
172+
throw new Error('KMS_TLS_CERT is required when TLS mode is MTLS');
173+
}
174+
if (config.kmsTlsCertPath) {
175+
try {
176+
config.kmsTlsCert = fs.readFileSync(config.kmsTlsCertPath, 'utf-8');
177+
logger.info(`Successfully loaded KMS TLS certificate from file: ${config.kmsTlsCertPath}`);
178+
} catch (e) {
179+
const err = e instanceof Error ? e : new Error(String(e));
180+
throw new Error(`Failed to read KMS TLS certificate from kmsTlsCert: ${err.message}`);
181+
}
182+
}
183+
169184
// Validate that certificates are properly loaded when TLS is enabled
170185
validateTlsCertificates(config);
171186
}

src/kms/kmsClient.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import debug from 'debug';
22
import * as superagent from 'superagent';
3-
import { EnclavedConfig, isMasterExpressConfig } from '../shared/types';
3+
import { EnclavedConfig, isMasterExpressConfig, TlsMode } from '../shared/types';
44
import { PostKeyKmsSchema, PostKeyParams, PostKeyResponse } from './types/postKey';
55
import { GetKeyKmsSchema, GetKeyParams, GetKeyResponse } from './types/getKey';
66
import {
@@ -25,14 +25,17 @@ export class KmsClient {
2525
if (isMasterExpressConfig(cfg)) {
2626
throw new Error('Configuration is not in enclaved express mode');
2727
}
28-
2928
if (!cfg.kmsUrl) {
3029
throw new Error('KMS URL not configured. Please set KMS_URL in your environment.');
3130
}
3231

3332
this.url = cfg.kmsUrl;
34-
if (cfg.kmsTlsMode === 'enabled' && cfg.kmsTlsCert) {
35-
this.agent = new https.Agent({ ca: cfg.kmsTlsCert });
33+
if (cfg.tlsMode === TlsMode.MTLS && cfg.kmsTlsCert) {
34+
this.agent = new https.Agent({
35+
ca: cfg.kmsTlsCert,
36+
cert: cfg.tlsCert,
37+
key: cfg.tlsKey,
38+
});
3639
}
3740
debugLogger('kmsClient initialized with URL: %s', this.url);
3841
}

src/shared/types/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ export interface EnclavedConfig extends BaseConfig {
2828
appMode: AppMode.ENCLAVED;
2929
// KMS settings
3030
kmsUrl: string;
31-
kmsTlsMode?: 'enabled' | 'disabled';
32-
kmsTlsCert?: string;
3331
kmsTlsCertPath?: string;
32+
kmsTlsCert?: string;
3433
// mTLS settings
3534
keyPath?: string;
3635
crtPath?: string;

0 commit comments

Comments
 (0)