diff --git a/.changeset/forty-bags-trade.md b/.changeset/forty-bags-trade.md new file mode 100644 index 0000000000..4908134498 --- /dev/null +++ b/.changeset/forty-bags-trade.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Generated development HTTPS backend certificate is now checked for expiration date instead of file age. diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 2a7e307774..8def408fca 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -75,6 +75,7 @@ "morgan": "^1.10.0", "node-abort-controller": "^3.0.1", "node-fetch": "^2.6.7", + "node-forge": "^1.3.1", "raw-body": "^2.4.1", "request": "^2.88.2", "selfsigned": "^2.0.0", @@ -105,6 +106,7 @@ "@types/minimist": "^1.2.0", "@types/mock-fs": "^4.13.0", "@types/morgan": "^1.9.0", + "@types/node-forge": "^1.3.0", "@types/recursive-readdir": "^2.2.0", "@types/stoppable": "^1.1.0", "@types/supertest": "^2.0.8", diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index 572d8483b1..57334062ea 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -21,8 +21,9 @@ import * as http from 'http'; import * as https from 'https'; import { Logger } from 'winston'; import { HttpsSettings } from './config'; +import forge from 'node-forge'; -const ALMOST_MONTH_IN_MS = 25 * 24 * 60 * 60 * 1000; +const FIVE_DAYS_IN_MS = 5 * 24 * 60 * 60 * 1000; const IP_HOSTNAME_REGEX = /:|^\d+\.\d+\.\d+\.\d+$/; @@ -82,6 +83,16 @@ export async function createHttpsServer( return https.createServer(credentials, app) as http.Server; } +function getCertificateExpiration(cert: string, logger?: Logger) { + try { + const crt = forge.pki.certificateFromPem(cert); + return crt.validity.notAfter.getTime() - Date.now(); + } catch (error) { + logger?.warn(`Unable to parse self-signed certificate. ${error}`); + return 0; + } +} + async function getGeneratedCertificate(hostname: string, logger?: Logger) { const hasModules = await fs.pathExists('node_modules'); let certPath; @@ -94,23 +105,18 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { certPath = resolvePath('.dev-cert.pem'); } - let cert = undefined; if (await fs.pathExists(certPath)) { - const stat = await fs.stat(certPath); - const ageMs = Date.now() - stat.ctimeMs; - if (stat.isFile() && ageMs < ALMOST_MONTH_IN_MS) { - cert = await fs.readFile(certPath); + const cert = await fs.readFile(certPath); + const remainingMs = getCertificateExpiration(cert.toString(), logger); + if (remainingMs > FIVE_DAYS_IN_MS) { + logger?.info('Using existing self-signed certificate'); + return { + key: cert, + cert, + }; } } - if (cert) { - logger?.info('Using existing self-signed certificate'); - return { - key: cert, - cert: cert, - }; - } - logger?.info('Generating new self-signed certificate'); const newCert = await createCertificate(hostname); await fs.writeFile(certPath, newCert.cert + newCert.key, 'utf8'); diff --git a/yarn.lock b/yarn.lock index 4cd41344a4..1b3b301817 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3164,6 +3164,7 @@ __metadata: "@types/minimist": ^1.2.0 "@types/mock-fs": ^4.13.0 "@types/morgan": ^1.9.0 + "@types/node-forge": ^1.3.0 "@types/recursive-readdir": ^2.2.0 "@types/stoppable": ^1.1.0 "@types/supertest": ^2.0.8 @@ -3201,6 +3202,7 @@ __metadata: mysql2: ^2.2.5 node-abort-controller: ^3.0.1 node-fetch: ^2.6.7 + node-forge: ^1.3.1 raw-body: ^2.4.1 recursive-readdir: ^2.2.2 request: ^2.88.2 @@ -14012,6 +14014,15 @@ __metadata: languageName: node linkType: hard +"@types/node-forge@npm:^1.3.0": + version: 1.3.0 + resolution: "@types/node-forge@npm:1.3.0" + dependencies: + "@types/node": "*" + checksum: f811885f997fbeebb0df2db8249b9b288ad5f5573beaecf6d323b019bb9a4c12f1476a8974c934f62329862f29986c116263a50aef0008364cc3f9867672a5a0 + languageName: node + linkType: hard + "@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0": version: 17.0.25 resolution: "@types/node@npm:17.0.25"