Search code examples
c#node.jsgoelectrongo-cobra

I need to run commands with elevated privileges on my backend


For an environment i am developing (elecron + bs5.3 + node.js + go + cobra ...), the objective of which is to create a web interface so that users can execute system commands using buttons and forms on: Windows, Linux or Mac to Manage Dockers Container Environments.

How do I do it if the commands must be executed with elevated privileges?

The only thing I have found is changes in the package.json some configuration (windows example):

"scripts": {
    "start": "electron src/main.js --trace-warnings --win32metadata.requested-execution-level=requireAdministrator",
    "build": "electron-builder"
},
...
"win": {
    "target": [
        "nsis"
    ],
    "artifactName": "webstack-deployer-for-docker-${version}.exe",
    "icon": "src/assets/icons/icon.ico",
    "requestedExecutionLevel": "requireAdministrator",
    "adminRequested": true
},

I found this answer which only allows me to evaluate how it is running, but this does not force or reopen/relaunch the application with elevated privileges on node.js and invoke the backend with same elevated privileges.

in C# i have and Class with this code, but I don't know how to port to node.js/project:

internal static void CheckElevated()
        {
            if (!IsRunAsAdmin())
            {
                RunApp();
            }
        }

        public static void RunApp()
        {
            Assembly? assemblyEntries = Assembly.GetEntryAssembly();

            if (assemblyEntries != null)
            {
                
                string executablePath = assemblyEntries.Location.Replace(".dll", ".exe");

                if (!File.Exists(executablePath) && Environment.ProcessPath != null)
                {
                    executablePath = Environment.ProcessPath;
                }

                if (File.Exists(executablePath))
                {
                    ProcessStartInfo processInfo = new()
                    {
                        UseShellExecute = true,
                        WorkingDirectory = Environment.CurrentDirectory,
                        FileName = executablePath,
                        Verb = "runas"
                    };

                    try
                    {
                        Process.Start(processInfo);
                        Environment.Exit(0);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("Error: This program must be run as an administrator! \n\n" + ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
                else
                {
                    MessageBox.Show("Error: This program must be run as an administrator!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        public static bool IsRunAsAdmin()
        {
            try
            {
                WindowsIdentity id = WindowsIdentity.GetCurrent();
                WindowsPrincipal principal = new(id);
                return principal.IsInRole(WindowsBuiltInRole.Administrator);
            }
            catch (Exception)
            {
                return false;
            }
        }

In the C# version of the application this only needed to be done once, but in this development, having two layers: (Front and Back) End, I assume that it should be evaluated in both areas...


I'll add updates on any progress soon, I think it's possible to port from C# to the node.js is-elevated implementation.

The goal is to relaunch the application with elevated privileges and the go backend as well, so that the user can easily create directories and files programmatically.


Solution

  • I have achieved it, It was necessary to detect the operating system and port the C# script to node.js, I'm not sure if this is the correct way to do it but it works fine for now.

    note: any suggestions will be welcome.

    const { exec, spawn } = require('child_process');
    const os = require('os');
    
    /**
     * Manages application elevation and admin privileges across different platforms
     */
    class AdminPrivilegesManager {
        /**
         * Checks and ensures the application runs with admin privileges
         * @returns {Promise<void>}
         */
        static async ensureAdminPrivileges() {
            const isAdmin =  this.checkPrivilegesAndRelaunch();
            console.log('isAdmin',isAdmin);
        }
    
        static async checkPrivilegesAndRelaunch() {
            if (os.platform() === 'win32') {
                await exec('net session', (err) => {
                    if (err) {
                        console.log("Not running as Administrator. Relaunching...");
                        this.relaunchAsAdmin();
                    } else {
                        console.log("Running as Administrator.");
                    }
                });
            } else {
                if (process.getuid && process.getuid() !== 0) {
                    console.log("Not running as root. Relaunching...");
                    this.relaunchAsAdmin();
                } else {
                    console.log("Running as root.");
                }
            }
            return true;
        }
    
        static relaunchAsAdmin() {
            const platform = os.platform();
            const appPath = process.argv[0]; // Path to Electron executable
            const scriptPath = process.argv[1]; // Path to main.js (or entry point)
            const workingDir = process.cwd(); // Ensure correct working directory
            const args = process.argv.slice(2).join(' '); // Preserve additional arguments
    
            if (platform === 'win32') {
                const command = `powershell -Command "Start-Process '${appPath}' -ArgumentList '${scriptPath} ${args}' -WorkingDirectory '${workingDir}' -Verb RunAs"`;
                exec(command, (err) => {
                    if (err) {
                        console.error("Failed to elevate to administrator:", err);
                    } else {
                        console.log("Restarting with administrator privileges...");
                        process.exit(0);
                    }
                });
            } else {
                const elevatedProcess = spawn('sudo', [appPath, scriptPath, ...process.argv.slice(2)], {
                    stdio: 'inherit',
                    detached: true,
                    cwd: workingDir, // Set correct working directory
                });
    
                elevatedProcess.on('error', (err) => {
                    console.error("Failed to elevate to root:", err);
                });
    
                elevatedProcess.on('spawn', () => {
                    console.log("Restarting with root privileges...");
                    process.exit(0);
                });
            }
        }
    }
    
    module.exports = AdminPrivilegesManager;
    

    Let's go with the script explanation so you don't feel lost:

    • exec: Executes commands on the command line and returns the output.
    • spawn: Executes processes in the background.
    • os: Obtains information about the operating system.

    Additional:

    • net session: is a command that only works with administrator permissions if it fails err exists, then it will relaunch the application with elevated privileges. For Linux, process.getuid() is used if it is 0 then it will be understood that it is root as if it had been opened with sudo.

    To relaunch the applications in Windows, PowerShell is used with the argument -Verb RunAs. This will allow the application to be relaunched with administrator privileges. In the case of Linux, spawn('sudo', [...])...

    To relaunch the application, it was necessary to perform a series of checks to obtain the platform data, the path to the electron executable, the main.js script, and the option to preserve some arguments, so that it works correctly in the development environment. At this point, I realize that I still have a correction to add for the production environment, but that is not the point of this post. work well on development.