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:
parent
daa4992f2b
commit
4fcac6b0d0
@ -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",
|
||||||
|
|||||||
12
src/common/custom-errors.ts
Normal file
12
src/common/custom-errors.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,6 +119,9 @@ 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>
|
||||||
@ -125,7 +129,22 @@ export class AddCluster extends React.Component {
|
|||||||
}
|
}
|
||||||
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
16
types/command-exists.d.ts
vendored
Normal 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;
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user