From 0f7b6945675ccf1baef614041d662bcc0fcb7be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Dudfield?= Date: Wed, 10 Jul 2024 07:40:12 +0200 Subject: [PATCH 1/6] headlamp-plugin/plugin-management: Improve module intro doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Dudfield --- plugins/headlamp-plugin/plugin-management-utils.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/headlamp-plugin/plugin-management-utils.js b/plugins/headlamp-plugin/plugin-management-utils.js index 074aec2b22..6aa8034350 100644 --- a/plugins/headlamp-plugin/plugin-management-utils.js +++ b/plugins/headlamp-plugin/plugin-management-utils.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'); From 1e0f4ab731c895f5b683530f4053828d7eea8065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Dudfield?= Date: Wed, 10 Jul 2024 09:31:59 +0200 Subject: [PATCH 2/6] headlamp-plugin/plugin-management: Fix making temp dir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So it is done in a more secure way. Signed-off-by: René Dudfield --- plugins/headlamp-plugin/plugin-management-utils.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/headlamp-plugin/plugin-management-utils.js b/plugins/headlamp-plugin/plugin-management-utils.js index 6aa8034350..09f07a2a4e 100644 --- a/plugins/headlamp-plugin/plugin-management-utils.js +++ b/plugins/headlamp-plugin/plugin-management-utils.js @@ -293,11 +293,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' }); From f131ff3350dc3cff9596a3ae0fc803f098508c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Dudfield?= Date: Wed, 10 Jul 2024 09:58:58 +0200 Subject: [PATCH 3/6] headlamp-plugin/plugin-management: Add validation of pluginName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To prevent paths like "../" causing mischief. Signed-off-by: René Dudfield --- .../headlamp-plugin/plugin-management-utils.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plugins/headlamp-plugin/plugin-management-utils.js b/plugins/headlamp-plugin/plugin-management-utils.js index 09f07a2a4e..2abfdc6720 100644 --- a/plugins/headlamp-plugin/plugin-management-utils.js +++ b/plugins/headlamp-plugin/plugin-management-utils.js @@ -242,6 +242,20 @@ 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); +} + /** * Downloads and extracts a plugin from the specified URL. * @param {string} URL - The URL of the plugin to download and extract. @@ -265,6 +279,10 @@ 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']; let checksum = pluginInfo.data['headlamp/plugin/archive-checksum']; if (!archiveURL || !checksum) { From 39a314ad7b24dfdf9b1f73b6fbf38d682341f0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Dudfield?= Date: Wed, 10 Jul 2024 11:46:40 +0200 Subject: [PATCH 4/6] headlamp-plugin/plugin-management: Add validation for artifact URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So that we limit it to only artifacthub ones for now. Signed-off-by: René Dudfield --- .../headlamp-plugin/plugin-management-utils.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/headlamp-plugin/plugin-management-utils.js b/plugins/headlamp-plugin/plugin-management-utils.js index 2abfdc6720..0dee89e384 100644 --- a/plugins/headlamp-plugin/plugin-management-utils.js +++ b/plugins/headlamp-plugin/plugin-management-utils.js @@ -256,6 +256,17 @@ function validatePluginName(pluginName) { 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. @@ -284,6 +295,10 @@ async function downloadExtractPlugin(URL, headlampVersion, progressCallback, sig } 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.'); From d5bf925c72a7f7bf8c408a19320f34ff788f982b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Dudfield?= Date: Thu, 11 Jul 2024 07:57:55 +0200 Subject: [PATCH 5/6] headlamp-plugin/plugin-management.test: Add #! to top of file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So test can be more easily executed. Signed-off-by: René Dudfield --- plugins/headlamp-plugin/headlamp-plugin-management.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/headlamp-plugin/headlamp-plugin-management.test.js b/plugins/headlamp-plugin/headlamp-plugin-management.test.js index ce9f84f63a..f0a9cf6c8f 100755 --- a/plugins/headlamp-plugin/headlamp-plugin-management.test.js +++ b/plugins/headlamp-plugin/headlamp-plugin-management.test.js @@ -1,3 +1,4 @@ +#!/bin/env node const { execSync } = require('child_process'); const assert = require('assert'); const fs = require('fs'); From 5eb25d6925b855bbf17a30e0059cd1b763b8c089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Dudfield?= Date: Thu, 11 Jul 2024 08:15:35 +0200 Subject: [PATCH 6/6] headlamp-plugin/plugin-management: Put files into a folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So everything related is under plugin-management/ now. Signed-off-by: René Dudfield --- Makefile | 4 ++-- app/electron/main.ts | 2 +- plugins/headlamp-plugin/bin/headlamp-plugin.js | 2 +- plugins/headlamp-plugin/package.json | 2 +- .../plugin-management.e2e.js} | 14 +++++++------- .../plugin-management.js} | 0 .../plugin-management.test.js} | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) rename plugins/headlamp-plugin/{headlamp-plugin-management.test.js => plugin-management/plugin-management.e2e.js} (83%) rename plugins/headlamp-plugin/{plugin-management-utils.js => plugin-management/plugin-management.js} (100%) rename plugins/headlamp-plugin/{plugin-management-utils.test.js => plugin-management/plugin-management.test.js} (98%) 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 83% rename from plugins/headlamp-plugin/headlamp-plugin-management.test.js rename to plugins/headlamp-plugin/plugin-management/plugin-management.e2e.js index f0a9cf6c8f..adae699adb 100755 --- a/plugins/headlamp-plugin/headlamp-plugin-management.test.js +++ b/plugins/headlamp-plugin/plugin-management/plugin-management.e2e.js @@ -29,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); @@ -41,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 100% rename from plugins/headlamp-plugin/plugin-management-utils.js rename to plugins/headlamp-plugin/plugin-management/plugin-management.js 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');