From 912482a5ae7c81aff4dc127cc6d4dc69f4133b70 Mon Sep 17 00:00:00 2001 From: "Jian-Chen Chen (jesse)" Date: Wed, 1 Jul 2020 01:05:29 +0800 Subject: [PATCH] feat: ca certificate is optional for https configuration (#1853) * setup https server * typed handleHttps * fix if condition * generate certificate on the fly * revert yarn.lock Co-authored-by: Juan Picado --- package.json | 5 ++-- src/lib/bootstrap.ts | 37 ++++++++++++++++++------------ test/unit/modules/cli/cli.spec.ts | 29 +++++++++++++++++++++++ yarn.lock | Bin 429999 -> 430555 bytes 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 4ccf519be..5b527eb00 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@typescript-eslint/eslint-plugin": "2.12.0", "@verdaccio/babel-preset": "^9.6.1", "@verdaccio/eslint-config": "^8.5.0", - "@verdaccio/types": "^9.3.0", + "@verdaccio/types": "^9.7.0", "all-contributors-cli": "6.16.0", "codecov": "3.7.0", "cross-env": "7.0.2", @@ -81,16 +81,17 @@ "fs-extra": "8.1.0", "get-stdin": "7.0.0", "husky": "2.7.0", - "lockfile-lint": "4.2.2", "in-publish": "2.0.1", "jest": "25.3.0", "jest-environment-node": "25.3.0", "jest-junit": "9.0.0", "lint-staged": "8.2.1", + "lockfile-lint": "4.2.2", "nock": "11.7.2", "prettier": "1.19.1", "puppeteer": "1.8.0", "rimraf": "3.0.2", + "selfsigned": "1.10.7", "standard-version": "8.0.0", "supertest": "4.0.2", "typescript": "3.7.5", diff --git a/src/lib/bootstrap.ts b/src/lib/bootstrap.ts index 79862cbf0..91a3ec8ff 100644 --- a/src/lib/bootstrap.ts +++ b/src/lib/bootstrap.ts @@ -1,4 +1,5 @@ import { assign, isObject, isFunction } from 'lodash'; +import express from 'express'; import URL from 'url'; import fs from 'fs'; import http from 'http'; @@ -8,7 +9,7 @@ import endPointAPI from '../api/index'; import { getListListenAddresses, resolveConfigPath } from './cli/utils'; import { API_ERROR, certPem, csrPem, keyPem } from './constants'; -import { Callback } from '@verdaccio/types'; +import { Callback, ConfigWithHttps, HttpsConfKeyCert, HttpsConfPfx } from '@verdaccio/types'; import { Application } from 'express'; const logger = require('./logger'); @@ -47,11 +48,6 @@ function startVerdaccio(config: any, cliListen: string, configPath: string, pkgV addresses.forEach(function(addr): void { let webServer; if (addr.proto === 'https') { - // https must either have key cert and ca or a pfx and (optionally) a passphrase - if (!config.https || !((config.https.key && config.https.cert && config.https.ca) || config.https.pfx)) { - logHTTPSWarning(configPath); - } - webServer = handleHTTPS(app, configPath, config); } else { // http @@ -79,7 +75,7 @@ function logHTTPSWarning(storageLocation) { logger.logger.fatal( [ 'You have enabled HTTPS and need to specify either ', - ' "https.key", "https.cert" and "https.ca" or ', + ' "https.key" and "https.cert" or ', ' "https.pfx" and optionally "https.passphrase" ', 'to run https server', '', @@ -98,28 +94,39 @@ function logHTTPSWarning(storageLocation) { ' https:', ` key: ${resolveConfigPath(storageLocation, keyPem)}`, ` cert: ${resolveConfigPath(storageLocation, certPem)}`, - ` ca: ${resolveConfigPath(storageLocation, csrPem)}`, ].join('\n') ); process.exit(2); } -function handleHTTPS(app, configPath, config) { +function handleHTTPS(app: express.Application, configPath: string, config: ConfigWithHttps): https.Server { try { let httpsOptions = { secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3, // disable insecure SSLv2 and SSLv3 }; - if (config.https.pfx) { + const keyCertConfig = config.https as HttpsConfKeyCert; + const pfxConfig = config.https as HttpsConfPfx; + + // https must either have key and cert or a pfx and (optionally) a passphrase + if (!((keyCertConfig.key && keyCertConfig.cert) || pfxConfig.pfx)) { + logHTTPSWarning(configPath); + } + + if (pfxConfig.pfx) { + const { pfx, passphrase } = pfxConfig; httpsOptions = assign(httpsOptions, { - pfx: fs.readFileSync(config.https.pfx), - passphrase: config.https.passphrase || '', + pfx: fs.readFileSync(pfx), + passphrase: passphrase || '', }); } else { + const { key, cert, ca } = keyCertConfig; httpsOptions = assign(httpsOptions, { - key: fs.readFileSync(config.https.key), - cert: fs.readFileSync(config.https.cert), - ca: fs.readFileSync(config.https.ca), + key: fs.readFileSync(key), + cert: fs.readFileSync(cert), + ...(ca && { + ca: fs.readFileSync(ca), + }), }); } return https.createServer(httpsOptions, app); diff --git a/test/unit/modules/cli/cli.spec.ts b/test/unit/modules/cli/cli.spec.ts index d46e9b558..d789d8a89 100644 --- a/test/unit/modules/cli/cli.spec.ts +++ b/test/unit/modules/cli/cli.spec.ts @@ -1,5 +1,8 @@ import path from 'path'; import _ from 'lodash'; +import selfsigned from 'selfsigned'; +import os from 'os'; +import fs from 'fs'; import startServer from '../../../../src'; import config from '../../partials/config'; @@ -140,6 +143,32 @@ describe('startServer via API', () => { global.process = realProcess; }); + test('should start a https server with key and cert', async (done) => { + const store = path.join(__dirname, 'partials/store'); + const serverName = 'verdaccio-test'; + const version = '1.0.0'; + const address = 'https://www.domain.com:443'; + const { private: key, cert } = selfsigned.generate(); + const keyPath = path.join(os.tmpdir(), 'key.pem'); + const certPath = path.join(os.tmpdir(), 'crt.pem'); + fs.writeFileSync(keyPath, key); + fs.writeFileSync(certPath, cert); + + const conf = config(); + conf.https = { + key: keyPath, + cert: certPath, + }; + + await startServer(conf, address, store, version, serverName, + (webServer, addrs) => { + expect(webServer).toBeDefined(); + expect(addrs).toBeDefined(); + expect(addrs.proto).toBe('https'); + done(); + }); + }) + test('should fails if config is missing', async () => { try { // @ts-ignore diff --git a/yarn.lock b/yarn.lock index 98c71dcb084c39285b8ed28f7ea03ff14ecf96c8..2742f51b228bcfdd063dee53215c58e675ba24a0 100644 GIT binary patch delta 613 zcmYL`J8#oq6o!?OGDKoviA3p;mI*~kKHt92W}&$!PD1K9v72n)&ABvXfzDDAf!H1(_-^y97c6rsD z@~WF`6=Xatl3}MInBy0ylO6SzgM}9YU+i+in#rCx3j!ZQWldbbQcy^`x@6BUO5u`^b%VRPS}X8) zvfQ*tolGlT#!yfSgB73Y70-6+fm?PYb5Sc3C(Pikt5_L5U&R%xQ>z)<*th8Hd%|_X zVc+BIOh^kLjh@94i8;RvhrX9My^S{b`0(TwN)P$}-5u1ON&P+Ce}`d36*PwdS!0TV zWJgrFs$z{$3K^l0Lr2m8+`M}liMQ2;w8-Vx+^*`L;kdF?tu309yj;n&vVj6jwNQd! z?X5?)Qnp^UsmaB;P2DLPOuE+jxOVPV?B+}TyT%iz`^vZL I>qF(&9|};&z5oCK delta 228 zcmcb;SZe)zsfI0#C2NI@^$e7(xD*u1Qj3Z+^YavxET{@7+6qW8#Qe}`6V31dkT@m4)Y~Y+>5n!4Y zkQtJbnPKSb6lv;SQRr)sT2$uiUT%?}