mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Cleanup shell sessions
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
fb4dca8e58
commit
c3f956675a
@ -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:<port>/<uid>
|
||||
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) {
|
||||
|
||||
@ -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}`);
|
||||
|
||||
/**
|
||||
|
||||
42
src/main/lens-proxy/get-cluster-for-request.injectable.ts
Normal file
42
src/main/lens-proxy/get-cluster-for-request.injectable.ts
Normal file
@ -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:<port>/<uid>
|
||||
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;
|
||||
@ -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;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<http.IncomingMessage, "url" | "method">;
|
||||
export type ProxyApiRequest = (args: ProxyApiRequestArgs) => void | Promise<void>;
|
||||
|
||||
interface Dependencies {
|
||||
getClusterForRequest: GetClusterForRequest;
|
||||
shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
|
||||
kubeApiUpgradeRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
|
||||
shellApiRequest: ProxyApiRequest;
|
||||
kubeApiUpgradeRequest: ProxyApiRequest;
|
||||
router: Router;
|
||||
proxy: httpProxy;
|
||||
lensProxyPort: { set: (portNumber: number) => void };
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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<void> };
|
||||
|
||||
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));
|
||||
});
|
||||
};
|
||||
@ -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;
|
||||
@ -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<ClusterId, Map<string, Uint8Array>>();
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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<Uint8Array>;
|
||||
}
|
||||
|
||||
const shellRequestAuthenticatorInjectable = getInjectable({
|
||||
id: "shell-request-authenticator",
|
||||
|
||||
instantiate: (di): ShellRequestAuthenticator => {
|
||||
const randomBytes = di.inject(randomBytesInjectable);
|
||||
const tokens = new Map<ClusterId, Map<string, Uint8Array>>();
|
||||
|
||||
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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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<string, string | undefined>) => Record<string, string | undefined>, 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:
|
||||
|
||||
@ -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<void>;
|
||||
|
||||
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;
|
||||
@ -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;
|
||||
@ -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() {
|
||||
|
||||
@ -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<void>;
|
||||
|
||||
const openNodeShellSessionInjectable = getInjectable({
|
||||
id: "node-shell-session",
|
||||
|
||||
instantiate: (): OpenNodeShellSession => (args) => new NodeShellSession(args).open(),
|
||||
});
|
||||
|
||||
export default openNodeShellSessionInjectable;
|
||||
39
src/main/shell-session/open-shell-session.injectable.ts
Normal file
39
src/main/shell-session/open-shell-session.injectable.ts
Normal file
@ -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<void>;
|
||||
|
||||
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;
|
||||
@ -9,4 +9,4 @@ export interface ShellEnvContext {
|
||||
catalogEntity: CatalogEntity;
|
||||
}
|
||||
|
||||
export type ShellEnvModifier = (ctx: ShellEnvContext, env: Record<string, string | undefined>) => Record<string, string | undefined>;
|
||||
export type ShellEnvModifier = (ctx: ShellEnvContext, env: Partial<Record<string, string>>) => Partial<Record<string, string>>;
|
||||
|
||||
@ -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;
|
||||
@ -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<LensMainExtension[]>;
|
||||
catalogEntityRegistry: CatalogEntityRegistry;
|
||||
}
|
||||
|
||||
export const terminalShellEnvModify = ({ extensions, catalogEntityRegistry }: Dependencies) =>
|
||||
(clusterId: ClusterId, env: Record<string, string | undefined>) => {
|
||||
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;
|
||||
};
|
||||
@ -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<Record<string, string>>) => Partial<Record<string, string>>;
|
||||
|
||||
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;
|
||||
|
||||
@ -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<string>;
|
||||
protected readonly kubeconfigPathP: Promise<string>;
|
||||
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<string, string | undefined>, 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 {
|
||||
|
||||
15
src/main/utils/random-bytes.injectable.ts
Normal file
15
src/main/utils/random-bytes.injectable.ts
Normal file
@ -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;
|
||||
Loading…
Reference in New Issue
Block a user