From 32d4b4059496514b9d07892018fbf8024b525394 Mon Sep 17 00:00:00 2001 From: Dylan Hicks <dylanh333@outlook.com> Date: Mon, 11 Jun 2018 22:03:09 +0800 Subject: [PATCH] Lots of changes - including init.js no longer needing root, and a (somewhat) working stub for login-service.js --- .gitignore | 2 +- dylan-testing/common/config.js | 15 ++++++ dylan-testing/common/constants.js | 3 ++ dylan-testing/common/socket-proxy.js | 44 +++++++++++++++ dylan-testing/config.json | 7 ++- dylan-testing/core/init.js | 80 ++++++++++++++++------------ dylan-testing/core/login-service.js | 20 ++++++- dylan-testing/core/pam-service.js | 1 - dylan-testing/core/proxy-service.js | 1 - dylan-testing/core/web-service.js | 25 +++++++++ dylan-testing/package.json | 5 +- 11 files changed, 163 insertions(+), 40 deletions(-) create mode 100644 dylan-testing/common/config.js create mode 100644 dylan-testing/common/constants.js create mode 100644 dylan-testing/common/socket-proxy.js delete mode 100644 dylan-testing/core/pam-service.js delete mode 100644 dylan-testing/core/proxy-service.js create mode 100644 dylan-testing/core/web-service.js diff --git a/.gitignore b/.gitignore index 959fcc6..de70e33 100644 --- a/.gitignore +++ b/.gitignore @@ -70,4 +70,4 @@ typings/ # Ignore temporary backup files created by vim *~ -.swp +*.swp diff --git a/dylan-testing/common/config.js b/dylan-testing/common/config.js new file mode 100644 index 0000000..f17ca72 --- /dev/null +++ b/dylan-testing/common/config.js @@ -0,0 +1,15 @@ +function getConfig(){ + var config; + + try {config = JSON.parse(process.env.UCCPORTAL_CONFIG);} + catch(e){ + throw new Error( + "Failed to retrieve config from UCCPORTAL_CONFIG " + + "environment variable: " + e.message + ); + } + + return config; +} + +module.exports = getConfig(); diff --git a/dylan-testing/common/constants.js b/dylan-testing/common/constants.js new file mode 100644 index 0000000..87d9c75 --- /dev/null +++ b/dylan-testing/common/constants.js @@ -0,0 +1,3 @@ +module.exports = { + +}; diff --git a/dylan-testing/common/socket-proxy.js b/dylan-testing/common/socket-proxy.js new file mode 100644 index 0000000..bb91afa --- /dev/null +++ b/dylan-testing/common/socket-proxy.js @@ -0,0 +1,44 @@ +var fs = require("fs"); + +/** + * Creates a new middleware for express that proxies all requests to the + * specified UNIX domain socket. + * + * @param {string} socketPath - the path to the socket + * @return {function} - an express middleware function + */ +module.exports = function socketProxy(socketPath){ + return function socketProxyMiddleware(req, res, next){ + fs.stat(socketPath, function(err, stats){ + // Check that we can actually open the socket, + // and that it's a socket + try { + if(err) throw err; + if(!stats.isSocket()) throw new Error("Not a socket"); + } + catch(e){ + res.status(500).end(); + console.error( + "Error proxying to socket \"" + socketPath + "\": " + + e.message + ); + return; + } + + // Initiate HTTP request to proxy target + var reqOpts = { + socketPath: socketPath, + method: req.method, + path: req.path, + headers: req.headers + }; + var target = http.request(reqOpts, function onResponse(targetRes){ + res.writeHead(targetRes.statusCode, targetRes.headers); + targetRes.pipe(res); + // .pipe() will automatically call res.end() + }); + req.pipe(target); + }); + }; +} + diff --git a/dylan-testing/config.json b/dylan-testing/config.json index f30002b..f9a4463 100644 --- a/dylan-testing/config.json +++ b/dylan-testing/config.json @@ -1,4 +1,7 @@ { - "serviceUser":"uccportal", - "varPath":"/var/lib/uccportal" + "configPath":"/etc/uccportal/config.json", + "varPath":"/var/lib/uccportal", + "user":"uccportal", + "host":"0.0.0.0", + "port":8080 } diff --git a/dylan-testing/core/init.js b/dylan-testing/core/init.js index 314219a..cfe5b85 100644 --- a/dylan-testing/core/init.js +++ b/dylan-testing/core/init.js @@ -20,10 +20,11 @@ function getUserIds(username){ return ids; } -function initConfig(configPath){ +function initConfig(configPath, shadowConfig){ var rawConfig, config; // Try to open config file + console.info("Loading config from \"" + configPath + "\""); try {rawConfig = fs.readFileSync(configPath);} catch(e){ throw new Error( @@ -32,7 +33,12 @@ function initConfig(configPath){ } // Try to parse config file as JSON - try {config = JSON.parse(rawConfig);} + try { + // Make config inherit properties from shadowConfig, so it they're + // undefined in the current config file, their defaults take precedence + // from the default config file. + config = Object.assign({}, shadowConfig, JSON.parse(rawConfig)); + } catch(e){ throw new Error( "\"" + configPath + "\" is not in a valid JSON format: " @@ -51,12 +57,23 @@ function initConfig(configPath){ ); } - // serviceUser: verify that a user and group of this name exist, - // then set serviceUid and serviceGid for later lookup - var ids = getUserIds(config.serviceUser); - config.serviceUid = ids.uid; - config.serviceGid = ids.gid; + // user: verify that a user and group of this name exist, + // then set uid and gid for later lookup + var ids = getUserIds(config.user); + config.uid = ids.uid; + config.gid = ids.gid; + // host: verify that host is in a (somewhat) valid format + // TODO: Improve this? + if(typeof config.host != "string" || !config.host.match( + /[a-z0-9:.-]+/i + )) throw new Error("host: not a valid hostname nor IP address"); + + // port: verify that a valid port was specified + if( + typeof config.port != "number" + || config.port%1 > 0 || config.port < 0 || config.port > 65536 + ) throw new Error("port: invalid port number specified"); } catch(e){ throw new Error( @@ -64,44 +81,41 @@ function initConfig(configPath){ ); } + // configPath: if the config file specifies a different path to a config + // file, then try to load this instead. + // Config files loaded later inherit from config files loaded earlier :) + if(config.configPath){ + // Make sure that we don't keep re-loading the current config file + // recursively and overflow the stack. + configPath = config.configPath; + delete config.configPath + return initConfig(configPath, config); + } + return config; } module.exports = function main(){ try { - // Verify that we're currently running as root - if(process.getuid() != 0){ - throw new Error( - "UCCPortal must be run as root to function correctly.\n" - + "If you're worried about security, privileges will be dropped " - + "to that of the serviceUser listed in config.json, once " - + "initialisation is complete. An exception to this is " - + "auth-service.js, which requires it for PAM to function " - + "correctly." - ); - } - - // Attempt to load config.json, or fail + // Attempt to load config.json var config = initConfig("config.json"); - console.dir(config); - + + // Set up environment for child processes + process.env.UCCPORTAL_CONFIG = JSON.stringify(config); + process.env.NODE_PATH = + (process.env.NODE_PATH ? process.env.NODE_PATH + ":" : "") + + process.cwd() + ; + // Launch services var nodejs = process.argv[0]; - var spawnOpts = { - stdio:"inherit", uid:config.serviceUid, gid:config.serviceGid - }; - var rootSpawnOpts = {stdio:"inherit", gid:config.serviceGid}; - cproc.spawn(nodejs, ["core/pam-service.js"], rootSpawnOpts); - cproc.spawn(nodejs, ["core/proxy-service.js"], spawnOpts); + var spawnOpts = {stdio: "inherit"}; + cproc.spawn(nodejs, ["core/web-service.js"], spawnOpts); cproc.spawn(nodejs, ["core/login-service.js"], spawnOpts); - - // Drop privileges - process.setgid(config.serviceGid); - process.setuid(config.serviceUid); } catch(e){ //console.error("Fatal Error: " + e.message); - throw e; process.exitCode = 1; + throw e; } } diff --git a/dylan-testing/core/login-service.js b/dylan-testing/core/login-service.js index 664ade7..fcbc241 100644 --- a/dylan-testing/core/login-service.js +++ b/dylan-testing/core/login-service.js @@ -1 +1,19 @@ -while(true); +var express = require("express"); +var fs = require("fs"); + +var config; +var app; + +function main(){ + // Read in config from environment + config = JSON.parse(process.env.UCCPORTAL_CONFIG); + + app = express(); + app.get("/", function(req, res){ + res.write("Hello World!"); + res.end(); + }); + + app.listen(config.varPath + "/login-service.sock"); +} +main(); diff --git a/dylan-testing/core/pam-service.js b/dylan-testing/core/pam-service.js deleted file mode 100644 index 664ade7..0000000 --- a/dylan-testing/core/pam-service.js +++ /dev/null @@ -1 +0,0 @@ -while(true); diff --git a/dylan-testing/core/proxy-service.js b/dylan-testing/core/proxy-service.js deleted file mode 100644 index 664ade7..0000000 --- a/dylan-testing/core/proxy-service.js +++ /dev/null @@ -1 +0,0 @@ -while(true); diff --git a/dylan-testing/core/web-service.js b/dylan-testing/core/web-service.js new file mode 100644 index 0000000..cef2e70 --- /dev/null +++ b/dylan-testing/core/web-service.js @@ -0,0 +1,25 @@ +var express = require("express"); +var http = require("http"); +var fs = require("fs"); +var config = require("common/config.js"); +var socketProxy = require("common/socket-proxy.js"); + +var config; +var app; + +function main(){ + // Set up express app and routes + app = express(); + app.use("/login", socketProxy(config.varPath + "/login-service.sock")); + + // Start web server or crash + console.info( + "Starting web-service on host \"" + config.host + + "\" and port " + config.port + ); + try {app.listen(config.port, config.host);} + catch(e){ + throw new Error("Failed to start web-service: " + e.message); + } +} +main(); diff --git a/dylan-testing/package.json b/dylan-testing/package.json index 94c7b19..74539c6 100644 --- a/dylan-testing/package.json +++ b/dylan-testing/package.json @@ -11,5 +11,8 @@ "url": "https://gitlab.ucc.asn.au/frekk/uccportal.git" }, "author": "dylanh333; frekk", - "license": "GPL-3.0" + "license": "GPL-3.0", + "dependencies": { + "express": "^4.16.3" + } } -- GitLab