mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Implement allow/denyList when loading/installing modules
This commit is contained in:
parent
fc459be531
commit
aacb92a7ae
@ -50,13 +50,15 @@ module.exports = {
|
|||||||
// Nodes
|
// Nodes
|
||||||
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
|
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
|
||||||
|
|
||||||
if (!settings.editorTheme || !settings.editorTheme.palette || settings.editorTheme.palette.upload !== false) {
|
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowInstall !== false) {
|
||||||
|
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowUpload !== false) {
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
const upload = multer({ storage: multer.memoryStorage() });
|
||||||
adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler);
|
adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler);
|
||||||
} else {
|
} else {
|
||||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
|
adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
|
||||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
|
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
|
||||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
|
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
|
||||||
|
@ -15,26 +15,42 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
|
|
||||||
var path = require("path");
|
const path = require("path");
|
||||||
var os = require("os");
|
const os = require("os");
|
||||||
var fs = require("fs-extra");
|
const fs = require("fs-extra");
|
||||||
var tar = require("tar");
|
const tar = require("tar");
|
||||||
|
|
||||||
var registry = require("./registry");
|
const registry = require("./registry");
|
||||||
var library = require("./library");
|
const registryUtil = require("./util");
|
||||||
|
const library = require("./library");
|
||||||
const {exec,log,events} = require("@node-red/util");
|
const {exec,log,events} = require("@node-red/util");
|
||||||
var child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||||
var installerEnabled = false;
|
let installerEnabled = false;
|
||||||
|
|
||||||
var settings;
|
let settings;
|
||||||
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
|
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
|
||||||
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
|
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
|
||||||
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
|
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
|
||||||
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
|
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
|
||||||
|
|
||||||
|
// Default allow/deny lists
|
||||||
|
let installAllowList = ['*'];
|
||||||
|
let installDenyList = [];
|
||||||
|
|
||||||
|
|
||||||
function init(_settings) {
|
function init(_settings) {
|
||||||
settings = _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();
|
var activePromise = Promise.resolve();
|
||||||
@ -118,6 +134,12 @@ function installModule(module,version,url) {
|
|||||||
reject(e);
|
reject(e);
|
||||||
return;
|
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);
|
isUpgrade = checkExistingModule(module,version);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
@ -215,6 +237,10 @@ async function getExistingPackageVersion(moduleName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function installTarball(tarball) {
|
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.
|
// Check this tarball contains a valid node-red module.
|
||||||
// Get its module name/version
|
// Get its module name/version
|
||||||
const moduleInfo = await getTarballModuleInfo(tarball);
|
const moduleInfo = await getTarballModuleInfo(tarball);
|
||||||
|
@ -14,10 +14,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var fs = require("fs");
|
const fs = require("fs");
|
||||||
var path = require("path");
|
const path = require("path");
|
||||||
var log = require("@node-red/util").log;
|
const log = require("@node-red/util").log;
|
||||||
var i18n = require("@node-red/util").i18n;
|
const i18n = require("@node-red/util").i18n;
|
||||||
|
const registryUtil = require("./util");
|
||||||
|
|
||||||
|
// Default allow/deny lists
|
||||||
|
let loadAllowList = ['*'];
|
||||||
|
let loadDenyList = [];
|
||||||
|
|
||||||
|
|
||||||
var settings;
|
var settings;
|
||||||
var disableNodePathScan = false;
|
var disableNodePathScan = false;
|
||||||
@ -25,6 +31,16 @@ var iconFileExtensions = [".png", ".gif", ".svg"];
|
|||||||
|
|
||||||
function init(_settings) {
|
function init(_settings) {
|
||||||
settings = _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) {
|
function isIncluded(name) {
|
||||||
@ -137,9 +153,13 @@ function scanDirForNodesModules(dir,moduleName) {
|
|||||||
try {
|
try {
|
||||||
var pkg = require(pkgfn);
|
var pkg = require(pkgfn);
|
||||||
if (pkg['node-red']) {
|
if (pkg['node-red']) {
|
||||||
|
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);
|
var moduleDir = path.join(dir,fn);
|
||||||
results.push({dir:moduleDir,package:pkg});
|
results.push({dir:moduleDir,package:pkg});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
if (err.code != "MODULE_NOT_FOUND") {
|
if (err.code != "MODULE_NOT_FOUND") {
|
||||||
// TODO: handle unexpected error
|
// TODO: handle unexpected error
|
||||||
@ -308,8 +328,7 @@ function getNodeFiles(disableNodePathScan) {
|
|||||||
} else {
|
} else {
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
log.debug("Module: "+mod.package.name+" "+mod.package.version+(result?"":" *ignored due to local copy*"));
|
log.debug((result?"":"! ")+"Module: "+mod.package.name+" "+mod.package.version+" "+mod.dir+(result?"":" *ignored due to local copy*"));
|
||||||
log.debug(" "+mod.dir);
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const semver = require("semver");
|
||||||
const {events,i18n,log} = require("@node-red/util");
|
const {events,i18n,log} = require("@node-red/util");
|
||||||
var runtime;
|
var runtime;
|
||||||
|
|
||||||
@ -104,9 +105,78 @@ function createNodeApi(node) {
|
|||||||
return red;
|
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 = {
|
module.exports = {
|
||||||
init: function(_runtime) {
|
init: function(_runtime) {
|
||||||
runtime = _runtime;
|
runtime = _runtime;
|
||||||
},
|
},
|
||||||
createNodeApi: createNodeApi
|
createNodeApi: createNodeApi,
|
||||||
|
parseModuleList: parseModuleList,
|
||||||
|
checkModuleAllowed: checkModuleAllowed
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ var api = module.exports = {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
if (opts.tarball) {
|
if (opts.tarball) {
|
||||||
if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette && runtime.settings.editorTheme.palette.upload === false) {
|
if (runtime.settings.externalModules && runtime.settings.externalModules.palette && runtime.settings.externalModules.palette.upload === false) {
|
||||||
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req);
|
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req);
|
||||||
var err = new Error("Invalid request");
|
var err = new Error("Invalid request");
|
||||||
err.code = "invalid_request";
|
err.code = "invalid_request";
|
||||||
|
@ -14,7 +14,54 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
const should = require("should");
|
||||||
|
const NR_TEST_UTILS = require("nr-test-utils");
|
||||||
|
const registryUtil = NR_TEST_UTILS.require("@node-red/registry/lib/util");
|
||||||
|
|
||||||
|
|
||||||
describe("red/nodes/registry/util",function() {
|
describe("red/nodes/registry/util",function() {
|
||||||
it.skip("NEEDS TESTS");
|
describe("createNodeApi", function() {
|
||||||
|
it.skip("needs tests");
|
||||||
|
});
|
||||||
|
describe("checkModuleAllowed", function() {
|
||||||
|
function checkList(module, version, allowList, denyList) {
|
||||||
|
return registryUtil.checkModuleAllowed(
|
||||||
|
module,
|
||||||
|
version,
|
||||||
|
registryUtil.parseModuleList(allowList),
|
||||||
|
registryUtil.parseModuleList(denyList)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("allows module with no allow/deny list provided", function() {
|
||||||
|
checkList("abc","1.2.3",[],[]).should.be.true();
|
||||||
|
})
|
||||||
|
it("defaults allow to * when only deny list is provided", function() {
|
||||||
|
checkList("abc","1.2.3",["*"],["def"]).should.be.true();
|
||||||
|
checkList("def","1.2.3",["*"],["def"]).should.be.false();
|
||||||
|
})
|
||||||
|
it("uses most specific matching rule", function() {
|
||||||
|
checkList("abc","1.2.3",["ab*"],["a*"]).should.be.true();
|
||||||
|
checkList("def","1.2.3",["d*"],["de*"]).should.be.false();
|
||||||
|
})
|
||||||
|
it("checks version string using semver rules", function() {
|
||||||
|
// Deny
|
||||||
|
checkList("abc","1.2.3",["abc@1.2.2"],["*"]).should.be.false();
|
||||||
|
checkList("abc","1.2.3",["abc@1.2.4"],["*"]).should.be.false();
|
||||||
|
checkList("abc","1.2.3",["abc@>1.2.3"],["*"]).should.be.false();
|
||||||
|
checkList("abc","1.2.3",["abc@>=1.2.3"],["abc"]).should.be.false();
|
||||||
|
|
||||||
|
|
||||||
|
checkList("node-red-contrib-foo","1.2.3",["*"],["*contrib*"]).should.be.false();
|
||||||
|
|
||||||
|
|
||||||
|
// Allow
|
||||||
|
checkList("abc","1.2.3",["abc@1.2.3"],["*"]).should.be.true();
|
||||||
|
checkList("abc","1.2.3",["abc@<1.2.4"],["*"]).should.be.true();
|
||||||
|
checkList("abc","1.2.3",["abc"],["abc@>1.2.3"]).should.be.true();
|
||||||
|
checkList("abc","1.2.3",["abc"],["abc@<1.2.3||>1.2.3"]).should.be.true();
|
||||||
|
checkList("node-red-contrib-foo","1.2.3",["*contrib*"],["*"]).should.be.true();
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user