From c3f956675a20b677de5556c9ceb7a1d88f392d67 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 16 May 2022 16:44:17 -0400 Subject: [PATCH] Cleanup shell sessions Signed-off-by: Sebastian Malton --- src/main/cluster-manager.ts | 25 +----- src/main/kubectl/kubectl.ts | 2 + .../get-cluster-for-request.injectable.ts | 42 ++++++++++ .../lens-proxy/lens-proxy-port.injectable.ts | 12 +-- src/main/lens-proxy/lens-proxy.injectable.ts | 30 +++---- src/main/lens-proxy/lens-proxy.ts | 9 +-- src/main/lens-proxy/proxy-functions/index.ts | 1 - .../kube-api-upgrade-request.ts | 66 ---------------- .../kube/api-upgrade-request.injectable.ts | 79 +++++++++++++++++++ .../shell-api-request.injectable.ts | 21 ----- .../shell-api-request/shell-api-request.ts | 46 ----------- .../shell-request-authenticator.injectable.ts | 20 ----- .../shell-request-authenticator.ts | 53 ------------- .../shell/api-request.injectable.ts | 43 ++++++++++ .../shell/authenticate-request.injectable.ts | 13 +++ .../shell/request-authenticator.injectable.ts | 55 +++++++++++++ .../create-shell-session.injectable.ts | 29 ------- .../local-shell-session.injectable.ts | 33 -------- .../local-shell-session.ts | 24 +++--- .../open-local-shell-session.injectable.ts | 35 ++++++++ .../node-shell-session.injectable.ts | 32 -------- .../node-shell-session/node-shell-session.ts | 14 ++-- .../open-node-shell-session.injectable.ts | 27 +++++++ .../open-shell-session.injectable.ts | 39 +++++++++ .../shell-env-modifier-registration.ts | 2 +- ...terminal-shell-env-modifiers.injectable.ts | 23 ++++++ .../terminal-shell-env-modifiers.ts | 42 ---------- .../terminal-shell-env-modify.injectable.ts | 35 ++++++-- src/main/shell-session/shell-session.ts | 19 ++++- src/main/utils/random-bytes.injectable.ts | 15 ++++ 30 files changed, 462 insertions(+), 424 deletions(-) create mode 100644 src/main/lens-proxy/get-cluster-for-request.injectable.ts delete mode 100644 src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.ts create mode 100644 src/main/lens-proxy/proxy-functions/kube/api-upgrade-request.injectable.ts delete mode 100644 src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.injectable.ts delete mode 100644 src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.ts delete mode 100644 src/main/lens-proxy/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.injectable.ts delete mode 100644 src/main/lens-proxy/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.ts create mode 100644 src/main/lens-proxy/proxy-functions/shell/api-request.injectable.ts create mode 100644 src/main/lens-proxy/proxy-functions/shell/authenticate-request.injectable.ts create mode 100644 src/main/lens-proxy/proxy-functions/shell/request-authenticator.injectable.ts delete mode 100644 src/main/shell-session/create-shell-session.injectable.ts delete mode 100644 src/main/shell-session/local-shell-session/local-shell-session.injectable.ts create mode 100644 src/main/shell-session/local-shell-session/open-local-shell-session.injectable.ts delete mode 100644 src/main/shell-session/node-shell-session/node-shell-session.injectable.ts create mode 100644 src/main/shell-session/node-shell-session/open-node-shell-session.injectable.ts create mode 100644 src/main/shell-session/open-shell-session.injectable.ts create mode 100644 src/main/shell-session/shell-env-modifier/terminal-shell-env-modifiers.injectable.ts delete mode 100644 src/main/shell-session/shell-env-modifier/terminal-shell-env-modifiers.ts create mode 100644 src/main/utils/random-bytes.injectable.ts diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index 787f2a1618..d9c9fabb21 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -4,12 +4,10 @@ */ import "../common/ipc/cluster"; -import type http from "http"; import { action, makeObservable, observable, observe, reaction, toJS } from "mobx"; import type { Cluster } from "../common/cluster/cluster"; import logger from "./logger"; -import { apiKubePrefix } from "../common/vars"; -import { getClusterIdFromHost, isErrnoException } from "../common/utils"; +import { isErrnoException } from "../common/utils"; import type { KubernetesClusterPrometheusMetrics } from "../common/catalog-entities/kubernetes-cluster"; import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../common/catalog-entities/kubernetes-cluster"; import { ipcMainOn } from "../common/ipc"; @@ -260,27 +258,6 @@ export class ClusterManager { cluster.disconnect(); }); } - - getClusterForRequest = (req: http.IncomingMessage): Cluster | undefined => { - if (!req.headers.host) { - return undefined; - } - - // lens-server is connecting to 127.0.0.1:/ - if (req.url && req.headers.host.startsWith("127.0.0.1")) { - const clusterId = req.url.split("/")[1]; - const cluster = this.dependencies.store.getById(clusterId); - - if (cluster) { - // we need to swap path prefix so that request is proxied to kube api - req.url = req.url.replace(`/${clusterId}`, apiKubePrefix); - } - - return cluster; - } - - return this.dependencies.store.getById(getClusterIdFromHost(req.headers.host)); - }; } export function catalogEntityFromCluster(cluster: Cluster) { diff --git a/src/main/kubectl/kubectl.ts b/src/main/kubectl/kubectl.ts index 55f7e4675d..37ac20c85b 100644 --- a/src/main/kubectl/kubectl.ts +++ b/src/main/kubectl/kubectl.ts @@ -72,6 +72,8 @@ export class Kubectl { version = new SemVer(Kubectl.bundledKubectlVersion); } + console.log(`Parsed ${clusterVersion} as ${version}`); + const fromMajorMinor = kubectlMap.get(`${version.major}.${version.minor}`); /** diff --git a/src/main/lens-proxy/get-cluster-for-request.injectable.ts b/src/main/lens-proxy/get-cluster-for-request.injectable.ts new file mode 100644 index 0000000000..23c916ad38 --- /dev/null +++ b/src/main/lens-proxy/get-cluster-for-request.injectable.ts @@ -0,0 +1,42 @@ +/** + * 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 type { IncomingMessage } from "http"; +import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable"; +import type { Cluster } from "../../common/cluster/cluster"; +import { getClusterIdFromHost } from "../../common/utils"; +import { apiKubePrefix } from "../../common/vars"; + +export type GetClusterForRequest = (req: IncomingMessage) => Cluster | undefined; + +const getClusterForRequestInjectable = getInjectable({ + id: "get-cluster-for-request", + instantiate: (di): GetClusterForRequest => { + const store = di.inject(clusterStoreInjectable); + + return (req) => { + if (!req.headers.host) { + return undefined; + } + + // lens-server is connecting to 127.0.0.1:/ + if (req.url && req.headers.host.startsWith("127.0.0.1")) { + const clusterId = req.url.split("/")[1]; + const cluster = store.getById(clusterId); + + if (cluster) { + // we need to swap path prefix so that request is proxied to kube api + req.url = req.url.replace(`/${clusterId}`, apiKubePrefix); + } + + return cluster; + } + + return store.getById(getClusterIdFromHost(req.headers.host)); + }; + }, +}); + +export default getClusterForRequestInjectable; diff --git a/src/main/lens-proxy/lens-proxy-port.injectable.ts b/src/main/lens-proxy/lens-proxy-port.injectable.ts index e6826f6df0..771b61e8a3 100644 --- a/src/main/lens-proxy/lens-proxy-port.injectable.ts +++ b/src/main/lens-proxy/lens-proxy-port.injectable.ts @@ -8,27 +8,27 @@ const lensProxyPortInjectable = getInjectable({ id: "lens-proxy-port", instantiate: () => { - let _portNumber: number; + let portNumber: number; return { get: () => { - if (!_portNumber) { + if (!portNumber) { throw new Error( "Tried to access port number of LensProxy while it has not been set yet.", ); } - return _portNumber; + return portNumber; }, - set: (portNumber: number) => { - if (_portNumber) { + set: (port: number) => { + if (port) { throw new Error( "Tried to set port number for LensProxy when it has already been set.", ); } - _portNumber = portNumber; + portNumber = port; }, }; }, diff --git a/src/main/lens-proxy/lens-proxy.injectable.ts b/src/main/lens-proxy/lens-proxy.injectable.ts index 34cb327339..dfabd0ba52 100644 --- a/src/main/lens-proxy/lens-proxy.injectable.ts +++ b/src/main/lens-proxy/lens-proxy.injectable.ts @@ -4,32 +4,24 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { LensProxy } from "./lens-proxy"; -import { kubeApiUpgradeRequest } from "./proxy-functions"; import routerInjectable from "../router/router.injectable"; import httpProxy from "http-proxy"; -import clusterManagerInjectable from "../cluster-manager.injectable"; -import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell-api-request.injectable"; +import shellApiRequestInjectable from "./proxy-functions/shell/api-request.injectable"; import lensProxyPortInjectable from "./lens-proxy-port.injectable"; +import getClusterForRequestInjectable from "./get-cluster-for-request.injectable"; +import kubeApiUpgradeRequestInjectable from "./proxy-functions/kube/api-upgrade-request.injectable"; const lensProxyInjectable = getInjectable({ id: "lens-proxy", - instantiate: (di) => { - const clusterManager = di.inject(clusterManagerInjectable); - const router = di.inject(routerInjectable); - const shellApiRequest = di.inject(shellApiRequestInjectable); - const proxy = httpProxy.createProxy(); - const lensProxyPort = di.inject(lensProxyPortInjectable); - - return new LensProxy({ - router, - proxy, - kubeApiUpgradeRequest, - shellApiRequest, - getClusterForRequest: clusterManager.getClusterForRequest, - lensProxyPort, - }); - }, + instantiate: (di) => new LensProxy({ + router: di.inject(routerInjectable), + proxy: httpProxy.createProxy(), + kubeApiUpgradeRequest: di.inject(kubeApiUpgradeRequestInjectable), + shellApiRequest: di.inject(shellApiRequestInjectable), + getClusterForRequest: di.inject(getClusterForRequestInjectable), + lensProxyPort: di.inject(lensProxyPortInjectable), + }), }); export default lensProxyInjectable; diff --git a/src/main/lens-proxy/lens-proxy.ts b/src/main/lens-proxy/lens-proxy.ts index 5e58104d60..f9ea49753f 100644 --- a/src/main/lens-proxy/lens-proxy.ts +++ b/src/main/lens-proxy/lens-proxy.ts @@ -11,21 +11,20 @@ import { apiPrefix, apiKubePrefix, contentSecurityPolicy } from "../../common/va import type { Router } from "../router/router"; import type { ClusterContextHandler } from "../context-handler/context-handler"; import logger from "../logger"; -import type { Cluster } from "../../common/cluster/cluster"; import type { ProxyApiRequestArgs } from "./proxy-functions"; import { appEventBus } from "../../common/app-event-bus/event-bus"; import { getBoolean } from "../utils/parse-query"; import assert from "assert"; import type { SetRequired } from "type-fest"; - -type GetClusterForRequest = (req: http.IncomingMessage) => Cluster | undefined; +import type { GetClusterForRequest } from "./get-cluster-for-request.injectable"; export type ServerIncomingMessage = SetRequired; +export type ProxyApiRequest = (args: ProxyApiRequestArgs) => void | Promise; interface Dependencies { getClusterForRequest: GetClusterForRequest; - shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise; - kubeApiUpgradeRequest: (args: ProxyApiRequestArgs) => void | Promise; + shellApiRequest: ProxyApiRequest; + kubeApiUpgradeRequest: ProxyApiRequest; router: Router; proxy: httpProxy; lensProxyPort: { set: (portNumber: number) => void }; diff --git a/src/main/lens-proxy/proxy-functions/index.ts b/src/main/lens-proxy/proxy-functions/index.ts index 5d374825ee..72553b2d2e 100644 --- a/src/main/lens-proxy/proxy-functions/index.ts +++ b/src/main/lens-proxy/proxy-functions/index.ts @@ -2,5 +2,4 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -export * from "./kube-api-upgrade-request"; export * from "./types"; diff --git a/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.ts b/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.ts deleted file mode 100644 index ddd8e66261..0000000000 --- a/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { chunk } from "lodash"; -import type { ConnectionOptions } from "tls"; -import { connect } from "tls"; -import url from "url"; -import { apiKubePrefix } from "../../../common/vars"; -import type { ProxyApiRequestArgs } from "./types"; - -const skipRawHeaders = new Set(["Host", "Authorization"]); - -export async function kubeApiUpgradeRequest({ req, socket, head, cluster }: ProxyApiRequestArgs) { - const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, ""); - const proxyCa = cluster.contextHandler.resolveAuthProxyCa(); - const apiUrl = url.parse(cluster.apiUrl); - const pUrl = url.parse(proxyUrl); - const connectOpts: ConnectionOptions = { - port: pUrl.port ? parseInt(pUrl.port) : undefined, - host: pUrl.hostname ?? undefined, - ca: proxyCa, - }; - - const proxySocket = connect(connectOpts, () => { - proxySocket.write(`${req.method} ${pUrl.path} HTTP/1.1\r\n`); - proxySocket.write(`Host: ${apiUrl.host}\r\n`); - - for (const [key, value] of chunk(req.rawHeaders, 2)) { - if (skipRawHeaders.has(key)) { - continue; - } - - proxySocket.write(`${key}: ${value}\r\n`); - } - - proxySocket.write("\r\n"); - proxySocket.write(head); - }); - - proxySocket.setKeepAlive(true); - socket.setKeepAlive(true); - proxySocket.setTimeout(0); - socket.setTimeout(0); - - proxySocket.on("data", function (chunk) { - socket.write(chunk); - }); - proxySocket.on("end", function () { - socket.end(); - }); - proxySocket.on("error", function () { - socket.write(`HTTP/${req.httpVersion} 500 Connection error\r\n\r\n`); - socket.end(); - }); - socket.on("data", function (chunk) { - proxySocket.write(chunk); - }); - socket.on("end", function () { - proxySocket.end(); - }); - socket.on("error", function () { - proxySocket.end(); - }); -} diff --git a/src/main/lens-proxy/proxy-functions/kube/api-upgrade-request.injectable.ts b/src/main/lens-proxy/proxy-functions/kube/api-upgrade-request.injectable.ts new file mode 100644 index 0000000000..f7b26cff3b --- /dev/null +++ b/src/main/lens-proxy/proxy-functions/kube/api-upgrade-request.injectable.ts @@ -0,0 +1,79 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { chunk, pick } from "lodash"; +import type { ConnectionOptions } from "tls"; +import { connect } from "tls"; +import url from "url"; +import { apiKubePrefix } from "../../../../common/vars"; +import { getInjectable } from "@ogre-tools/injectable"; +import loggerInjectable from "../../../../common/logger.injectable"; +import type { ProxyApiRequest } from "../../lens-proxy"; + +const skipRawHeaders = new Set(["Host", "Authorization"]); + +const kubeApiUpgradeRequestInjectable = getInjectable({ + id: "kube-api-upgrade-request", + instantiate: (di): ProxyApiRequest => { + const logger = di.inject(loggerInjectable); + + return async ({ req, socket, head, cluster }) => { + const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, ""); + const proxyCa = cluster.contextHandler.resolveAuthProxyCa(); + const apiUrl = url.parse(cluster.apiUrl); + const pUrl = url.parse(proxyUrl); + const connectOpts: ConnectionOptions = { + port: pUrl.port ? parseInt(pUrl.port) : undefined, + host: pUrl.hostname ?? undefined, + ca: proxyCa, + }; + + logger.debug(`[KUBE-API-UPGRADE]: connecting for clusterId=${cluster.id} for url=${proxyUrl}`, pick(connectOpts, "port", "host")); + + const proxySocket = connect(connectOpts, () => { + proxySocket.write(`${req.method} ${pUrl.path} HTTP/1.1\r\n`); + proxySocket.write(`Host: ${apiUrl.host}\r\n`); + + for (const [key, value] of chunk(req.rawHeaders, 2)) { + if (skipRawHeaders.has(key)) { + continue; + } + + proxySocket.write(`${key}: ${value}\r\n`); + } + + proxySocket.write("\r\n"); + proxySocket.write(head); + }); + + proxySocket.setKeepAlive(true); + socket.setKeepAlive(true); + proxySocket.setTimeout(0); + socket.setTimeout(0); + + proxySocket.on("data", function (chunk) { + socket.write(chunk); + }); + proxySocket.on("end", function () { + socket.end(); + }); + proxySocket.on("error", function () { + socket.write(`HTTP/${req.httpVersion} 500 Connection error\r\n\r\n`); + socket.end(); + }); + socket.on("data", function (chunk) { + proxySocket.write(chunk); + }); + socket.on("end", function () { + proxySocket.end(); + }); + socket.on("error", function () { + proxySocket.end(); + }); + }; + }, +}); + +export default kubeApiUpgradeRequestInjectable; diff --git a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.injectable.ts b/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.injectable.ts deleted file mode 100644 index b491ab5456..0000000000 --- a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.injectable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 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 { shellApiRequest } from "./shell-api-request"; -import createShellSessionInjectable from "../../../shell-session/create-shell-session.injectable"; -import shellRequestAuthenticatorInjectable from "./shell-request-authenticator/shell-request-authenticator.injectable"; -import clusterManagerInjectable from "../../../cluster-manager.injectable"; - -const shellApiRequestInjectable = getInjectable({ - id: "shell-api-request", - - instantiate: (di) => shellApiRequest({ - createShellSession: di.inject(createShellSessionInjectable), - authenticateRequest: di.inject(shellRequestAuthenticatorInjectable).authenticate, - clusterManager: di.inject(clusterManagerInjectable), - }), -}); - -export default shellApiRequestInjectable; diff --git a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.ts b/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.ts deleted file mode 100644 index 97b6dedd4c..0000000000 --- a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import logger from "../../../logger"; -import type WebSocket from "ws"; -import { Server as WebSocketServer } from "ws"; -import type { ProxyApiRequestArgs } from "../types"; -import type { ClusterManager } from "../../../cluster-manager"; -import URLParse from "url-parse"; -import type { Cluster } from "../../../../common/cluster/cluster"; -import type { ClusterId } from "../../../../common/cluster-types"; - -interface Dependencies { - authenticateRequest: (clusterId: ClusterId, tabId: string, shellToken: string | undefined) => boolean; - - createShellSession: (args: { - webSocket: WebSocket; - cluster: Cluster; - tabId: string; - nodeName?: string; - }) => { open: () => Promise }; - - clusterManager: ClusterManager; -} - -export const shellApiRequest = ({ createShellSession, authenticateRequest, clusterManager }: Dependencies) => ({ req, socket, head }: ProxyApiRequestArgs): void => { - const cluster = clusterManager.getClusterForRequest(req); - const { query: { node: nodeName, shellToken, id: tabId }} = new URLParse(req.url, true); - - if (!tabId || !cluster || !authenticateRequest(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 = createShellSession({ webSocket, cluster, tabId, nodeName }); - - shell.open() - .catch(error => logger.error(`[SHELL-SESSION]: failed to open a ${nodeName ? "node" : "local"} shell`, error)); - }); -}; diff --git a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.injectable.ts b/src/main/lens-proxy/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.injectable.ts deleted file mode 100644 index c273f105d0..0000000000 --- a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.injectable.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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 { ShellRequestAuthenticator } from "./shell-request-authenticator"; - -const shellRequestAuthenticatorInjectable = getInjectable({ - id: "shell-request-authenticator", - - instantiate: () => { - const authenticator = new ShellRequestAuthenticator(); - - authenticator.init(); - - return authenticator; - }, -}); - -export default shellRequestAuthenticatorInjectable; diff --git a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.ts b/src/main/lens-proxy/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.ts deleted file mode 100644 index fe0a8f643d..0000000000 --- a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getOrInsertMap } from "../../../../../common/utils"; -import type { ClusterId } from "../../../../../common/cluster-types"; -import { ipcMainHandle } from "../../../../../common/ipc"; -import crypto from "crypto"; -import { promisify } from "util"; - -const randomBytes = promisify(crypto.randomBytes); - -export class ShellRequestAuthenticator { - private tokens = new Map>(); - - init() { - ipcMainHandle("cluster:shell-api", async (event, clusterId, tabId) => { - const authToken = Uint8Array.from(await randomBytes(128)); - const forCluster = getOrInsertMap(this.tokens, clusterId); - - forCluster.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 | undefined): boolean => { - const clusterTokens = this.tokens.get(clusterId); - - if (!clusterTokens || !tabId || !token) { - 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; - }; -} diff --git a/src/main/lens-proxy/proxy-functions/shell/api-request.injectable.ts b/src/main/lens-proxy/proxy-functions/shell/api-request.injectable.ts new file mode 100644 index 0000000000..b84b9ed761 --- /dev/null +++ b/src/main/lens-proxy/proxy-functions/shell/api-request.injectable.ts @@ -0,0 +1,43 @@ +/** + * 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 openShellSessionInjectable from "../../../shell-session/open-shell-session.injectable"; +import loggerInjectable from "../../../../common/logger.injectable"; +import type { ProxyApiRequest } from "../../lens-proxy"; +import getClusterForRequestInjectable from "../../get-cluster-for-request.injectable"; +import { Server } from "ws"; +import authenticateRequestInjectable from "./authenticate-request.injectable"; + +const shellApiRequestInjectable = getInjectable({ + id: "shell-api-request", + + instantiate: (di): ProxyApiRequest => { + const openShellSession = di.inject(openShellSessionInjectable); + const authenticateRequest = di.inject(authenticateRequestInjectable); + const logger = di.inject(loggerInjectable); + const getClusterForRequest = di.inject(getClusterForRequestInjectable); + + return ({ req, socket, head }) => { + const cluster = getClusterForRequest(req); + const { searchParams } = new URL(req.url); + const nodeName = searchParams.get("node") || undefined; + const shellToken = searchParams.get("shellToken"); + const tabId = searchParams.get("id"); + + if (!tabId || !cluster || !shellToken || !authenticateRequest(cluster.id, tabId, shellToken)) { + socket.write("Invalid shell request"); + socket.end(); + } else { + new Server({ noServer: true }) + .handleUpgrade(req, socket, head, (websocket) => { + openShellSession({ websocket, cluster, tabId, nodeName }) + .catch(error => logger.error(`[SHELL-SESSION]: failed to open a ${nodeName ? "node" : "local"} shell`, error)); + }); + } + }; + }, +}); + +export default shellApiRequestInjectable; diff --git a/src/main/lens-proxy/proxy-functions/shell/authenticate-request.injectable.ts b/src/main/lens-proxy/proxy-functions/shell/authenticate-request.injectable.ts new file mode 100644 index 0000000000..a2529e6dbb --- /dev/null +++ b/src/main/lens-proxy/proxy-functions/shell/authenticate-request.injectable.ts @@ -0,0 +1,13 @@ +/** + * 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 shellRequestAuthenticatorInjectable from "./request-authenticator.injectable"; + +const authenticateRequestInjectable = getInjectable({ + id: "authenticate-request", + instantiate: (di) => di.inject(shellRequestAuthenticatorInjectable).authenticate, +}); + +export default authenticateRequestInjectable; diff --git a/src/main/lens-proxy/proxy-functions/shell/request-authenticator.injectable.ts b/src/main/lens-proxy/proxy-functions/shell/request-authenticator.injectable.ts new file mode 100644 index 0000000000..fd2173ed5e --- /dev/null +++ b/src/main/lens-proxy/proxy-functions/shell/request-authenticator.injectable.ts @@ -0,0 +1,55 @@ +/** + * 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 { timingSafeEqual } from "crypto"; +import type { ClusterId } from "../../../../common/cluster-types"; +import { getOrInsertMap } from "../../../../common/utils"; +import randomBytesInjectable from "../../../utils/random-bytes.injectable"; + +export interface ShellRequestAuthenticator { + authenticate(clusterId: ClusterId, tabId: string, token: string | undefined): boolean; + getTokenFor(clusterId: ClusterId, tabId: string): Promise; +} + +const shellRequestAuthenticatorInjectable = getInjectable({ + id: "shell-request-authenticator", + + instantiate: (di): ShellRequestAuthenticator => { + const randomBytes = di.inject(randomBytesInjectable); + const tokens = new Map>(); + + return { + authenticate: (clusterId, tabId, token) => { + const clusterTokens = tokens.get(clusterId); + + if (!clusterTokens || !tabId || !token) { + return false; + } + + const authToken = clusterTokens.get(tabId); + const buf = Uint8Array.from(Buffer.from(token, "base64")); + + if (authToken instanceof Uint8Array && authToken.length === buf.length && timingSafeEqual(authToken, buf)) { + // remove the token because it is a single use token + clusterTokens.delete(tabId); + + return true; + } + + return false; + }, + getTokenFor: async (clusterId, tabId) => { + const authToken = Uint8Array.from(await randomBytes(128)); + const forCluster = getOrInsertMap(tokens, clusterId); + + forCluster.set(tabId, authToken); + + return authToken; + }, + }; + }, +}); + +export default shellRequestAuthenticatorInjectable; diff --git a/src/main/shell-session/create-shell-session.injectable.ts b/src/main/shell-session/create-shell-session.injectable.ts deleted file mode 100644 index 5bd5569053..0000000000 --- a/src/main/shell-session/create-shell-session.injectable.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * 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 type { Cluster } from "../../common/cluster/cluster"; -import type WebSocket from "ws"; -import localShellSessionInjectable from "./local-shell-session/local-shell-session.injectable"; -import nodeShellSessionInjectable from "./node-shell-session/node-shell-session.injectable"; - -interface Args { - webSocket: WebSocket; - cluster: Cluster; - tabId: string; - nodeName?: string; -} - -const createShellSessionInjectable = getInjectable({ - id: "create-shell-session", - - instantiate: - (di) => - ({ nodeName, ...rest }: Args) => - !nodeName - ? di.inject(localShellSessionInjectable, rest) - : di.inject(nodeShellSessionInjectable, { nodeName, ...rest }), -}); - -export default createShellSessionInjectable; diff --git a/src/main/shell-session/local-shell-session/local-shell-session.injectable.ts b/src/main/shell-session/local-shell-session/local-shell-session.injectable.ts deleted file mode 100644 index ed9b86819b..0000000000 --- a/src/main/shell-session/local-shell-session/local-shell-session.injectable.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -import { LocalShellSession } from "./local-shell-session"; -import type { Cluster } from "../../../common/cluster/cluster"; -import type WebSocket from "ws"; -import createKubectlInjectable from "../../kubectl/create-kubectl.injectable"; -import terminalShellEnvModifiersInjectable from "../shell-env-modifier/terminal-shell-env-modify.injectable"; - -interface InstantiationParameter { - webSocket: WebSocket; - cluster: Cluster; - tabId: string; -} - -const localShellSessionInjectable = getInjectable({ - id: "local-shell-session", - - instantiate: (di, { cluster, tabId, webSocket }: InstantiationParameter) => { - const createKubectl = di.inject(createKubectlInjectable); - const localShellEnvModify = di.inject(terminalShellEnvModifiersInjectable); - - const kubectl = createKubectl(cluster.version); - - return new LocalShellSession(localShellEnvModify, kubectl, webSocket, cluster, tabId); - }, - - lifecycle: lifecycleEnum.transient, -}); - -export default localShellSessionInjectable; diff --git a/src/main/shell-session/local-shell-session/local-shell-session.ts b/src/main/shell-session/local-shell-session/local-shell-session.ts index 9f42c7f242..6565323bff 100644 --- a/src/main/shell-session/local-shell-session/local-shell-session.ts +++ b/src/main/shell-session/local-shell-session/local-shell-session.ts @@ -3,24 +3,26 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type WebSocket from "ws"; import path from "path"; import { UserStore } from "../../../common/user-store"; -import type { Cluster } from "../../../common/cluster/cluster"; -import type { ClusterId } from "../../../common/cluster-types"; +import type { TerminalShellEnvModify } from "../shell-env-modifier/terminal-shell-env-modify.injectable"; +import type { ShellSessionArgs } from "../shell-session"; import { ShellSession } from "../shell-session"; -import type { Kubectl } from "../../kubectl/kubectl"; -import { baseBinariesDir } from "../../../common/vars"; + +export interface LocalShellSessionDependencies { + terminalShellEnvModify: TerminalShellEnvModify; + readonly baseBundeledBinariesDirectory: string; +} export class LocalShellSession extends ShellSession { ShellType = "shell"; - constructor(protected shellEnvModify: (clusterId: ClusterId, env: Record) => Record, kubectl: Kubectl, websocket: WebSocket, cluster: Cluster, terminalId: string) { - super(kubectl, websocket, cluster, terminalId); + constructor(protected readonly dependencies: LocalShellSessionDependencies, args: ShellSessionArgs) { + super(args); } protected getPathEntries(): string[] { - return [baseBinariesDir.get()]; + return [this.dependencies.baseBundeledBinariesDirectory]; } protected get cwd(): string | undefined { @@ -31,7 +33,7 @@ export class LocalShellSession extends ShellSession { let env = await this.getCachedShellEnv(); // extensions can modify the env - env = this.shellEnvModify(this.cluster.id, env); + env = this.dependencies.terminalShellEnvModify(this.cluster.id, env); const shell = env.PTYSHELL; @@ -50,11 +52,11 @@ export class LocalShellSession extends ShellSession { switch(path.basename(shell)) { case "powershell.exe": - return ["-NoExit", "-command", `& {$Env:PATH="${kubectlPathDir};${baseBinariesDir.get()};$Env:PATH"}`]; + return ["-NoExit", "-command", `& {$Env:PATH="${kubectlPathDir};${this.dependencies.baseBundeledBinariesDirectory};$Env:PATH"}`]; case "bash": return ["--init-file", path.join(await this.kubectlBinDirP, ".bash_set_path")]; case "fish": - return ["--login", "--init-command", `export PATH="${kubectlPathDir}:${baseBinariesDir.get()}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`]; + return ["--login", "--init-command", `export PATH="${kubectlPathDir}:${this.dependencies.baseBundeledBinariesDirectory}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`]; case "zsh": return ["--login"]; default: diff --git a/src/main/shell-session/local-shell-session/open-local-shell-session.injectable.ts b/src/main/shell-session/local-shell-session/open-local-shell-session.injectable.ts new file mode 100644 index 0000000000..5794cbc3d6 --- /dev/null +++ b/src/main/shell-session/local-shell-session/open-local-shell-session.injectable.ts @@ -0,0 +1,35 @@ +/** + * 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 type { LocalShellSessionDependencies } from "./local-shell-session"; +import { LocalShellSession } from "./local-shell-session"; +import type { Cluster } from "../../../common/cluster/cluster"; +import type WebSocket from "ws"; +import terminalShellEnvModifyInjectable from "../shell-env-modifier/terminal-shell-env-modify.injectable"; +import baseBundeledBinariesDirectoryInjectable from "../../../common/vars/base-bundled-binaries-dir.injectable"; +import type { Kubectl } from "../../kubectl/kubectl"; + +export interface OpenLocalShellSessionArgs { + websocket: WebSocket; + cluster: Cluster; + tabId: string; + kubectl: Kubectl; +} + +export type OpenLocalShellSession = (args: OpenLocalShellSessionArgs) => Promise; + +const openLocalShellSessionInjectable = getInjectable({ + id: "open-local-shell-session", + instantiate: (di): OpenLocalShellSession => { + const deps: LocalShellSessionDependencies = { + terminalShellEnvModify: di.inject(terminalShellEnvModifyInjectable), + baseBundeledBinariesDirectory: di.inject(baseBundeledBinariesDirectoryInjectable), + }; + + return (args) => new LocalShellSession(deps, args).open(); + }, +}); + +export default openLocalShellSessionInjectable; diff --git a/src/main/shell-session/node-shell-session/node-shell-session.injectable.ts b/src/main/shell-session/node-shell-session/node-shell-session.injectable.ts deleted file mode 100644 index e2514708e6..0000000000 --- a/src/main/shell-session/node-shell-session/node-shell-session.injectable.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -import type { Cluster } from "../../../common/cluster/cluster"; -import type WebSocket from "ws"; -import createKubectlInjectable from "../../kubectl/create-kubectl.injectable"; -import { NodeShellSession } from "./node-shell-session"; - -interface InstantiationParameter { - webSocket: WebSocket; - cluster: Cluster; - tabId: string; - nodeName: string; -} - -const nodeShellSessionInjectable = getInjectable({ - id: "node-shell-session", - - instantiate: (di, { cluster, tabId, webSocket, nodeName }: InstantiationParameter) => { - const createKubectl = di.inject(createKubectlInjectable); - - const kubectl = createKubectl(cluster.version); - - return new NodeShellSession(nodeName, kubectl, webSocket, cluster, tabId); - }, - - lifecycle: lifecycleEnum.transient, -}); - -export default nodeShellSessionInjectable; diff --git a/src/main/shell-session/node-shell-session/node-shell-session.ts b/src/main/shell-session/node-shell-session/node-shell-session.ts index 4c619a612c..7bfbb16a70 100644 --- a/src/main/shell-session/node-shell-session/node-shell-session.ts +++ b/src/main/shell-session/node-shell-session/node-shell-session.ts @@ -3,28 +3,32 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type WebSocket from "ws"; import { v4 as uuid } from "uuid"; import { Watch, CoreV1Api } from "@kubernetes/client-node"; import type { KubeConfig } from "@kubernetes/client-node"; -import type { Cluster } from "../../../common/cluster/cluster"; +import type { ShellSessionArgs } from "../shell-session"; import { ShellOpenError, ShellSession } from "../shell-session"; import { get, once } from "lodash"; import { Node, NodeApi } from "../../../common/k8s-api/endpoints"; import { KubeJsonApi } from "../../../common/k8s-api/kube-json-api"; import logger from "../../logger"; -import type { Kubectl } from "../../kubectl/kubectl"; import { TerminalChannels } from "../../../common/terminal/channels"; +export interface NodeShellSessionArgs extends ShellSessionArgs { + nodeName: string; +} + export class NodeShellSession extends ShellSession { ShellType = "node-shell"; protected readonly podName = `node-shell-${uuid()}`; protected readonly cwd: string | undefined = undefined; + protected readonly nodeName: string; - constructor(protected nodeName: string, kubectl: Kubectl, socket: WebSocket, cluster: Cluster, terminalId: string) { - super(kubectl, socket, cluster, terminalId); + constructor({ nodeName, ...args }: NodeShellSessionArgs) { + super(args); + this.nodeName = nodeName; } public async open() { diff --git a/src/main/shell-session/node-shell-session/open-node-shell-session.injectable.ts b/src/main/shell-session/node-shell-session/open-node-shell-session.injectable.ts new file mode 100644 index 0000000000..1281b8516c --- /dev/null +++ b/src/main/shell-session/node-shell-session/open-node-shell-session.injectable.ts @@ -0,0 +1,27 @@ +/** + * 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 type { Cluster } from "../../../common/cluster/cluster"; +import type WebSocket from "ws"; +import { NodeShellSession } from "./node-shell-session"; +import type { Kubectl } from "../../kubectl/kubectl"; + +export interface OpenNodeShellSessionArgs { + websocket: WebSocket; + cluster: Cluster; + tabId: string; + nodeName: string; + kubectl: Kubectl; +} + +export type OpenNodeShellSession = (args: OpenNodeShellSessionArgs) => Promise; + +const openNodeShellSessionInjectable = getInjectable({ + id: "node-shell-session", + + instantiate: (): OpenNodeShellSession => (args) => new NodeShellSession(args).open(), +}); + +export default openNodeShellSessionInjectable; diff --git a/src/main/shell-session/open-shell-session.injectable.ts b/src/main/shell-session/open-shell-session.injectable.ts new file mode 100644 index 0000000000..0a6009cfc9 --- /dev/null +++ b/src/main/shell-session/open-shell-session.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 type { Cluster } from "../../common/cluster/cluster"; +import type WebSocket from "ws"; +import openLocalShellSessionInjectable from "./local-shell-session/open-local-shell-session.injectable"; +import openNodeShellSessionInjectable from "./node-shell-session/open-node-shell-session.injectable"; +import createKubectlInjectable from "../kubectl/create-kubectl.injectable"; + +export interface OpenShellSessionArgs { + websocket: WebSocket; + cluster: Cluster; + tabId: string; + nodeName?: string; +} + +export type OpenShellSession = (args: OpenShellSessionArgs) => Promise; + +const openShellSessionInjectable = getInjectable({ + id: "open-shell-session", + + instantiate: (di): OpenShellSession => { + const openLocalShellSession = di.inject(openLocalShellSessionInjectable); + const openNodeShellSession = di.inject(openNodeShellSessionInjectable); + const createKubectl = di.inject(createKubectlInjectable); + + return ({ nodeName, cluster, ...args }) => { + const kubectl = createKubectl(cluster.version); + + return nodeName + ? openNodeShellSession({ nodeName, cluster, kubectl, ...args }) + : openLocalShellSession({ cluster, kubectl, ...args }); + }; + }, +}); + +export default openShellSessionInjectable; diff --git a/src/main/shell-session/shell-env-modifier/shell-env-modifier-registration.ts b/src/main/shell-session/shell-env-modifier/shell-env-modifier-registration.ts index de08a51b3b..2cdf368d93 100644 --- a/src/main/shell-session/shell-env-modifier/shell-env-modifier-registration.ts +++ b/src/main/shell-session/shell-env-modifier/shell-env-modifier-registration.ts @@ -9,4 +9,4 @@ export interface ShellEnvContext { catalogEntity: CatalogEntity; } -export type ShellEnvModifier = (ctx: ShellEnvContext, env: Record) => Record; +export type ShellEnvModifier = (ctx: ShellEnvContext, env: Partial>) => Partial>; diff --git a/src/main/shell-session/shell-env-modifier/terminal-shell-env-modifiers.injectable.ts b/src/main/shell-session/shell-env-modifier/terminal-shell-env-modifiers.injectable.ts new file mode 100644 index 0000000000..eb7acbc023 --- /dev/null +++ b/src/main/shell-session/shell-env-modifier/terminal-shell-env-modifiers.injectable.ts @@ -0,0 +1,23 @@ +/** + * 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 { computed } from "mobx"; +import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable"; +import { isDefined } from "../../../common/utils"; + +const terminalShellEnvModifiersInjectable = getInjectable({ + id: "terminal-shell-env-modifiers", + instantiate: (di) => { + const extensions = di.inject(mainExtensionsInjectable); + + return computed(() => ( + extensions.get() + .map((extension) => extension.terminalShellEnvModifier) + .filter(isDefined) + )); + }, +}); + +export default terminalShellEnvModifiersInjectable; diff --git a/src/main/shell-session/shell-env-modifier/terminal-shell-env-modifiers.ts b/src/main/shell-session/shell-env-modifier/terminal-shell-env-modifiers.ts deleted file mode 100644 index bf6a290052..0000000000 --- a/src/main/shell-session/shell-env-modifier/terminal-shell-env-modifiers.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { IComputedValue } from "mobx"; -import { computed } from "mobx"; -import type { ClusterId } from "../../../common/cluster-types"; -import { isDefined } from "../../../common/utils"; -import type { LensMainExtension } from "../../../extensions/lens-main-extension"; -import type { CatalogEntityRegistry } from "../../catalog"; - -interface Dependencies { - extensions: IComputedValue; - catalogEntityRegistry: CatalogEntityRegistry; -} - -export const terminalShellEnvModify = ({ extensions, catalogEntityRegistry }: Dependencies) => - (clusterId: ClusterId, env: Record) => { - const terminalShellEnvModifiers = computed(() => ( - extensions.get() - .map((extension) => extension.terminalShellEnvModifier) - .filter(isDefined) - )) - .get(); - - if (terminalShellEnvModifiers.length === 0) { - return env; - } - - const entity = catalogEntityRegistry.findById(clusterId); - - if (entity) { - const ctx = { catalogEntity: entity }; - - // clone it so the passed value is not also modified - env = JSON.parse(JSON.stringify(env)); - env = terminalShellEnvModifiers.reduce((env, modifier) => modifier(ctx, env), env); - } - - return env; - }; diff --git a/src/main/shell-session/shell-env-modifier/terminal-shell-env-modify.injectable.ts b/src/main/shell-session/shell-env-modifier/terminal-shell-env-modify.injectable.ts index 7d8cb37a1c..07c5e98aea 100644 --- a/src/main/shell-session/shell-env-modifier/terminal-shell-env-modify.injectable.ts +++ b/src/main/shell-session/shell-env-modifier/terminal-shell-env-modify.injectable.ts @@ -4,18 +4,39 @@ */ import { getInjectable } from "@ogre-tools/injectable"; -import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable"; -import { terminalShellEnvModify } from "./terminal-shell-env-modifiers"; +import type { ClusterId } from "../../../common/cluster-types"; import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable"; +import terminalShellEnvModifiersInjectable from "./terminal-shell-env-modifiers.injectable"; + +export type TerminalShellEnvModify = (clusterId: ClusterId, env: Partial>) => Partial>; const terminalShellEnvModifyInjectable = getInjectable({ id: "terminal-shell-env-modify", - instantiate: (di) => - terminalShellEnvModify({ - extensions: di.inject(mainExtensionsInjectable), - catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable), - }), + instantiate: (di): TerminalShellEnvModify => { + const terminalShellEnvModifiers = di.inject(terminalShellEnvModifiersInjectable); + const entityRegistry = di.inject(catalogEntityRegistryInjectable); + + return (clusterId, env) => { + const modifiers = terminalShellEnvModifiers.get(); + + if (modifiers.length === 0) { + return env; + } + + const entity = entityRegistry.findById(clusterId); + + if (entity) { + const ctx = { catalogEntity: entity }; + + // clone it so the passed value is not also modified + env = JSON.parse(JSON.stringify(env)); + env = modifiers.reduce((env, modifier) => modifier(ctx, env), env); + } + + return env; + }; + }, }); export default terminalShellEnvModifyInjectable; diff --git a/src/main/shell-session/shell-session.ts b/src/main/shell-session/shell-session.ts index 42286ee24d..b068f90f84 100644 --- a/src/main/shell-session/shell-session.ts +++ b/src/main/shell-session/shell-session.ts @@ -104,6 +104,13 @@ export enum WebSocketCloseEvent { TlsHandshake = 1015, } +export interface ShellSessionArgs { + kubectl: Kubectl; + websocket: WebSocket; + cluster: Cluster; + tabId: string; +} + export abstract class ShellSession { abstract readonly ShellType: string; @@ -130,8 +137,11 @@ export abstract class ShellSession { protected readonly kubectlBinDirP: Promise; protected readonly kubeconfigPathP: Promise; protected readonly terminalId: string; + protected readonly kubectl: Kubectl; + protected readonly websocket: WebSocket; + protected readonly cluster: Cluster; - protected abstract get cwd(): string | undefined; + protected abstract readonly cwd: string | undefined; protected ensureShellProcess(shell: string, args: string[], env: Record, cwd: string): { shellProcess: pty.IPty; resume: boolean } { const resume = ShellSession.processes.has(this.terminalId); @@ -152,10 +162,13 @@ export abstract class ShellSession { return { shellProcess, resume }; } - constructor(protected readonly kubectl: Kubectl, protected readonly websocket: WebSocket, protected readonly cluster: Cluster, terminalId: string) { + constructor({ cluster, kubectl, tabId, websocket }: ShellSessionArgs) { + this.cluster = cluster; + this.kubectl = kubectl; + this.websocket = websocket; this.kubeconfigPathP = this.cluster.getProxyKubeconfigPath(); this.kubectlBinDirP = this.kubectl.binDir(); - this.terminalId = `${cluster.id}:${terminalId}`; + this.terminalId = `${cluster.id}:${tabId}`; } protected send(message: TerminalMessage): void { diff --git a/src/main/utils/random-bytes.injectable.ts b/src/main/utils/random-bytes.injectable.ts new file mode 100644 index 0000000000..88a107ee2a --- /dev/null +++ b/src/main/utils/random-bytes.injectable.ts @@ -0,0 +1,15 @@ +/** + * 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 { randomBytes } from "crypto"; +import { promisify } from "util"; + +const randomBytesInjectable = getInjectable({ + id: "random-bytes", + instantiate: () => promisify(randomBytes), + causesSideEffects: true, +}); + +export default randomBytesInjectable;