Pr/163 (#164)
* Add configuration option to restrict connections to specified subnets Signed-off-by: Matt Oswalt <matt@keepingitclassless.net> * Remove accidentally included message Signed-off-by: Matt Oswalt <matt@keepingitclassless.net> * Move to cidr-matcher Signed-off-by: Matt Oswalt <matt@keepingitclassless.net> * feat: Add configuration option to restrict connections to specified subnets
This commit is contained in:
parent
9a96637cb4
commit
16a27ce62a
6 changed files with 76 additions and 3 deletions
|
@ -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.
|
* **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.cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true.
|
||||||
|
|
||||||
* **terminal.scrollback** - _integer_ - Lines in the scrollback buffer. **Default:** 10000.
|
* **terminal.scrollback** - _integer_ - Lines in the scrollback buffer. **Default:** 10000.
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
"term": "xterm-color",
|
"term": "xterm-color",
|
||||||
"readyTimeout": 20000,
|
"readyTimeout": 20000,
|
||||||
"keepaliveInterval": 120000,
|
"keepaliveInterval": 120000,
|
||||||
"keepaliveCountMax": 10
|
"keepaliveCountMax": 10,
|
||||||
|
"allowedSubnets": []
|
||||||
},
|
},
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"cursorBlink": true,
|
"cursorBlink": true,
|
||||||
|
|
53
app/package-lock.json
generated
53
app/package-lock.json
generated
|
@ -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": {
|
"assign-symbols": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
|
||||||
|
@ -1080,6 +1085,14 @@
|
||||||
"tslib": "^1.9.0"
|
"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": {
|
"cipher-base": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
"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": {
|
"fancy-log": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
|
"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==",
|
"integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
|
||||||
"dev": true
|
"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": {
|
"ipaddr.js": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||||
|
@ -5039,6 +5066,11 @@
|
||||||
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
|
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
|
||||||
"dev": true
|
"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": {
|
"json-schema-traverse": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
@ -5067,6 +5099,17 @@
|
||||||
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
|
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
|
||||||
"dev": true
|
"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": {
|
"jsx-ast-utils": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
"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": {
|
"vinyl": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz",
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
"validator": "~12.0.0",
|
"validator": "~12.0.0",
|
||||||
"xterm-addon-fit": "^0.3.0",
|
"xterm-addon-fit": "^0.3.0",
|
||||||
"xterm-addon-search": "^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": {
|
"scripts": {
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
|
|
|
@ -28,7 +28,8 @@ let config = {
|
||||||
term: 'xterm-color',
|
term: 'xterm-color',
|
||||||
readyTimeout: 20000,
|
readyTimeout: 20000,
|
||||||
keepaliveInterval: 120000,
|
keepaliveInterval: 120000,
|
||||||
keepaliveCountMax: 10
|
keepaliveCountMax: 10,
|
||||||
|
allowedSubnets: []
|
||||||
},
|
},
|
||||||
terminal: {
|
terminal: {
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
|
@ -153,6 +154,7 @@ app.get('/ssh/host/:host?', function (req, res, next) {
|
||||||
algorithms: config.algorithms,
|
algorithms: config.algorithms,
|
||||||
keepaliveInterval: config.ssh.keepaliveInterval,
|
keepaliveInterval: config.ssh.keepaliveInterval,
|
||||||
keepaliveCountMax: config.ssh.keepaliveCountMax,
|
keepaliveCountMax: config.ssh.keepaliveCountMax,
|
||||||
|
allowedSubnets: config.ssh.allowedSubnets,
|
||||||
term: (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) &&
|
term: (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) &&
|
||||||
req.query.sshterm) || config.ssh.term,
|
req.query.sshterm) || config.ssh.term,
|
||||||
terminal: {
|
terminal: {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable complexity */
|
||||||
'use strict'
|
'use strict'
|
||||||
/* jshint esversion: 6, asi: true, node: true */
|
/* jshint esversion: 6, asi: true, node: true */
|
||||||
// socket.js
|
// socket.js
|
||||||
|
@ -6,6 +7,7 @@
|
||||||
var debug = require('debug')
|
var debug = require('debug')
|
||||||
var debugWebSSH2 = require('debug')('WebSSH2')
|
var debugWebSSH2 = require('debug')('WebSSH2')
|
||||||
var SSH = require('ssh2').Client
|
var SSH = require('ssh2').Client
|
||||||
|
var CIDRMatcher = require('cidr-matcher');
|
||||||
// var fs = require('fs')
|
// var fs = require('fs')
|
||||||
// var hostkeys = JSON.parse(fs.readFileSync('./hostkeyhashes.json', 'utf8'))
|
// var hostkeys = JSON.parse(fs.readFileSync('./hostkeyhashes.json', 'utf8'))
|
||||||
var termCols, termRows
|
var termCols, termRows
|
||||||
|
@ -21,6 +23,18 @@ module.exports = function socket (socket) {
|
||||||
socket.disconnect(true)
|
socket.disconnect(true)
|
||||||
return
|
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()
|
var conn = new SSH()
|
||||||
socket.on('geometry', function socketOnGeometry (cols, rows) {
|
socket.on('geometry', function socketOnGeometry (cols, rows) {
|
||||||
termCols = cols
|
termCols = cols
|
||||||
|
|
Loading…
Reference in a new issue