Implement allow/denyList when loading/installing modules

This commit is contained in:
Nick O'Leary
2020-12-27 12:49:17 +00:00
parent fc459be531
commit aacb92a7ae
6 changed files with 191 additions and 27 deletions

View File

@@ -15,26 +15,42 @@
**/
var path = require("path");
var os = require("os");
var fs = require("fs-extra");
var tar = require("tar");
const path = require("path");
const os = require("os");
const fs = require("fs-extra");
const tar = require("tar");
var registry = require("./registry");
var library = require("./library");
const registry = require("./registry");
const registryUtil = require("./util");
const library = require("./library");
const {exec,log,events} = require("@node-red/util");
var child_process = require('child_process');
var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
var installerEnabled = false;
const child_process = require('child_process');
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
let installerEnabled = false;
var settings;
let settings;
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
// Default allow/deny lists
let installAllowList = ['*'];
let installDenyList = [];
function init(_settings) {
settings = _settings;
// TODO: This is duplicated in localfilesystem.js
// Should it *all* be managed by util?
if (settings.externalModules && settings.externalModules.palette) {
if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) {
installAllowList = settings.externalModules.palette.allowList;
installDenyList = settings.externalModules.palette.denyList;
}
}
installAllowList = registryUtil.parseModuleList(installAllowList);
installDenyList = registryUtil.parseModuleList(installDenyList);
}
var activePromise = Promise.resolve();
@@ -118,6 +134,12 @@ function installModule(module,version,url) {
reject(e);
return;
}
if (!registryUtil.checkModuleAllowed(module,version,installAllowList,installDenyList)) {
const e = new Error("Install not allowed");
e.code = "install_not_allowed";
reject(e);
return
}
isUpgrade = checkExistingModule(module,version);
} catch(err) {
return reject(err);
@@ -215,6 +237,10 @@ async function getExistingPackageVersion(moduleName) {
}
async function installTarball(tarball) {
if (settings.externalModules && settings.externalModules.palette && settings.externalModules.palette.allowUpload === false) {
throw new Error("Module upload disabled")
}
// Check this tarball contains a valid node-red module.
// Get its module name/version
const moduleInfo = await getTarballModuleInfo(tarball);

View File

@@ -14,10 +14,16 @@
* limitations under the License.
**/
var fs = require("fs");
var path = require("path");
var log = require("@node-red/util").log;
var i18n = require("@node-red/util").i18n;
const fs = require("fs");
const path = require("path");
const log = require("@node-red/util").log;
const i18n = require("@node-red/util").i18n;
const registryUtil = require("./util");
// Default allow/deny lists
let loadAllowList = ['*'];
let loadDenyList = [];
var settings;
var disableNodePathScan = false;
@@ -25,6 +31,16 @@ var iconFileExtensions = [".png", ".gif", ".svg"];
function init(_settings) {
settings = _settings;
// TODO: This is duplicated in installer.js
// Should it *all* be managed by util?
if (settings.externalModules && settings.externalModules.palette) {
if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) {
loadAllowList = settings.externalModules.palette.allowList;
loadDenyList = settings.externalModules.palette.denyList;
}
}
loadAllowList = registryUtil.parseModuleList(loadAllowList);
loadDenyList = registryUtil.parseModuleList(loadDenyList);
}
function isIncluded(name) {
@@ -137,8 +153,12 @@ function scanDirForNodesModules(dir,moduleName) {
try {
var pkg = require(pkgfn);
if (pkg['node-red']) {
var moduleDir = path.join(dir,fn);
results.push({dir:moduleDir,package:pkg});
if (!registryUtil.checkModuleAllowed(pkg.name,pkg.version,loadAllowList,loadDenyList)) {
log.debug("! Module: "+pkg.name+" "+pkg.version+ " *ignored due to denyList*");
} else {
var moduleDir = path.join(dir,fn);
results.push({dir:moduleDir,package:pkg});
}
}
} catch(err) {
if (err.code != "MODULE_NOT_FOUND") {
@@ -308,8 +328,7 @@ function getNodeFiles(disableNodePathScan) {
} else {
result = false;
}
log.debug("Module: "+mod.package.name+" "+mod.package.version+(result?"":" *ignored due to local copy*"));
log.debug(" "+mod.dir);
log.debug((result?"":"! ")+"Module: "+mod.package.name+" "+mod.package.version+" "+mod.dir+(result?"":" *ignored due to local copy*"));
return result;
});

View File

@@ -15,6 +15,7 @@
**/
const path = require("path");
const semver = require("semver");
const {events,i18n,log} = require("@node-red/util");
var runtime;
@@ -104,9 +105,78 @@ function createNodeApi(node) {
return red;
}
function checkAgainstList(module,version,list) {
for (let i=0;i<list.length;i++) {
let rule = list[i];
if (rule.module.test(module)) {
if (version && rule.version) {
if (semver.satisfies(version,rule.version)) {
return rule;
}
} else {
return rule;
}
}
}
}
function checkModuleAllowed(module,version,allowList,denyList) {
// console.log("checkModuleAllowed",module,version);//,allowList,denyList)
if (!allowList && !denyList) {
// Default to allow
return true;
}
if (allowList.length === 0 && denyList.length === 0) {
return true;
}
var allowedRule = checkAgainstList(module,version,allowList);
var deniedRule = checkAgainstList(module,version,denyList);
// console.log("A",allowedRule)
// console.log("D",deniedRule)
if (allowedRule && !deniedRule) {
return true;
}
if (!allowedRule && deniedRule) {
return false;
}
if (!allowedRule && !deniedRule) {
return true;
}
if (allowedRule.wildcardPos !== deniedRule.wildcardPos) {
return allowedRule.wildcardPos > deniedRule.wildcardPos
} else {
// First wildcard in same position.
// Go with the longer matching rule. This isn't going to be 100%
// right, but we are deep into edge cases at this point.
return allowedRule.module.toString().length > deniedRule.module.toString().length
}
return false;
}
function parseModuleList(list) {
list = list || ["*"];
return list.map(rule => {
let m = /^(.+?)(?:@(.*))?$/.exec(rule);
let wildcardPos = m[1].indexOf("*");
wildcardPos = wildcardPos===-1?Infinity:wildcardPos;
return {
module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"),
version: m[2],
wildcardPos: wildcardPos
}
})
}
module.exports = {
init: function(_runtime) {
runtime = _runtime;
},
createNodeApi: createNodeApi
createNodeApi: createNodeApi,
parseModuleList: parseModuleList,
checkModuleAllowed: checkModuleAllowed
}