navy 7.0.0-alpha.4 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cli/__tests__/https.js +193 -1
- package/lib/cli/https.js +103 -18
- package/lib/cli/program.js +11 -1
- package/lib/navy/__tests__/index.js +142 -0
- package/lib/navy/index.js +12 -2
- package/lib/util/__tests__/https.js +187 -10
- package/lib/util/__tests__/install-root-ca.js +282 -0
- package/lib/util/https.js +166 -40
- package/lib/util/install-root-ca.js +114 -0
- package/package.json +1 -1
|
@@ -18,6 +18,8 @@ describe('cli/https', function () {
|
|
|
18
18
|
let createCertStub;
|
|
19
19
|
let generateRootCaStub;
|
|
20
20
|
let removeCertStub;
|
|
21
|
+
let issueLeafCertForUrlStub;
|
|
22
|
+
let installRootCaToTrustStoreStub;
|
|
21
23
|
let fsStub;
|
|
22
24
|
let consoleLogStub;
|
|
23
25
|
let httpsCli;
|
|
@@ -40,6 +42,12 @@ describe('cli/https', function () {
|
|
|
40
42
|
createCertStub = sandbox.stub().resolves();
|
|
41
43
|
generateRootCaStub = sandbox.stub().resolves();
|
|
42
44
|
removeCertStub = sandbox.stub().resolves();
|
|
45
|
+
issueLeafCertForUrlStub = sandbox.stub().resolves({
|
|
46
|
+
certPath: '/out/host.crt',
|
|
47
|
+
keyPath: '/out/host.key',
|
|
48
|
+
caCopyPath: '/out/navy-root-ca.crt'
|
|
49
|
+
});
|
|
50
|
+
installRootCaToTrustStoreStub = sandbox.stub();
|
|
43
51
|
fsStub = {
|
|
44
52
|
existsSync: sandbox.stub().returns(true),
|
|
45
53
|
readdirSync: sandbox.stub().returns(['web.local.crt', 'api.local.crt', 'something.txt'])
|
|
@@ -63,7 +71,11 @@ describe('cli/https', function () {
|
|
|
63
71
|
'../util/https': {
|
|
64
72
|
createCert: createCertStub,
|
|
65
73
|
generateRootCa: generateRootCaStub,
|
|
66
|
-
removeCert: removeCertStub
|
|
74
|
+
removeCert: removeCertStub,
|
|
75
|
+
issueLeafCertForUrl: issueLeafCertForUrlStub
|
|
76
|
+
},
|
|
77
|
+
'../util/install-root-ca': {
|
|
78
|
+
installRootCaToTrustStore: installRootCaToTrustStoreStub
|
|
67
79
|
},
|
|
68
80
|
fs: fsStub
|
|
69
81
|
});
|
|
@@ -205,6 +217,186 @@ describe('cli/https', function () {
|
|
|
205
217
|
const checkedPaths = fsStub.existsSync.getCalls().map(c => c.args[0]);
|
|
206
218
|
(0, _chai.expect)(checkedPaths.some(p => p === '/custom-ca/ca.crt')).to.equal(true);
|
|
207
219
|
});
|
|
220
|
+
it('should accept a single string service name', async function () {
|
|
221
|
+
await httpsCli('web', {
|
|
222
|
+
navy: 'env-1'
|
|
223
|
+
});
|
|
224
|
+
(0, _chai.expect)(createCertStub.callCount).to.equal(1);
|
|
225
|
+
});
|
|
226
|
+
it('should ignore non-string entries in a services array', async function () {
|
|
227
|
+
await httpsCli(['web', '', null, 42, 'api'], {
|
|
228
|
+
navy: 'env-1'
|
|
229
|
+
});
|
|
230
|
+
(0, _chai.expect)(createCertStub.callCount).to.equal(2);
|
|
231
|
+
});
|
|
232
|
+
it('should treat unsupported service arg types as an empty list', async function () {
|
|
233
|
+
fsStub.existsSync.returns(false);
|
|
234
|
+
await httpsCli(42, {
|
|
235
|
+
navy: 'env-1'
|
|
236
|
+
});
|
|
237
|
+
(0, _chai.expect)(createCertStub.called).to.equal(false);
|
|
238
|
+
(0, _chai.expect)(generateRootCaStub.called).to.equal(false);
|
|
239
|
+
});
|
|
240
|
+
it('should treat an empty string service arg as list mode', async function () {
|
|
241
|
+
fsStub.existsSync.returns(false);
|
|
242
|
+
await httpsCli('', {
|
|
243
|
+
navy: 'env-1'
|
|
244
|
+
});
|
|
245
|
+
(0, _chai.expect)(createCertStub.called).to.equal(false);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
describe('validation', function () {
|
|
249
|
+
async function expectNavyError(promise, messagePattern) {
|
|
250
|
+
let caught;
|
|
251
|
+
try {
|
|
252
|
+
await promise;
|
|
253
|
+
} catch (err) {
|
|
254
|
+
caught = err;
|
|
255
|
+
}
|
|
256
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
257
|
+
(0, _chai.expect)(caught.message).to.match(messagePattern);
|
|
258
|
+
}
|
|
259
|
+
it('should reject combining disable with setup', async function () {
|
|
260
|
+
await expectNavyError(httpsCli([], {
|
|
261
|
+
disable: 'web',
|
|
262
|
+
setup: true
|
|
263
|
+
}), /cannot be combined/);
|
|
264
|
+
});
|
|
265
|
+
it('should reject combining disable with all', async function () {
|
|
266
|
+
await expectNavyError(httpsCli([], {
|
|
267
|
+
disable: 'web',
|
|
268
|
+
all: true
|
|
269
|
+
}), /cannot be combined/);
|
|
270
|
+
});
|
|
271
|
+
it('should reject combining disable with issue', async function () {
|
|
272
|
+
await expectNavyError(httpsCli([], {
|
|
273
|
+
disable: 'web',
|
|
274
|
+
issue: 'https://host'
|
|
275
|
+
}), /cannot be combined/);
|
|
276
|
+
});
|
|
277
|
+
it('should reject combining setup with all', async function () {
|
|
278
|
+
await expectNavyError(httpsCli([], {
|
|
279
|
+
setup: true,
|
|
280
|
+
all: true
|
|
281
|
+
}), /Use only one/);
|
|
282
|
+
});
|
|
283
|
+
it('should reject combining setup with issue', async function () {
|
|
284
|
+
await expectNavyError(httpsCli([], {
|
|
285
|
+
setup: true,
|
|
286
|
+
issue: 'https://host'
|
|
287
|
+
}), /Use only one/);
|
|
288
|
+
});
|
|
289
|
+
it('should reject combining all with issue', async function () {
|
|
290
|
+
await expectNavyError(httpsCli([], {
|
|
291
|
+
all: true,
|
|
292
|
+
issue: 'https://host'
|
|
293
|
+
}), /Use only one/);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
describe('setup mode', function () {
|
|
297
|
+
it('should generate the root CA and install it in the trust store', async function () {
|
|
298
|
+
getConfigStub.returns({
|
|
299
|
+
tlsRootCaDir: '/custom-ca'
|
|
300
|
+
});
|
|
301
|
+
await httpsCli([], {
|
|
302
|
+
setup: true
|
|
303
|
+
});
|
|
304
|
+
(0, _chai.expect)(generateRootCaStub.calledOnce).to.equal(true);
|
|
305
|
+
(0, _chai.expect)(installRootCaToTrustStoreStub.calledOnce).to.equal(true);
|
|
306
|
+
(0, _chai.expect)(installRootCaToTrustStoreStub.firstCall.args[0]).to.equal('/custom-ca/ca.crt');
|
|
307
|
+
const printed = consoleLogStub.getCalls().map(c => c.args[0] || '').join('\n');
|
|
308
|
+
(0, _chai.expect)(printed).to.contain('trust store');
|
|
309
|
+
});
|
|
310
|
+
it('should reject service names with setup', async function () {
|
|
311
|
+
let caught;
|
|
312
|
+
try {
|
|
313
|
+
await httpsCli(['web'], {
|
|
314
|
+
setup: true
|
|
315
|
+
});
|
|
316
|
+
} catch (err) {
|
|
317
|
+
caught = err;
|
|
318
|
+
}
|
|
319
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
320
|
+
(0, _chai.expect)(caught.message).to.contain('does not take service names');
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
describe('all mode', function () {
|
|
324
|
+
it('should enable HTTPS for every available service', async function () {
|
|
325
|
+
await httpsCli([], {
|
|
326
|
+
all: true,
|
|
327
|
+
navy: 'env-1'
|
|
328
|
+
});
|
|
329
|
+
(0, _chai.expect)(createCertStub.callCount).to.equal(2);
|
|
330
|
+
(0, _chai.expect)(reconfigureHTTPProxyStub.calledOnce).to.equal(true);
|
|
331
|
+
const printed = consoleLogStub.getCalls().map(c => c.args[0] || '').join('\n');
|
|
332
|
+
(0, _chai.expect)(printed).to.contain('web');
|
|
333
|
+
(0, _chai.expect)(printed).to.contain('api');
|
|
334
|
+
});
|
|
335
|
+
it('should generate the root CA when cert files are missing', async function () {
|
|
336
|
+
fsStub.existsSync.callsFake(p => !p.endsWith('ca.crt') && !p.endsWith('ca.key'));
|
|
337
|
+
await httpsCli([], {
|
|
338
|
+
all: true,
|
|
339
|
+
navy: 'env-1'
|
|
340
|
+
});
|
|
341
|
+
(0, _chai.expect)(generateRootCaStub.calledOnce).to.equal(true);
|
|
342
|
+
});
|
|
343
|
+
it('should warn and return when no services are available', async function () {
|
|
344
|
+
navyStub.getAvailableServiceNames.resolves([]);
|
|
345
|
+
await httpsCli([], {
|
|
346
|
+
all: true,
|
|
347
|
+
navy: 'env-1'
|
|
348
|
+
});
|
|
349
|
+
(0, _chai.expect)(createCertStub.called).to.equal(false);
|
|
350
|
+
const printed = consoleLogStub.getCalls().map(c => c.args[0] || '').join('\n');
|
|
351
|
+
(0, _chai.expect)(printed).to.contain('No services found');
|
|
352
|
+
});
|
|
353
|
+
it('should reject service names with all', async function () {
|
|
354
|
+
let caught;
|
|
355
|
+
try {
|
|
356
|
+
await httpsCli(['web'], {
|
|
357
|
+
all: true
|
|
358
|
+
});
|
|
359
|
+
} catch (err) {
|
|
360
|
+
caught = err;
|
|
361
|
+
}
|
|
362
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
363
|
+
(0, _chai.expect)(caught.message).to.contain('does not take service names');
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
describe('issue mode', function () {
|
|
367
|
+
it('should issue a leaf cert for an external URL', async function () {
|
|
368
|
+
const cwdStub = sandbox.stub(process, 'cwd').returns('/current');
|
|
369
|
+
await httpsCli([], {
|
|
370
|
+
issue: 'https://external.local',
|
|
371
|
+
navy: 'env-1'
|
|
372
|
+
});
|
|
373
|
+
(0, _chai.expect)(issueLeafCertForUrlStub.calledOnce).to.equal(true);
|
|
374
|
+
(0, _chai.expect)(issueLeafCertForUrlStub.firstCall.args).to.eql(['https://external.local', '/current']);
|
|
375
|
+
const printed = consoleLogStub.getCalls().map(c => c.args[0] || '').join('\n');
|
|
376
|
+
(0, _chai.expect)(printed).to.contain('/out/host.crt');
|
|
377
|
+
(0, _chai.expect)(printed).to.contain('/out/host.key');
|
|
378
|
+
(0, _chai.expect)(printed).to.contain('/out/navy-root-ca.crt');
|
|
379
|
+
cwdStub.restore();
|
|
380
|
+
});
|
|
381
|
+
it('should honour issueOut when provided', async function () {
|
|
382
|
+
await httpsCli([], {
|
|
383
|
+
issue: 'https://external.local',
|
|
384
|
+
issueOut: '/custom/out'
|
|
385
|
+
});
|
|
386
|
+
(0, _chai.expect)(issueLeafCertForUrlStub.firstCall.args[1]).to.equal('/custom/out');
|
|
387
|
+
});
|
|
388
|
+
it('should reject service names with issue', async function () {
|
|
389
|
+
let caught;
|
|
390
|
+
try {
|
|
391
|
+
await httpsCli(['web'], {
|
|
392
|
+
issue: 'https://external.local'
|
|
393
|
+
});
|
|
394
|
+
} catch (err) {
|
|
395
|
+
caught = err;
|
|
396
|
+
}
|
|
397
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
398
|
+
(0, _chai.expect)(caught.message).to.contain('does not take service names');
|
|
399
|
+
});
|
|
208
400
|
});
|
|
209
401
|
});
|
|
210
402
|
});
|
package/lib/cli/https.js
CHANGED
|
@@ -5,16 +5,64 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.default = _default;
|
|
8
|
+
var _path = _interopRequireDefault(require("path"));
|
|
8
9
|
var _chalk = _interopRequireDefault(require("chalk"));
|
|
9
10
|
var _errors = require("../errors");
|
|
10
11
|
var _navy = require("../navy");
|
|
11
12
|
var _config = require("../config");
|
|
12
13
|
var _httpProxy = require("../http-proxy");
|
|
13
14
|
var _https = require("../util/https");
|
|
15
|
+
var _installRootCa = require("../util/install-root-ca");
|
|
14
16
|
var _fs = _interopRequireDefault(require("fs"));
|
|
17
|
+
function normaliseServiceArgs(services) {
|
|
18
|
+
if (services == null) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(services)) {
|
|
22
|
+
const result = [];
|
|
23
|
+
for (const s of services) {
|
|
24
|
+
if (typeof s === 'string' && s !== '') {
|
|
25
|
+
result.push(s);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
if (typeof services === 'string') {
|
|
31
|
+
return services ? [services] : [];
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
async function issueHttpsForServices(navy, serviceNames, availableServices) {
|
|
36
|
+
const httpsReadyServices = [];
|
|
37
|
+
for (const service of serviceNames) {
|
|
38
|
+
if (!availableServices.includes(service)) {
|
|
39
|
+
console.log(`❌ ${service} not found, skipping`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const serviceUrl = await navy.url(service);
|
|
43
|
+
try {
|
|
44
|
+
await (0, _https.createCert)({
|
|
45
|
+
serviceUrl
|
|
46
|
+
});
|
|
47
|
+
httpsReadyServices.push(service);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.log(error);
|
|
50
|
+
throw new _errors.NavyError(`Could not generate TLS cert for ${service}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return httpsReadyServices;
|
|
54
|
+
}
|
|
15
55
|
async function _default(services, opts) {
|
|
16
56
|
const tlsRootCaDir = (0, _config.getConfig)().tlsRootCaDir || _config.DEFAULT_TLS_ROOT_CA_DIR;
|
|
17
57
|
const configDir = (0, _config.getConfigDir)();
|
|
58
|
+
const serviceList = normaliseServiceArgs(services);
|
|
59
|
+
const hasIssue = Boolean(opts.issue);
|
|
60
|
+
if (opts.disable && (opts.setup || opts.all || hasIssue)) {
|
|
61
|
+
throw new _errors.NavyError('`--disable` cannot be combined with `--setup`, `--all`, or `--issue`.');
|
|
62
|
+
}
|
|
63
|
+
if ([opts.setup, opts.all, hasIssue].filter(Boolean).length > 1) {
|
|
64
|
+
throw new _errors.NavyError('Use only one of `--setup`, `--all`, or `--issue`.');
|
|
65
|
+
}
|
|
18
66
|
if (opts.disable) {
|
|
19
67
|
await (0, _https.removeCert)(opts);
|
|
20
68
|
|
|
@@ -31,7 +79,60 @@ async function _default(services, opts) {
|
|
|
31
79
|
console.log();
|
|
32
80
|
return;
|
|
33
81
|
}
|
|
34
|
-
if (
|
|
82
|
+
if (opts.setup) {
|
|
83
|
+
if (serviceList.length > 0) {
|
|
84
|
+
throw new _errors.NavyError('`navy https --setup` does not take service names. Run `navy https <service>…` separately to issue service certificates.');
|
|
85
|
+
}
|
|
86
|
+
await (0, _https.generateRootCa)();
|
|
87
|
+
const caCrt = _path.default.join(tlsRootCaDir, 'ca.crt');
|
|
88
|
+
(0, _installRootCa.installRootCaToTrustStore)(caCrt);
|
|
89
|
+
console.log();
|
|
90
|
+
console.log(_chalk.default.green('✅ Navy root CA is installed in your trust store (where supported).'));
|
|
91
|
+
console.log(_chalk.default.dim('Browsers that use their own NSS store (e.g. Firefox) may still need the CA imported there.'));
|
|
92
|
+
console.log();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (opts.all) {
|
|
96
|
+
if (serviceList.length > 0) {
|
|
97
|
+
throw new _errors.NavyError('`navy https --all` does not take service names. Omit them, or name services without `--all`.');
|
|
98
|
+
}
|
|
99
|
+
if (!_fs.default.existsSync(`${tlsRootCaDir}/ca.crt`) || !_fs.default.existsSync(`${tlsRootCaDir}/ca.key`)) {
|
|
100
|
+
await (0, _https.generateRootCa)();
|
|
101
|
+
}
|
|
102
|
+
const navy = await (0, _navy.getNavy)(opts.navy);
|
|
103
|
+
await navy.ensurePluginsLoaded();
|
|
104
|
+
const availableServices = await navy.getAvailableServiceNames();
|
|
105
|
+
if (availableServices.length === 0) {
|
|
106
|
+
console.log();
|
|
107
|
+
console.log(_chalk.default.yellow('No services found in this navy; nothing to enable HTTPS for.'));
|
|
108
|
+
console.log();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const httpsReadyServices = await issueHttpsForServices(navy, availableServices, availableServices);
|
|
112
|
+
await (0, _httpProxy.reconfigureHTTPProxy)({
|
|
113
|
+
restart: true,
|
|
114
|
+
navyFile: await navy.getNavyFile()
|
|
115
|
+
});
|
|
116
|
+
console.log();
|
|
117
|
+
console.log(_chalk.default.green(`✅ Service(s) ${httpsReadyServices.join(', ')} now accessible via HTTPS🔒`));
|
|
118
|
+
console.log();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (hasIssue) {
|
|
122
|
+
if (serviceList.length > 0) {
|
|
123
|
+
throw new _errors.NavyError('`navy https --issue` does not take service names.');
|
|
124
|
+
}
|
|
125
|
+
const outDir = opts.issueOut ? _path.default.resolve(opts.issueOut) : process.cwd();
|
|
126
|
+
const paths = await (0, _https.issueLeafCertForUrl)(String(opts.issue), outDir);
|
|
127
|
+
console.log();
|
|
128
|
+
console.log(_chalk.default.green('✅ TLS certificate issued for use outside Navy:'));
|
|
129
|
+
console.log(` ${paths.certPath}`);
|
|
130
|
+
console.log(` ${paths.keyPath}`);
|
|
131
|
+
console.log(` ${paths.caCopyPath} (root CA; configure clients to trust this or append as a chain file)`);
|
|
132
|
+
console.log();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (serviceList.length === 0) {
|
|
35
136
|
if (!_fs.default.existsSync(`${configDir}/tls-certs`)) return;
|
|
36
137
|
const files = _fs.default.readdirSync(`${configDir}/tls-certs`);
|
|
37
138
|
const urls = files.filter(file => file.endsWith('.crt')).map(crt => {
|
|
@@ -48,23 +149,7 @@ async function _default(services, opts) {
|
|
|
48
149
|
const navy = await (0, _navy.getNavy)(opts.navy);
|
|
49
150
|
await navy.ensurePluginsLoaded();
|
|
50
151
|
const availableServices = await navy.getAvailableServiceNames();
|
|
51
|
-
const httpsReadyServices =
|
|
52
|
-
for (const service of services) {
|
|
53
|
-
if (!availableServices.includes(service)) {
|
|
54
|
-
console.log(`❌ ${service} not found, skipping`);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const serviceUrl = await navy.url(service);
|
|
58
|
-
try {
|
|
59
|
-
await (0, _https.createCert)({
|
|
60
|
-
serviceUrl
|
|
61
|
-
});
|
|
62
|
-
httpsReadyServices.push(service);
|
|
63
|
-
} catch (error) {
|
|
64
|
-
console.log(error);
|
|
65
|
-
throw new _errors.NavyError(`Could not generate TLS cert for ${service}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
152
|
+
const httpsReadyServices = await issueHttpsForServices(navy, serviceList, availableServices);
|
|
68
153
|
await (0, _httpProxy.reconfigureHTTPProxy)({
|
|
69
154
|
restart: true,
|
|
70
155
|
navyFile: await navy.getNavyFile()
|
package/lib/cli/program.js
CHANGED
|
@@ -171,7 +171,7 @@ _commander.program.command('health').option('-e, --navy [env]', `set the navy na
|
|
|
171
171
|
_commander.program.command('wait-for-healthy [services...]').option('-e, --navy [env]', `set the navy name to be used [${defaultNavy}]`, defaultNavy).description('Waits for the given services to be healthy').action(lazyRequire('./wait-for-healthy'));
|
|
172
172
|
_commander.program.command('use-tag <service> <tag>').option('-e, --navy [env]', `set the navy name to be used [${defaultNavy}]`, defaultNavy).description('Uses a specific tag for the given service').action(basicCliWrapper('useTag'));
|
|
173
173
|
_commander.program.command('reset-tag <service>').option('-e, --navy [env]', `set the navy name to be used [${defaultNavy}]`, defaultNavy).description('Resets any tag override on the given service').action(basicCliWrapper('resetTag'));
|
|
174
|
-
_commander.program.command('https [services...]').option('-e, --navy [env]', `set the navy name to be used [${defaultNavy}]`, defaultNavy).option('-d, --disable <service>', 'disable https (deletes cert) for a given service', null).description('Prints or enables HTTPS services').action(lazyRequire('./https')).on('--help', () => console.log(`
|
|
174
|
+
_commander.program.command('https [services...]').option('-e, --navy [env]', `set the navy name to be used [${defaultNavy}]`, defaultNavy).option('-d, --disable <service>', 'disable https (deletes cert) for a given service', null).option('-s, --setup', 'generate the Navy root CA (if needed) and install it in the system trust store').option('-a, --all', 'enable HTTPS for every service in this navy').option('-i, --issue <url>', 'issue a TLS certificate and key for a URL (non-Navy service), signed with the Navy root CA').option('-o, --issue-out <dir>', 'output directory for --issue (default: current working directory)').description('Prints or enables HTTPS services').action(lazyRequire('./https')).on('--help', () => console.log(`
|
|
175
175
|
Examples:
|
|
176
176
|
List urls of services that listen on https:
|
|
177
177
|
$ navy https
|
|
@@ -179,8 +179,18 @@ _commander.program.command('https [services...]').option('-e, --navy [env]', `se
|
|
|
179
179
|
Enable https for mywebservice and anotherwebservice services
|
|
180
180
|
$ navy https mywebservice anotherwebservice
|
|
181
181
|
|
|
182
|
+
Enable https for all services
|
|
183
|
+
$ navy https --all
|
|
184
|
+
|
|
182
185
|
Disable https for mywebservice
|
|
183
186
|
$ navy https -d mywebservice
|
|
187
|
+
|
|
188
|
+
Generate the Navy root CA and install it for this user (no service names)
|
|
189
|
+
$ navy https --setup
|
|
190
|
+
|
|
191
|
+
Issue a cert for an external host (writes leaf key/cert + navy-root-ca.crt)
|
|
192
|
+
$ navy https --issue https://api.example.test
|
|
193
|
+
$ navy https --issue https://api.example.test -o ./tls
|
|
184
194
|
`));
|
|
185
195
|
_commander.program.command('use-port <service> <internal> <external>').option('-e, --navy [env]', `set the navy name to be used [${defaultNavy}]`, defaultNavy).description('Uses a specific external port for the given service and internal port').action(basicCliWrapper('usePort'));
|
|
186
196
|
_commander.program.command('reset-port <service> <internal>').option('-e, --navy [env]', `set the navy name to be used [${defaultNavy}]`, defaultNavy).description('Resets a specific external port mapping set by use-port').action(basicCliWrapper('resetPort'));
|
|
@@ -776,6 +776,148 @@ describe('navy/index', function () {
|
|
|
776
776
|
(0, _chai.expect)(driver.destroy.calledOnce).to.equal(true);
|
|
777
777
|
(0, _chai.expect)(deleteState.calledOnce).to.equal(true);
|
|
778
778
|
});
|
|
779
|
+
it('should pass navyFile from a remaining navy when reconfiguring the proxy', async function () {
|
|
780
|
+
const driver = makeDriver();
|
|
781
|
+
const envPath = '/abs/env/Navyfile.js';
|
|
782
|
+
const otherPath = '/abs/other/Navyfile.js';
|
|
783
|
+
const destroyedNavyFile = {
|
|
784
|
+
httpProxyImage: 'destroyed/proxy:1'
|
|
785
|
+
};
|
|
786
|
+
const remainingNavyFile = {
|
|
787
|
+
httpProxyImage: 'remaining/proxy:1',
|
|
788
|
+
httpProxyEnv: {
|
|
789
|
+
FOO: 'bar'
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
const createProvider = path => ({
|
|
793
|
+
getNavyFilePath: async () => path
|
|
794
|
+
});
|
|
795
|
+
const resolveConfigProvider = _sinon.default.stub().callsFake(() => navy => createProvider(navy.normalisedName === 'other' ? otherPath : envPath));
|
|
796
|
+
const reconfigureHTTPProxy = _sinon.default.stub().resolves();
|
|
797
|
+
const deleteState = _sinon.default.stub().resolves();
|
|
798
|
+
const {
|
|
799
|
+
Navy
|
|
800
|
+
} = _proxyquire.default.noCallThru()('../', {
|
|
801
|
+
'../util/fs': {
|
|
802
|
+
readdirAsync: _sinon.default.stub().resolves(['env', 'other']),
|
|
803
|
+
lstatSync: _sinon.default.stub().returns({
|
|
804
|
+
isDirectory: () => true
|
|
805
|
+
})
|
|
806
|
+
},
|
|
807
|
+
'./state': {
|
|
808
|
+
getState: _sinon.default.stub().resolves({
|
|
809
|
+
driver: 'docker-compose',
|
|
810
|
+
configProvider: 'filesystem'
|
|
811
|
+
}),
|
|
812
|
+
saveState: _sinon.default.stub().resolves(),
|
|
813
|
+
deleteState,
|
|
814
|
+
pathToNavys: _sinon.default.stub().returns('/home/test/.navy/navies'),
|
|
815
|
+
pathToNavy: _sinon.default.stub().callsFake(n => `/home/test/.navy/navies/${n}`)
|
|
816
|
+
},
|
|
817
|
+
'../driver': {
|
|
818
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
819
|
+
},
|
|
820
|
+
'../config-provider': {
|
|
821
|
+
resolveConfigProviderFromName: resolveConfigProvider
|
|
822
|
+
},
|
|
823
|
+
'../config': {
|
|
824
|
+
getConfig: _sinon.default.stub().returns({
|
|
825
|
+
externalIP: null
|
|
826
|
+
})
|
|
827
|
+
},
|
|
828
|
+
'./plugin-interface': {
|
|
829
|
+
loadPlugins: _sinon.default.stub().resolves([])
|
|
830
|
+
},
|
|
831
|
+
'./middleware': {
|
|
832
|
+
middlewareRunner: _sinon.default.stub().resolves()
|
|
833
|
+
},
|
|
834
|
+
'../http-proxy': {
|
|
835
|
+
reconfigureHTTPProxy
|
|
836
|
+
},
|
|
837
|
+
'../util/external-ip': {
|
|
838
|
+
getExternalIP: _sinon.default.stub().resolves('127.0.0.1')
|
|
839
|
+
},
|
|
840
|
+
'../util/service-host': {
|
|
841
|
+
createUrlForService: _sinon.default.stub().resolves('http://svc.local'),
|
|
842
|
+
getUrlFromService: _sinon.default.stub().returns(null)
|
|
843
|
+
},
|
|
844
|
+
[envPath]: destroyedNavyFile,
|
|
845
|
+
[otherPath]: remainingNavyFile
|
|
846
|
+
});
|
|
847
|
+
await new Navy('env').destroy();
|
|
848
|
+
(0, _chai.expect)(reconfigureHTTPProxy.calledOnce).to.equal(true);
|
|
849
|
+
(0, _chai.expect)(reconfigureHTTPProxy.firstCall.args[0].navies).to.eql(['other']);
|
|
850
|
+
(0, _chai.expect)(reconfigureHTTPProxy.firstCall.args[0].navyFile).to.eql(remainingNavyFile);
|
|
851
|
+
});
|
|
852
|
+
it('should skip uninitialised remaining navies when resolving navyFile for the proxy', async function () {
|
|
853
|
+
const driver = makeDriver();
|
|
854
|
+
const otherPath = '/abs/other/Navyfile.js';
|
|
855
|
+
const remainingNavyFile = {
|
|
856
|
+
httpProxyImage: 'remaining/proxy:1'
|
|
857
|
+
};
|
|
858
|
+
const createProvider = path => ({
|
|
859
|
+
getNavyFilePath: async () => path
|
|
860
|
+
});
|
|
861
|
+
const resolveConfigProvider = _sinon.default.stub().callsFake(() => navy => createProvider(navy.normalisedName === 'other' ? otherPath : '/abs/env/Navyfile.js'));
|
|
862
|
+
const reconfigureHTTPProxy = _sinon.default.stub().resolves();
|
|
863
|
+
const deleteState = _sinon.default.stub().resolves();
|
|
864
|
+
const getState = _sinon.default.stub().callsFake(name => {
|
|
865
|
+
if (name === 'uninit') return Promise.resolve(null);
|
|
866
|
+
return Promise.resolve({
|
|
867
|
+
driver: 'docker-compose',
|
|
868
|
+
configProvider: 'filesystem'
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
const {
|
|
872
|
+
Navy
|
|
873
|
+
} = _proxyquire.default.noCallThru()('../', {
|
|
874
|
+
'../util/fs': {
|
|
875
|
+
readdirAsync: _sinon.default.stub().resolves(['env', 'uninit', 'other']),
|
|
876
|
+
lstatSync: _sinon.default.stub().returns({
|
|
877
|
+
isDirectory: () => true
|
|
878
|
+
})
|
|
879
|
+
},
|
|
880
|
+
'./state': {
|
|
881
|
+
getState,
|
|
882
|
+
saveState: _sinon.default.stub().resolves(),
|
|
883
|
+
deleteState,
|
|
884
|
+
pathToNavys: _sinon.default.stub().returns('/home/test/.navy/navies'),
|
|
885
|
+
pathToNavy: _sinon.default.stub().callsFake(n => `/home/test/.navy/navies/${n}`)
|
|
886
|
+
},
|
|
887
|
+
'../driver': {
|
|
888
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
889
|
+
},
|
|
890
|
+
'../config-provider': {
|
|
891
|
+
resolveConfigProviderFromName: resolveConfigProvider
|
|
892
|
+
},
|
|
893
|
+
'../config': {
|
|
894
|
+
getConfig: _sinon.default.stub().returns({
|
|
895
|
+
externalIP: null
|
|
896
|
+
})
|
|
897
|
+
},
|
|
898
|
+
'./plugin-interface': {
|
|
899
|
+
loadPlugins: _sinon.default.stub().resolves([])
|
|
900
|
+
},
|
|
901
|
+
'./middleware': {
|
|
902
|
+
middlewareRunner: _sinon.default.stub().resolves()
|
|
903
|
+
},
|
|
904
|
+
'../http-proxy': {
|
|
905
|
+
reconfigureHTTPProxy
|
|
906
|
+
},
|
|
907
|
+
'../util/external-ip': {
|
|
908
|
+
getExternalIP: _sinon.default.stub().resolves('127.0.0.1')
|
|
909
|
+
},
|
|
910
|
+
'../util/service-host': {
|
|
911
|
+
createUrlForService: _sinon.default.stub().resolves('http://svc.local'),
|
|
912
|
+
getUrlFromService: _sinon.default.stub().returns(null)
|
|
913
|
+
},
|
|
914
|
+
[otherPath]: remainingNavyFile
|
|
915
|
+
});
|
|
916
|
+
await new Navy('env').destroy();
|
|
917
|
+
(0, _chai.expect)(reconfigureHTTPProxy.calledOnce).to.equal(true);
|
|
918
|
+
(0, _chai.expect)(reconfigureHTTPProxy.firstCall.args[0].navies).to.eql(['uninit', 'other']);
|
|
919
|
+
(0, _chai.expect)(reconfigureHTTPProxy.firstCall.args[0].navyFile).to.eql(remainingNavyFile);
|
|
920
|
+
});
|
|
779
921
|
it('should still delete state even when driver.destroy throws', async function () {
|
|
780
922
|
const driver = makeDriver({
|
|
781
923
|
destroy: _sinon.default.stub().rejects(new Error('boom'))
|
package/lib/navy/index.js
CHANGED
|
@@ -237,9 +237,10 @@ class Navy extends _eventemitter.EventEmitter2 {
|
|
|
237
237
|
if (!(await this.isInitialised())) {
|
|
238
238
|
throw new _errors.NavyNotInitialisedError(this.name);
|
|
239
239
|
}
|
|
240
|
+
const remainingNavyNames = (await getLaunchedNavyNames()).filter(navy => navy !== this.normalisedName);
|
|
240
241
|
await (0, _httpProxy.reconfigureHTTPProxy)({
|
|
241
|
-
navies:
|
|
242
|
-
navyFile: await
|
|
242
|
+
navies: remainingNavyNames,
|
|
243
|
+
navyFile: await getNavyFileFromNavies(remainingNavyNames)
|
|
243
244
|
});
|
|
244
245
|
try {
|
|
245
246
|
await (await this.safeGetDriver()).destroy();
|
|
@@ -499,6 +500,15 @@ function getNavy(navyName) {
|
|
|
499
500
|
(0, _invariant.default)(navyName, "NO_NAVY_PROVIDED: No Navy provided");
|
|
500
501
|
return new Navy(navyName);
|
|
501
502
|
}
|
|
503
|
+
async function getNavyFileFromNavies(navyNames) {
|
|
504
|
+
for (const navyName of navyNames) {
|
|
505
|
+
const navy = getNavy(navyName);
|
|
506
|
+
if (!(await navy.isInitialised())) continue;
|
|
507
|
+
const navyFile = await navy.getNavyFile();
|
|
508
|
+
if (navyFile) return navyFile;
|
|
509
|
+
}
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
502
512
|
|
|
503
513
|
/**
|
|
504
514
|
* Returns an array of `Navy` instances which are currently imported and launched.
|