From ccbe327cd634f092adac91aa6e511131decac165 Mon Sep 17 00:00:00 2001 From: Matt Oswalt Date: Fri, 22 Nov 2019 23:02:04 -0800 Subject: [PATCH] Add configuration option to restrict connections to specified subnets Signed-off-by: Matt Oswalt --- README.md | 2 ++ app/config.json.sample | 3 ++- app/package.json | 3 ++- app/server/app.js | 4 +++- app/server/socket.js | 21 +++++++++++++++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ff06c62..81b26f2 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,8 @@ docker run --name webssh2 -d -p 2222:2222 -v `pwd`/app/config.json:/usr/src/conf * **ssh.keepaliveCountMax** - _integer_ - How many consecutive, unanswered SSH-level keepalive packets that can be sent to the server before disconnection (similar to OpenSSH's ServerAliveCountMax config option). **Default:** 10. +* **allowedSubnets** - _array_ - A list of subnets that the server is allowed to connect to via SSH. An empty array means all subnets are permitted; no restriction. **Default:** empty array. + * **terminal.cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true. * **terminal.scrollback** - _integer_ - Lines in the scrollback buffer. **Default:** 10000. diff --git a/app/config.json.sample b/app/config.json.sample index fe7adb2..6e540d0 100644 --- a/app/config.json.sample +++ b/app/config.json.sample @@ -16,7 +16,8 @@ "term": "xterm-color", "readyTimeout": 20000, "keepaliveInterval": 120000, - "keepaliveCountMax": 10 + "keepaliveCountMax": 10, + "allowedSubnets": [] }, "terminal": { "cursorBlink": true, diff --git a/app/package.json b/app/package.json index f152c3e..7f84d34 100644 --- a/app/package.json +++ b/app/package.json @@ -41,7 +41,8 @@ "validator": "~12.0.0", "xterm-addon-fit": "^0.3.0", "xterm-addon-search": "^0.3.0", - "xterm-addon-web-links": "^0.2.1" + "xterm-addon-web-links": "^0.2.1", + "netmask": "1.0.6" }, "scripts": { "start": "node index.js", diff --git a/app/server/app.js b/app/server/app.js index d2ea822..5f0b490 100644 --- a/app/server/app.js +++ b/app/server/app.js @@ -28,7 +28,8 @@ let config = { term: 'xterm-color', readyTimeout: 20000, keepaliveInterval: 120000, - keepaliveCountMax: 10 + keepaliveCountMax: 10, + allowedSubnets: [] }, terminal: { cursorBlink: true, @@ -153,6 +154,7 @@ app.get('/ssh/host/:host?', function (req, res, next) { algorithms: config.algorithms, keepaliveInterval: config.ssh.keepaliveInterval, keepaliveCountMax: config.ssh.keepaliveCountMax, + allowedSubnets: config.ssh.allowedSubnets, term: (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) && req.query.sshterm) || config.ssh.term, terminal: { diff --git a/app/server/socket.js b/app/server/socket.js index 156f9e2..dccb1a6 100644 --- a/app/server/socket.js +++ b/app/server/socket.js @@ -6,6 +6,7 @@ var debug = require('debug') var debugWebSSH2 = require('debug')('WebSSH2') var SSH = require('ssh2').Client +var Netmask = require('netmask').Netmask // var fs = require('fs') // var hostkeys = JSON.parse(fs.readFileSync('./hostkeyhashes.json', 'utf8')) var termCols, termRows @@ -21,6 +22,26 @@ module.exports = function socket (socket) { socket.disconnect(true) return } + + // If configured, check that requsted host is in a permitted subnet + if (socket.request.session.ssh.allowedSubnets.length > 0) { + var permitted = false; + for (const subnet of socket.request.session.ssh.allowedSubnets) { + var subnetBlock = new Netmask(subnet); + if (subnetBlock.contains(socket.request.session.ssh.host)) { + permitted = true; + break; + } + } + if (!permitted) { + socket.emit('401 UNAUTHORIZED') + socket.emit('status', 'SSH CONNECTION ESTABLISHED') + debugWebSSH2('SOCKET: Requested host outside configured subnets / REJECTED') + socket.disconnect(true) + return + } + } + var conn = new SSH() socket.on('geometry', function socketOnGeometry (cols, rows) { termCols = cols