-
Notifications
You must be signed in to change notification settings - Fork 784
Description
Background
I just got some micro desktops with vPro support, and I wanted to install MeshCentral using the Docker image in uldiseihenbergs/meshcentral-builds. A best practice for Docker containers is to run services as a unique user (particularly, non-root), which can be accomplished with --user or user: in a docker compose file. Unfortunately, doing that didn't work because of a permissions issue with "module installation".
I dug into the code and discovered that dependencies are being gathered and npm install is being executed by the main script at runtime:
Lines 4019 to 4107 in 990da39
| // Build the list of required modules | |
| // NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN Dockerfile | |
| var modules = ['[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','@yetzt/nedb','[email protected]','[email protected]','[email protected]','[email protected]']; | |
| if (require('os').platform() == 'win32') { modules.push('[email protected]'); modules.push('[email protected]'); if (sspi == true) { modules.push('[email protected]'); } } // Add Windows modules | |
| if (ldap == true) { modules.push('[email protected]'); } | |
| if (ssh == true) { modules.push('[email protected]'); } | |
| if (passport != null) { modules.push(...passport); } | |
| if (captcha == true) { modules.push('[email protected]'); } | |
| if (sessionRecording == true) { modules.push('[email protected]'); } // Need to get the remote desktop JPEG sizes to index the recodring file. | |
| if (config.letsencrypt != null) { modules.push('[email protected]'); } // Add acme-client module. We need to force v4.2.4 or higher since olver versions using SHA-1 which is no longer supported by Let's Encrypt. | |
| if (config.settings.mqtt != null) { modules.push('[email protected]'); } // Add MQTT Modules | |
| if (config.settings.mysql != null) { modules.push('[email protected]'); } // Add MySQL. | |
| //if (config.settings.mysql != null) { modules.push('@mysql/xdevapi@8.0.33'); } // Add MySQL, official driver (https://dev.mysql.com/doc/dev/connector-nodejs/8.0/) | |
| if (config.settings.mongodb != null) { modules.push('[email protected]'); modules.push('[email protected]'); } // Add MongoDB, official driver. | |
| if (config.settings.postgres != null) { modules.push('[email protected]'); modules.push('[email protected]'); } // Add Postgres, Postgres driver. | |
| if (config.settings.mariadb != null) { modules.push('[email protected]'); } // Add MariaDB, official driver. | |
| if (config.settings.acebase != null) { modules.push('[email protected]'); } // Add AceBase, official driver. | |
| if (config.settings.sqlite3 != null) { modules.push('[email protected]'); } // Add sqlite3, official driver. | |
| if (config.settings.vault != null) { modules.push('[email protected]'); } // Add official HashiCorp's Vault module. | |
| if (config.settings.plugins != null) { modules.push('[email protected]'); } // Required for version compat testing and update checks | |
| if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('[email protected]'); } // Required for HTTP/HTTPS proxy support | |
| else if (config.settings.xmongodb != null) { modules.push('[email protected]'); } // Add MongoJS, old driver. | |
| if (nodemailer || ((config.smtp != null) && (config.smtp.name != 'console')) || (config.sendmail != null)) { modules.push('[email protected]'); } // Add SMTP support | |
| if (sendgrid || (config.sendgrid != null)) { modules.push('@sendgrid/mail'); } // Add SendGrid support | |
| if ((args.translate || args.dev) && (Number(process.version.match(/^v(\d+\.\d+)/)[1]) >= 16)) { modules.push('[email protected]'); modules.push('[email protected]'); modules.push('[email protected]'); modules.push('[email protected]'); } // Translation support | |
| if (typeof config.settings.crowdsec == 'object') { modules.push('@crowdsec/[email protected]'); } // Add CrowdSec bounser module (https://www.npmjs.com/package/@crowdsec/express-bouncer) | |
| if (typeof config.settings.autobackup == 'object') { | |
| // Setup encrypted zip support if needed | |
| if (config.settings.autobackup.zippassword) { modules.push('[email protected]'); } | |
| // Enable Google Drive Support | |
| if (typeof config.settings.autobackup.googledrive == 'object') { modules.push('[email protected]'); } | |
| // Enable WebDAV Support | |
| if (typeof config.settings.autobackup.webdav == 'object') { | |
| if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('[email protected]'); } | |
| } | |
| } | |
| // Setup common password blocking | |
| if (wildleek == true) { modules.push('[email protected]'); } | |
| // Setup 2nd factor authentication | |
| if (config.settings.no2factorauth !== true) { | |
| // Setup YubiKey OTP if configured | |
| if (yubikey == true) { modules.push('[email protected]'); } // Add YubiKey OTP support | |
| if (allsspi == false) { modules.push('[email protected]'); } // Google Authenticator support (v10 supports older NodeJS versions). | |
| } | |
| // Desktop multiplexor support | |
| if (config.settings.desktopmultiplex === true) { modules.push('[email protected]'); } | |
| // SMS support | |
| if (config.sms != null) { | |
| if (config.sms.provider == 'twilio') { modules.push('[email protected]'); } | |
| if (config.sms.provider == 'plivo') { modules.push('[email protected]'); } | |
| if (config.sms.provider == 'telnyx') { modules.push('[email protected]'); } | |
| } | |
| // Messaging support | |
| if (config.messaging != null) { | |
| if (config.messaging.telegram != null) { modules.push('[email protected]'); modules.push('[email protected]'); } | |
| if (config.messaging.discord != null) { if (nodeVersion >= 17) { modules.push('[email protected]'); } else { delete config.messaging.discord; addServerWarning('This NodeJS version does not support Discord.js.', 26); } } | |
| if (config.messaging.xmpp != null) { modules.push('@xmpp/[email protected]'); } | |
| if (config.messaging.pushover != null) { modules.push('[email protected]'); } | |
| if (config.messaging.zulip != null) { modules.push('[email protected]'); } | |
| } | |
| // Setup web based push notifications | |
| if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) { modules.push('[email protected]'); } | |
| // Firebase Support | |
| // Avoid 0.1.8 due to bugs: https://github.com/guness/node-xcs/issues/43 | |
| if (config.firebase != null) { modules.push('[email protected]'); } | |
| // Syslog support | |
| if ((require('os').platform() != 'win32') && (config.settings.syslog || config.settings.syslogjson)) { modules.push('[email protected]'); } | |
| if (config.settings.syslogtcp) { modules.push('[email protected]'); } | |
| // Setup heapdump support if needed, useful for memory leak debugging | |
| // https://www.arbazsiddiqui.me/a-practical-guide-to-memory-leaks-in-nodejs/ | |
| if (config.settings.heapdump === true) { modules.push('[email protected]'); } | |
| // Install any missing modules and launch the server | |
| InstallModules(modules, args, function () { | |
| if (require('os').platform() == 'win32') { try { require('node-windows'); } catch (ex) { console.log("Module node-windows can't be loaded. Restart MeshCentral."); process.exit(); return; } } | |
| meshserver = CreateMeshCentralServer(config, args); | |
| meshserver.Start(); | |
| }); |
Docker Issues
Permissions
The above means that the permissions issue in Docker can never really be resolved. The image must be created with a particular user selected at build time, and that generally should be root. The container should then ideally be able to run as any user, but in order to be able do an npm install at run time, the build user would have to be chosen in advance to be the same as the run user. (Or you could grant everyone write permissions, but that's not a great choice for security).
Non-ephemeral container
There's also a separate but related issue when it comes to Docker, and that is that you should not write directly to the container's writable layer because 1) the container is ephemeral and should contain everything needed to run the application, and 2) for performance reasons. You can get around this with a volume, but then you run into issues with volume persistence in addition to violating Docker image design principles.
Dangers With Runtime Module Installation
I did a lot of searching, and I couldn't find any other npm packages, services, etc, that install modules at runtime in this way. From my own experience, and according to the nodejs docs, this is a bad practice. It's fragile in that you have to maintain the main dependency list in two places (package.json and meshcentral.js), and from looking through the issues it seems that module installation issues come up frequently. The whole point of npm is to resolve dependencies and install packages from a package.json (really: package-lock.json). It's made to take care of this for you, and if you properly define all of your dependencies I would guess you'll get far fewer reports of issues with installation and update because all of the versioning is frozen and reproducible.
Disk Space
I'm curious why it was done this way. I'm guessing it's to save the disk space of modules that aren't actually used by the config, and I am seeing 21MB on a "default" install, and 357MB with all modules installed, and 100MB of that is googleapis. Nonetheless, there's a danger in modifying your "executable" based on user configuration, and I don't think even the full 357MB is so concerning that it's worth trying to save.
Request
I think the project (and Docker users!) would benefit from moving all of the dependencies into package.json and removing the runtime install code. I'd also be happy to contribute the change, including documentation updates, etc. Another benefit of doing this is it makes installing and running meshcentral trivial with npx
Bonus: I'd be happy to contribute an official docker container too, including github actions to automatically publish it.
The exception to the rule
npm doesn't support os-specific dependencies, so you can't add windows-only packages to dependencies or Unix systems will fail installation. You would add it to optionalDependencies, where the failure won't prevent Unix/Mac installs, but windows users should get the packages. There is some risk that those packages fail to install on windows for some other reason, so a runtime check might be warranted.