diff --git a/.gitignore b/.gitignore index 959fcc6a6577499d9714f92bd17e6597496e3866..de70e3317e0561b13726a9388ac518b9ad7dc711 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 0000000000000000000000000000000000000000..f17ca7247f359214d82ce3ffbe57a2fdfcf159c2 --- /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 0000000000000000000000000000000000000000..87d9c75b1078419f0ebda88c724555fbc0c98d09 --- /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 0000000000000000000000000000000000000000..bb91afa31a7b1eab0c350463d44cbf1277f9d352 --- /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 f30002be68497d2e94e014fceb4e354c4a5766f7..f9a446381879969343bd2fd0ce5420b540fd8959 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 314219abc47dfd40e9682f110cea05cb85c47c6b..cfe5b85fc058d34abb1317ab7282a8d81d85c471 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 664ade79430d72f39f58816a0d3eb18f1b02979d..fcbc2410a70af0686e7b840e30447392357c6e71 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 664ade79430d72f39f58816a0d3eb18f1b02979d..0000000000000000000000000000000000000000 --- 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 664ade79430d72f39f58816a0d3eb18f1b02979d..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..cef2e70bf379de11e870921ca6f5e6b1e272baff --- /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 94c7b195d3068badbe5b289991d577c4537ae6c3..74539c6bfd36d0612abeb968b236bada8e4b0bcb 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" + } }