From 578623d63bff2e46731cad48d8ad307e1ff01ebe Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 3 Oct 2022 11:41:59 -0300 Subject: [PATCH] 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 --- .../__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 | 44 ++++++++ .../request-system-cas.injectable.linux.ts | 14 +++ ...quest-system-cas.injectable.testing-env.ts | 14 +++ .../request-system-cas.injectable.win32.ts | 45 ++++++++ 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 | 12 --- 24 files changed, 263 insertions(+), 270 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 e5cca7c19c0e..1d7d7029fbed 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 e1474a43323a..4b9cc26b8c74 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 6d56cc1f8c94..02aef4f3fdd5 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 a14c46c8f525..a27b3220186b 100644 --- a/package.json +++ b/package.json @@ -250,7 +250,6 @@ "js-yaml": "^4.1.0", "jsdom": "^16.7.0", "lodash": "^4.17.15", - "mac-ca": "^1.0.6", "marked": "^4.2.1", "md5-file": "^5.0.0", "mobx": "^6.6.2", diff --git a/src/common/__tests__/system-ca.test.ts b/src/common/__tests__/system-ca.test.ts deleted file mode 100644 index 473fe6ed57a1..000000000000 --- 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 000000000000..b6223fa38ef0 --- /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 000000000000..c69b0bd8b053 --- /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 000000000000..7b0425bbe12b --- /dev/null +++ b/src/common/certificate-authorities/request-system-cas.injectable.darwin.ts @@ -0,0 +1,44 @@ +/** + * 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"; + +// 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[]) => { + const output = await execFile("/usr/bin/security", args); + + return output.split(certSplitPattern); + }; + + return async () => { + try { + 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])]; + } catch (error) { + logger.warn(`[INJECT-CAS]: Error injecting root CAs from MacOSX: ${error}`); + } + + 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 000000000000..1d7bf10350ed --- /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 000000000000..1d7bf10350ed --- /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 000000000000..af9366970d96 --- /dev/null +++ b/src/common/certificate-authorities/request-system-cas.injectable.win32.ts @@ -0,0 +1,45 @@ +/** + * 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 { 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); + + 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 output = await execFile(wincaRootsExePath); + + return output + .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 4a462932c094..f026e0db3ad7 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 }), - }); - } else { - resolve({ - callWasSuccessful: true, - response: stdout, + 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, + }); + } }); - } - }); - }), + }); + }; + }, causesSideEffects: true, }); diff --git a/src/common/system-ca.ts b/src/common/system-ca.ts deleted file mode 100644 index 4e57ca2d7b2c..000000000000 --- 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 41fa87389b30..69e94c4fb275 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 17d9cc36b780..8b71cca39ba2 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 94589fca7ded..b5219dbf4f28 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 3ee10748f936..cc8a0bafccfb 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 348c5e369b39..28e111bc9703 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 000000000000..bd8e094308c9 --- /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 000000000000..c0b543935510 --- /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 fd01e929d648..3826aac8291b 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 397ffb2968ca..84efd63f2205 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 c8c87e856ffe..6bad203ca744 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 2e5b3be88f0a..603a3241710c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8563,13 +8563,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" @@ -9143,11 +9136,6 @@ node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" -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"