remoteCommands added plugin and master API endpoint for executing things

This commit is contained in:
Danielv123 committed 2018-04-09 11:25:57 +02:00
1 parent be49deb170
commit 43c1f2ae93
7 files changed
+108 -8

No files matched your search

+3 -1
View File
@@ -34,6 +34,7 @@ Factorio*
database/items.db
config.json
package-lock.json
secret-api-token.txt
mod_cache.json
static/pictures/spritesheet.js
static/pictures/spritesheet.png
@@ -44,4 +45,5 @@ static/external
sharedPlugins/*
!sharedPlugins/inventoryImports/
!sharedPlugins/UPSdisplay/
!sharedPlugins/remoteMap/
!sharedPlugins/remoteMap/
!sharedPlugins/remoteCommands/
+3 -1
View File
@@ -445,7 +445,7 @@ function instanceManagement() {
global.subscribedFiles = {};
}
if(pluginConfig.binary == "nodePackage"){
// require index.js.main() of plugin and execute it as a function
// require index.js.main() of plugin and execute it as a class
let pluginClass = require("./sharedPlugins/" + pluginDirectories[I] + "/index.js");
plugins[I] = new pluginClass(combinedConfig, function(data){
if(data.toString('utf8')[0] != "/") {
@@ -453,6 +453,8 @@ function instanceManagement() {
} else {
messageInterface(data.toString('utf8'));
}
}, { // extra functions to pass in object. Should have done it like this from the start, but won't break backwards compat.
socket, // socket.io connection to master (and ES6 destructuring, yay)
});
if(pluginConfig.scriptOutputFileSubscription && typeof pluginConfig.scriptOutputFileSubscription == "string"){
if(global.subscribedFiles[pluginConfig.scriptOutputFileSubscription]) {
+6 -1
View File
@@ -8,6 +8,10 @@
"__comment2": "IP the master web server runs at, can be ignored on the master system",
"masterIP": "localhost",
"masterPort": 8080,
"masterAuthToken": "",
"__commentAuthSecret": "This is a long random string generated by keyboard mashing. Please mash you own string for security.",
"masterAuthSecret": "anfeåpvunuennuusnvzunsrevunåzeufnewnufasiojfåqwoiuajknwkjfdabewfkjadfnewåfundjkcvnewmfqewrmn123",
"__comment3": "Your factorio.com username for factorio MP services, also username will be admin",
"username": "",
@@ -30,5 +34,6 @@
"entriesPerSecond": 0.01666666667
},
"logItemTransfers": false,
"uploadModsToMaster": true
"uploadModsToMaster": true,
"allowRemoteCommandExecution": false
}
+57 -5
View File
@@ -36,13 +36,15 @@ const averagedTimeSeries = require("averaged-timeseries");
const deepmerge = require("deepmerge");
const path = require("path");
const fs = require("fs");
var nedb = require("nedb");
const nedb = require("nedb");
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
var express = require("express");
var ejs = require("ejs");
const express = require("express");
const ejs = require("ejs");
// Required for express post requests
var bodyParser = require("body-parser");
var fileUpload = require('express-fileupload');
const bodyParser = require("body-parser");
const fileUpload = require('express-fileupload');
var app = express();
app.use(bodyParser.json());
@@ -685,6 +687,43 @@ app.post("/api/getTimelineStats", function(req,res) {
}
});
/**
POST endpoint for running commands on slaves.
Requires x-access-token header to be set. Find you api token in secret-api-token.txt on the master (after running it once)
@memberof clusterioMaster
@instance
@alias api/runCommand
@param {object} JSON {instanceID:19412312, command:"/c game.print('hello')"}
@returns {object} Status {auth: bool, message: "Informative error"}
*/
// write an auth token to file
fs.writeFileSync("secret-api-token.txt", jwt.sign({ id: "api" }, config.masterAuthSecret, {
expiresIn: 86400*365 // expires in 1 year
}));
app.post("/api/runCommand", (req,res) => {
var token = req.headers['x-access-token'];
if(!token) return res.status(401).send({ auth: false, message: 'No token provided.' });
jwt.verify(token, config.masterAuthSecret, function(err, decoded) {
if(err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });
// validate request.body
let body = req.body;
if(body.instanceID
&& Object.keys(wsSlaves).includes(body.instanceID)
&& body.command
&& typeof body.command == "string"
&& body.command[0] == "/"){
// execute command
wsSlaves[body.instanceID].runCommand(body.command, data => {
res.status(200).send({auth: true, message: "success", data});
});
} else {
res.status(400).send({auth: true, message: "Error: invalid request.body"});
}
});
});
/**
GET endpoint. Returns factorio's base locale as a JSON object.
@memberof clusterioMaster
@@ -792,6 +831,19 @@ class wsSlave {
});
}
});
// handle command return values
this.commandsWaitingForReturn = {};
this.socket.on("runCommandReturnValue", resp => {
if(resp.commandID && resp.body && this.commandsWaitingForReturn[resp.commandID] && this.commandsWaitingForReturn[resp.commandID].callback && typeof this.commandsWaitingForReturn[resp.commandID].callback == "function"){
this.commandsWaitingForReturn[resp.commandID].callback(resp.body);
delete this.commandsWaitingForReturn[resp.commandID];
}
});
}
runCommand(command, callback){
let commandID = Math.random().toString();
this.socket.emit("runCommand", {command, commandID});
this.commandsWaitingForReturn[commandID] = {callback, timestamp: Date.now()};
}
}
io.on('connection', function (socket) {
+2
View File
@@ -21,6 +21,7 @@
"app-root-path": "^2.0.1",
"as-table": "^1.0.31",
"averaged-timeseries": "^1.4.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.18.2",
"codecov": "^2.3.1",
"deepmerge": "^1.5.2",
@@ -33,6 +34,7 @@
"hash-files": "^1.1.1",
"html5-validator": "^1.0.7",
"ini": "^1.3.5",
"jsonwebtoken": "^8.2.1",
"leveldown": "^1.9.0",
"linvodb3": "^3.25.1",
"luamin": "^1.0.4",
+14
View File
@@ -0,0 +1,14 @@
/*
Config for clusterio remote command plugin.
Allows you to execute commands on slaves with a web API.
Careful, may allow for RCE vulnerabilities.
*/
var modInfo = require("./info.json");
module.exports = {
// Name of package. For display somewhere I guess.
name: "remoteCommands",
version: "1.0.0",
binary: "nodePackage",
description: "Config for clusterio remote command plugin. Allows you to execute commands on slaves with a web API.",
}
+23
View File
@@ -0,0 +1,23 @@
module.exports.main = class remoteCommands {
constructor(mergedConfig, messageInterface, extras){
console.log = messageInterface;
this.messageInterface = messageInterface;
this.config = mergedConfig;
this.socket = extras.socket;
if(this.config.allowRemoteCommandExecution){
this.socket.on("runCommand", data => {
let {commandID} = data;
this.messageInterface(data.command);
if(commandID){
this.socket.emit("runCommandReturnValue", {
commandID,
body: {
info: "Command was sent to factorio",
},
});
}
});
}
}
}