From 0eee5a07c56ea2dc0f5b06c1176554dfc6295a12 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 22 Nov 2022 12:23:01 -0800 Subject: [PATCH] Remove mac-ca usage since it was only in tests (#6043) (#6321) * Remove mac-ca usage since it was only in tests (#6043) * Make injecting CAs injectable, remove mac-ca as dependency * Fix win-ca failing on electron renderer on windows * Fix the matcher under features/ for main Signed-off-by: Sebastian Malton * Fix type errors from new types Signed-off-by: Sebastian Malton * Temp change to see windows errors on CI Signed-off-by: Sebastian Malton * Fix temp change Signed-off-by: Sebastian Malton * Change error message for windows Signed-off-by: Sebastian Malton * Increase maxBuffer size when reading windows CAs Signed-off-by: Sebastian Malton * Switch back to running integration tests on windows Signed-off-by: Sebastian Malton * Fix usage after rebase Signed-off-by: Sebastian Malton * Update lock file Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- .../__tests__/app-preferences.tests.ts | 7 +- integration/__tests__/cluster-pages.tests.ts | 3 +- integration/helpers/utils.ts | 8 -- package.json | 1 - src/common/__tests__/system-ca.test.ts | 99 ----------------- .../inject-system-cas.injectable.ts | 39 +++++++ .../request-system-cas-token.ts | 10 ++ .../request-system-cas.injectable.darwin.ts | 57 ++++++++++ .../request-system-cas.injectable.linux.ts | 14 +++ ...quest-system-cas.injectable.testing-env.ts | 14 +++ .../request-system-cas.injectable.win32.ts | 56 ++++++++++ src/common/fs/exec-file.injectable.ts | 52 ++++++--- src/common/system-ca.ts | 102 ------------------ src/jest.setup.ts | 4 +- src/main/getDi.ts | 8 +- .../runnables/setup-system-ca.injectable.ts | 12 +-- .../root-frame/setup-system-ca.injectable.ts | 12 +-- src/renderer/getDi.tsx | 8 +- src/test-utils/skippers.ts | 18 ++++ types/from-webpack.d.ts | 8 ++ types/mocks.d.ts | 1 - webpack/main.ts | 6 ++ webpack/renderer.ts | 6 ++ yarn.lock | 19 ++-- 24 files changed, 293 insertions(+), 271 deletions(-) delete mode 100644 src/common/__tests__/system-ca.test.ts create mode 100644 src/common/certificate-authorities/inject-system-cas.injectable.ts create mode 100644 src/common/certificate-authorities/request-system-cas-token.ts create mode 100644 src/common/certificate-authorities/request-system-cas.injectable.darwin.ts create mode 100644 src/common/certificate-authorities/request-system-cas.injectable.linux.ts create mode 100644 src/common/certificate-authorities/request-system-cas.injectable.testing-env.ts create mode 100644 src/common/certificate-authorities/request-system-cas.injectable.win32.ts delete mode 100644 src/common/system-ca.ts create mode 100644 src/test-utils/skippers.ts create mode 100644 types/from-webpack.d.ts diff --git a/integration/__tests__/app-preferences.tests.ts b/integration/__tests__/app-preferences.tests.ts index e5cca7c19c..1d7d7029fb 100644 --- a/integration/__tests__/app-preferences.tests.ts +++ b/integration/__tests__/app-preferences.tests.ts @@ -24,9 +24,10 @@ describe("preferences page tests", () => { await app.evaluate(async ({ app }) => { await app.applicationMenu - .getMenuItemById(process.platform === "darwin" ? "mac" : "file") - .submenu.getMenuItemById("navigate-to-preferences") - .click(); + ?.getMenuItemById(process.platform === "darwin" ? "mac" : "file") + ?.submenu + ?.getMenuItemById("navigate-to-preferences") + ?.click(); }); }, 10*60*1000); diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index c2896309f4..842a38da88 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -14,10 +14,11 @@ import { minikubeReady } from "../helpers/minikube"; import type { Frame, Page } from "playwright"; import { groupBy, toPairs } from "lodash/fp"; import { pipeline } from "@ogre-tools/fp"; +import { describeIf } from "../../src/test-utils/skippers"; const TEST_NAMESPACE = "integration-tests"; -utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { +describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { let window: Page; let cleanup: undefined | (() => Promise); let frame: Frame; diff --git a/integration/helpers/utils.ts b/integration/helpers/utils.ts index 6d56cc1f8c..02aef4f3fd 100644 --- a/integration/helpers/utils.ts +++ b/integration/helpers/utils.ts @@ -18,14 +18,6 @@ export const appPaths: Partial> = { "darwin": "./dist/mac/OpenLens.app/Contents/MacOS/OpenLens", }; -export function itIf(condition: boolean) { - return condition ? it : it.skip; -} - -export function describeIf(condition: boolean) { - return condition ? describe : describe.skip; -} - async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise { return new Promise((resolve, reject) => { const cleanup = disposer(); diff --git a/package.json b/package.json index d849b84e26..1d598b735a 100644 --- a/package.json +++ b/package.json @@ -252,7 +252,6 @@ "js-yaml": "^4.1.0", "jsdom": "^16.7.0", "lodash": "^4.17.15", - "mac-ca": "^1.0.6", "marked": "^4.2.3", "md5-file": "^5.0.0", "mobx": "^6.7.0", diff --git a/src/common/__tests__/system-ca.test.ts b/src/common/__tests__/system-ca.test.ts deleted file mode 100644 index 473fe6ed57..0000000000 --- a/src/common/__tests__/system-ca.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import https from "https"; -import os from "os"; -import { getMacRootCA, getWinRootCA, injectCAs, DSTRootCAX3 } from "../system-ca"; -import { dependencies, devDependencies } from "../../../package.json"; -import assert from "assert"; - -const deps = { ...dependencies, ...devDependencies }; - -// Skip the test if mac-ca is not installed, or os is not darwin -(deps["mac-ca"] && os.platform().includes("darwin") ? describe: describe.skip)("inject CA for Mac", () => { - // for reset https.globalAgent.options.ca after testing - let _ca: string | Buffer | (string | Buffer)[] | undefined; - - beforeEach(() => { - _ca = https.globalAgent.options.ca; - }); - - afterEach(() => { - https.globalAgent.options.ca = _ca; - }); - - /** - * The test to ensure using getMacRootCA + injectCAs injects CAs in the same way as using - * the auto injection (require('mac-ca')) - */ - it("should inject the same ca as mac-ca", async () => { - const osxCAs = await getMacRootCA(); - - injectCAs(osxCAs); - const injected = https.globalAgent.options.ca as (string | Buffer)[]; - - await import("mac-ca"); - const injectedByMacCA = https.globalAgent.options.ca as (string | Buffer)[]; - - expect(new Set(injected)).toEqual(new Set(injectedByMacCA)); - }); - - it("shouldn't included the expired DST Root CA X3 on Mac", async () => { - const osxCAs = await getMacRootCA(); - - injectCAs(osxCAs); - const injected = https.globalAgent.options.ca; - - assert(injected); - expect(injected.includes(DSTRootCAX3)).toBeFalsy(); - }); -}); - -// Skip the test if win-ca is not installed, or os is not win32 -(deps["win-ca"] && os.platform().includes("win32") ? describe: describe.skip)("inject CA for Windows", () => { - // for reset https.globalAgent.options.ca after testing - let _ca: string | Buffer | (string | Buffer)[] | undefined; - - beforeEach(() => { - _ca = https.globalAgent.options.ca; - }); - - afterEach(() => { - https.globalAgent.options.ca = _ca; - }); - - /** - * The test to ensure using win-ca/api injects CAs in the same way as using - * the auto injection (require('win-ca').inject('+')) - */ - it("should inject the same ca as winca.inject('+')", async () => { - const winCAs = await getWinRootCA(); - - const wincaAPI = await import("win-ca/api"); - - wincaAPI.inject("+", winCAs); - const injected = https.globalAgent.options.ca as (string | Buffer)[]; - - const winca = await import("win-ca"); - - winca.inject("+"); // see: https://github.com/ukoloff/win-ca#caveats - const injectedByWinCA = https.globalAgent.options.ca as (string | Buffer)[]; - - expect(new Set(injected)).toEqual(new Set(injectedByWinCA)); - }); - - it("shouldn't included the expired DST Root CA X3 on Windows", async () => { - const winCAs = await getWinRootCA(); - - const wincaAPI = await import("win-ca/api"); - - wincaAPI.inject("true", winCAs); - const injected = https.globalAgent.options.ca as (string | Buffer)[]; - - expect(injected.includes(DSTRootCAX3)).toBeFalsy(); - }); -}); - - - diff --git a/src/common/certificate-authorities/inject-system-cas.injectable.ts b/src/common/certificate-authorities/inject-system-cas.injectable.ts new file mode 100644 index 0000000000..b6223fa38e --- /dev/null +++ b/src/common/certificate-authorities/inject-system-cas.injectable.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectable } from "@ogre-tools/injectable"; +import { globalAgent } from "https"; +import { requestSystemCAsInjectionToken } from "./request-system-cas-token"; + +// DST Root CA X3, which was expired on 9.30.2021 +const DSTRootCAX3 = "-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n"; + +function isCertActive(cert: string) { + const isExpired = typeof cert !== "string" || cert.includes(DSTRootCAX3); + + return !isExpired; +} + +const injectSystemCAsInjectable = getInjectable({ + id: "inject-system-cas", + instantiate: (di) => { + const requestSystemCAs = di.inject(requestSystemCAsInjectionToken); + + return async () => { + for (const cert of await requestSystemCAs()) { + if (isCertActive(cert)) { + if (Array.isArray(globalAgent.options.ca) && !globalAgent.options.ca.includes(cert)) { + globalAgent.options.ca.push(cert); + } else { + globalAgent.options.ca = [cert]; + } + } + } + }; + }, +}); + +export default injectSystemCAsInjectable; + diff --git a/src/common/certificate-authorities/request-system-cas-token.ts b/src/common/certificate-authorities/request-system-cas-token.ts new file mode 100644 index 0000000000..c69b0bd8b0 --- /dev/null +++ b/src/common/certificate-authorities/request-system-cas-token.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; + +export const requestSystemCAsInjectionToken = getInjectionToken<() => Promise>({ + id: "request-system-cas-token", +}); diff --git a/src/common/certificate-authorities/request-system-cas.injectable.darwin.ts b/src/common/certificate-authorities/request-system-cas.injectable.darwin.ts new file mode 100644 index 0000000000..c471c954e4 --- /dev/null +++ b/src/common/certificate-authorities/request-system-cas.injectable.darwin.ts @@ -0,0 +1,57 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import execFileInjectable from "../fs/exec-file.injectable"; +import loggerInjectable from "../logger.injectable"; +import type { AsyncResult } from "../utils/async-result"; +import { requestSystemCAsInjectionToken } from "./request-system-cas-token"; + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions +const certSplitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g; + +const requestSystemCAsInjectable = getInjectable({ + id: "request-system-cas", + instantiate: (di) => { + const execFile = di.inject(execFileInjectable); + const logger = di.inject(loggerInjectable); + + const execSecurity = async (...args: string[]): Promise> => { + const result = await execFile("/usr/bin/security", args); + + if (!result.callWasSuccessful) { + return { + callWasSuccessful: false, + error: result.error.stderr || result.error.message, + }; + } + + return { + callWasSuccessful: true, + response: result.response.split(certSplitPattern), + }; + }; + + return async () => { + const [trustedResult, rootCAResult] = await Promise.all([ + execSecurity("find-certificate", "-a", "-p"), + execSecurity("find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain"), + ]); + + if (!trustedResult.callWasSuccessful) { + logger.warn(`[INJECT-CAS]: Error retreiving trusted CAs: ${trustedResult.error}`); + } else if (!rootCAResult.callWasSuccessful) { + logger.warn(`[INJECT-CAS]: Error retreiving root CAs: ${rootCAResult.error}`); + } else { + return [...new Set([...trustedResult.response, ...rootCAResult.response])]; + } + + return []; + }; + }, + causesSideEffects: true, + injectionToken: requestSystemCAsInjectionToken, +}); + +export default requestSystemCAsInjectable; diff --git a/src/common/certificate-authorities/request-system-cas.injectable.linux.ts b/src/common/certificate-authorities/request-system-cas.injectable.linux.ts new file mode 100644 index 0000000000..1d7bf10350 --- /dev/null +++ b/src/common/certificate-authorities/request-system-cas.injectable.linux.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { requestSystemCAsInjectionToken } from "./request-system-cas-token"; + +const requestSystemCAsInjectable = getInjectable({ + id: "request-system-cas", + instantiate: () => async () => [], + injectionToken: requestSystemCAsInjectionToken, +}); + +export default requestSystemCAsInjectable; diff --git a/src/common/certificate-authorities/request-system-cas.injectable.testing-env.ts b/src/common/certificate-authorities/request-system-cas.injectable.testing-env.ts new file mode 100644 index 0000000000..1d7bf10350 --- /dev/null +++ b/src/common/certificate-authorities/request-system-cas.injectable.testing-env.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { requestSystemCAsInjectionToken } from "./request-system-cas-token"; + +const requestSystemCAsInjectable = getInjectable({ + id: "request-system-cas", + instantiate: () => async () => [], + injectionToken: requestSystemCAsInjectionToken, +}); + +export default requestSystemCAsInjectable; diff --git a/src/common/certificate-authorities/request-system-cas.injectable.win32.ts b/src/common/certificate-authorities/request-system-cas.injectable.win32.ts new file mode 100644 index 0000000000..4940aa2a7b --- /dev/null +++ b/src/common/certificate-authorities/request-system-cas.injectable.win32.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import execFileInjectable from "../fs/exec-file.injectable"; +import loggerInjectable from "../logger.injectable"; +import { requestSystemCAsInjectionToken } from "./request-system-cas-token"; + +const pemEncoding = (hexEncodedCert: String) => { + const certData = Buffer.from(hexEncodedCert, "hex").toString("base64"); + const lines = ["-----BEGIN CERTIFICATE-----"]; + + for (let i = 0; i < certData.length; i += 64) { + lines.push(certData.substring(i, i + 64)); + } + + lines.push("-----END CERTIFICATE-----", ""); + + return lines.join("\r\n"); +}; + +const requestSystemCAsInjectable = getInjectable({ + id: "request-system-cas", + instantiate: (di) => { + const wincaRootsExePath: string = __non_webpack_require__.resolve("win-ca/lib/roots.exe"); + const execFile = di.inject(execFileInjectable); + const logger = di.inject(loggerInjectable); + + return async () => { + /** + * This needs to be done manually because for some reason calling the api from "win-ca" + * directly fails to load "child_process" correctly on renderer + */ + const result = await execFile(wincaRootsExePath, { + maxBuffer: 128 * 1024 * 1024, // 128 MiB + }); + + if (!result.callWasSuccessful) { + logger.warn(`[INJECT-CAS]: Error retreiving CAs`, result.error); + + return []; + } + + return result + .response + .split("\r\n") + .filter(Boolean) + .map(pemEncoding); + }; + }, + causesSideEffects: true, + injectionToken: requestSystemCAsInjectionToken, +}); + +export default requestSystemCAsInjectable; diff --git a/src/common/fs/exec-file.injectable.ts b/src/common/fs/exec-file.injectable.ts index 4a462932c0..f026e0db3a 100644 --- a/src/common/fs/exec-file.injectable.ts +++ b/src/common/fs/exec-file.injectable.ts @@ -7,28 +7,50 @@ import type { ExecFileException, ExecFileOptions } from "child_process"; import { execFile } from "child_process"; import type { AsyncResult } from "../utils/async-result"; +export type ExecFileError = ExecFileException & { stderr: string }; + export interface ExecFile { - (filePath: string, args: string[], options?: ExecFileOptions): Promise>; + (filePath: string): Promise>; + (filePath: string, argsOrOptions: string[] | ExecFileOptions): Promise>; + (filePath: string, args: string[], options: ExecFileOptions): Promise>; } const execFileInjectable = getInjectable({ id: "exec-file", - instantiate: (): ExecFile => (filePath, args, options) => new Promise((resolve) => { - execFile(filePath, args, options ?? {}, (error, stdout, stderr) => { - if (error) { - resolve({ - callWasSuccessful: false, - error: Object.assign(error, { stderr }), + instantiate: (): ExecFile => { + return (filePath: string, argsOrOptions?: string[] | ExecFileOptions, maybeOptions?: ExecFileOptions) => { + const { args, options } = (() => { + if (Array.isArray(argsOrOptions)) { + return { + args: argsOrOptions, + options: maybeOptions ?? {}, + }; + } else { + return { + args: [], + options: argsOrOptions ?? {}, + }; + } + })(); + + return new Promise((resolve) => { + execFile(filePath, args, options, (error, stdout, stderr) => { + if (error) { + resolve({ + callWasSuccessful: false, + error: Object.assign(error, { stderr }), + }); + } else { + resolve({ + callWasSuccessful: true, + response: stdout, + }); + } }); - } else { - resolve({ - callWasSuccessful: true, - response: stdout, - }); - } - }); - }), + }); + }; + }, causesSideEffects: true, }); diff --git a/src/common/system-ca.ts b/src/common/system-ca.ts deleted file mode 100644 index 4e57ca2d7b..0000000000 --- a/src/common/system-ca.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { isMac, isWindows } from "./vars"; -import wincaAPI from "win-ca/api"; -import https from "https"; -import { promiseExecFile } from "./utils/promise-exec"; - -// DST Root CA X3, which was expired on 9.30.2021 -export const DSTRootCAX3 = "-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n"; - -export function isCertActive(cert: string) { - const isExpired = typeof cert !== "string" || cert.includes(DSTRootCAX3); - - return !isExpired; -} - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions -const certSplitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g; - -async function execSecurity(...args: string[]): Promise { - const { stdout } = await promiseExecFile("/usr/bin/security", args); - - return stdout.split(certSplitPattern); -} - -/** - * Get root CA certificate from MacOSX system keychain - * Only return non-expred certificates. - */ -export async function getMacRootCA() { - // inspired mac-ca https://github.com/jfromaniello/mac-ca - const [trusted, rootCA] = await Promise.all([ - execSecurity("find-certificate", "-a", "-p"), - execSecurity("find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain"), - ]); - - return [...new Set([...trusted, ...rootCA])].filter(isCertActive); -} - -/** - * Get root CA certificate from Windows system certificate store. - * Only return non-expred certificates. - */ -export function getWinRootCA(): Promise { - return new Promise((resolve) => { - const CAs: string[] = []; - - wincaAPI({ - format: wincaAPI.der2.pem, - inject: false, - ondata: (ca: string) => { - CAs.push(ca); - }, - onend: () => { - resolve(CAs.filter(isCertActive)); - }, - }); - }); -} - - -/** - * Add (or merge) CAs to https.globalAgent.options.ca - */ -export function injectCAs(CAs: string[]) { - for (const cert of CAs) { - if (Array.isArray(https.globalAgent.options.ca) && !https.globalAgent.options.ca.includes(cert)) { - https.globalAgent.options.ca.push(cert); - } else { - https.globalAgent.options.ca = [cert]; - } - } -} - -/** - * Inject CAs found in OS's (Windoes/MacOSX only) root certificate store to https.globalAgent.options.ca - */ -export async function injectSystemCAs() { - if (isMac) { - try { - const osxRootCAs = await getMacRootCA(); - - injectCAs(osxRootCAs); - } catch (error) { - console.warn(`[MAC-CA]: Error injecting root CAs from MacOSX. ${error}`); - } - } - - if (isWindows) { - try { - const winRootCAs = await getWinRootCA(); - - wincaAPI.inject("+", winRootCAs); - - } catch (error) { - console.warn(`[WIN-CA]: Error injecting root CAs from Windows. ${error}`); - } - } -} diff --git a/src/jest.setup.ts b/src/jest.setup.ts index 41fa87389b..69e94c4fb2 100644 --- a/src/jest.setup.ts +++ b/src/jest.setup.ts @@ -54,7 +54,7 @@ const getInjectables = (environment: "renderer" | "main", filePathGlob: string) }), ].map(x => path.resolve(__dirname, x)); -(global as any).rendererInjectablePaths = getInjectables("renderer", "*.injectable.{ts,tsx}"); +(global as any).rendererInjectablePaths = getInjectables("renderer", "*.{injectable,injectable.testing-env}.{ts,tsx}"); (global as any).rendererGlobalOverridePaths = getInjectables("renderer", "*.global-override-for-injectable.{ts,tsx}"); -(global as any).mainInjectablePaths = getInjectables("main", "*.injectable.{ts,tsx}"); +(global as any).mainInjectablePaths = getInjectables("main", "*.{injectable,injectable.testing-env}.{ts,tsx}"); (global as any).mainGlobalOverridePaths = getInjectables("main", "*.global-override-for-injectable.{ts,tsx}"); diff --git a/src/main/getDi.ts b/src/main/getDi.ts index 17d9cc36b7..8b71cca39b 100644 --- a/src/main/getDi.ts +++ b/src/main/getDi.ts @@ -19,10 +19,10 @@ export const getDi = () => { autoRegister({ di, requireContexts: [ - require.context("./", true, /\.injectable\.(ts|tsx)$/), - require.context("../extensions", true, /\.injectable\.(ts|tsx)$/), - require.context("../common", true, /\.injectable\.(ts|tsx)$/), - require.context("../features", true, /.*\/(main|common)\/.*\.injectable\.(ts|tsx)$/), + require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../extensions", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../features", true, CONTEXT_MATCHER_FOR_FEATURES), ], }); }); diff --git a/src/main/start-main-application/runnables/setup-system-ca.injectable.ts b/src/main/start-main-application/runnables/setup-system-ca.injectable.ts index 94589fca7d..b5219dbf4f 100644 --- a/src/main/start-main-application/runnables/setup-system-ca.injectable.ts +++ b/src/main/start-main-application/runnables/setup-system-ca.injectable.ts @@ -2,22 +2,16 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { injectSystemCAs } from "../../../common/system-ca"; import { getInjectable } from "@ogre-tools/injectable"; import { beforeApplicationIsLoadingInjectionToken } from "../runnable-tokens/before-application-is-loading-injection-token"; +import injectSystemCAsInjectable from "../../../common/certificate-authorities/inject-system-cas.injectable"; const setupSystemCaInjectable = getInjectable({ id: "setup-system-ca", - - instantiate: () => ({ + instantiate: (di) => ({ id: "setup-system-ca", - run: async () => { - await injectSystemCAs(); - }, + run: di.inject(injectSystemCAsInjectable), }), - - causesSideEffects: true, - injectionToken: beforeApplicationIsLoadingInjectionToken, }); diff --git a/src/renderer/frames/root-frame/setup-system-ca.injectable.ts b/src/renderer/frames/root-frame/setup-system-ca.injectable.ts index 3ee10748f9..cc8a0bafcc 100644 --- a/src/renderer/frames/root-frame/setup-system-ca.injectable.ts +++ b/src/renderer/frames/root-frame/setup-system-ca.injectable.ts @@ -2,22 +2,16 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { injectSystemCAs } from "../../../common/system-ca"; import { getInjectable } from "@ogre-tools/injectable"; import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token"; +import injectSystemCAsInjectable from "../../../common/certificate-authorities/inject-system-cas.injectable"; const setupSystemCaInjectable = getInjectable({ id: "setup-system-ca", - - instantiate: () => ({ + instantiate: (di) => ({ id: "setup-system-ca", - run: async () => { - await injectSystemCAs(); - }, + run: di.inject(injectSystemCAsInjectable), }), - - causesSideEffects: true, - injectionToken: beforeFrameStartsInjectionToken, }); diff --git a/src/renderer/getDi.tsx b/src/renderer/getDi.tsx index 348c5e369b..28e111bc97 100644 --- a/src/renderer/getDi.tsx +++ b/src/renderer/getDi.tsx @@ -20,10 +20,10 @@ export const getDi = () => { autoRegister({ di, requireContexts: [ - require.context("./", true, /\.injectable\.(ts|tsx)$/), - require.context("../common", true, /\.injectable\.(ts|tsx)$/), - require.context("../extensions", true, /\.injectable\.(ts|tsx)$/), - require.context("../features", true, /.*\/(renderer|common)\/.*\.injectable\.(ts|tsx)$/), + require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../extensions", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../features", true, CONTEXT_MATCHER_FOR_FEATURES), ], }); }); diff --git a/src/test-utils/skippers.ts b/src/test-utils/skippers.ts new file mode 100644 index 0000000000..bd8e094308 --- /dev/null +++ b/src/test-utils/skippers.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * Conditionally run a test + */ +export function itIf(condition: boolean) { + return condition ? it : it.skip; +} + +/** + * Conditionally run a block of tests + */ +export function describeIf(condition: boolean) { + return condition ? describe : describe.skip; +} diff --git a/types/from-webpack.d.ts b/types/from-webpack.d.ts new file mode 100644 index 0000000000..c0b5439355 --- /dev/null +++ b/types/from-webpack.d.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +// These variables will be replaced by webpack at compile time +declare const CONTEXT_MATCHER_FOR_NON_FEATURES: RegExp; +declare const CONTEXT_MATCHER_FOR_FEATURES: RegExp; diff --git a/types/mocks.d.ts b/types/mocks.d.ts index fd01e929d6..3826aac829 100644 --- a/types/mocks.d.ts +++ b/types/mocks.d.ts @@ -2,7 +2,6 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -declare module "mac-ca" declare module "win-ca" declare module "win-ca/api" diff --git a/webpack/main.ts b/webpack/main.ts index 397ffb2968..84efd63f22 100755 --- a/webpack/main.ts +++ b/webpack/main.ts @@ -11,7 +11,9 @@ import getTypeScriptLoader from "./get-typescript-loader"; import CircularDependencyPlugin from "circular-dependency-plugin"; import { iconsAndImagesWebpackRules } from "./renderer"; import type { WebpackPluginInstance } from "webpack"; +import { DefinePlugin } from "webpack"; import { buildDir, isDevelopment, mainDir } from "./vars"; +import { platform } from "process"; const configs: { (): webpack.Configuration }[] = []; @@ -53,6 +55,10 @@ configs.push((): webpack.Configuration => { ], }, plugins: [ + new DefinePlugin({ + CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`, + CONTEXT_MATCHER_FOR_FEATURES: `/\\/(main|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`, + }), new ForkTsCheckerPlugin(), new CircularDependencyPlugin({ cwd: __dirname, diff --git a/webpack/renderer.ts b/webpack/renderer.ts index c8c87e856f..6bad203ca7 100755 --- a/webpack/renderer.ts +++ b/webpack/renderer.ts @@ -12,8 +12,10 @@ import MonacoWebpackPlugin from "monaco-editor-webpack-plugin"; import CircularDependencyPlugin from "circular-dependency-plugin"; import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin"; import type { WebpackPluginInstance } from "webpack"; +import { DefinePlugin } from "webpack"; import getTypescriptLoader from "./get-typescript-loader"; import { assetsFolderName, isDevelopment, rendererDir, buildDir, appName, htmlTemplate, publicPath, sassCommonVars } from "./vars"; +import { platform } from "process"; export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration { if (showVars) { @@ -85,6 +87,10 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura }, plugins: [ + new DefinePlugin({ + CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`, + CONTEXT_MATCHER_FOR_FEATURES: `/\\/(renderer|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`, + }), new ForkTsCheckerPlugin(), // see also: https://github.com/Microsoft/monaco-editor-webpack-plugin#options diff --git a/yarn.lock b/yarn.lock index 3dc0c19e08..a481f18dfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8580,13 +8580,6 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= -mac-ca@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/mac-ca/-/mac-ca-1.0.6.tgz#89860edfeebcc4593567044281ab3500961ec15f" - integrity sha512-uuCaT+41YtIQlDDvbigP1evK1iUk97zRirP9+8rZJz8x0eIQZG8Z7YQegMTsCiMesLPb6LBgCS95uyAvVA1tmg== - dependencies: - node-forge "^0.10.0" - make-dir@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -8700,7 +8693,12 @@ markdown@^0.5.0: dependencies: nopt "~2.1.1" -marked@^4.0.19, marked@^4.2.3: +marked@^4.0.19: + version "4.2.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.0.tgz#f1683b077626a6c53e28926b798a18184aa13a91" + integrity sha512-1qWHjHlBKwjnDfrkxd0L3Yx4LTad/WO7+d13YsXAC/ZfKj7p0xkLV3sDXJzfWgL7GfW4IBZwMAYWaz+ifyQouQ== + +marked@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.3.tgz#bd76a5eb510ff1d8421bc6c3b2f0b93488c15bea" integrity sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw== @@ -9172,11 +9170,6 @@ node-fetch@^3.3.0: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== - node-forge@^1, node-forge@^1.2.1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"