diff --git a/Makefile b/Makefile index 360bf72aa0..2d42682522 100644 --- a/Makefile +++ b/Makefile @@ -115,8 +115,8 @@ frontend-test: plugins-test: cd plugins/headlamp-plugin && npm install && ./test-headlamp-plugin.js cd plugins/headlamp-plugin && ./test-plugins-examples.sh - cd plugins/headlamp-plugin && node ./headlamp-plugin-management.test.js - cd plugins/headlamp-plugin && npx jest ./plugin-management-utils.test.js + cd plugins/headlamp-plugin/plugin-management && node ./plugin-management.e2e.js + cd plugins/headlamp-plugin/plugin-management && npx jest ./plugin-management.test.js # IMAGE_BASE can be used to specify a base final image. # IMAGE_BASE=debian:latest make image diff --git a/app/electron/main.ts b/app/electron/main.ts index 8b5f418e7b..0fe9cd1320 100644 --- a/app/electron/main.ts +++ b/app/electron/main.ts @@ -23,7 +23,7 @@ import open from 'open'; import path from 'path'; import url from 'url'; import yargs from 'yargs'; -import PluginManager from '../../plugins/headlamp-plugin/plugin-management-utils'; +import PluginManager from '../../plugins/headlamp-plugin/plugin-management/plugin-management'; import i18n from './i18next.config'; import windowSize from './windowSize'; diff --git a/plugins/headlamp-plugin/bin/headlamp-plugin.js b/plugins/headlamp-plugin/bin/headlamp-plugin.js index 1c0dc0fa13..ac7b72089e 100755 --- a/plugins/headlamp-plugin/bin/headlamp-plugin.js +++ b/plugins/headlamp-plugin/bin/headlamp-plugin.js @@ -13,7 +13,7 @@ const child_process = require('child_process'); const validate = require('validate-npm-package-name'); const yargs = require('yargs/yargs'); const headlampPluginPkg = require('../package.json'); -const pluginManager = require('../plugin-management-utils'); +const pluginManager = require('../plugin-management/plugin-management'); const { table } = require('table'); /** diff --git a/plugins/headlamp-plugin/package.json b/plugins/headlamp-plugin/package.json index dbb02731e4..4ac483f504 100644 --- a/plugins/headlamp-plugin/package.json +++ b/plugins/headlamp-plugin/package.json @@ -166,7 +166,7 @@ "lib", "types", ".storybook", - "plugin-management-utils.js" + "plugin-management/plugin-management.js" ], "keywords": [ "headlamp", diff --git a/plugins/headlamp-plugin/headlamp-plugin-management.test.js b/plugins/headlamp-plugin/plugin-management/plugin-management.e2e.js similarity index 82% rename from plugins/headlamp-plugin/headlamp-plugin-management.test.js rename to plugins/headlamp-plugin/plugin-management/plugin-management.e2e.js index ce9f84f63a..adae699adb 100755 --- a/plugins/headlamp-plugin/headlamp-plugin-management.test.js +++ b/plugins/headlamp-plugin/plugin-management/plugin-management.e2e.js @@ -1,3 +1,4 @@ +#!/bin/env node const { execSync } = require('child_process'); const assert = require('assert'); const fs = require('fs'); @@ -28,7 +29,7 @@ if (!fs.existsSync(pluginsDir)) { } // List plugins initially -let output = runCommand('node ./bin/headlamp-plugin.js list --json'); +let output = runCommand('node ../bin/headlamp-plugin.js list --json'); console.log('Initial list output:', output); let plugins = JSON.parse(output); console.log('Initial plugins:', plugins); @@ -40,33 +41,33 @@ assert.strictEqual(pluginExists, false, 'Plugin should not be initially installe // Install the plugin const pluginURL = 'https://artifacthub.io/packages/headlamp/test-123/prometheus_headlamp_plugin'; -output = runCommand(`node ./bin/headlamp-plugin.js install ${pluginURL}`); +output = runCommand(`node ../bin/headlamp-plugin.js install ${pluginURL}`); console.log('Install output:', output); // List plugins to verify installation -output = runCommand('node ./bin/headlamp-plugin.js list --json'); +output = runCommand('node ../bin/headlamp-plugin.js list --json'); plugins = JSON.parse(output); console.log('Plugins after install:', plugins); pluginExists = plugins.some(plugin => plugin.pluginName === pluginName); assert.strictEqual(pluginExists, true, 'Plugin should be installed'); // Update the plugin -output = runCommand(`node ./bin/headlamp-plugin.js update ${pluginName}`); +output = runCommand(`node ../bin/headlamp-plugin.js update ${pluginName}`); console.log('Update output:', output); // List plugins to verify update -output = runCommand('node ./bin/headlamp-plugin.js list --json'); +output = runCommand('node ../bin/headlamp-plugin.js list --json'); plugins = JSON.parse(output); console.log('Plugins after update:', plugins); pluginExists = plugins.some(plugin => plugin.pluginName === pluginName); assert.strictEqual(pluginExists, true, 'Plugin should still be installed after update'); // Uninstall the plugin -output = runCommand(`node ./bin/headlamp-plugin.js uninstall ${pluginName}`); +output = runCommand(`node ../bin/headlamp-plugin.js uninstall ${pluginName}`); console.log('Uninstall output:', output); // List plugins to verify uninstallation -output = runCommand('node ./bin/headlamp-plugin.js list --json'); +output = runCommand('node ../bin/headlamp-plugin.js list --json'); console.log('Initial list output:', output); plugins = JSON.parse(output); console.log('Plugins after uninstall:', plugins); diff --git a/plugins/headlamp-plugin/plugin-management-utils.js b/plugins/headlamp-plugin/plugin-management/plugin-management.js similarity index 92% rename from plugins/headlamp-plugin/plugin-management-utils.js rename to plugins/headlamp-plugin/plugin-management/plugin-management.js index 074aec2b22..0dee89e384 100644 --- a/plugins/headlamp-plugin/plugin-management-utils.js +++ b/plugins/headlamp-plugin/plugin-management/plugin-management.js @@ -1,6 +1,11 @@ -/*plugin-management-utils.js has the core logic for managing plugins in Headlamp. - * It provides methods for installing, updating, and uninstalling plugins, as well as listing installed plugins. - * It is used by headlamp-plugin cli and the Headlamp electron app to manage plugins. +/** + * plugin-management-utils.js has the core logic for managing plugins in Headlamp. + * + * Provides methods for installing, updating, listing and uninstalling plugins. + * + * Used by: + * - plugins/headlamp-plugin/bin/headlamp-plugin.js cli + * - app/ to manage plugins. */ const fetch = require('node-fetch').default; const fs = require('fs'); @@ -237,6 +242,31 @@ class PluginManager { } } +/** + * Checks the plugin name is a valid one. + * + * Look for "..", "/", or "\" in the plugin name. + * + * @param {string} pluginName + * + * @returns true if the name is valid. + */ +function validatePluginName(pluginName) { + const invalidPattern = /[\/\\]|(\.\.)/; + return !invalidPattern.test(pluginName); +} + +/** + * @param {string} archiveURL - the one to validate + * @returns true if the archiveURL looks good. + */ +function validateArchiveURL(archiveURL) { + return ( + archiveURL.startsWith('https://artifacthub.io/packages/') || + archiveURL.startsWith('https://github.com/yolossn/headlamp-plugins/') + ); +} + /** * Downloads and extracts a plugin from the specified URL. * @param {string} URL - The URL of the plugin to download and extract. @@ -260,7 +290,15 @@ async function downloadExtractPlugin(URL, headlampVersion, progressCallback, sig progressCallback({ type: 'info', message: 'Plugin Metadata Fetched' }); } const pluginName = pluginInfo.name; + if (!validatePluginName(pluginName)) { + throw new Error('Invalid plugin name'); + } + const archiveURL = pluginInfo.data['headlamp/plugin/archive-url']; + if (!validateArchiveURL(archiveURL)) { + throw new Error('Invalid plugin/archive-url'); + } + let checksum = pluginInfo.data['headlamp/plugin/archive-checksum']; if (!archiveURL || !checksum) { throw new Error('Invalid plugin metadata. Please check the plugin details.'); @@ -288,11 +326,8 @@ async function downloadExtractPlugin(URL, headlampVersion, progressCallback, sig throw new Error('Download cancelled'); } - // create a temp folder - const tempFolder = fs.mkdirSync( - path.join(os.tmpdir() + `/${pluginName}-${Date.now().toString()}/${pluginName}`), - { recursive: true } - ); + const tempDir = await fs.mkdtempSync(path.join(os.tmpdir(), 'headlamp-plugin-temp-')); + const tempFolder = fs.mkdirSync(path.join(tempDir, pluginName), { recursive: true }); if (progressCallback) { progressCallback({ type: 'info', message: 'Downloading Plugin' }); diff --git a/plugins/headlamp-plugin/plugin-management-utils.test.js b/plugins/headlamp-plugin/plugin-management/plugin-management.test.js similarity index 98% rename from plugins/headlamp-plugin/plugin-management-utils.test.js rename to plugins/headlamp-plugin/plugin-management/plugin-management.test.js index e824ddca01..00957edc8e 100644 --- a/plugins/headlamp-plugin/plugin-management-utils.test.js +++ b/plugins/headlamp-plugin/plugin-management/plugin-management.test.js @@ -1,4 +1,4 @@ -const PluginManager = require('./plugin-management-utils.js'); +const PluginManager = require('./plugin-management.js'); const tmp = require('tmp'); const fs = require('fs'); const semver = require('semver');