diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..4cde0f3a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Zlux Server Framework Changelog + +All notable changes to the Zlux Server Framework package will be documented in this file. +This repo is part of the app-server Zowe Component, and the change logs here may appear on Zowe.org in that section. + +## 1.12.0 + +- Bugfix: Server handles if implementationDefaults or mediationLayer objects are missing +- Bugfix: SSH connecting from terminal-proxy was very slow on node v12+ +- Bugfix: Lease info for mediation layer was a value that caused periodic heartbeat failure +- Add ability to state where a plugin path is relative to, instead of just where the server is running. +- Bugfix: Logout now allows security plugins to clear cookies +- Removed tokenInjector from sso-auth, since when SSO is being used token injection logic is not needed anymore. +- Bugfix: When trying to dynamically load a plugin with unmet dependencies, the response from the server would hang +- Support for reading keys, certificates, and certificate authority content from SAF keyrings via safkeyring:// specification in the node.https configuration object +- App server will now reattempt to connect to zss if it doesn't initially + diff --git a/lib/apiml.js b/lib/apiml.js index 9a662a7e..2e779ce3 100644 --- a/lib/apiml.js +++ b/lib/apiml.js @@ -38,8 +38,8 @@ const MEDIATION_LAYER_INSTANCE_DEFAULTS = { name: 'MyOwn' }, leaseInfo: { - durationInSecs: 10, - renewalIntervalInSecs: 10 + durationInSecs: 90, // 3 * heartbeatInterval + renewalIntervalInSecs: 30 // heartbeatInterval }, metadata: { "routed-services.1.gateway-url": "/api/v1", @@ -72,15 +72,33 @@ const MEDIATION_LAYER_INSTANCE_DEFAULTS = { } }; -function ApimlConnector({ hostName, ipAddr, httpPort, httpsPort, apimlHost, +function ApimlConnector({ hostName, httpPort, httpsPort, apimlHost, apimlPort, tlsOptions, eurekaOverrides }) { - Object.assign(this, { hostName, ipAddr, httpPort, httpsPort, apimlHost, + Object.assign(this, { hostName, httpPort, httpsPort, apimlHost, apimlPort, tlsOptions, eurekaOverrides }); this.vipAddress = hostName; } ApimlConnector.prototype = { constructor: ApimlConnector, + + setBestIpFromConfig: Promise.coroutine(function *getBaseIpFromConfig(nodeConfig) { + const nodeIps = yield zluxUtil.uniqueIps(nodeConfig.https && nodeConfig.https.ipAddresses ? nodeConfig.https.ipAddresses : nodeConfig.http.ipAddresses); + const eurekaIp = yield zluxUtil.uniqueIps([nodeConfig.mediationLayer.server.hostname]); + if (nodeIps.includes(eurekaIp)) { + this.ipAddr = zluxUtil.getLoopbackAddress(nodeIps); + return this.ipAddr; + } else { + for (let i = 0; i < nodeIps.length; i++) { + if (nodeIps[i] != '0.0.0.0') { + this.ipAddr = nodeIps[i]; + return this.ipAddr; + } + } + this.ipAddr = zluxUtil.getLoopbackAddress(nodeIps); + return this.ipAddr; + } + }), _makeMainInstanceProperties(overrides) { const instance = Object.assign({}, MEDIATION_LAYER_INSTANCE_DEFAULTS); @@ -189,13 +207,15 @@ ApimlConnector.prototype = { const zluxServerEurekaClient = new eureka(zluxProxyServerInstanceConfig); //zluxServerEurekaClient.logger.level('debug'); this.zluxServerEurekaClient = zluxServerEurekaClient; + const ipAddr = this.ipAddr; + const appServerUrl = `https://${this.apimlHost}:${this.apimlPort}/ui/v1/${zluxProxyServerInstanceConfig.instance.app}/`; return new Promise(function (resolve, reject) { zluxServerEurekaClient.start(function (error) { if (error) { log.warn('ZWED0005W', error); //log.warn(error); reject(error); } else { - log.info('ZWED0021I'); //log.info('Eureka Client Registered'); + log.info('ZWED0021I', ipAddr, appServerUrl); //log.info('Eureka Client Registered from %s. Available at %s'); resolve(); } }); diff --git a/lib/assets/i18n/log/messages_en.json b/lib/assets/i18n/log/messages_en.json index 9dc0585e..20718e7f 100644 --- a/lib/assets/i18n/log/messages_en.json +++ b/lib/assets/i18n/log/messages_en.json @@ -1,7 +1,7 @@ { "0":"Main Server Resource File", "ZWED0020I":"Registering at %s", - "ZWED0021I":"Eureka Client Registered", + "ZWED0021I":"Eureka Client Registered from %s. Available at %s", "ZWED0022I":"RESERVED: Fork worker %s", "ZWED0023I":"RESERVED: Restart worker %s", "ZWED0024I":"RESERVED: Keys=%s", @@ -346,5 +346,12 @@ "ZWED0112E":"The server found no plugin implementing the specified default authentication type of %s.", "ZWED0113E":"The server found no authentication types. Verify that the server configuration file defines server authentication.", "ZWED0114E":"The server found no plugin implementing the specified default authentication type of %s.", - "ZWED0115E":"RESERVED: Unable to retrieve storage object from cluster. This is probably due to a timeout.\nYou may change the default of '%s' ms by setting 'node.cluster.storageTimeout' within the config. %s" + "ZWED0115E":"RESERVED: Unable to retrieve storage object from cluster. This is probably due to a timeout.\nYou may change the default of '%s' ms by setting 'node.cluster.storageTimeout' within the config. %s", + "ZWED0145E":"Cannot load SAF keyring content outside of z/OS", + "ZWED0146E":"SAF keyring data had no attribute \"%s\". Attributes=", + "ZWED0147E":"SAF keyring data was not found for \"%s\"", + "ZWED0148E":"Exception thrown when reading SAF keyring, e=", + "ZWED0149E":"SAF keyring reference missing userId \"%s\", keyringName \"%s\", or label \"%s\"", + "ZWED0150E":"Cannot load SAF keyring due to missing keyring_js library", + "ZWED0151E":"RESERVED: Env var %s not found" } diff --git a/lib/auth-manager.js b/lib/auth-manager.js index 5a9805df..585b4084 100644 --- a/lib/auth-manager.js +++ b/lib/auth-manager.js @@ -114,7 +114,7 @@ AuthManager.prototype = { pluginsByCategory = []; this.authTypes[category] = pluginsByCategory; } - if (this.config.implementationDefaults[category]) { + if (this.config.implementationDefaults && this.config.implementationDefaults[category]) { const index = this.config.implementationDefaults[category].plugins.indexOf(plugin.identifier); if (index != -1) { pluginsByCategory.splice(index, 0, plugin.identifier); diff --git a/lib/clusterManager.js b/lib/clusterManager.js index 4e73ca27..c49f3477 100644 --- a/lib/clusterManager.js +++ b/lib/clusterManager.js @@ -221,9 +221,7 @@ if (cluster.isMaster) { } ClusterManager.prototype.setStorageAll = function(pluginId, dict, resultHandler) { - const allStorage = this.storage.getAll(pluginId); - allStorage[pluginId] = dict; - this.storage.setAll(allStorage, pluginId); + this.storage.setAll(dict, pluginId); return resultHandler(true); } diff --git a/lib/depgraph.js b/lib/depgraph.js index 14cf77a3..3cabced6 100644 --- a/lib/depgraph.js +++ b/lib/depgraph.js @@ -206,7 +206,7 @@ DependencyGraph.prototype = { const pluginsSorted = []; let time = 0; for (let importedPlugin of Object.values(graph)) { - visit(importedPlugin, true); + visit(importedPlugin); } return pluginsSorted; diff --git a/lib/index.js b/lib/index.js index f74b2281..d86088ba 100755 --- a/lib/index.js +++ b/lib/index.js @@ -12,6 +12,7 @@ 'use strict'; const Promise = require('bluebird'); const util = require('./util'); +const os = require('os'); const WebServer = require('./webserver'); const PluginLoader = require('./plugin-loader'); const makeWebApp = require('./webapp').makeWebApp; @@ -214,7 +215,7 @@ Server.prototype = { ((this.startUpConfig.proxiedHost !== undefined) || (this.startUpConfig.proxiedPort !== undefined))) { const host = this.startUpConfig.proxiedHost; const port = this.startUpConfig.proxiedPort; - yield checkProxiedHost(host, port); + yield checkProxiedHost(host, port, this.userConfig.agent.handshakeTimeout); } this.webApp = makeWebApp(webAppOptions); yield this.webServer.startListening(this.webApp); @@ -307,7 +308,8 @@ Server.prototype = { for (let i = 0; i < this.langManagers.length; i++) { yield this.langManagers[i].startAll(); } - if (this.userConfig.node.mediationLayer.enabled) { + if ((this.userConfig.node.mediationLayer && this.userConfig.node.mediationLayer.enabled) + && (!process.clusterManager || process.clusterManager.getIndexInCluster() == 0)) { const apimlConfig = this.userConfig.node.mediationLayer; let apimlTlsOptions; if (apimlConfig.tlsOptions != null) { @@ -320,8 +322,7 @@ Server.prototype = { //installLogger.info('The https port given to the APIML is: ', webAppOptions.httpsPort); //installLogger.info('The zlux-apiml config are: ', apimlConfig); this.apiml = new ApimlConnector({ - hostName: 'localhost', - ipAddr: '127.0.0.1', + hostName: os.hostname(), httpPort: webAppOptions.httpPort, httpsPort: webAppOptions.httpsPort, apimlHost: apimlConfig.server.hostname, @@ -329,6 +330,7 @@ Server.prototype = { tlsOptions: apimlTlsOptions, eurekaOverrides: apimlConfig.eureka }); + yield this.apiml.setBestIpFromConfig(webAppOptions.serverConfig.node); yield this.apiml.registerMainServerInstance(); } }), diff --git a/lib/plugin-loader.js b/lib/plugin-loader.js index 5b80d0b5..e680bc84 100644 --- a/lib/plugin-loader.js +++ b/lib/plugin-loader.js @@ -562,7 +562,22 @@ PluginLoader.prototype = { bootstrapLogger.debug("ZWED0121I", util.inspect(pluginPtrDef)); //bootstrapLogger.log(bootstrapLogger.FINER, util.inspect(pluginPtrDef)); let pluginBasePath = pluginPtrDef.pluginLocation; if (!path.isAbsolute(pluginBasePath)) { - pluginBasePath = this.options.relativePathResolver(pluginBasePath, process.cwd()); + let relativeTo = process.cwd(); + if (typeof pluginPtrDef.relativeTo == 'string') { + if (pluginPtrDef.relativeTo.startsWith('$')) { + const envVar = process.env[pluginPtrDef.relativeTo.substr(1)]; + if (envVar) { + relativeTo = envVar; + } else { + return {location: pluginBasePath, + identifier: pluginPtrDef.identifier, + error: new Error(`ZWED0151E - Env var ${pluginPtrDef.relativeTo} not found`)}; + } + } else { + relativeTo = pluginPtrDef.relativeTo; + } + } + pluginBasePath = this.options.relativePathResolver(pluginBasePath, relativeTo); } if (!fs.existsSync(pluginBasePath)) { return {location: pluginBasePath, @@ -602,7 +617,22 @@ PluginLoader.prototype = { bootstrapLogger.log(bootstrapLogger.FINER, util.inspect(pluginPtrDef)); let pluginBasePath = pluginPtrDef.pluginLocation; if (!path.isAbsolute(pluginBasePath)) { - pluginBasePath = this.options.relativePathResolver(pluginBasePath, process.cwd()); + let relativeTo = process.cwd(); + if (typeof pluginPtrDef.relativeTo == 'string') { + if (pluginPtrDef.relativeTo.startsWith('$')) { + const envVar = process.env[pluginPtrDef.relativeTo.substr(1)]; + if (envVar) { + relativeTo = envVar; + } else { + return {location: pluginBasePath, + identifier: pluginPtrDef.identifier, + error: new Error(`ZWED0151E - Env var ${pluginPtrDef.relativeTo} not found`)}; + } + } else { + relativeTo = pluginPtrDef.relativeTo; + } + } + pluginBasePath = this.options.relativePathResolver(pluginBasePath, relativeTo); } let pluginDefPath = path.join(pluginBasePath, 'pluginDefinition.json'); jsonUtils.readJSONFileWithCommentsAsync(pluginDefPath).then(function(pluginDef){ @@ -688,7 +718,7 @@ PluginLoader.prototype = { }); } } else { - bootstrapLogger.warn('Could not read plugins dir, e=',e); + bootstrapLogger.warn('Could not read plugins dir, e=',err); } }); }); @@ -735,10 +765,12 @@ PluginLoader.prototype = { return !this.pluginMap[plugin.identifier]; }); for (const rejectedPlugin of sortedAndRejectedPlugins.rejects) { - bootstrapLogger.warn(`ZWED0033W`, rejectedPlugin.pluginId, zluxUtil.formatErrorStatus(rejectedPlugin.validationError, DependencyGraph.statuses)); //bootstrapLogger.warn(`Could not initialize plugin` + const rejectionError = zluxUtil.formatErrorStatus(rejectedPlugin.validationError, DependencyGraph.statuses); + bootstrapLogger.warn(`ZWED0033W`, rejectedPlugin.pluginId, rejectionError); //bootstrapLogger.warn(`Could not initialize plugin` //+ ` ${rejectedPlugin.pluginId}: ` //+ zluxUtil.formatErrorStatus(rejectedPlugin.validationError, //DependencyGraph.statuses)); + newPlugins.push(Object.assign(rejectedPlugin, {error: rejectionError})); } let isFirstRun = Object.keys(this.pluginMap).length === 0; diff --git a/lib/proxy.js b/lib/proxy.js index 20c7b5c4..5a0ef83c 100644 --- a/lib/proxy.js +++ b/lib/proxy.js @@ -24,6 +24,8 @@ const WebSocket = require('ws'); const net = require('net'); const proxyLog = util.loggers.proxyLogger; +const RECONNECT_DELAY = 5000; +const DEFAULT_HANDSHAKE_TIMEOUT = 30000; function convertOptions(request, realHost, realPort, urlPrefix) { var options = {}; @@ -301,24 +303,38 @@ function makeWsProxy(host, port, urlPrefix, isHttps) { }; }; -function checkProxiedHost(host, port) { +function checkProxiedHost(host, port, handshakeTimeout) { const client = new net.Socket(); + let timeLeft = handshakeTimeout ? handshakeTimeout : DEFAULT_HANDSHAKE_TIMEOUT; return new Promise((resolve, reject) => { - client.connect(port, host, () => { + const connect = () => { + client.connect(port, host) + timeLeft = timeLeft - RECONNECT_DELAY; + } + + client.once("connect", () => { client.destroy(); resolve(); }); + client.on('error', (e) => { - proxyLog.warn(`ZWED0045W`, host, port); //proxyLog.warn(`Failed to reach the auth services host for address ${host}:${port}`); - if (host === '127.0.0.1') { - proxyLog.warn("ZWED0046W"); //proxyLog.warn("The auth services host system was not " + + if (timeLeft <= 0) { + proxyLog.warn(`ZWED0045W`, host, port); //proxyLog.warn(`Failed to reach the auth services host for address ${host}:${port}`); + if (host === '127.0.0.1') { + proxyLog.warn("ZWED0046W"); //proxyLog.warn("The auth services host system was not " + //"specified at startup, and defaulted to 127.0.0.1.\n" + //"Verify that the auth services server is running, " + //"or specify at startup the remote host and port to connect to. " + //"See documentation for details."); + } + reject(`Communication with ${host}:${port} failed: ${e.toString()}`); + } else if (e.code == 'ECONNREFUSED') { + proxyLog.warn("Failed to connect to the server, will attempt again...") + setTimeout(connect, RECONNECT_DELAY) } - reject(`Communication with ${host}:${port} failed: ${e.toString()}`); }); + + connect(); }); } diff --git a/lib/unp-constants.js b/lib/unp-constants.js index 88846cb5..ef2c863c 100644 --- a/lib/unp-constants.js +++ b/lib/unp-constants.js @@ -15,7 +15,7 @@ exports.EXIT_AUTH = 3; exports.EXIT_PFX_READ = 4; exports.EXIT_HTTPS_LOAD = 5; exports.EXIT_NO_PLUGINS = 6; - +exports.EXIT_NO_SAFKEYRING = 7; diff --git a/lib/webapp.js b/lib/webapp.js index 8899a7be..ff59be71 100644 --- a/lib/webapp.js +++ b/lib/webapp.js @@ -178,8 +178,8 @@ function createDataserviceStorage(pluginId) { return process.clusterManager.deleteStorageByKey(pluginId, key); } - storageObj.deleteAll = function (key) { - return process.clusterManager.deleteStorageByKey(pluginId, {}); + storageObj.deleteAll = function () { + return process.clusterManager.setStorageAll(pluginId, {}); } contentLogger.debug("'" + pluginId + "' context is loaded with storage object: ", storageObj); @@ -1171,7 +1171,7 @@ function WebApp(options){ secure: 'auto' } })); - this.loopbackSecret = process.env.loopbackSecret ? process.env.loopbackSecret : 'different', + this.loopbackSecret = process.env.loopbackSecret ? process.env.loopbackSecret : 'different'; process.env.expressSessionSecret = undefined; process.env.loopbackSecret = undefined; @@ -1257,7 +1257,7 @@ WebApp.prototype = { installStaticHanders() { const webdir = path.join(path.join(this.options.productDir, this.options.productCode), 'web'); - const rootPage = this.options.rootRedirectURL? this.options.rootRedirectURL + const rootPage = this.options.rootRedirectURL? '.'+this.options.rootRedirectURL : '/'; if (rootPage != '/') { this.expressApp.get('/', function(req,res) { diff --git a/lib/webauth.js b/lib/webauth.js index 5ccd5362..9f42e123 100644 --- a/lib/webauth.js +++ b/lib/webauth.js @@ -209,7 +209,7 @@ module.exports = function(authManager, cookiePort, isSecurePort) { } else { handlerResult = {success: true}; } - result.addHandlerResult(handlerResult, handler, res); + result.addHandlerResult(handlerResult, handler, res, handlerResult.cookies); if (req.session.authPlugins) { delete req.session.authPlugins[pluginID]; } diff --git a/lib/webserver.js b/lib/webserver.js index 21e491f1..6e4760e4 100644 --- a/lib/webserver.js +++ b/lib/webserver.js @@ -29,6 +29,22 @@ const contentLogger = util.loggers.contentLogger; const childLogger = util.loggers.childLogger; const networkLogger = util.loggers.network; +const os = require('os'); +let keyring_js; +try { + if (os.platform() == 'os390') { + keyring_js = require('keyring_js'); + } +} catch (e) { + bootstrapLogger.warn('Could not load zcrypto library, SAF keyrings will be unavailable'); +} + +const CRYPTO_CONTENT_CERT=0; +const CRYPTO_CONTENT_KEY=1; +const CRYPTO_CONTENT_CA=2; +const CRYPTO_CONTENT_CRL=3; + + function readCiphersFromArray(stringArray) { if (stringArray && Array.isArray(stringArray)) { let uppercase = []; @@ -45,8 +61,120 @@ function readCiphersFromArray(stringArray) { } }; +function parseSafKeyringAddress(safEntry) { + const endUserIndex = safEntry.indexOf('/'); + if (endUserIndex == -1) { + return null; + } else { + const userId = safEntry.substring(0,endUserIndex); + const endNameIndex = safEntry.indexOf('&',endUserIndex+1); + if (endNameIndex == -1 || endNameIndex == safEntry.length-1) { + return null; + } else { + return { + userId, + keyringName: safEntry.substring(endUserIndex+1,endNameIndex), + label: safEntry.substring(endNameIndex+1) + }; + } + } +} + +function getAttributeNameForCryptoType(locationType, cryptoType) { + switch (locationType) { + case 'safkeyring': + default: + if (cryptoType == CRYPTO_CONTENT_CERT || cryptoType == CRYPTO_CONTENT_CA) { + return 'certificate'; + } else if (cryptoType == CRYPTO_CONTENT_KEY) { + return 'key'; + } else { + return null; + } + } +} + +function splitCryptoLocationsByType(locations) { + const locationsByType = {}; + locations.forEach((location)=> { + const index = location.indexOf('://'); + if (index != -1 && (location.length > index+3)) { + const type = location.substring(0,index); + const typeArray = locationsByType[type] || []; + typeArray.push(location.substring(index+3)); + locationsByType[type] = typeArray; + } + else { + const typeArray = locationsByType['file'] || []; + typeArray.push(location); + locationsByType['file'] = typeArray; + } + }); + return locationsByType; +} + +// safkeyring:// +function loadPem(locations, type, keyrings) { + const locationsByType = splitCryptoLocationsByType(locations); + let content = []; + const saf = locationsByType['safkeyring']; + if (saf && os.platform() != 'os390') { + bootstrapLogger.severe('ZWED0145E');//Cannot load SAF keyring content outside of z/OS' + process.exit(constants.EXIT_NO_SAFKEYRING); + } else if (saf && keyring_js) { + saf.forEach((safEntry)=> { + /* + In the latest code it's possible the entry could start with + safkeyring://// instead of safkeyring://, so ignore extra slashes + TODO: Is this a possibility for other key types also? Keep an eye on this during future enhancements + */ + const safRingAddress = safEntry.startsWith('//') ? safEntry.substr(2) : safEntry; + const {userId, keyringName, label} = parseSafKeyringAddress(safRingAddress); + if (userId && keyringName && label) { + const cachedKey = 'safkeyring://'+safRingAddress; + let keyringData = keyrings[cachedKey]; + const attribute = getAttributeNameForCryptoType('safkeyring', type); + try { + if (!keyringData) { + bootstrapLogger.debug(`Cache not found for ${cachedKey}`); + keyringData = keyring_js.getPemEncodedData(userId, keyringName, label); + keyrings[cachedKey] = keyringData; + } + if (keyringData) { + if (keyringData[attribute]) { + content.push(keyringData[attribute]); + } else { + //SAF keyring data had no attribute "%s". Attributes=',attribute,Object.keys(keyringData)); + bootstrapLogger.warn('ZWED0146E',attribute,Object.keys(keyringData)); + } + } else { + //SAF keyring data was not found for %s',cachedKey); + bootstrapLogger.warn('ZWED0147E',cachedKey); + } + } catch (e) { + //Exception thrown when reading SAF keyring, e=',e); + bootstrapLogger.warn('ZWED0148E',e); + } + } else { + //SAF keyring reference missing userId %s, keyringName %s, or label %s', + bootstrapLogger.warn('ZWED0149E', + userId, keyringName, label); + } + }); + } else if (saf && !keyring_js) { + //Cannot load SAF keyring due to missing keyring_js library'); + bootstrapLogger.warn('ZWED0150E'); + } + const files = locationsByType['file']; + if (files) { + content = util.readFilesToArray(files).concat(content); + } + return {content, keyrings}; +} function readTlsOptionsFromConfig(config, httpsOptions) { + //in case keys and certs can be read from the same keyring, store them here for later retrieval + let keyrings = {}; if (config.https.pfx) { try { httpsOptions.pfx = fs.readFileSync(config.https.pfx); @@ -59,19 +187,18 @@ function readTlsOptionsFromConfig(config, httpsOptions) { } } else { if (config.https.certificates) { - httpsOptions.cert = util.readFilesToArray( - config.https.certificates); - bootstrapLogger.info('ZWED0072I', config.https.certificates); //bootstrapLogger.info('Using Certificate: ' + config.https.certificates); + httpsOptions.cert = loadPem(config.https.certificates, CRYPTO_CONTENT_CERT, keyrings).content; + bootstrapLogger.info('ZWED0072I', config.https.certificates); //bootstrapLogger.info('Using Certificate: ' + config.https.certificates); } if (config.https.keys) { - httpsOptions.key = util.readFilesToArray(config.https.keys); + httpsOptions.key = loadPem(config.https.keys, CRYPTO_CONTENT_KEY, keyrings).content; } } if (config.https.certificateAuthorities) { - httpsOptions.ca = util.readFilesToArray(config.https.certificateAuthorities); + httpsOptions.ca = loadPem(config.https.certificateAuthorities, CRYPTO_CONTENT_CA, keyrings).content; } if (config.https.certificateRevocationLists) { - httpsOptions.crl = util.readFilesToArray(config.https.certificateRevocationLists); + httpsOptions.crl = loadPem(config.https.certificateRevocationLists, CRYPTO_CONTENT_CRL, keyrings).content; } } diff --git a/package-lock.json b/package-lock.json index 4e538a46..963c3b05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,19 +55,20 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.3.tgz", - "integrity": "sha512-sHEsvEzjqN+zLbqP+8OXTipc10yH1QLR+hnr5uw29gi9AhCAAAdri8ClNV7iMdrJrIzXIQtlkPvq8tJGhj3QJQ==", + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", + "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", "dev": true, "requires": { "@types/node": "*", + "@types/qs": "*", "@types/range-parser": "*" } }, "@types/mime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", - "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", + "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==", "dev": true }, "@types/node": { @@ -76,6 +77,12 @@ "integrity": "sha512-N33cKXGSqhOYaPiT4xUGsYlPPDwFtQM/6QxJxuMXA/7BcySW+lkn2yigWP7vfs4daiL/7NJNU6DMCqg5N4B+xQ==", "dev": true }, + "@types/qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==", + "dev": true + }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", @@ -83,9 +90,9 @@ "dev": true }, "@types/serve-static": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", - "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", + "integrity": "sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==", "dev": true, "requires": { "@types/express-serve-static-core": "*", @@ -117,9 +124,9 @@ } }, "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -205,6 +212,11 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" }, + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + }, "body-parser": { "version": "1.18.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", @@ -231,6 +243,11 @@ "concat-map": "0.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -401,6 +418,16 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -806,6 +833,15 @@ "verror": "1.10.0" } }, + "keyring_js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/keyring_js/-/keyring_js-1.0.1.tgz", + "integrity": "sha512-A5vMeGoSHnFuveee7OjyNFJd9Mo+yZs4weLIhegCvBu/3OjgFUNmeWUFbM4NwX/FBz3/lGX4uB8E/jpgjd8VwA==", + "optional": true, + "requires": { + "node-gyp-build": "^4.2.1" + } + }, "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", @@ -836,22 +872,31 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" }, "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "requires": { - "mime-db": "1.43.0" + "mime-db": "1.44.0" } }, "minimatch": { @@ -890,6 +935,12 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-gyp-build": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.2.tgz", + "integrity": "sha512-Lqh7mrByWCM8Cf9UPqpeoVBBo5Ugx+RKu885GAzmLBVYjeywScxHXPGLa4JfYNZmcNGwzR0Glu5/9GaQZMFqyA==", + "optional": true + }, "normalize-url": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.3.0.tgz", @@ -1012,6 +1063,14 @@ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1092,9 +1151,9 @@ } }, "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safer-buffer": { "version": "2.1.2", diff --git a/package.json b/package.json index 3e700854..f525c9f8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,12 @@ "name": "zlux-server-framework", "version": "0.0.0-zlux.version.replacement", "description": "A Server for zLUX Plugins, Dataservices, and Proxying to other Servers.", - "license": "private", + "license": "EPL-2.0", + "homepage": "zowe.org", + "repository": { + "type": "git", + "url": "https://github.com/zowe/zlux-server-framework.git" + }, "author": { "name": "Fyodor Kovin", "email": "fkovin@rocketsoftware.com" @@ -31,6 +36,7 @@ "bluebird": "~3.5.1", "body-parser": "~1.18.3", "cookie-parser": "~1.4.3", + "diffie-hellman": "^5.0.3", "eureka-js-client": "~4.4.1", "express": "~4.16.3", "express-session": "~1.15.6", @@ -56,5 +62,8 @@ "chai": "~4.2.0", "chai-http": "~4.2.0", "typescript": "2.7.1" + }, + "optionalDependencies": { + "keyring_js": "~1.0.0" } } diff --git a/plugins/config/lib/configService.js b/plugins/config/lib/configService.js index 62f23c1c..37ffe0a8 100644 --- a/plugins/config/lib/configService.js +++ b/plugins/config/lib/configService.js @@ -432,8 +432,10 @@ function createMissingFolderAsync(resourcePath, scopeDirectory, callback){ function encodeDirectoryName(inputName){ - var resourceLength = inputName.length; - var percentEncodedResource = percentEncode(inputName,percentEncodedResource,resourceLength); + //var resourceLength = inputName.length; + // TODO: Refactor percentEnconde method to include parameters for resource + resource length + //var percentEncodedResource = percentEncode(inputName,percentEncodedResource,resourceLength); + var percentEncodedResource = percentEncode(inputName); return percentEncodedResource; } @@ -1115,7 +1117,7 @@ function respondWithConfigFile(response, filename, resource, directories, scope, default: { var msg = "ZWED0067E - Aggregation policy type="+policy+" unhandled"; - respondWithJsonError(response,msg,HTTP_STATUS_BAD_REQUEST,"Bad Request",location); + respondWithJsonError(response,msg,HTTP_STATUS_BAD_REQUEST,location); logger.warn("ZWED0101W", policy); //logger.warn(msg); } } diff --git a/plugins/sso-auth/lib/ssoAuth.js b/plugins/sso-auth/lib/ssoAuth.js index a7a245f0..c3945b6a 100644 --- a/plugins/sso-auth/lib/ssoAuth.js +++ b/plugins/sso-auth/lib/ssoAuth.js @@ -47,13 +47,11 @@ function cleanupSessionGeneric(sessionState) { function SsoAuthenticator(pluginDef, pluginConf, serverConf, context) { this.usingApiml = doesApimlExist(serverConf); this.usingZss = doesZssExist(serverConf); - //TODO this seems temporary, will need to unconditionally say usingApiml=usingSso when sso support is complete - //this.apimlSsoEnabled = process.env['APIML_ENABLE_SSO'] == 'true'; - //TODO temporary, once zss is finished we can switch over to true - this.apimlSsoEnabled = false; + const tokenName = process.env['PKCS11_TOKEN_NAME']; + const zssCanHandleToken = !tokenName || tokenName=='' ? false : true; //Sso here meaning just authenticate to apiml, and handle jwt - this.usingSso = this.apimlSsoEnabled && this.usingApiml; + this.usingSso = this.usingApiml && zssCanHandleToken; this.pluginConf = pluginConf; this.instanceID = serverConf.instanceID; @@ -135,6 +133,8 @@ SsoAuthenticator.prototype = { }).catch((e) => { resolve(this._insertHandlerStatus({success: false, reason: e.message})); }); + } else { //only zss? + resolve(this._insertHandlerStatus({success: (zssResult.success), cookies: zssResult.cookies})); } }).catch((e) => { resolve(this._insertHandlerStatus({success: false, reason: e.message})); diff --git a/plugins/sso-auth/lib/tokenInjector.js b/plugins/sso-auth/lib/tokenInjector.js deleted file mode 100644 index 9ff065d8..00000000 --- a/plugins/sso-auth/lib/tokenInjector.js +++ /dev/null @@ -1,30 +0,0 @@ -const express = require('express'); - -module.exports = pluginContext => { - const r = express.Router(); - try { - const apimlConfig = pluginContext.plugin.server.config.user.node.mediationLayer.server; - const gatewayUrl = `https://${this.apimlConfig.hostname}:${this.apimlConfig.gatewayPort}`; - r.get('/**', (req, res) => { - const apimlSession = req.session.authPlugins['org.zowe.zlux.auth.safsso']; - if (apimlSession === undefined) { - res.status(401).send("Missing APIML authentication token in zLUX session"); - } - else { - const token = apimlSession.apimlToken; - const newUrl = gatewayUrl + req.url.replace("1.0.0/", "") + "?apimlAuthenticationToken=" + token; - res.redirect(newUrl); - } - }) - } catch (e) { - r.get('/**', (req, res) => { - res.status(500).send("Missing APIML configuration"); - }) - } - - return { - then(f) { - f(r); - } - } -}; diff --git a/plugins/sso-auth/pluginDefinition.json b/plugins/sso-auth/pluginDefinition.json index 8eae8e72..cb318671 100644 --- a/plugins/sso-auth/pluginDefinition.json +++ b/plugins/sso-auth/pluginDefinition.json @@ -3,16 +3,7 @@ "pluginType": "nodeAuthentication", "authenticationCategories": ["saf","zss","apiml"], "apiVersion": "1.2.0", - "pluginVersion": "1.1.0", + "pluginVersion": "1.2.0", "license": "EPL-2.0", - "filename": "ssoAuth.js", - "dataServices": [ - { - "type": "router", - "name": "tokenInjector", - "fileName": "tokenInjector.js", - "version": "1.0.0", - "dependenciesIncluded": true - } - ] + "filename": "ssoAuth.js" } diff --git a/plugins/terminal-proxy/lib/ssh.js b/plugins/terminal-proxy/lib/ssh.js index 7b502629..9e6514b8 100644 --- a/plugins/terminal-proxy/lib/ssh.js +++ b/plugins/terminal-proxy/lib/ssh.js @@ -10,13 +10,19 @@ Copyright Contributors to the Zowe Project. */ -var crypto = require("crypto"); +const crypto = require("crypto"); +const nodeMajorIndex = process.versions.node.indexOf('.'); +const nodeVersion = Number(process.versions.node.substr(0,nodeMajorIndex)); +if (nodeVersion >= 12) { + var swcrypto = require("diffie-hellman/browser"); +} + var clientVersion = 'SSH-2.0-UNP_1.1'; var traceCrypto = false; var traceSSHMessage = false; -var traceBasic = true; var MAX_SEQNO = 4294967295; +var sshLogger; var hex = function(x){ if (x || x === 0){ @@ -44,7 +50,7 @@ var hexDump = function(a, offset, length){ } -var SSH_MESSAGE = exports.MESSAGE = { +const SSH_MESSAGE = exports.MESSAGE = { SSH_MSG_DISCONNECT : 1, // followed by a String with caose of disconnection SSH_MSG_SERVICE_REQUEST : 5, @@ -91,6 +97,13 @@ var SSH_MESSAGE = exports.MESSAGE = { ERROR : 1000 } +const CODE_TO_NAME = {}; +const SSH_MESSAGE_KEYS = Object.keys(SSH_MESSAGE); +for (let i = 0; i < SSH_MESSAGE_KEYS.length; i++) { + CODE_TO_NAME[SSH_MESSAGE[SSH_MESSAGE_KEYS[i]]] = SSH_MESSAGE_KEYS[i]; +} + + const TERMINAL_TYPE_DEFAULT = 'vt100'; const VT_COLUMN_DEFAULT = 80; const VT_ROW_DEFAULT = 24; @@ -206,6 +219,10 @@ var SUPPORTED_COMPRESS = [ function ssh(){ } +exports.setLogger = function(logger) { + sshLogger = logger; +} + exports.processEncryptedData = function(terminalWebsocketProxy,data) { return ssh.processEncryptedData(terminalWebsocketProxy,data); }; @@ -221,6 +238,7 @@ ssh.sendSSHData = function (terminalWebsocketProxy,jsonData){ } var msgCode = jsonData.msgCode; var pdu; + sshLogger.debug('send=' + CODE_TO_NAME[msgCode]); switch (msgCode) { case SSH_MESSAGE.SSH_MSG_USERAUTH_INFO_RESPONSE: { //numResponses, responses @@ -271,7 +289,7 @@ ssh.sendSSHData = function (terminalWebsocketProxy,jsonData){ pdu = new SSHv2PDU(SSH_MESSAGE.SSH_MSG_CHANNEL_DATA,generateSSHv2PDUBytes(shellData)); } else { //TODO this occurs occasionally, something must be unimplemented. - if (traceBasic) console.log('Not sending MSG_CHANNEL_REQUEST because channel & primaryShellChannel cannot be found\n'); + sshLogger.warn('Not sending MSG_CHANNEL_REQUEST because channel & primaryShellChannel cannot be found\n'); } break; } @@ -289,12 +307,13 @@ ssh.sendSSHData = function (terminalWebsocketProxy,jsonData){ } else { //TODO this occurs occasionally, something must be unimplemented. - if (traceBasic) console.log('Not sending MSG_CHANNEL_REQUEST because channel & primaryShellChannel cannot be found\n'); + sshLogger.warn('Not sending MSG_CHANNEL_REQUEST because channel & primaryShellChannel cannot be found\n'); } break; } } + sshLogger.debug('send=%s done', CODE_TO_NAME[msgCode]); if (pdu!=null){ if(sessionData.isKeyExchanging){ sessionData.rekeyQueue.push(pdu); @@ -370,6 +389,7 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ sshMessage.type = msgCode; sessionData.sshMessageCode = msgCode; } + sshLogger.debug('rcv=' + CODE_TO_NAME[msgCode]); switch (msgCode) { case SSH_MESSAGE.SSH_MSG_SERVICE_ACCEPT:{ sshMessages.push(sshMessage); @@ -378,33 +398,29 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ case SSH_MESSAGE.SSH_MSG_DISCONNECT:{ sshMessage.reasonCode = sshv2PDU.readInt(); sshMessage.reasonLine = sshv2PDU.readSizedData(); - if(traceBasic) console.log("processed SSH_MSG_DISCONNECT: "+ sshMessage.reasonCode +'\n'+ Buffer.from(sshMessage.reasonLine,'ascii').toString()); + sshLogger.debug("processed SSH_MSG_DISCONNECT: "+ sshMessage.reasonCode +'\n', Buffer.from(sshMessage.reasonLine,'ascii').toString()); sshMessages.push(sshMessage); break; } case SSH_MESSAGE.SSH_MSG_PK_OK: { - if(traceSSHMessage) console.log("SSH_MESSAGE.SSH_MSG_PK_OK "); sshMessage.algorithm=sshv2PDU.readSizedData(); sshMessage.blob=sshv2PDU.readSizedData(); sshMessages.push(sshMessage); break; } case SSH_MESSAGE.SSH_MSG_USERAUTH_BANNER : { - if(traceSSHMessage) console.log("SSH_MESSAGE.SSH_MSG_USERAUTH_BANNER "); sshMessage.message=sshv2PDU.readSizedData(); sshMessage.language=sshv2PDU.readSizedData(); sshMessages.push(sshMessage); break; } case SSH_MESSAGE.SSH_MSG_USERAUTH_FAILURE:{ - if(traceSSHMessage) console.log('SSH_MESSAGE.SSH_MSG_USERAUTH_FAILURE '); sshMessages.push(sshMessage); // processData = Buffer.alloc(sshv2PDU.data.length); // sshv2PDU.data.copy(processData,0); break; } case SSH_MESSAGE.SSH_MSG_USERAUTH_INFO_REQUEST:{ - if(traceSSHMessage) console.log('SSH_MESSAGE.SSH_MSG_USERAUTH_INFO_REQUEST '); sshMessage.name = sshv2PDU.readSizedData(); sshMessage.inst = sshv2PDU.readSizedData(); sshMessage.lang = sshv2PDU.readSizedData(); @@ -420,7 +436,6 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ break; } case SSH_MESSAGE.SSH_MSG_USERAUTH_SUCCESS:{ - if(traceBasic) console.log('SSH_MSG_USERAUTH_SUCCESS '); sshMessages.push(sshMessage); terminalWebsocketProxy.sshAuthenticated = true; @@ -523,7 +538,6 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ break; } case SSH_MESSAGE.SSH_MSG_CHANNEL_FAILURE:{ - if(traceBasic) console.log('SSH_MSG_CHANNEL_FAILURE '); break; } case SSH_MESSAGE.SSH_MSG_CHANNEL_DATA : { @@ -562,7 +576,6 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ sessionData.highestChannel++; sessionData.sshMessageCode = SSH_MESSAGE.SSH_MSG_CHANNEL_SUCCESS; - if(traceBasic) console.log('SSH_MSG_CHANNEL_SUCCESS built, channel number: ' + sessionData.primaryShellChannel); break ; } } @@ -585,7 +598,7 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ if (sessionData.expectedReplyMsgCode){ if (sessionData.expectedReplyMsgCode!=msgCode){ - if(traceBasic)console.log("SSH rekeying... "); + sshLogger.debug("SSH rekeying"); } } sshv2PDU.readBytes(sessionData.serverCookie); @@ -703,19 +716,24 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ if (sessionData.expectedReplyMsgCode){ if (sessionData.expectedReplyMsgCode!=msgCode){ //DISCONNECT_ERROR + sshLogger.warn('Disconnect_error'); return returnError('Unexpected reply message code SSH_MSG_KEX_DH_GEX_GROUP ('+msgCode+'). Expected='+sessionData.expectedReplyMsgCode); } } if (sessionData.sentMsgCode==SSH_MESSAGE.SSH_MSG_KEX_DH_GEX_REQUEST){ sessionData.prime = sshv2PDU.readSizedData(); sessionData.generator = sshv2PDU.readSizedData(); - var dh = crypto.createDiffieHellman(sessionData.prime,sessionData.generator); + sshLogger.debug('sessionData.generator'); + var dh = nodeVersion >= 12 + ? swcrypto.createDiffieHellman(sessionData.prime,sessionData.generator) + : crypto.createDiffieHellman(sessionData.prime,sessionData.generator); + sshLogger.debug('made dh'); sessionData.expectedReplyMsgCode = SSH_MESSAGE.SSH_MSG_KEX_DH_GEX_REPLY; var msgCodeBuffer = Buffer.alloc(1); msgCodeBuffer.writeUInt8(SSH_MESSAGE.SSH_MSG_KEX_DH_GEX_INIT); dh.generateKeys(); + sshLogger.debug('made keys'); var publicKeys = dh.getPublicKey(); - var idx = 0; var len = publicKeys.length; while (publicKeys[idx] === 0x00) { @@ -945,16 +963,13 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ exchangeDataBuffer[cp++] = 0; sessionData.k.copy(exchangeDataBuffer, cp, kIndex); - if(traceCrypto) console.log("e: length = "+sessionData.e.length); - if(traceCrypto) console.log( sessionData.e.toString('hex')+"\n"); - if(traceCrypto) console.log("f: length = "+sessionData.f.length); - if(traceCrypto) console.log( sessionData.f.toString('hex')+"\n"); - if(traceCrypto) console.log("k: length = "+sessionData.k.length); - if(traceCrypto) console.log( sessionData.k.toString('hex')+"\n"); - if(traceCrypto) console.log("exchangeDataBuffer:"); - if(traceCrypto) console.log( exchangeDataBuffer.toString('hex')+"\n"); - if(traceCrypto) console.log("exchangeDataBuffer length :"); - if(traceCrypto) console.log( exchangeDataBuffer.length+"\n"); + if(traceCrypto) { + console.log(`e: length = ${sessionData.e.length}\n${sessionData.e.toString('hex')}`); + console.log(`f: length = ${sessionData.f.length}\n${sessionData.f.toString('hex')}`); + console.log(`k: length = ${sessionData.k.length}\n${sessionData.k.toString('hex')}`); + console.log("exchangeDataBuffer:\n", exchangeDataBuffer.toString('hex')); + console.log(`exchangeDataBuffer length : ${exchangeDataBuffer.length}`); + } sessionData.h = sessionData.hash.update(exchangeDataBuffer).digest(); // the H if (sessionData.id == null){ @@ -987,7 +1002,7 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ if (sessionData.isKeyExchanging){ sessionData.isKeyExchanging = false; sessionData.isKeyExchangingInitSent = false; - if(traceBasic)console.log("SSH key exchanged finished... "); + sshLogger.debug("SSH key exchanged finished... "); var pdu = sessionData.rekeyQueue.shift(); while (pdu){ writeSSHv2PDU(function(buffer) {terminalWebsocketProxy.netSend(buffer);},sessionData,pdu); @@ -1008,11 +1023,12 @@ ssh.processEncryptedData = function (terminalWebsocketProxy,rawData){ break; } default :{ - if(traceBasic)console.log("unknown msgCode, something unimplemented "+ msgCode); + sshLogger.warn("unknown msgCode, something unimplemented "+ msgCode); break; } - } + } + sshLogger.debug('rcv=%s done', CODE_TO_NAME[msgCode]); terminalWebsocketProxy.sshSessionData=sessionData; } if (traceCrypto) console.log('Done with socket read'); @@ -1272,12 +1288,18 @@ function readSSHv2PDUData(sessionData,rawData,currentPosition){ var receivedMac = Buffer.alloc(sessionData.readingMACLength); rawData.copy(receivedMac,0,currentPosition); currentPosition+=receivedMac.length; - if (receivedMac.toString('hex')!=computedMac.toString('hex')){ - if(traceBasic)console.log("mac verification failed. "); - } - if(traceCrypto) console.log("received mac: "+receivedMac.toString('hex')); - if(traceCrypto) console.log("computed mac: "+computedMac.toString('hex')); + if (computedMac == undefined) { + sshLogger.warn('mac verification failed - unable to get computed version'); + } else { + if (receivedMac.toString('hex')!=computedMac.toString('hex')){ + sshLogger.warn("mac verification failed - computed does not match received"); + } + if(traceCrypto) { + console.log("received mac: "+receivedMac.toString('hex')); + console.log("computed mac: "+computedMac.toString('hex')); + } //should disconnect if not matching. + } } return {readLength: currentPosition, sshv2PDU: new SSHv2PDU(0,payload)}; } diff --git a/plugins/terminal-proxy/lib/terminalProxy.js b/plugins/terminal-proxy/lib/terminalProxy.js index ea37775e..40a603fa 100644 --- a/plugins/terminal-proxy/lib/terminalProxy.js +++ b/plugins/terminal-proxy/lib/terminalProxy.js @@ -16,7 +16,7 @@ var net = require('net'); const tls = require('tls'); const crypto = require('crypto'); const fs = require('fs'); -const ssh = require('./ssh'); // TODO Sean to create a setter for the logger +const ssh = require('./ssh'); const SSH_MESSAGE = ssh.MESSAGE; const base64BitValues = [ @@ -786,6 +786,7 @@ exports.tn5250WebsocketRouter = function(context) { }; exports.vtWebsocketRouter = function(context) { let handlers = scanAndImportHandlers(context.logger); + ssh.setLogger(context.logger); return new Promise(function(resolve, reject) { let securityConfig = context.plugin.server.config.user.node.https; if (securityConfig && !TerminalWebsocketProxy.securityObjects) { diff --git a/sonar-project.properties b/sonar-project.properties index 61582186..7067013f 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,4 +1,11 @@ -sonar.projectKey=zlux:zlux-proxy-server -sonar.projectName=ZLUX Proxy Server -sonar.projectVersion=1.0.0 -sonar.sources=js,plugins \ No newline at end of file +# must be unique in a given SonarQube instance +sonar.projectKey=zowe_zlux-server-framework +sonar.organization=zowe +# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1. +sonar.projectName=ZLUX Server Framework +# this will be automatically set by Jenkins Library +# sonar.projectVersion=0.9.4 +# Project source repository. +sonar.links.scm=https://github.com/zowe/zlux-server-framework + +sonar.sources=lib,plugins