1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/main/proxy-functions/shell-api-request.ts
2021-11-09 14:24:32 -05:00

100 lines
3.7 KiB
TypeScript

/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import logger from "../logger";
import { Server as WebSocketServer } from "ws";
import { NodeShellSession, LocalShellSession } from "../shell-session";
import type { ProxyApiRequestArgs } from "./types";
import { ClusterManager } from "../cluster-manager";
import URLParse from "url-parse";
import { ExtendedMap, Singleton } from "../../common/utils";
import type { ClusterId } from "../../common/cluster-types";
import { ipcMainHandle } from "../../common/ipc";
import crypto from "crypto";
import { promisify } from "util";
const randomBytes = promisify(crypto.randomBytes);
export class ShellRequestAuthenticator extends Singleton {
private tokens = new ExtendedMap<ClusterId, Map<string, Uint8Array>>();
init() {
ipcMainHandle("cluster:shell-api", async (event, clusterId, tabId) => {
const authToken = Uint8Array.from(await randomBytes(128));
this.tokens
.getOrInsert(clusterId, () => new Map())
.set(tabId, authToken);
return authToken;
});
}
/**
* Authenticates a single use token for creating a new shell
* @param clusterId The `ClusterId` for the shell
* @param tabId The ID for the shell
* @param token The value that is being presented as a one time authentication token
* @returns `true` if `token` was valid, false otherwise
*/
authenticate(clusterId: ClusterId, tabId: string, token: string): boolean {
const clusterTokens = this.tokens.get(clusterId);
if (!clusterTokens) {
return false;
}
const authToken = clusterTokens.get(tabId);
const buf = Uint8Array.from(Buffer.from(token, "base64"));
if (authToken instanceof Uint8Array && authToken.length === buf.length && crypto.timingSafeEqual(authToken, buf)) {
// remove the token because it is a single use token
clusterTokens.delete(tabId);
return true;
}
return false;
}
}
export function shellApiRequest({ req, socket, head }: ProxyApiRequestArgs): void {
const cluster = ClusterManager.getInstance().getClusterForRequest(req);
const { query: { node, shellToken, id: tabId }} = new URLParse(req.url, true);
if (!cluster || !ShellRequestAuthenticator.getInstance().authenticate(cluster.id, tabId, shellToken)) {
socket.write("Invalid shell request");
return void socket.end();
}
const ws = new WebSocketServer({ noServer: true });
ws.handleUpgrade(req, socket, head, (webSocket) => {
const shell = node
? new NodeShellSession(webSocket, cluster, node, tabId)
: new LocalShellSession(webSocket, cluster, tabId);
shell.open()
.catch(error => logger.error(`[SHELL-SESSION]: failed to open a ${node ? "node" : "local"} shell`, error));
});
}