diff --git a/CHANGELOG.md b/CHANGELOG.md index 66e05049..5a37b2d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ 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. +## 2.17.0 +- Enhancement: Added function `isClientAttls(zoweConfig)` within `libs/util.js`. Whenever a plugin makes a network request, it should always use this to determine if a normally HTTPS request should instead be made as HTTP due to AT-TLS handling the TLS when enabled. (#544) +- Bugfix: Fixed function `isServerAttls(zoweConfig)` within `libs/util.js`, which was preventing using AT-TLS with app-server. (#544) + ## 2.15.0 - Bugfix: App-server could not run in HTTP mode for AT-TLS setup because it was not able to merge HTTPS and HTTP addresses. (#984) diff --git a/lib/apiml.js b/lib/apiml.js index 62aa582b..afd21566 100644 --- a/lib/apiml.js +++ b/lib/apiml.js @@ -12,6 +12,7 @@ const Promise = require("bluebird"); const eureka = require('@rocketsoftware/eureka-js-client').Eureka; const zluxUtil = require('./util'); const https = require('https'); +const http = require('http'); const log = zluxUtil.loggers.apiml; @@ -77,10 +78,10 @@ const MEDIATION_LAYER_INSTANCE_DEFAULTS = (zluxProto, zluxHostname, zluxPort) => } }}; -function ApimlConnector({ hostName, port, isHttps, discoveryUrls, - discoveryPort, tlsOptions, eurekaOverrides }) { - Object.assign(this, { hostName, port, isHttps, discoveryUrls, - discoveryPort, tlsOptions, eurekaOverrides }); +function ApimlConnector({ hostName, port, discoveryUrls, + discoveryPort, tlsOptions, eurekaOverrides, isClientAttls }) { + Object.assign(this, { hostName, port, discoveryUrls, + discoveryPort, tlsOptions, eurekaOverrides, isClientAttls }); this.vipAddress = hostName; } @@ -121,8 +122,10 @@ ApimlConnector.prototype = { } let data = []; + + let httpModule = this.isClientAttls ? http : https; - const req = https.request(options, (res) => { + const req = httpModule.request(options, (res) => { res.on('data', (chunk) => data.push(chunk)); res.on('end', () => { log.debug(`Query rc=`,res.statusCode); @@ -165,10 +168,10 @@ ApimlConnector.prototype = { // If the HTTP port is set to 0 then the API ML doesn't load zlux httpPort: Number(this.port), httpsPort: Number(this.port), - httpEnabled: !this.isHttps, - httpsEnabled: this.isHttps + httpEnabled: false, + httpsEnabled: true }; - const proto = this.isHttps? 'https' : 'http'; + const proto = 'https'; log.debug("ZWED0141I", proto, this.port); //"Protocol:", proto, "Port", port); log.debug("ZWED0142I", JSON.stringify(protocolObject)); //"Protocol Object:", JSON.stringify(protocolObject)); diff --git a/lib/apimlStorage.ts b/lib/apimlStorage.ts index 77314228..0b6bc34a 100644 --- a/lib/apimlStorage.ts +++ b/lib/apimlStorage.ts @@ -16,10 +16,17 @@ import { AxiosInstance } from 'axios'; let apimlClient: AxiosInstance; export function configure(settings: ApimlStorageSettings) { - apimlClient = axios.create({ - baseURL: `https://${settings.host}:${settings.port}`, - httpsAgent: new https.Agent(settings.tlsOptions) - }); + if (settings.isHttps) { + apimlClient = axios.create({ + baseURL: `https://${settings.host}:${settings.port}`, + httpsAgent: new https.Agent(settings.tlsOptions) + }); + } else { + apimlClient = axios.create({ + baseURL: `https://${settings.host}:${settings.port}`, + httpAgent: new http.Agent() + }); + } } export function isConfigured(): boolean { @@ -30,6 +37,7 @@ export interface ApimlStorageSettings { host: string; port: number; tlsOptions: https.AgentOptions; + isHttps?: boolean; } @@ -375,4 +383,4 @@ class ApimlStorage { SPDX-License-Identifier: EPL-2.0 Copyright Contributors to the Zowe Project. -*/ \ No newline at end of file +*/ diff --git a/lib/auth-manager.js b/lib/auth-manager.js index 783ff90e..beb31684 100644 --- a/lib/auth-manager.js +++ b/lib/auth-manager.js @@ -116,7 +116,8 @@ AuthManager.prototype = { plugin, this.configuration, componentConfig, - new AuthPluginContext(plugin, tlsOptions)); + new AuthPluginContext(plugin, tlsOptions), + config); // at this time we should have resolved plugin configuration to have a // nice list of info about what we are using to authenticate against if ((typeof authenticationHandler.authenticate) !== 'function') { diff --git a/lib/index.js b/lib/index.js index 072dfe50..23076726 100755 --- a/lib/index.js +++ b/lib/index.js @@ -218,10 +218,10 @@ Server.prototype = { this.apiml = new ApimlConnector({ hostName: webAppOptions.hostname, port: this.port, - isHttps: util.isServerHttps(this.zoweConfig), discoveryUrls: apimlConfig.server.discoveryUrls || [`https://${apimlConfig.server.hostname}:${apimlConfig.server.port}/eureka/`], tlsOptions: this.tlsOptions, - eurekaOverrides: apimlConfig.eureka + eurekaOverrides: apimlConfig.eureka, + isClientAttls: util.isClientAttls(this.zoweConfig) }); yield this.apiml.setBestIpFromConfig(this.componentConfig.node); yield this.apiml.registerMainServerInstance(); @@ -263,7 +263,7 @@ Server.prototype = { bootstrapLogger.info('ZWED0302I', util.isHaMode() ? 'enabled' : 'disabled'); // "HA mode is %s" if (apimlConfig.cachingService?.enabled) { - this.configureApimlStorage(apimlConfig); + this.configureApimlStorage(apimlConfig, util.isClientAttls(this.zoweConfig)); } const plugins = yield this.loadPlugins(); @@ -335,11 +335,12 @@ Server.prototype = { return yield this.pluginLoader.loadPlugins(); }), - configureApimlStorage(apimlConfig) { + configureApimlStorage(apimlConfig, isHttps) { apimlStorage.configure({ host: apimlConfig.server.gatewayHostname, port: apimlConfig.server.gatewayPort, - tlsOptions: this.tlsOptions + tlsOptions: this.tlsOptions, + isHttps: isHttps }); bootstrapLogger.info(`ZWED0300I`); // Caching Service configured }, diff --git a/lib/util.js b/lib/util.js index a06cad1a..c1594dca 100644 --- a/lib/util.js +++ b/lib/util.js @@ -138,9 +138,10 @@ module.exports.getAgentRequestOptions = function(zoweConfig, tlsOptions, include zoweConfig.components['app-server'].node.mediationLayer && zoweConfig.components['app-server'].node.mediationLayer.server); - const isHttps = useApiml || - (agentConfig.https && agentConfig.https.port) || - (agentConfig.http && agentConfig.http.port && agentConfig.http.attls); + const isHttps = !isClientAttls(zoweConfig) && + (useApiml || + (agentConfig.https && agentConfig.https.port) || + (agentConfig.http && agentConfig.http.port && agentConfig.http.attls)); if (isHttps && !tlsOptions) { console.log('return undefined, ishttps without tlsoptions'); return undefined; @@ -496,13 +497,25 @@ const isHaMode = module.exports.isHaMode = function () { return isHaMode; } -//TODO, ATTLS module.exports.isServerHttps = function(zoweConfig) { - return !!zoweConfig.components['app-server'].node.https; + return Number.isInteger(zoweConfig.components['app-server'].node.https?.port); } +function isClientAttls(zoweConfig) { + let clientGlobalAttls = zoweConfig.zowe.network?.client?.tls?.attls; + let clientLocalAttls = zoweConfig.components['app-server'].zowe?.network?.client?.tls?.attls; + let clientAttls = clientGlobalAttls || clientLocalAttls; + if ((clientGlobalAttls !== false) && (clientLocalAttls !== false) && (!clientAttls)) { + // If client attls not explicitly false OR truthy, have client follow server attls variable. it simplifies common case in which users want both. + return zoweConfig.zowe.network?.server?.tls?.attls || zoweConfig.components['app-server'].zowe?.network?.server?.tls?.attls; + } else { + return clientAttls; + } +} +module.exports.isClientAttls = isClientAttls; + module.exports.getBestPort = function(zoweConfig) { - return zoweConfig.components['app-server'].node.https + return zoweConfig.components['app-server'].node.https?.port ? zoweConfig.components['app-server'].node.https.port : zoweConfig.components['app-server'].node.http.port; } diff --git a/lib/webapp.js b/lib/webapp.js index 48b5e631..89853eca 100644 --- a/lib/webapp.js +++ b/lib/webapp.js @@ -953,7 +953,7 @@ WebServiceHandle.prototype = { requestOptions.hostname = this.service.host; requestOptions.port = this.service.port; requestOptions.path = this.service.urlPrefix; - requestOptions.protocol = this.service.isHttps ? 'https:': 'http:'; + requestOptions.protocol = !this.environment.isClientAttls && this.service.isHttps ? 'https:': 'http:'; } else { requestOptions.hostname = this.environment.agentRequestOptions.host; requestOptions.port = this.environment.agentRequestOptions.port; @@ -1037,7 +1037,7 @@ WebServiceHandle.prototype = { } else { routingLog.debug(`Call loopback path=%s`, requestOptions.path); //loopback call to get to router - httpOrHttps = this.environment.loopbackConfig.isHttps ? https : http; + httpOrHttps = !this.environment.isClientAttls && this.environment.loopbackConfig.isHttps ? https : http; } } //if not internal routing @@ -1400,7 +1400,8 @@ function WebApp(options){ this.setValidReferrers(); this.wsEnvironment = { loopbackConfig: this.loopbackConfig, - agentRequestOptions: zluxUtil.getAgentRequestOptions(options.zoweConfig, options.tlsOptions, false) + agentRequestOptions: zluxUtil.getAgentRequestOptions(options.zoweConfig, options.tlsOptions, false), + isClientAttls: zluxUtil.isClientAttls(options.zoweConfig) } this.auth = options.auth; this.configLocation = options.configLocation; @@ -1503,7 +1504,7 @@ WebApp.prototype = { host = requestOptions.host; port = requestOptions.port; options.urlPrefix = requestOptions.path; - options.isHttps = requestOptions.protocol == 'https:'; + options.isHttps = !zluxUtil.isClientAttls(this.options.zoweConfig) && (requestOptions.protocol == 'https:'); options.requestProcessingOptions = requestOptions.requestProcessingOptions; } @@ -1522,6 +1523,7 @@ WebApp.prototype = { let tlsOptions = Object.assign({}, this.options.tlsOptions); delete tlsOptions.key; delete tlsOptions.cert; + isHttps = !zluxUtil.isClientAttls(this.options.zoweConfig) && isHttps; installLog.info(`ZWED0053I`, `${isHttps? 'HTTPS' : 'HTTP'}`, `${pluginID}:${serviceName}`, `${host}:${port}/${urlPrefix}`); //installLog.info(`Setting up ${isHttps? 'HTTPS' : 'HTTP'} proxy ` +`(${pluginID}:${serviceName}) to destination=${host}:${port}/${urlPrefix}`); let myProxy = proxy.makeSimpleProxy(host, port, { urlPrefix, @@ -1785,6 +1787,7 @@ WebApp.prototype = { }, _makeRouter: function *(service, plugin, pluginContext, pluginChain) { + const isHttps = !this.wsEnvironment.isClientAttls && service.isHttps; const serviceRouterWithMiddleware = pluginChain.slice(); serviceRouterWithMiddleware.push(commonMiddleware.injectServiceDef( service)); @@ -1840,7 +1843,7 @@ WebApp.prototype = { case "external": // installLog.info(`${plugin.identifier}: installing external proxy at ${subUrl}`); router = this.makeExternalProxy(service.host, service.port, - service.urlPrefix, service.isHttps, + service.urlPrefix, isHttps, undefined, plugin.identifier, service.name); break; default: diff --git a/lib/webserver.js b/lib/webserver.js index 8b3619cc..e1a0a95f 100644 --- a/lib/webserver.js +++ b/lib/webserver.js @@ -334,7 +334,7 @@ WebServer.prototype = { if (nodeConfig.http && nodeConfig.http.port) { this.httpOptions = {}; } - if (nodeConfig.https && nodeConfig.https.port) { + if (typeof nodeConfig.https == 'object') { let options = nodeConfig.https; this.httpsOptions = {}; if (typeof nodeConfig.allowInvalidTLSProxy == 'boolean') { diff --git a/plugins/sso-auth/lib/apimlHandler.js b/plugins/sso-auth/lib/apimlHandler.js index 3659f341..18da936b 100644 --- a/plugins/sso-auth/lib/apimlHandler.js +++ b/plugins/sso-auth/lib/apimlHandler.js @@ -10,7 +10,10 @@ const Promise = require('bluebird'); const https = require('https'); +const http = require('http'); const fs = require('fs'); +const zluxUtil = require('../../../lib/util'); + /*495 minutes default session length for zosmf * TODO: This is the session length of a zosmf session according to their documentation. @@ -52,11 +55,19 @@ function readUtf8FilesToArray(fileArray) { class ApimlHandler { - constructor(pluginDef, pluginConf, serverConf, context) { + constructor(pluginDef, pluginConf, componentConf, context, zoweConf) { this.logger = context.logger; - this.apimlConf = serverConf.node.mediationLayer.server; + this.apimlConf = componentConf.node.mediationLayer.server; this.gatewayUrl = `https://${this.apimlConf.gatewayHostname}:${this.apimlConf.gatewayPort}`; - this.httpsAgent = new https.Agent(context.tlsOptions); + this.isHttps = !zluxUtil.isClientAttls(zoweConf); + if (this.isHttps) { + this.httpsAgent = new https.Agent(context.tlsOptions); + this.httpModule = https; + } else { + this.httpAgent = new http.Agent(); + this.httpModule = http; + } + } logout(request, sessionState) { @@ -77,7 +88,7 @@ class ApimlHandler { agent: this.httpsAgent } this.logger.debug(`Sending logout request for ${sessionState.username} to path=${options.path}`); - const req = https.request(options, (res) => { + const req = this.httpModule.request(options, (res) => { let data = []; res.on('data', (d) => {data.push(d)}); res.on('end', () => { @@ -223,7 +234,7 @@ class ApimlHandler { let data = []; this.logger.debug(`Sending query token request to path=${options.path}`); - const req = https.request(options, (res) => { + const req = this.httpModule.request(options, (res) => { res.on('data', (chunk) => data.push(chunk)); res.on('end', () => { this.logger.debug(`Query rc=`,res.statusCode); @@ -263,7 +274,7 @@ class ApimlHandler { const options = this.makeOptions('/gateway/api/v1/auth/login','POST', undefined, data.length); this.logger.debug(`Sending login request for ${request.body.username} to path=${options.path}`); - const req = https.request(options, (res) => { + const req = this.httpModule.request(options, (res) => { res.on('data', (d) => {}); res.on('end', () => { this.logger.debug(`Login rc=`,res.statusCode); @@ -400,6 +411,6 @@ class ApimlHandler { } } -module.exports = function(pluginDef, pluginConf, serverConf, context) { - return new ApimlHandler(pluginDef, pluginConf, serverConf, context); +module.exports = function(pluginDef, pluginConf, componentConf, context, zoweConf) { + return new ApimlHandler(pluginDef, pluginConf, componentConf, context, zoweConf); } diff --git a/plugins/sso-auth/lib/ssoAuth.js b/plugins/sso-auth/lib/ssoAuth.js index be38e022..2c5ccd7b 100644 --- a/plugins/sso-auth/lib/ssoAuth.js +++ b/plugins/sso-auth/lib/ssoAuth.js @@ -52,7 +52,7 @@ function cleanupSessionGeneric(sessionState) { delete sessionState.sessionExpTime; } -function SsoAuthenticator(pluginDef, pluginConf, serverConf, context) { +function SsoAuthenticator(pluginDef, pluginConf, serverConf, context, zoweConf) { this.usingApiml = doesApimlExist(serverConf); this.usingZss = doesZssExist(serverConf); @@ -65,7 +65,7 @@ function SsoAuthenticator(pluginDef, pluginConf, serverConf, context) { this.logger = context.logger; this.categories = ['saf']; if (this.usingApiml) { - this.apimlHandler = apimlHandlerFactory(pluginDef, pluginConf, serverConf, context); + this.apimlHandler = apimlHandlerFactory(pluginDef, pluginConf, serverConf, context, zoweConf); this.categories.push('apiml'); } @@ -325,6 +325,6 @@ SsoAuthenticator.prototype = { } }; -module.exports = function (pluginDef, pluginConf, serverConf, context) { - return Promise.resolve(new SsoAuthenticator(pluginDef, pluginConf, serverConf, context)); +module.exports = function (pluginDef, pluginConf, serverConf, context, zoweConf) { + return Promise.resolve(new SsoAuthenticator(pluginDef, pluginConf, serverConf, context, zoweConf)); }