Allow websocket server to take ssh host as query parameter. This allows sockets to be opened without loading the client page first to set up the session. This simplifies use when using WebSSH2 with an alternative client.

Cleaned up the derivation of configuration from defaults, static config file, request params & session
This commit is contained in:
Nils Lundquist 2020-01-13 12:47:24 -07:00
parent 2289036605
commit f1e5d4a13c
3 changed files with 248 additions and 246 deletions

View file

@ -3,105 +3,18 @@
// app.js // app.js
var path = require('path') var path = require('path')
var fs = require('fs')
var nodeRoot = path.dirname(require.main.filename) var nodeRoot = path.dirname(require.main.filename)
var configPath = path.join(nodeRoot, 'config.json')
var publicPath = path.join(nodeRoot, 'client', 'public') var publicPath = path.join(nodeRoot, 'client', 'public')
console.log('WebSSH2 service reading config from: ' + configPath) var config = require('./config')
var express = require('express') var express = require('express')
var logger = require('morgan') var logger = require('morgan')
var app = express()
// sane defaults if config.json or parts are missing var compression = require('compression')
let config = { var server = require('http').Server(app)
listen: { var myutil = require('./util')
ip: '0.0.0.0', var io = require('socket.io')(server, { serveClient: false })
port: 2222 var socket = require('./socket')
}, var expressOptions = require('./expressOptions')
user: {
name: null,
password: null,
privatekey: null
},
ssh: {
host: null,
port: 22,
term: 'xterm-color',
readyTimeout: 20000,
keepaliveInterval: 120000,
keepaliveCountMax: 10,
allowedSubnets: []
},
terminal: {
cursorBlink: true,
scrollback: 10000,
tabStopWidth: 8,
bellStyle: 'sound'
},
header: {
text: null,
background: 'green'
},
session: {
name: 'WebSSH2',
secret: 'mysecret'
},
options: {
challengeButton: true,
allowreauth: true
},
algorithms: {
kex: [
'ecdh-sha2-nistp256',
'ecdh-sha2-nistp384',
'ecdh-sha2-nistp521',
'diffie-hellman-group-exchange-sha256',
'diffie-hellman-group14-sha1'
],
cipher: [
'aes128-ctr',
'aes192-ctr',
'aes256-ctr',
'aes128-gcm',
'aes128-gcm@openssh.com',
'aes256-gcm',
'aes256-gcm@openssh.com',
'aes256-cbc'
],
hmac: [
'hmac-sha2-256',
'hmac-sha2-512',
'hmac-sha1'
],
compress: [
'none',
'zlib@openssh.com',
'zlib'
]
},
serverlog: {
client: false,
server: false
},
accesslog: false,
verify: false
}
// test if config.json exists, if not provide error message but try to run
// anyway
try {
if (fs.existsSync(configPath)) {
console.log('ephemeral_auth service reading config from: ' + configPath)
config = require('read-config')(configPath)
} else {
console.error('\n\nERROR: Missing config.json for webssh. Current config: ' + JSON.stringify(config))
console.error('\n See config.json.sample for details\n\n')
}
} catch (err) {
console.error('\n\nERROR: Missing config.json for webssh. Current config: ' + JSON.stringify(config))
console.error('\n See config.json.sample for details\n\n')
console.error('ERROR:\n\n ' + err)
}
var session = require('express-session')({ var session = require('express-session')({
secret: config.session.secret, secret: config.session.secret,
name: config.session.name, name: config.session.name,
@ -109,15 +22,6 @@ var session = require('express-session')({
saveUninitialized: false, saveUninitialized: false,
unset: 'destroy' unset: 'destroy'
}) })
var app = express()
var compression = require('compression')
var server = require('http').Server(app)
var myutil = require('./util')
myutil.setDefaultCredentials(config.user.name, config.user.password, config.user.privatekey);
var validator = require('validator')
var io = require('socket.io')(server, { serveClient: false })
var socket = require('./socket')
var expressOptions = require('./expressOptions')
// express // express
app.use(compression({ level: 9 })) app.use(compression({ level: 9 }))
@ -137,44 +41,6 @@ app.get('/reauth', function (req, res, next) {
// eslint-disable-next-line complexity // eslint-disable-next-line complexity
app.get('/ssh/host/:host?', function (req, res, next) { app.get('/ssh/host/:host?', function (req, res, next) {
res.sendFile(path.join(path.join(publicPath, 'client.htm'))) res.sendFile(path.join(path.join(publicPath, 'client.htm')))
// capture, assign, and validated variables
req.session.ssh = {
host: (validator.isIP(req.params.host + '') && req.params.host) ||
(validator.isFQDN(req.params.host) && req.params.host) ||
(/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.params.host) &&
req.params.host) || config.ssh.host,
port: (validator.isInt(req.query.port + '', { min: 1, max: 65535 }) &&
req.query.port) || config.ssh.port,
localAddress: config.ssh.localAddress,
localPort: config.ssh.localPort,
header: {
name: req.query.header || config.header.text,
background: req.query.headerBackground || config.header.background
},
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: {
cursorBlink: (validator.isBoolean(req.query.cursorBlink + '') ? myutil.parseBool(req.query.cursorBlink) : config.terminal.cursorBlink),
scrollback: (validator.isInt(req.query.scrollback + '', { min: 1, max: 200000 }) && req.query.scrollback) ? req.query.scrollback : config.terminal.scrollback,
tabStopWidth: (validator.isInt(req.query.tabStopWidth + '', { min: 1, max: 100 }) && req.query.tabStopWidth) ? req.query.tabStopWidth : config.terminal.tabStopWidth,
bellStyle: ((req.query.bellStyle) && (['sound', 'none'].indexOf(req.query.bellStyle) > -1)) ? req.query.bellStyle : config.terminal.bellStyle
},
allowreplay: config.options.challengeButton || (validator.isBoolean(req.headers.allowreplay + '') ? myutil.parseBool(req.headers.allowreplay) : false),
allowreauth: config.options.allowreauth || false,
mrhsession: ((validator.isAlphanumeric(req.headers.mrhsession + '') && req.headers.mrhsession) ? req.headers.mrhsession : 'none'),
serverlog: {
client: config.serverlog.client || false,
server: config.serverlog.server || false
},
readyTimeout: (validator.isInt(req.query.readyTimeout + '', { min: 1, max: 300000 }) &&
req.query.readyTimeout) || config.ssh.readyTimeout
}
if (req.session.ssh.header.name) validator.escape(req.session.ssh.header.name)
if (req.session.ssh.header.background) validator.escape(req.session.ssh.header.background)
}) })
// express error handling // express error handling

View file

@ -4,32 +4,200 @@
// socket.js // socket.js
// private // private
var config = require('./config')
var validator = require('validator')
var debug = require('debug') var debug = require('debug')
var myutil = require('./util')
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 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
var menuData = '<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>' + var menuData = '<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>' +
'<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>' '<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>'
// return a subset of keys from an object if they exist
function pick (obj, keys) {
return keys.reduce((acc, key) => {
if (obj[key]) {
acc[key] = obj[key]
}
return acc
}, {})
}
const baseSocketConfig = {
host: config.ssh.host,
port: config.ssh.port,
localAddress: config.ssh.localAddress,
localPort: config.ssh.localPort,
term: config.ssh.term,
readyTimeout: config.ssh.readyTimeout,
algorithms: config.algorithms,
keepaliveInterval: config.ssh.keepaliveInterval,
keepaliveCountMax: config.ssh.keepaliveCountMax,
allowedSubnets: config.ssh.allowedSubnets || [],
header: {
name: config.header.text,
background: config.header.background
},
terminal: {
cursorBlink: config.terminal.cursorBlink,
scrollBack: config.terminal.scrollback,
tabStopWidth: config.terminal.tabStopWidth,
bellStyle: config.terminal.bellStyle
},
serverlog: {
client: config.serverlog.client || false,
server: config.serverlog.server || false
}
}
function getValidatedRequestConfig (queryParams) {
const processedParams = {}
const validators = {
host: (host) => validator.isIP(host + '') || validator.isFQDN(host) || /^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(host),
port: (port) => validator.isInt(port + '', { min: 1, max: 65535 }),
sshterm: (sshterm) => /^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(sshterm),
cursorBlink: (cursorBlink) => validator.isBoolean(cursorBlink + ''),
scrollback: (scrollback) => validator.isInt(scrollback + '', { min: 1, max: 200000 }),
tabStopWidth: (tabStopWidth) => validator.isInt(tabStopWidth + '', { min: 1, max: 100 }),
bellStyle: (bellStyle) => (['sound', 'none'].indexOf(bellStyle) > -1),
readyTimeout: (readyTimeout) => validator.isInt(readyTimeout + '', { min: 1, max: 300000 }),
header: () => true,
headerBackground: () => true
}
const transformations = {
cursorBlink: (cursorBlink) => myutil.parseBool(cursorBlink)
}
const rename = {
sshterm: 'term'
}
// validate & transform and rename query parameters
for (const key in queryParams) {
const value = queryParams[key]
const validator = validators[key] || (() => false)
const transformation = transformations[key] || ((i) => i)
const newName = rename[key] || key
if (value !== undefined && validator(value)) {
processedParams[newName] = transformation(value)
}
}
// todo: address all this!!
// const allowreplay = config.options.challengeButton || (validator.isBoolean(req.headers.allowreplay + '') ? myutil.parseBool(req.headers.allowreplay) : false)
// const allowreauth = config.options.allowreauth || false
// const mrhsession = ((validator.isAlphanumeric(req.headers.mrhsession + '') && req.headers.mrhsession) ? req.headers.mrhsession : 'none')
// if (req.session.ssh.header.name) validator.escape(req.session.ssh.header.name)
// if (req.session.ssh.header.background) validator.escape(req.session.ssh.header.background)
// todo: do this when creating base config?
// if (socketConfig.header.name) {
// validator.escape(socketConfig.header.name)
// }
// if (socketConfig.header.background) {
// validator.escape(socketConfig.header.background)
// }
// create config object from query parameters
const config = pick(processedParams, ['host', 'port', 'readyTimeout', 'term'])
config.terminal = pick(processedParams, ['cursorBlink', 'scrollback', 'tabStopWidth', 'bellStyle'])
config.header = pick(processedParams, ['header', 'headerBackground'])
return config
}
function getCredentials (session) {
if (session.username && session.userpassword) {
return {
username: session.username,
userpassword: session.userpassword
}
} else {
return myutil.defaultCredentials
}
}
/**
* Error handling for various events. Outputs error to client, logs to
* server, destroys session and disconnects socket.
* @param {string} callerName Function calling this function
* @param {object} err Error object or error message
* @param {object} context Additional information about the state when the error occurred
* @param {object} context.socket The socket.io socket object at the time of failure
* @param {object} context.socketConfig The config object based on the base config and the request query parameters
* @param {object} context.credentials The credentials used during the connection that failed
*/
// eslint-disable-next-line complexity
function SSHError (callerName, err, { socket, credentials, socketConfig }) {
var theError
const session = socket.request.session
if (session) {
// we just want the first error of the session to pass to the client
session.error = session.error || ((err) ? err.message : undefined)
theError = session.error ? ': ' + session.error : ''
// log unsuccessful login attempt
if (err && (err.level === 'client-authentication')) {
console.log('WebSSH2 ' + 'error: Authentication failure'.red.bold +
' user=' + credentials.username.yellow.bold.underline +
' from=' + socket.handshake.address.yellow.bold.underline)
socket.emit('allowreauth', socketConfig.allowreauth)
socket.emit('reauth')
} else {
console.log('WebSSH2 Logout: user=' + credentials.username +
' from=' + socket.handshake.address +
' host=' + socketConfig.host +
' port=' + socketConfig.port +
' sessionID=' + socket.request.sessionID + '/' + socket.id +
' allowreplay=' + socketConfig.allowreplay +
' term=' + socketConfig.term
)
if (err) {
theError = err ? ': ' + err.message : ''
console.log('WebSSH2 error' + theError)
}
}
socket.emit('ssherror', 'SSH ' + callerName + theError)
session.destroy()
} else {
theError = (err) ? ': ' + err.message : ''
}
socket.disconnect(true)
debugWebSSH2('SSHError ' + callerName + theError)
}
// public // public
module.exports = function socket (socket) { module.exports = function socket (socket) {
// if websocket connection arrives without an express session, kill it // create new config by merging config object from disk with config object from the request
if (!socket.request.session) { const socketConfig = Object.assign({}, baseSocketConfig, getValidatedRequestConfig(socket.handshake.query))
socket.emit('401 UNAUTHORIZED') const credentials = getCredentials(socket.request.session)
debugWebSSH2('SOCKET: No Express Session / REJECTED') const hasCredentials = credentials.username && (credentials.userpassword || credentials.privatekey)
const errorContext = { socket, credentials, socketConfig };
if (!(hasCredentials && socketConfig)) {
debugWebSSH2('Attempt to connect without session.username/password or session varialbles defined, ' +
'potentially previously abandoned client session. disconnecting websocket client.\r\n' +
'Handshake information: \r\n ' + JSON.stringify(socket.handshake))
socket.emit('ssherror', 'WEBSOCKET ERROR - Refresh the browser and try again')
socket.request.session.destroy()
socket.disconnect(true) socket.disconnect(true)
return return
} }
// If configured, check that requsted host is in a permitted subnet // 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 ) ) { if (socketConfig.allowedSubnets.length > 0) {
var matcher = new CIDRMatcher(socket.request.session.ssh.allowedSubnets); const matcher = new CIDRMatcher(socketConfig.allowedSubnets)
if (!matcher.contains(socket.request.session.ssh.host)) { if (!matcher.contains(socketConfig.host)) {
console.log('WebSSH2 ' + 'error: Requested host outside configured subnets / REJECTED'.red.bold + console.log('WebSSH2 ' + 'error: Requested host outside configured subnets / REJECTED'.red.bold +
' user=' + socket.request.session.username.yellow.bold.underline + ' user=' + credentials.username.yellow.bold.underline +
' from=' + socket.handshake.address.yellow.bold.underline) ' from=' + socket.handshake.address.yellow.bold.underline)
socket.emit('ssherror', '401 UNAUTHORIZED') socket.emit('ssherror', '401 UNAUTHORIZED')
socket.disconnect(true) socket.disconnect(true)
@ -37,11 +205,13 @@ module.exports = function socket (socket) {
} }
} }
var conn = new SSH() const conn = new SSH()
socket.on('geometry', function socketOnGeometry (cols, rows) { socket.on('geometry', function socketOnGeometry (cols, rows) {
termCols = cols termCols = cols
termRows = rows termRows = rows
}) })
conn.on('banner', function connOnBanner (data) { conn.on('banner', function connOnBanner (data) {
// need to convert to cr/lf for proper formatting // need to convert to cr/lf for proper formatting
data = data.replace(/\r?\n/g, '\r\n') data = data.replace(/\r?\n/g, '\r\n')
@ -49,35 +219,45 @@ module.exports = function socket (socket) {
}) })
conn.on('ready', function connOnReady () { conn.on('ready', function connOnReady () {
console.log('WebSSH2 Login: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' mrhsession=' + socket.request.session.ssh.mrhsession + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term) console.log('WebSSH2 Login: user=' + credentials.username +
' from=' + socket.handshake.address +
' host=' + socketConfig.host +
' port=' + socketConfig.port +
' sessionID=' + socket.request.sessionID + '/' + socket.id +
' mrhsession=' + socketConfig.mrhsession +
' allowreplay=' + socketConfig.allowreplay +
' term=' + socketConfig.term
)
socket.emit('menu', menuData) socket.emit('menu', menuData)
socket.emit('allowreauth', socket.request.session.ssh.allowreauth) socket.emit('allowreauth', socketConfig.allowreauth)
socket.emit('setTerminalOpts', socket.request.session.ssh.terminal) socket.emit('setTerminalOpts', socketConfig.terminal)
socket.emit('title', 'ssh://' + socket.request.session.ssh.host) socket.emit('title', 'ssh://' + socketConfig.host)
if (socket.request.session.ssh.header.background) socket.emit('headerBackground', socket.request.session.ssh.header.background) if (socketConfig.header.background) socket.emit('headerBackground', socketConfig.header.background)
if (socket.request.session.ssh.header.name) socket.emit('header', socket.request.session.ssh.header.name) if (socketConfig.header.name) socket.emit('header', socketConfig.header.name)
socket.emit('footer', 'ssh://' + socket.request.session.username + '@' + socket.request.session.ssh.host + ':' + socket.request.session.ssh.port) socket.emit('footer', 'ssh://' + credentials.username + '@' + socketConfig.host + ':' + socketConfig.port)
socket.emit('status', 'SSH CONNECTION ESTABLISHED') socket.emit('status', 'SSH CONNECTION ESTABLISHED')
socket.emit('statusBackground', 'green') socket.emit('statusBackground', 'green')
socket.emit('allowreplay', socket.request.session.ssh.allowreplay) socket.emit('allowreplay', socketConfig.allowreplay)
conn.shell({ conn.shell({
term: socket.request.session.ssh.term, term: socketConfig.term,
cols: termCols, cols: termCols,
rows: termRows rows: termRows
}, function connShell (err, stream) { }, function connShell (err, stream) {
if (err) { if (err) {
SSHerror('EXEC ERROR' + err) SSHError('EXEC ERROR', err, errorContext)
conn.end() conn.end()
return return
} }
// poc to log commands from client // poc to log commands from client
if (socket.request.session.ssh.serverlog.client) var dataBuffer if (socketConfig.serverlog.client) var dataBuffer
socket.on('data', function socketOnData (data) { socket.on('data', function socketOnData (data) {
stream.write(data) stream.write(data)
// poc to log commands from client // poc to log commands from client
if (socket.request.session.ssh.serverlog.client) { if (socketConfig.serverlog.client) {
if (data === '\r') { if (data === '\r') {
console.log('serverlog.client: ' + socket.request.session.id + '/' + socket.id + ' host: ' + socket.request.session.ssh.host + ' command: ' + dataBuffer) console.log('serverlog.client: ' + socket.request.session.id + '/' + socket.id + ' host: ' + socketConfig.host + ' command: ' + dataBuffer)
dataBuffer = undefined dataBuffer = undefined
} else { } else {
dataBuffer = (dataBuffer) ? dataBuffer + data : data dataBuffer = (dataBuffer) ? dataBuffer + data : data
@ -87,8 +267,8 @@ module.exports = function socket (socket) {
socket.on('control', function socketOnControl (controlData) { socket.on('control', function socketOnControl (controlData) {
switch (controlData) { switch (controlData) {
case 'replayCredentials': case 'replayCredentials':
if (socket.request.session.ssh.allowreplay) { if (socketConfig.allowreplay) {
stream.write(socket.request.session.userpassword + '\n') stream.write(credentials.userpassword + '\n')
} }
/* falls through */ /* falls through */
default: default:
@ -102,19 +282,19 @@ module.exports = function socket (socket) {
socket.on('disconnect', function socketOnDisconnect (reason) { socket.on('disconnect', function socketOnDisconnect (reason) {
debugWebSSH2('SOCKET DISCONNECT: ' + reason) debugWebSSH2('SOCKET DISCONNECT: ' + reason)
err = { message: reason } err = { message: reason }
SSHerror('CLIENT SOCKET DISCONNECT', err) SSHError('CLIENT SOCKET DISCONNECT', err, errorContext)
conn.end() conn.end()
// socket.request.session.destroy() // socket.request.session.destroy()
}) })
socket.on('error', function socketOnError (err) { socket.on('error', function socketOnError (err) {
SSHerror('SOCKET ERROR', err) SSHError('SOCKET ERROR', err, errorContext)
conn.end() conn.end()
}) })
stream.on('data', function streamOnData (data) { socket.emit('data', data.toString('utf-8')) }) stream.on('data', function streamOnData (data) { socket.emit('data', data.toString('utf-8')) })
stream.on('close', function streamOnClose (code, signal) { stream.on('close', function streamOnClose (code, signal) {
err = { message: ((code || signal) ? (((code) ? 'CODE: ' + code : '') + ((code && signal) ? ' ' : '') + ((signal) ? 'SIGNAL: ' + signal : '')) : undefined) } err = { message: ((code || signal) ? (((code) ? 'CODE: ' + code : '') + ((code && signal) ? ' ' : '') + ((signal) ? 'SIGNAL: ' + signal : '')) : undefined) }
SSHerror('STREAM CLOSE', err) SSHError('STREAM CLOSE', err, errorContext)
conn.end() conn.end()
}) })
stream.stderr.on('data', function streamStderrOnData (data) { stream.stderr.on('data', function streamStderrOnData (data) {
@ -123,71 +303,28 @@ module.exports = function socket (socket) {
}) })
}) })
conn.on('end', function connOnEnd (err) { SSHerror('CONN END BY HOST', err) }) conn.on('end', function connOnEnd (err) { SSHError('CONN END BY HOST', err, errorContext) })
conn.on('close', function connOnClose (err) { SSHerror('CONN CLOSE', err) }) conn.on('close', function connOnClose (err) { SSHError('CONN CLOSE', err, errorContext) })
conn.on('error', function connOnError (err) { SSHerror('CONN ERROR', err) }) conn.on('error', function connOnError (err) { SSHError('CONN ERROR', err, errorContext) })
conn.on('keyboard-interactive', function connOnKeyboardInteractive (name, instructions, instructionsLang, prompts, finish) { conn.on('keyboard-interactive', function connOnKeyboardInteractive (name, instructions, instructionsLang, prompts, finish) {
debugWebSSH2('conn.on(\'keyboard-interactive\')') debugWebSSH2('conn.on(\'keyboard-interactive\')')
finish([socket.request.session.userpassword]) finish([credentials.userpassword])
}) })
if (socket.request.session.username && (socket.request.session.userpassword || socket.request.session.privatekey) && socket.request.session.ssh) {
// console.log('hostkeys: ' + hostkeys[0].[0]) // console.log('hostkeys: ' + hostkeys[0].[0])
conn.connect({ conn.connect({
host: socket.request.session.ssh.host, host: socketConfig.host,
port: socket.request.session.ssh.port, port: socketConfig.port,
localAddress: socket.request.session.ssh.localAddress, localAddress: socketConfig.localAddress,
localPort: socket.request.session.ssh.localPort, localPort: socketConfig.localPort,
username: socket.request.session.username, username: credentials.username,
password: socket.request.session.userpassword, password: credentials.userpassword,
privateKey: socket.request.session.privatekey, privateKey: credentials.privatekey,
tryKeyboard: true, tryKeyboard: true,
algorithms: socket.request.session.ssh.algorithms, algorithms: socketConfig.algorithms,
readyTimeout: socket.request.session.ssh.readyTimeout, readyTimeout: socketConfig.readyTimeout,
keepaliveInterval: socket.request.session.ssh.keepaliveInterval, keepaliveInterval: socketConfig.keepaliveInterval,
keepaliveCountMax: socket.request.session.ssh.keepaliveCountMax, keepaliveCountMax: socketConfig.keepaliveCountMax,
debug: debug('ssh2') debug: debug('ssh2')
}) })
} else {
debugWebSSH2('Attempt to connect without session.username/password or session varialbles defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ' + JSON.stringify(socket.handshake))
socket.emit('ssherror', 'WEBSOCKET ERROR - Refresh the browser and try again')
socket.request.session.destroy()
socket.disconnect(true)
}
/**
* Error handling for various events. Outputs error to client, logs to
* server, destroys session and disconnects socket.
* @param {string} myFunc Function calling this function
* @param {object} err error object or error message
*/
// eslint-disable-next-line complexity
function SSHerror (myFunc, err) {
var theError
if (socket.request.session) {
// we just want the first error of the session to pass to the client
socket.request.session.error = (socket.request.session.error) || ((err) ? err.message : undefined)
theError = (socket.request.session.error) ? ': ' + socket.request.session.error : ''
// log unsuccessful login attempt
if (err && (err.level === 'client-authentication')) {
console.log('WebSSH2 ' + 'error: Authentication failure'.red.bold +
' user=' + socket.request.session.username.yellow.bold.underline +
' from=' + socket.handshake.address.yellow.bold.underline)
socket.emit('allowreauth', socket.request.session.ssh.allowreauth)
socket.emit('reauth')
} else {
console.log('WebSSH2 Logout: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
if (err) {
theError = (err) ? ': ' + err.message : ''
console.log('WebSSH2 error' + theError)
}
}
socket.emit('ssherror', 'SSH ' + myFunc + theError)
socket.request.session.destroy()
socket.disconnect(true)
} else {
theError = (err) ? ': ' + err.message : ''
socket.disconnect(true)
}
debugWebSSH2('SSHerror ' + myFunc + theError)
}
} }

View file

@ -4,37 +4,36 @@
// private // private
require('colors') // allow for color property extensions in log messages require('colors') // allow for color property extensions in log messages
var config = require('./config')
var debug = require('debug')('WebSSH2') var debug = require('debug')('WebSSH2')
var Auth = require('basic-auth') var Auth = require('basic-auth')
let defaultCredentials = {username: null, password: null, privatekey: null}; exports.defaultCredentials = {
username: config.user.name,
exports.setDefaultCredentials = function (username, password, privatekey) { userpassword: config.user.password,
defaultCredentials.username = username privatekey: config.user.privatekey,
defaultCredentials.password = password
defaultCredentials.privatekey = privatekey
} }
exports.basicAuth = function basicAuth (req, res, next) { exports.basicAuth = function basicAuth (req, res, next) {
var myAuth = Auth(req) var myAuth = Auth(req)
let password = exports.defaultCredentials.userpassword
if (myAuth && myAuth.pass !== '') { if (myAuth && myAuth.pass !== '') {
req.session.username = myAuth.name req.session.username = myAuth.name
req.session.userpassword = myAuth.pass req.session.userpassword = password = myAuth.pass
debug('myAuth.name: ' + myAuth.name.yellow.bold.underline + debug('myAuth.name: ' + myAuth.name.yellow.bold.underline +
' and password ' + ((myAuth.pass) ? 'exists'.yellow.bold.underline ' and password ' + ((myAuth.pass) ? 'exists'.yellow.bold.underline
: 'is blank'.underline.red.bold)) : 'is blank'.underline.red.bold))
} else {
req.session.username = defaultCredentials.username;
req.session.userpassword = defaultCredentials.password;
req.session.privatekey = defaultCredentials.privatekey;
} }
if ( (!req.session.userpassword) && (!req.session.privatekey) ) {
if (!(password || exports.defaultCredentials.privatekey)) {
res.statusCode = 401 res.statusCode = 401
debug('basicAuth credential request (401)') debug('basicAuth credential request (401)')
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"') res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"')
res.end('Username and password required for web SSH service.') res.end('Username and password required for web SSH service.')
return return
} }
next() next()
} }