Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
}
],
"engines": {
"node": ">= 6"
"node": ">= 10"
},
"bugs": {
"url": "https://github.com/billchurch/WebSSH2/issues"
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/webpack.common.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
/* const CleanWebpackPlugin = require('clean-webpack-plugin') */
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
Expand Down
8 changes: 4 additions & 4 deletions app/scripts/webpack.prod.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const TerserPlugin = require('terser-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin')
const merge = require('webpack-merge')
const common = require('./webpack.common.js')

Expand All @@ -9,8 +9,8 @@ module.exports = merge(common, {
terserOptions: {
parallel: 4,
ie8: false,
safari10: false
safari10: false
}
})],
})]
}
})
})
232 changes: 82 additions & 150 deletions app/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,104 +2,28 @@
/* jshint esversion: 6, asi: true, node: true */
// app.js

var path = require('path')
var fs = require('fs')
var nodeRoot = path.dirname(require.main.filename)
var configPath = path.join(nodeRoot, 'config.json')
var publicPath = path.join(nodeRoot, 'client', 'public')
console.log('WebSSH2 service reading config from: ' + configPath)
var express = require('express')
var logger = require('morgan')

// sane defaults if config.json or parts are missing
let config = {
listen: {
ip: '0.0.0.0',
port: 2222
},
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
}
const compression = require('compression')
const config = require('./app_config')
const express = require('express')
const http = require('http')
const logger = require('morgan')
const path = require('path')
const socketIo = require('socket.io')
const bodyParser = require('body-parser')

// 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)
const expressOptions = require('./expressOptions')
const myutil = require('./util')
const socket = require('./socket')
const sshSessionConfig = require('./ssh_session_config')

// Credential Token Store
const DEFAULT_CC_TTL = 60 * 1000 * 5
const cachedCredentials = {}
function cachedCredentialSetTTL (key, ttl) {
setTimeout(() => {
console.log()
delete cachedCredentials[key]
}, ttl)
}

var session = require('express-session')({
Expand All @@ -109,72 +33,80 @@ var session = require('express-session')({
saveUninitialized: false,
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')

myutil.setDefaultCredentials(config.user.name, config.user.password, config.user.privatekey)

// express
const app = express()
const server = http.Server(app)
const io = socketIo(server, { serveClient: false })
app.use(bodyParser.json())
app.use(compression({ level: 9 }))
app.use(session)
app.use(myutil.basicAuth)
// app.use(myutil.basicAuth)
if (config.accesslog) app.use(logger('common'))
app.disable('x-powered-by')

// static files
const publicPath = path.join(path.dirname(require.main.filename), 'client', 'public')
app.use(express.static(publicPath, expressOptions))

app.get('/reauth', function (req, res, next) {
var r = req.headers.referer || '/'
res.status(401).send('<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=' + r + '"></head><body bgcolor="#000"></body></html>')
})
//app.get('/reauth', function (req, res, next) {
// var r = req.headers.referer || '/'
// res.status(401).send('<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=' + r + '"></head><body bgcolor="#000"></body></html>')
//})

// eslint-disable-next-line complexity
//app.get('/ssh/host/:host?', function (req, res, next) {
// res.sendFile(path.join(path.join(publicPath, 'client.htm')))
//
// // Setup the session data
// req.session.ssh = sshSessionConfig(config, Object.assign({}, req.params, req.query, req.headers))
//})

// eslint-disable-next-line complexity
app.get('/ssh/host/:host?', function (req, res, next) {
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
app.get('/ssh/token_session/:token', function (req, res, next) {
const token = req.params.token
const creds = cachedCredentials[token]

if (creds) {
delete cachedCredentials[token]
res.sendFile(path.join(path.join(publicPath, 'client.htm')))

const { username, userpassword, host, port, label } = creds

// Setup the session data
req.session.username = username
req.session.userpassword = userpassword
req.session.label = label
req.session.ssh = sshSessionConfig(config, Object.assign({}, req.query, req.headers, { host: host, port: port }))
console.log(`[GET] /ssh/token_session/${token} - Claimed session`)
} else {
console.log(`[GET] /ssh/token_session/${token} - 404`)
res.status(404).send('<!DOCTYPE html><html><head></head><body bgcolor="#fff"><center><h1>Session Not Found</h1><p>404</p></center></body></html>')
}
})

app.post('/ssh/set_session_credentials', (req, res) => {
const data = req.body

if (!data.username || !data.userpassword || !data.host) {
res.status(400).send('{"Error": "Missing required field"}')
} else {
const token = myutil.uuidv4()
const sshConfig = {
username: data.username,
userpassword: data.userpassword,
label: data.label,
host: data.host,
port: data.port || config.ssh.port
}

console.log(`[POST] /ssh/set_session_credentials - ${data.host} | ${data.username} 200`)
cachedCredentials[token] = sshConfig
cachedCredentialSetTTL(token, DEFAULT_CC_TTL)
res.status(200).send(JSON.stringify({ token: token, ttl: DEFAULT_CC_TTL }))
}
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
Expand Down
99 changes: 99 additions & 0 deletions app/server/app_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const path = require('path')
const fs = require('fs')

const configPath = path.join(path.dirname(require.main.filename), 'config.json')

console.log('WebSSH2 service reading config from: ' + configPath)

// sane defaults if config.json or parts are missing
let config = {
listen: {
ip: '0.0.0.0',
port: 2222
},
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)
}

module.exports = config
Loading