1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Added additional checks on the command used in the Exec plugin in a kubeconfig (#1013)

- Added check to see if the program being referenced in the command field of the
exec object in the User construct exists. If it doesn't an error will be raised.
If more than 1 context is selected when adding a kubeconfig then valid contexts
will be added and any with an error will not be.

Signed-off-by: Steve Richards <srichards@mirantis.com>

Co-authored-by: Steve Richards <srichards@mirantis.com>
This commit is contained in:
steve richards 2020-10-12 08:20:08 +01:00 committed by GitHub
parent daa4992f2b
commit 4fcac6b0d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 90 additions and 7 deletions

View File

@ -175,6 +175,7 @@
"@types/tar": "^4.0.3", "@types/tar": "^4.0.3",
"array-move": "^3.0.0", "array-move": "^3.0.0",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"command-exists": "1.2.9",
"conf": "^7.0.1", "conf": "^7.0.1",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"electron-updater": "^4.3.1", "electron-updater": "^4.3.1",

View File

@ -0,0 +1,12 @@
export class ExecValidationNotFoundError extends Error {
constructor(execPath: string, isAbsolute: boolean) {
super(`User Exec command "${execPath}" not found on host.`);
let message = `User Exec command "${execPath}" not found on host.`;
if (!isAbsolute) {
message += ` Please ensure binary is found in PATH or use absolute path to binary in Kubeconfig`;
}
this.message = message;
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}

View File

@ -4,6 +4,8 @@ import path from "path"
import os from "os" import os from "os"
import yaml from "js-yaml" import yaml from "js-yaml"
import logger from "../main/logger"; import logger from "../main/logger";
import commandExists from "command-exists";
import { ExecValidationNotFoundError } from "./custom-errors";
export const kubeConfigDefaultPath = path.join(os.homedir(), '.kube', 'config'); export const kubeConfigDefaultPath = path.join(os.homedir(), '.kube', 'config');
@ -140,3 +142,28 @@ export function getNodeWarningConditions(node: V1Node) {
c.status.toLowerCase() === "true" && c.type !== "Ready" && c.type !== "HostUpgrades" c.status.toLowerCase() === "true" && c.type !== "Ready" && c.type !== "HostUpgrades"
) )
} }
/**
* Validates kubeconfig supplied in the add clusters screen. At present this will just validate
* the User struct, specifically the command passed to the exec substructure.
*/
export function validateKubeConfig (config: KubeConfig) {
// we only receive a single context, cluster & user object here so lets validate them as this
// will be called when we add a new cluster to Lens
logger.debug(`validateKubeConfig: validating kubeconfig - ${JSON.stringify(config)}`);
// Validate the User Object
const user = config.getCurrentUser();
if (user.exec) {
const execCommand = user.exec["command"];
// check if the command is absolute or not
const isAbsolute = path.isAbsolute(execCommand);
// validate the exec struct in the user object, start with the command field
logger.debug(`validateKubeConfig: validating user exec command - ${JSON.stringify(execCommand)}`);
if (!commandExists.sync(execCommand)) {
logger.debug(`validateKubeConfig: exec command ${String(execCommand)} in kubeconfig ${config.currentContext} not found`);
throw new ExecValidationNotFoundError(execCommand, isAbsolute);
}
}
}

View File

@ -13,7 +13,7 @@ import { AceEditor } from "../ace-editor";
import { Button } from "../button"; import { Button } from "../button";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { WizardLayout } from "../layout/wizard-layout"; import { WizardLayout } from "../layout/wizard-layout";
import { kubeConfigDefaultPath, loadConfig, splitConfig, validateConfig } from "../../../common/kube-helpers"; import { kubeConfigDefaultPath, loadConfig, splitConfig, validateConfig, validateKubeConfig } from "../../../common/kube-helpers";
import { ClusterModel, ClusterStore, clusterStore } from "../../../common/cluster-store"; import { ClusterModel, ClusterStore, clusterStore } from "../../../common/cluster-store";
import { workspaceStore } from "../../../common/workspace-store"; import { workspaceStore } from "../../../common/workspace-store";
import { v4 as uuid } from "uuid" import { v4 as uuid } from "uuid"
@ -23,6 +23,7 @@ import { clusterViewURL } from "../cluster-manager/cluster-view.route";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { Notifications } from "../notifications"; import { Notifications } from "../notifications";
import { Tab, Tabs } from "../tabs"; import { Tab, Tabs } from "../tabs";
import { ExecValidationNotFoundError } from "../../../common/custom-errors";
enum KubeConfigSourceTab { enum KubeConfigSourceTab {
FILE = "file", FILE = "file",
@ -118,14 +119,32 @@ export class AddCluster extends React.Component {
} }
addClusters = () => { addClusters = () => {
const configValidationErrors:string[] = [];
let newClusters: ClusterModel[] = [];
try { try {
if (!this.selectedContexts.length) { if (!this.selectedContexts.length) {
this.error = <Trans>Please select at least one cluster context</Trans> this.error = <Trans>Please select at least one cluster context</Trans>
return; return;
} }
this.error = "" this.error = ""
this.isWaiting = true this.isWaiting = true
const newClusters: ClusterModel[] = this.selectedContexts.map(context => {
newClusters = this.selectedContexts.filter(context => {
try {
const kubeConfig = this.kubeContexts.get(context);
validateKubeConfig(kubeConfig);
return true;
} catch (err) {
this.error = String(err.message)
if (err instanceof ExecValidationNotFoundError ) {
Notifications.error(<Trans>Error while adding cluster(s): {this.error}</Trans>);
return false;
} else {
throw new Error(err);
}
}
}).map(context => {
const clusterId = uuid(); const clusterId = uuid();
const kubeConfig = this.kubeContexts.get(context); const kubeConfig = this.kubeContexts.get(context);
const kubeConfigPath = this.sourceTab === KubeConfigSourceTab.FILE const kubeConfigPath = this.sourceTab === KubeConfigSourceTab.FILE
@ -141,7 +160,8 @@ export class AddCluster extends React.Component {
httpsProxy: this.proxyServer || undefined, httpsProxy: this.proxyServer || undefined,
}, },
} }
}); })
runInAction(() => { runInAction(() => {
clusterStore.addCluster(...newClusters); clusterStore.addCluster(...newClusters);
if (newClusters.length === 1) { if (newClusters.length === 1) {
@ -149,9 +169,11 @@ export class AddCluster extends React.Component {
clusterStore.setActive(clusterId); clusterStore.setActive(clusterId);
navigate(clusterViewURL({ params: { clusterId } })); navigate(clusterViewURL({ params: { clusterId } }));
} else { } else {
Notifications.ok( if (newClusters.length > 1) {
<Trans>Successfully imported <b>{newClusters.length}</b> cluster(s)</Trans> Notifications.ok(
); <Trans>Successfully imported <b>{newClusters.length}</b> cluster(s)</Trans>
);
}
} }
}) })
this.refreshContexts(); this.refreshContexts();

16
types/command-exists.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
// Type definitions for command-exists 1.2
// Project: https://github.com/mathisonian/command-exists
// Definitions by: BendingBender <https://github.com/BendingBender>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
export = commandExists;
declare function commandExists(commandName: string): Promise<string>;
declare function commandExists(
commandName: string,
cb: (error: null, exists: boolean) => void
): void;
declare namespace commandExists {
function sync(commandName: string): boolean;
}

View File

@ -3888,6 +3888,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies: dependencies:
delayed-stream "~1.0.0" delayed-stream "~1.0.0"
command-exists@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
commander@*, commander@^5.1.0: commander@*, commander@^5.1.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"