diff --git a/.yarnrc b/.yarnrc index 0ffbd73e54..195689e5b6 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "12.2.1" +target "12.2.2" runtime "electron" diff --git a/package.json b/package.json index 4f294b73a2..844192cf1b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.2.6", + "version": "5.2.7", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", @@ -326,7 +326,7 @@ "css-loader": "^5.2.6", "deepdash": "^5.3.5", "dompurify": "^2.3.1", - "electron": "^12.2.1", + "electron": "^12.2.2", "electron-builder": "^22.10.5", "electron-notarize": "^0.3.0", "esbuild": "^0.12.24", diff --git a/src/common/__tests__/system-ca.test.ts b/src/common/__tests__/system-ca.test.ts new file mode 100644 index 0000000000..d1fd836c17 --- /dev/null +++ b/src/common/__tests__/system-ca.test.ts @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import https from "https"; +import os from "os"; +import { getMacRootCA, getWinRootCA, injectCAs, DSTRootCAX3 } from "../system-ca"; +import { dependencies, devDependencies } from "../../../package.json"; + +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)[]; + + 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; + + 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)[]; + + 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/system-ca.ts b/src/common/system-ca.ts index 7ed0120569..904ec01b97 100644 --- a/src/common/system-ca.ts +++ b/src/common/system-ca.ts @@ -20,22 +20,93 @@ */ import { isMac, isWindows } from "./vars"; -import winca from "win-ca"; -import macca from "mac-ca"; -import logger from "../main/logger"; +import wincaAPI from "win-ca/api"; +import https from "https"; +import { promiseExec } from "./utils/promise-exec"; -if (isMac) { - for (const crt of macca.all()) { - const attributes = crt.issuer?.attributes?.map((a: any) => `${a.name}=${a.value}`); +// 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"; - logger.debug(`Using host CA: ${attributes.join(",")}`); +export function isCertActive(cert: string) { + const isExpired = typeof cert !== "string" || cert.includes(DSTRootCAX3); + + return !isExpired; +} + +/** + * 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 args = "find-certificate -a -p"; + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions + const splitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g; + const systemRootCertsPath = "/System/Library/Keychains/SystemRootCertificates.keychain"; + const bin = "/usr/bin/security"; + const trusted = (await promiseExec(`${bin} ${args}`)).stdout.toString().split(splitPattern); + const rootCA = (await promiseExec(`${bin} ${args} ${systemRootCertsPath}`)).stdout.toString().split(splitPattern); + + 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]; + } } } -if (isWindows) { - try { - winca.inject("+"); // see: https://github.com/ukoloff/win-ca#caveats - } catch (error) { - logger.error(`[CA]: failed to force load: ${error}`); +/** + * 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?.message}`); + } + } + + if (isWindows) { + try { + const winRootCAs = await getWinRootCA(); + + wincaAPI.inject("+", winRootCAs); + + } catch (error) { + console.warn(`[WIN-CA]: Error injecting root CAs from Windows. ${error?.message}`); + } } } diff --git a/src/main/promise-exec.ts b/src/common/utils/promise-exec.ts similarity index 100% rename from src/main/promise-exec.ts rename to src/common/utils/promise-exec.ts diff --git a/src/main/helm/helm-chart-manager.ts b/src/main/helm/helm-chart-manager.ts index b8261845db..a87396e7ec 100644 --- a/src/main/helm/helm-chart-manager.ts +++ b/src/main/helm/helm-chart-manager.ts @@ -24,7 +24,7 @@ import v8 from "v8"; import * as yaml from "js-yaml"; import type { HelmRepo } from "./helm-repo-manager"; import logger from "../logger"; -import { promiseExec } from "../promise-exec"; +import { promiseExec } from "../../common/utils/promise-exec"; import { helmCli } from "./helm-cli"; import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api"; import { sortCharts } from "../../common/utils"; diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index b8a18a1021..7a1f12a8b6 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -22,7 +22,7 @@ import * as tempy from "tempy"; import fse from "fs-extra"; import * as yaml from "js-yaml"; -import { promiseExec } from "../promise-exec"; +import { promiseExec } from "../../common/utils/promise-exec"; import { helmCli } from "./helm-cli"; import type { Cluster } from "../cluster"; import { toCamelCase } from "../../common/utils/camelCase"; diff --git a/src/main/helm/helm-repo-manager.ts b/src/main/helm/helm-repo-manager.ts index 9e4038a10f..5f205c09e1 100644 --- a/src/main/helm/helm-repo-manager.ts +++ b/src/main/helm/helm-repo-manager.ts @@ -21,7 +21,7 @@ import yaml from "js-yaml"; import { readFile } from "fs-extra"; -import { promiseExec } from "../promise-exec"; +import { promiseExec } from "../../common/utils/promise-exec"; import { helmCli } from "./helm-cli"; import { Singleton } from "../../common/utils/singleton"; import { customRequestPromise } from "../../common/request"; diff --git a/src/main/index.ts b/src/main/index.ts index 3dd478fc76..fede47740d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -21,7 +21,7 @@ // Main process -import "../common/system-ca"; +import { injectSystemCAs } from "../common/system-ca"; import { initialize as initializeRemote } from "@electron/remote/main"; import * as Mobx from "mobx"; import * as LensExtensionsCommonApi from "../extensions/common-api"; @@ -64,7 +64,9 @@ import { Router } from "./router"; import { initMenu } from "./menu"; import { initTray } from "./tray"; import * as path from "path"; -import { kubeApiRequest, shellApiRequest } from "./proxy-functions"; +import { kubeApiRequest, shellApiRequest, ShellRequestAuthenticator } from "./proxy-functions"; + +injectSystemCAs(); const onCloseCleanup = disposer(); const onQuitCleanup = disposer(); @@ -74,6 +76,7 @@ const workingDir = path.join(app.getPath("appData"), appName); SentryInit(); app.setName(appName); + logger.info(`📟 Setting ${productName} as protocol client for lens://`); if (app.setAsDefaultProtocolClient("lens")) { @@ -140,6 +143,7 @@ app.on("ready", async () => { registerFileProtocol("static", __static); PrometheusProviderRegistry.createInstance(); + ShellRequestAuthenticator.createInstance().init(); initializers.initPrometheusProviderRegistry(); /** diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index 4695e8272e..729f2e4f95 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -21,7 +21,7 @@ import path from "path"; import fs from "fs"; -import { promiseExec } from "./promise-exec"; +import { promiseExec } from "../common/utils/promise-exec"; import logger from "./logger"; import { ensureDir, pathExists } from "fs-extra"; import * as lockFile from "proper-lockfile"; diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index 7ce2ec29df..3136896d0e 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -82,7 +82,7 @@ export class LensProxy extends Singleton { const reqHandler = isInternal ? shellApiRequest : kubeApiRequest; (async () => reqHandler({ req, socket, head }))() - .catch(error => logger.error(logger.error(`[LENS-PROXY]: failed to handle proxy upgrade: ${error}`))); + .catch(error => logger.error("[LENS-PROXY]: failed to handle proxy upgrade", error)); }); } diff --git a/src/main/proxy-functions/shell-api-request.ts b/src/main/proxy-functions/shell-api-request.ts index 3edaecff81..cdc91a2f18 100644 --- a/src/main/proxy-functions/shell-api-request.ts +++ b/src/main/proxy-functions/shell-api-request.ts @@ -19,29 +19,81 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type http from "http"; -import url from "url"; import logger from "../logger"; -import * as WebSocket from "ws"; +import { Server as WebSocketServer } from "ws"; import { NodeShellSession, LocalShellSession } from "../shell-session"; import type { ProxyApiRequestArgs } from "./types"; import { ClusterManager } from "../cluster-manager"; +import URLParse from "url-parse"; +import { ExtendedMap, Singleton } from "../../common/utils"; +import type { ClusterId } from "../../common/cluster-types"; +import { ipcMainHandle } from "../../common/ipc"; +import crypto from "crypto"; +import { promisify } from "util"; -export function shellApiRequest({ req, socket, head }: ProxyApiRequestArgs) { - const ws = new WebSocket.Server({ noServer: true }); +const randomBytes = promisify(crypto.randomBytes); - ws.on("connection", ((socket: WebSocket, req: http.IncomingMessage) => { - const cluster = ClusterManager.getInstance().getClusterForRequest(req); - const nodeParam = url.parse(req.url, true).query["node"]?.toString(); - const shell = nodeParam - ? new NodeShellSession(socket, cluster, nodeParam) - : new LocalShellSession(socket, cluster); +export class ShellRequestAuthenticator extends Singleton { + private tokens = new ExtendedMap>(); + + init() { + ipcMainHandle("cluster:shell-api", async (event, clusterId, tabId) => { + const authToken = Uint8Array.from(await randomBytes(128)); + + this.tokens + .getOrInsert(clusterId, () => new Map()) + .set(tabId, authToken); + + return authToken; + }); + } + + /** + * Authenticates a single use token for creating a new shell + * @param clusterId The `ClusterId` for the shell + * @param tabId The ID for the shell + * @param token The value that is being presented as a one time authentication token + * @returns `true` if `token` was valid, false otherwise + */ + authenticate(clusterId: ClusterId, tabId: string, token: string): boolean { + const clusterTokens = this.tokens.get(clusterId); + + if (!clusterTokens) { + return false; + } + + const authToken = clusterTokens.get(tabId); + const buf = Uint8Array.from(Buffer.from(token, "base64")); + + if (authToken instanceof Uint8Array && authToken.length === buf.length && crypto.timingSafeEqual(authToken, buf)) { + // remove the token because it is a single use token + clusterTokens.delete(tabId); + + return true; + } + + return false; + } +} + +export function shellApiRequest({ req, socket, head }: ProxyApiRequestArgs): void { + const cluster = ClusterManager.getInstance().getClusterForRequest(req); + const { query: { node, shellToken, id: tabId }} = new URLParse(req.url, true); + + if (!cluster || !ShellRequestAuthenticator.getInstance().authenticate(cluster.id, tabId, shellToken)) { + socket.write("Invalid shell request"); + + return void socket.end(); + } + + const ws = new WebSocketServer({ noServer: true }); + + ws.handleUpgrade(req, socket, head, (webSocket) => { + const shell = node + ? new NodeShellSession(webSocket, cluster, node) + : new LocalShellSession(webSocket, cluster); shell.open() - .catch(error => logger.error(`[SHELL-SESSION]: failed to open: ${error}`, { error })); - })); - - ws.handleUpgrade(req, socket, head, (con) => { - ws.emit("connection", con, req); + .catch(error => logger.error(`[SHELL-SESSION]: failed to open a ${node ? "node" : "local"} shell`, error)); }); } diff --git a/src/main/resource-applier.ts b/src/main/resource-applier.ts index a305ff1091..1db9e6ff65 100644 --- a/src/main/resource-applier.ts +++ b/src/main/resource-applier.ts @@ -30,7 +30,7 @@ import logger from "./logger"; import { appEventBus } from "../common/event-bus"; import { cloneJsonObject } from "../common/utils"; import type { Patch } from "rfc6902"; -import { promiseExecFile } from "./promise-exec"; +import { promiseExecFile } from "../common/utils/promise-exec"; export class ResourceApplier { constructor(protected cluster: Cluster) {} diff --git a/src/main/shell-session/node-shell-session.ts b/src/main/shell-session/node-shell-session.ts index 5f36e1d2f0..b073f6cdff 100644 --- a/src/main/shell-session/node-shell-session.ts +++ b/src/main/shell-session/node-shell-session.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type * as WebSocket from "ws"; +import type WebSocket from "ws"; import { v4 as uuid } from "uuid"; import * as k8s from "@kubernetes/client-node"; import type { KubeConfig } from "@kubernetes/client-node"; diff --git a/src/main/shell-session/shell-session.ts b/src/main/shell-session/shell-session.ts index 4771ec8b77..76b0e7a12e 100644 --- a/src/main/shell-session/shell-session.ts +++ b/src/main/shell-session/shell-session.ts @@ -22,7 +22,7 @@ import fse from "fs-extra"; import type { Cluster } from "../cluster"; import { Kubectl } from "../kubectl"; -import type * as WebSocket from "ws"; +import type WebSocket from "ws"; import { shellEnv } from "../utils/shell-env"; import { app } from "electron"; import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars"; diff --git a/src/renderer/api/terminal-api.ts b/src/renderer/api/terminal-api.ts index 59e248fba5..93c0a81c58 100644 --- a/src/renderer/api/terminal-api.ts +++ b/src/renderer/api/terminal-api.ts @@ -19,13 +19,13 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { boundMethod, base64, EventEmitter } from "../utils"; +import { boundMethod, base64, EventEmitter, getHostedClusterId } from "../utils"; import { WebSocketApi } from "./websocket-api"; import isEqual from "lodash/isEqual"; import { isDevelopment } from "../../common/vars"; import url from "url"; import { makeObservable, observable } from "mobx"; -import type { ParsedUrlQueryInput } from "querystring"; +import { ipcRenderer } from "electron"; export enum TerminalChannels { STDIN = 0, @@ -50,7 +50,7 @@ enum TerminalColor { export type TerminalApiQuery = Record & { id: string; node?: string; - type?: string | "node"; + type?: string; }; export class TerminalApi extends WebSocketApi { @@ -58,9 +58,8 @@ export class TerminalApi extends WebSocketApi { public onReady = new EventEmitter<[]>(); @observable public isReady = false; - public readonly url: string; - constructor(protected options: TerminalApiQuery) { + constructor(protected query: TerminalApiQuery) { super({ logging: isDevelopment, flushOnOpen: false, @@ -68,30 +67,35 @@ export class TerminalApi extends WebSocketApi { }); makeObservable(this); - const { hostname, protocol, port } = location; - const query: ParsedUrlQueryInput = { - id: options.id, - }; + if (query.node) { + query.type ||= "node"; + } + } - if (options.node) { - query.node = options.node; - query.type = options.type || "node"; + async connect() { + this.emitStatus("Connecting ..."); + + const authTokenArray = await ipcRenderer.invoke("cluster:shell-api", getHostedClusterId(), this.query.id); + + if (!(authTokenArray instanceof Uint8Array)) { + throw new TypeError("ShellApi token is not a Uint8Array"); } - this.url = url.format({ + const { hostname, protocol, port } = location; + const socketUrl = url.format({ protocol: protocol.includes("https") ? "wss" : "ws", hostname, port, pathname: "/api", - query, + query: { + ...this.query, + shellToken: Buffer.from(authTokenArray).toString("base64"), + }, slashes: true, }); - } - connect() { - this.emitStatus("Connecting ..."); this.onData.addListener(this._onReady, { prepend: true }); - super.connect(this.url); + super.connect(socketUrl); } destroy() { diff --git a/src/renderer/lens-app.tsx b/src/renderer/lens-app.tsx index 9bf6545c24..f56e41481f 100644 --- a/src/renderer/lens-app.tsx +++ b/src/renderer/lens-app.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "../common/system-ca"; +import { injectSystemCAs } from "../common/system-ca"; import React from "react"; import { Route, Router, Switch } from "react-router"; import { observer } from "mobx-react"; @@ -40,6 +40,8 @@ import { registerKeyboardShortcuts } from "./keyboard-shortcuts"; import logger from "../common/logger"; import { unmountComponentAtNode } from "react-dom"; +injectSystemCAs(); + @observer export class LensApp extends React.Component { static async init(rootElem: HTMLElement) { diff --git a/types/mocks.d.ts b/types/mocks.d.ts index 4c013fc930..57e2812dcb 100644 --- a/types/mocks.d.ts +++ b/types/mocks.d.ts @@ -20,6 +20,7 @@ */ declare module "mac-ca" declare module "win-ca" +declare module "win-ca/api" declare module "@hapi/call" declare module "@hapi/subtext" diff --git a/yarn.lock b/yarn.lock index 50f66150ff..0a85619c00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5205,10 +5205,10 @@ electron@*: "@types/node" "^12.0.12" extract-zip "^1.0.3" -electron@^12.2.1: - version "12.2.1" - resolved "https://registry.yarnpkg.com/electron/-/electron-12.2.1.tgz#ef138fde11efd01743934c3e0df717cc53ee362b" - integrity sha512-Gp+rO81qoaRDP7PTVtBOvnSgDgGlwUuAEWXxi621uOJMIlYFas9ChXe8pjdL0R0vyUpiHVzp6Vrjx41VZqEpsw== +electron@^12.2.2: + version "12.2.2" + resolved "https://registry.yarnpkg.com/electron/-/electron-12.2.2.tgz#9627594d6b5bb589f00355989d316b6542539e54" + integrity sha512-Oma/nIfvgql9JjAxdB9gQk//qxpJaI6PgMocYMiW4kFyLi+8jS6oGn33QG3FESS//cw09KRnWmA9iutuFAuXtw== dependencies: "@electron/get" "^1.0.1" "@types/node" "^14.6.2"