From 16a27ce62aa4c495d9fdb766e77d25932929991e Mon Sep 17 00:00:00 2001 From: Bill Church Date: Sat, 23 Nov 2019 08:45:59 -0500 Subject: [PATCH 1/4] Pr/163 (#164) * Add configuration option to restrict connections to specified subnets Signed-off-by: Matt Oswalt * Remove accidentally included message Signed-off-by: Matt Oswalt * Move to cidr-matcher Signed-off-by: Matt Oswalt * feat: Add configuration option to restrict connections to specified subnets --- README.md | 2 ++ app/config.json.sample | 3 ++- app/package-lock.json | 53 ++++++++++++++++++++++++++++++++++++++++++ app/package.json | 3 ++- app/server/app.js | 4 +++- app/server/socket.js | 14 +++++++++++ 6 files changed, 76 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-lock.json b/app/package-lock.json index b159ce1..a5bc7f6 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -586,6 +586,11 @@ } } }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -1080,6 +1085,14 @@ "tslib": "^1.9.0" } }, + "cidr-matcher": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cidr-matcher/-/cidr-matcher-2.1.1.tgz", + "integrity": "sha512-QPJRz4HDQxpB8AZWEqd6ejVp+siArXh3u1MYaUFV85cd293StGSMb87jVe0z9gS92KsFwxCxjb3utO3e5HKHTw==", + "requires": { + "ip6addr": "^0.2.2" + } + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -3136,6 +3149,11 @@ } } }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, "fancy-log": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", @@ -4737,6 +4755,15 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, + "ip6addr": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/ip6addr/-/ip6addr-0.2.3.tgz", + "integrity": "sha512-qA9DXRAUW+lT47/i/4+Q3GHPwZjGt/atby1FH/THN6GVATA6+Pjp2nztH7k6iKeil7hzYnBwfSsxjthlJ8lJKw==", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.4.0" + } + }, "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", @@ -5039,6 +5066,11 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5067,6 +5099,17 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "jsx-ast-utils": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", @@ -8451,6 +8494,16 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "vinyl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", diff --git a/app/package.json b/app/package.json index f152c3e..82545ab 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", + "cidr-matcher": "2.1.1" }, "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..31e98d7 100644 --- a/app/server/socket.js +++ b/app/server/socket.js @@ -1,3 +1,4 @@ +/* eslint-disable complexity */ 'use strict' /* jshint esversion: 6, asi: true, node: true */ // socket.js @@ -6,6 +7,7 @@ var debug = require('debug') var debugWebSSH2 = require('debug')('WebSSH2') var SSH = require('ssh2').Client +var CIDRMatcher = require('cidr-matcher'); // var fs = require('fs') // var hostkeys = JSON.parse(fs.readFileSync('./hostkeyhashes.json', 'utf8')) var termCols, termRows @@ -21,6 +23,18 @@ 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 && ( socket.request.session.ssh.allowedSubnets.length > 0 ) ) { + var matcher = new CIDRMatcher(socket.request.session.ssh.allowedSubnets); + if (!matcher.contains(socket.request.session.ssh.host)) { + socket.emit('401 UNAUTHORIZED') + 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 From e796f9fb5874d6557433f25e8976b7aa58fa8144 Mon Sep 17 00:00:00 2001 From: Bill Church Date: Sat, 23 Nov 2019 09:00:28 -0500 Subject: [PATCH 2/4] fix: subnet unauthorized now emits "ssherror" which persists across websocket termination --- ChangeLog.md | 4 ++++ app/package.json | 2 +- app/server/socket.js | 6 ++++-- scripts/ver.sh | 12 ++++++------ 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index c2ebc13..778172c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,8 @@ # Change Log +### 0.3.0 [TBD] +### Added +- Add configuration option to restrict connections to specified subnets thanks to @Mierdin + ### 0.2.9 [2019-06-13] ### Changes - Missing require('fs') in `server/app.js` See issue [#135](../../issues/135) diff --git a/app/package.json b/app/package.json index 82545ab..d818522 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "webssh2", - "version": "0.2.10-0", + "version": "0.2.10-1", "ignore": [ ".gitignore" ], diff --git a/app/server/socket.js b/app/server/socket.js index 31e98d7..da880f8 100644 --- a/app/server/socket.js +++ b/app/server/socket.js @@ -28,8 +28,10 @@ module.exports = function socket (socket) { if ( (((socket.request.session || {}).ssh || {}).allowedSubnets || {}).length && ( socket.request.session.ssh.allowedSubnets.length > 0 ) ) { var matcher = new CIDRMatcher(socket.request.session.ssh.allowedSubnets); if (!matcher.contains(socket.request.session.ssh.host)) { - socket.emit('401 UNAUTHORIZED') - debugWebSSH2('SOCKET: Requested host outside configured subnets / REJECTED') + console.log('WebSSH2 ' + 'error: Requested host outside configured subnets / REJECTED'.red.bold + + ' user=' + socket.request.session.username.yellow.bold.underline + + ' from=' + socket.handshake.address.yellow.bold.underline) + socket.emit('ssherror', '401 UNAUTHORIZED') socket.disconnect(true) return } diff --git a/scripts/ver.sh b/scripts/ver.sh index 72101c3..9602899 100755 --- a/scripts/ver.sh +++ b/scripts/ver.sh @@ -7,10 +7,10 @@ source ./scripts/util.sh echo # get current version of workspace, ask to change or rebuild -webssh_ilx_ver=$(jq -r ".version" ./workspace/extensions/webssh2/package.json 2>&1) -if [[ $? -ne 0 ]]; then exit; echo "error reading ILX irule version";fi +webssh_ver=$(jq -r ".version" ./app/package.json 2>&1) +if [[ $? -ne 0 ]]; then exit; echo "error reading package version";fi -echo "Current version of $webssh_workspace_name is: $webssh_ilx_ver" +echo "Current version of package is: $webssh_ver" echo -n "If you want to change this version, enter it now otherwise press enter to retain: " @@ -19,11 +19,11 @@ read newver echo if [[ ("$newver" != "") ]]; then - echo "Updating version of ILX to: $newver" + echo "Updating version of package to: $newver" export newver - jq --arg newver "$newver" '.version = $newver' < ./workspace/extensions/webssh2/package.json > ./workspace/extensions/webssh2/package.json.new + jq --arg newver "$newver" '.version = $newver' < ./app/package.json > ./app/package.json.new if [[ $? -ne 0 ]]; then exit; echo "error changing version - ilx";fi - mv ./workspace/extensions/webssh2/package.json.new ./workspace/extensions/webssh2/package.json + mv ./app/package.json.new ./app/package.json else echo "No changes made" fi From d79e050b875e20fcf9fef9abe930f32c5b1bd053 Mon Sep 17 00:00:00 2001 From: Bill Church Date: Sat, 23 Nov 2019 09:03:01 -0500 Subject: [PATCH 3/4] Update ChangeLog.md --- ChangeLog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 778172c..5e9fb58 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,5 @@ # Change Log -### 0.3.0 [TBD] +### 0.2.10 [TBD] ### Added - Add configuration option to restrict connections to specified subnets thanks to @Mierdin From 2289036605d57f000585c2320790122218e15015 Mon Sep 17 00:00:00 2001 From: Bill Church Date: Sat, 23 Nov 2019 09:21:36 -0500 Subject: [PATCH 4/4] chore: update validator to 12.1 for better IPv6 support --- app/package-lock.json | 8 ++++---- app/package.json | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index a5bc7f6..c024204 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,6 +1,6 @@ { "name": "webssh2", - "version": "0.2.10-0", + "version": "0.2.10-1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -8485,9 +8485,9 @@ } }, "validator": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-12.0.0.tgz", - "integrity": "sha512-r5zA1cQBEOgYlesRmSEwc9LkbfNLTtji+vWyaHzRZUxCTHdsX3bd+sdHfs5tGZ2W6ILGGsxWxCNwT/h3IY/3ng==" + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-12.1.0.tgz", + "integrity": "sha512-gIC2RBuFRi574Rb9vewGCJ7TCLxHXNx6EKthEgs+Iz0pYa9a9Te1VLG/bGLsAyGWrqR5FfR7tbFUI7FEF2LiGA==" }, "vary": { "version": "1.1.2", diff --git a/app/package.json b/app/package.json index d818522..032725e 100644 --- a/app/package.json +++ b/app/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "basic-auth": "~2.0.1", + "cidr-matcher": "2.1.1", "colors": "~1.4.0", "compression": "~1.7.4", "debug": "^4.1.1", @@ -38,11 +39,10 @@ "socket.io": "2.2.0", "ssh2": "~0.8.6", "terser-webpack-plugin": "^2.2.1", - "validator": "~12.0.0", + "validator": "^12.1.0", "xterm-addon-fit": "^0.3.0", "xterm-addon-search": "^0.3.0", - "xterm-addon-web-links": "^0.2.1", - "cidr-matcher": "2.1.1" + "xterm-addon-web-links": "^0.2.1" }, "scripts": { "start": "node index.js",