From 8055a2557df8dde044ef06d40117396b9c86cabc Mon Sep 17 00:00:00 2001 From: Max Hauser Date: Fri, 25 Oct 2024 18:32:23 +0200 Subject: [PATCH] [fix]: UI upgrade now runs as the same user as the js-controller (#2950) * the UI upgrade now runs as the same user as the js-controller * jsdoc * jsdoc * make method private --- CHANGELOG.md | 3 +- packages/controller/src/lib/upgradeManager.ts | 50 ++++++++++++++++++- packages/controller/src/main.ts | 11 +++- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 257e1294bb..edfe8d313f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ## __WORK IN PROGRESS__ --> -## __WORK IN PROGRESS__ +## __WORK IN PROGRESS__ - Lucy +* (@foxriver76) the UI upgrade now runs as the same user as the js-controller * (@foxriver76) fixed `iob validate` command and `setup custom` migration ## 7.0.1 (2024-10-21) - Lucy diff --git a/packages/controller/src/lib/upgradeManager.ts b/packages/controller/src/lib/upgradeManager.ts index d9a0c04ad7..14bcd02d57 100644 --- a/packages/controller/src/lib/upgradeManager.ts +++ b/packages/controller/src/lib/upgradeManager.ts @@ -11,12 +11,18 @@ import fs from 'fs-extra'; import type { Socket } from 'node:net'; import type { Duplex } from 'node:stream'; import url from 'node:url'; +import process from 'node:process'; +/** The upgrade arguments provided to the constructor of the UpgradeManager */ export interface UpgradeArguments { /** Version of controller to upgrade too */ version: string; /** Admin instance which triggered the upgrade */ adminInstance: number; + /** User id the process should run as */ + uid: number; + /** Group id the process should run as */ + gid: number; } interface Certificates { @@ -63,6 +69,10 @@ class UpgradeManager { private readonly adminInstance: number; /** Desired controller version */ private readonly version: string; + /** Group id the process should run as */ + private readonly gid: number; + /** User id the process should run as */ + private readonly uid: number; /** Response send by webserver */ private readonly response: ServerResponse = { running: true, @@ -85,6 +95,30 @@ class UpgradeManager { this.adminInstance = args.adminInstance; this.version = args.version; this.logger = this.setupLogger(); + this.gid = args.gid; + this.uid = args.uid; + + this.applyUser(); + } + + /** + * To prevent commands (including npm) running as root, we apply the passed in gid and uid + */ + private applyUser(): void { + if (!process.setuid || !process.setgid) { + const errMessage = 'Cannot ensure user and group ids on this system, because no POSIX platform'; + this.log(errMessage, true); + throw new Error(errMessage); + } + + try { + process.setgid(this.gid); + process.setuid(this.uid); + } catch (e) { + const errMessage = `Could not ensure user and group ids on this system: ${e.message}`; + this.log(errMessage, true); + throw new Error(errMessage); + } } /** @@ -103,6 +137,8 @@ class UpgradeManager { const version = additionalArgs[0]; const adminInstance = parseInt(additionalArgs[1]); + const uid = parseInt(additionalArgs[2]); + const gid = parseInt(additionalArgs[3]); const isValid = !!valid(version); @@ -116,7 +152,17 @@ class UpgradeManager { throw new Error('Please provide a valid admin instance'); } - return { version, adminInstance }; + if (isNaN(uid)) { + UpgradeManager.printUsage(); + throw new Error('Please provide a valid uid'); + } + + if (isNaN(gid)) { + UpgradeManager.printUsage(); + throw new Error('Please provide a valid gid'); + } + + return { version, adminInstance, uid, gid }; } /** @@ -163,7 +209,7 @@ class UpgradeManager { * Print how the module should be used */ static printUsage(): void { - console.info('Example usage: "node upgradeManager.js "'); + console.info('Example usage: "node upgradeManager.js "'); } /** diff --git a/packages/controller/src/main.ts b/packages/controller/src/main.ts index 70269edb0e..edfc6e5cd4 100644 --- a/packages/controller/src/main.ts +++ b/packages/controller/src/main.ts @@ -2760,7 +2760,12 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise { * @param options Arguments passed to the UpgradeManager process */ async function startUpgradeManager(options: UpgradeArguments): Promise { - const { version, adminInstance } = options; + const { version, adminInstance, uid, gid } = options; const upgradeProcessPath = require.resolve('./lib/upgradeManager'); let upgradeProcess: cp.ChildProcess; @@ -5864,6 +5869,8 @@ async function startUpgradeManager(options: UpgradeArguments): Promise { upgradeProcessPath, version, adminInstance.toString(), + uid.toString(), + gid.toString(), ], { detached: true,