diff --git a/CHANGELOG.md b/CHANGELOG.md index a20c32ff..823b2322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to the Zlux Server Framework package will be documented in t This repo is part of the app-server Zowe Component, and the change logs here may appear on Zowe.org in that section. ## 2.14.0 +- Bugfix: App-server could not load when multiple discovery servers were present and the app-server was unable to reach the first one specified. Now, the app-server will iterate through the list of servers until an accessible one is reached. (#522) - Bugfix: App-server would not correctly detect when it was running in a high-availability configuration environment. (#521) - Bugfix: A call to GET /plugins would trigger an authorization check regardless of if rbac was set on or off (#523) diff --git a/lib/apiml.js b/lib/apiml.js index 7b71082b..62aa582b 100644 --- a/lib/apiml.js +++ b/lib/apiml.js @@ -77,9 +77,9 @@ const MEDIATION_LAYER_INSTANCE_DEFAULTS = (zluxProto, zluxHostname, zluxPort) => } }}; -function ApimlConnector({ hostName, port, isHttps, discoveryHost, +function ApimlConnector({ hostName, port, isHttps, discoveryUrls, discoveryPort, tlsOptions, eurekaOverrides }) { - Object.assign(this, { hostName, port, isHttps, discoveryHost, + Object.assign(this, { hostName, port, isHttps, discoveryUrls, discoveryPort, tlsOptions, eurekaOverrides }); this.vipAddress = hostName; } @@ -110,23 +110,13 @@ ApimlConnector.prototype = { const end = Date.now() + timer; return new Promise((resolve, reject) => { - const options = Object.assign({ - host: this.discoveryHost, - port: this.discoveryPort, - method: 'GET', - path: `/eureka/apps/${serviceName}`, - headers: {'accept':'application/json'} - }, this.tlsOptions); - - if (!this.tlsOptions.rejectUnauthorized) { - //Keeping these certs causes an openssl error 46, unknown cert error in a dev environment - delete options.cert; - delete options.key; - } //else, apiml expects a cert and will give a 403. + const optionsArray = this.getRequestOptionsArray('GET', `/eureka/apps/${serviceName}`); + let optionsIndex = 0; const issueRequest = () => { + const options = optionsArray[optionsIndex]; if (Date.now() > end) { - log.warn(`ZWED0045`, this.discoveryHost, this.discoveryPort); + log.warn(`ZWED0045W`, this.discoveryHost, this.discoveryPort); return reject(new Error(`Call timeout when fetching agent status from APIML`)); } @@ -156,7 +146,9 @@ ApimlConnector.prototype = { reject(new Error(`Call timeout when fetching agent status from APIML`)); }); req.on('error', (error) => { - log.warn("APIML query error:", error.message); + log.warn("ZWED0180W", options.host, options.port, error.message); + // + optionsIndex = (optionsIndex+1) % optionsArray.length; setTimeout(issueRequest, AGENT_CHECK_RECONNECT_DELAY); }); req.end(); @@ -249,8 +241,7 @@ ApimlConnector.prototype = { } log.debug("ZWED0144I", JSON.stringify(zluxProxyServerInstanceConfig, null, 2)); //log.debug("zluxProxyServerInstanceConfig: " //+ JSON.stringify(zluxProxyServerInstanceConfig, null, 2)) - const defaultUrl = `https://${this.discoveryHost}:${this.discoveryPort}/eureka/apps`; - const serviceUrls = this.getServiceUrls(defaultUrl); + const serviceUrls = this.getServiceUrls(); zluxProxyServerInstanceConfig.eureka.serviceUrls = { default: serviceUrls }; log.info(`ZWED0020I`, serviceUrls.join(',')); //log.info(`Registering at ${url}...`); log.debug("ZWED0145I", JSON.stringify(zluxProxyServerInstanceConfig)); //log.debug(`zluxProxyServerInstanceConfig ${JSON.stringify(zluxProxyServerInstanceConfig)}`) @@ -285,17 +276,29 @@ ApimlConnector.prototype = { }); }, - getServiceUrls(defaultUrl) { - const discoveryServiceList = process.env['ZWE_DISCOVERY_SERVICES_LIST'] || ''; - const serviceUrls = discoveryServiceList - .split(',') - .map(url => url.trim()) - .filter(url => url.length > 0) - .map(url => url + (url.endsWith('/') ? '' : '/') + 'apps'); - if (serviceUrls.length === 0) { - serviceUrls.push(defaultUrl); - } - return serviceUrls; + getServiceUrls() { + return this.discoveryUrls.map(url => url + (url.endsWith('/') ? '' : '/') + 'apps'); + }, + + getRequestOptionsArray(method, path) { + return this.discoveryUrls.map((url)=>{ + //in the form of https://host:port/eureka/, trim from https:// and following slash. + const hostAndPort = url.substring(8, url.indexOf('/', 8)).split(':'); + const options = Object.assign({ + host: hostAndPort[0], + port: hostAndPort[1], + method: method, + path: path, + headers: {'accept':'application/json'} + }, this.tlsOptions); + + if (!this.tlsOptions.rejectUnauthorized) { + //Keeping these certs causes an openssl error 46, unknown cert error in a dev environment + delete options.cert; + delete options.key; + } //else, apiml expects a cert and will give a 403. + return options; + }); } /* diff --git a/lib/assets/i18n/log/messages_en.json b/lib/assets/i18n/log/messages_en.json index 46725905..a32d573f 100644 --- a/lib/assets/i18n/log/messages_en.json +++ b/lib/assets/i18n/log/messages_en.json @@ -307,6 +307,7 @@ "ZWED0177W":"Unable to load %s for '%s' into config", "ZWED0178W":"Skipping authentication plugin %s because it's not HA compatible", "ZWED0179W":"Unable to retrieve the list of certificate authorities from the keyring=%s owner=%s Error: %s", + "ZWED0180W":"Failed to query discovery server (%s:%s) for agent access: %s", "ZWED0001E":"RESERVED: Error: %s", "ZWED0002E":"Could not stop language manager for types=%s", diff --git a/lib/index.js b/lib/index.js index 0a058589..072dfe50 100755 --- a/lib/index.js +++ b/lib/index.js @@ -219,8 +219,7 @@ Server.prototype = { hostName: webAppOptions.hostname, port: this.port, isHttps: util.isServerHttps(this.zoweConfig), - discoveryHost: apimlConfig.server.hostname, - discoveryPort: apimlConfig.server.port, + discoveryUrls: apimlConfig.server.discoveryUrls || [`https://${apimlConfig.server.hostname}:${apimlConfig.server.port}/eureka/`], tlsOptions: this.tlsOptions, eurekaOverrides: apimlConfig.eureka }); @@ -236,7 +235,7 @@ Server.prototype = { && this.componentConfig.agent.mediationLayer.serviceName && this.componentConfig.node.mediationLayer.server?.gatewayPort) { //at this point, we expect zss to also be attached to the mediation layer, so lets adjust. - webAppOptions.proxiedHost = apimlConfig.server.hostname; + webAppOptions.proxiedHost = apimlConfig.server.gatewayHostname; webAppOptions.proxiedPort = this.componentConfig.node.mediationLayer.server.gatewayPort; if (firstWorker) { yield this.apiml.checkAgent(this.componentConfig.agent.handshakeTimeout, diff --git a/lib/webapp.js b/lib/webapp.js index 96e0b68a..48b5e631 100644 --- a/lib/webapp.js +++ b/lib/webapp.js @@ -268,7 +268,8 @@ function getUserEnv(rbac, zoweConfig){ "ZWED_node_mediationLayer_enabled": nodeConfig.mediationLayer.enabled, "ZWED_node_mediationLayer_server_hostname": nodeConfig.mediationLayer.server.hostname, - + "ZWED_node_mediationLayer_server_gatewayHostname": nodeConfig.mediationLayer.server.gatewayHostname, + //may diverge from above "ZWE_EXTERNAL_HOSTS": process.env.ZWE_EXTERNAL_HOSTS ? process.env.ZWE_EXTERNAL_HOSTS : zoweConfig.zowe.externalDomains.join(','), "ZWE_zowe_externalDomains": zoweConfig.zowe.externalDomains.join(','), diff --git a/plugins/sso-auth/lib/apimlHandler.js b/plugins/sso-auth/lib/apimlHandler.js index 16ebc8d5..17f95d19 100644 --- a/plugins/sso-auth/lib/apimlHandler.js +++ b/plugins/sso-auth/lib/apimlHandler.js @@ -55,7 +55,7 @@ class ApimlHandler { constructor(pluginDef, pluginConf, serverConf, context) { this.logger = context.logger; this.apimlConf = serverConf.node.mediationLayer.server; - this.gatewayUrl = `https://${this.apimlConf.hostname}:${this.apimlConf.gatewayPort}`; + this.gatewayUrl = `https://${this.apimlConf.gatewayHostname}:${this.apimlConf.gatewayPort}`; this.httpsAgent = new https.Agent(context.tlsOptions); } @@ -66,7 +66,7 @@ class ApimlHandler { } const gatewayUrl = this.gatewayUrl; const options = { - hostname: this.apimlConf.hostname, + hostname: this.apimlConf.gatwayHostname, port: this.apimlConf.gatewayPort, //TODO uncertainty about using apicatalog route instead of something part of the gateway itself path: '/apicatalog/api/v1/auth/logout', @@ -194,7 +194,7 @@ class ApimlHandler { } return { - hostname: this.apimlConf.hostname, + hostname: this.apimlConf.gatewayHostname, port: this.apimlConf.gatewayPort, path: path, method: method, diff --git a/plugins/sso-auth/lib/ssoAuth.js b/plugins/sso-auth/lib/ssoAuth.js index f73510fe..be38e022 100644 --- a/plugins/sso-auth/lib/ssoAuth.js +++ b/plugins/sso-auth/lib/ssoAuth.js @@ -18,7 +18,7 @@ const apimlHandlerFactory = require('./apimlHandler'); function doesApimlExist(serverConf) { return ((serverConf.node.mediationLayer !== undefined) && (serverConf.node.mediationLayer.server !== undefined) - && (serverConf.node.mediationLayer.server.hostname !== undefined) + && (serverConf.node.mediationLayer.server.gatewayHostname !== undefined) && (serverConf.node.mediationLayer.server.gatewayPort !== undefined) && (serverConf.node.mediationLayer.server.port !== undefined) && (serverConf.node.mediationLayer.enabled == true))