Skip to content
This repository has been archived by the owner on Dec 14, 2022. It is now read-only.

Commit

Permalink
Adding support for Multi-ANM, Multi-Domains
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Wiechmann committed Sep 17, 2021
1 parent d2020b9 commit f04147a
Show file tree
Hide file tree
Showing 16 changed files with 431 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const https = require('https');
const { sendRequest, _getCookie, getManagerConfig } = require('./utils');
const { sendRequest, _getCookie, getManagerConfig, getANMConfig } = require('./utils');
const fs = require('fs');
const path = require('path');

Expand Down Expand Up @@ -39,7 +39,7 @@ const securityDeviceTypes = {
* does not define "next", the first defined output).
*/
async function lookupCurrentUser(params, options) {
const { requestHeaders, getApiManagerUser } = params;
const { requestHeaders, getApiManagerUser, region } = params;
var { unrestrictedPermissions } = params;
const logger = options.logger;
cache = options.pluginContext.cache;
Expand All @@ -60,9 +60,9 @@ async function lookupCurrentUser(params, options) {
}
if(requestHeaders.authorization) {
logger.debug(`Trying to authorize user based on Authorization header.`);
user.loginName = await _getCurrentGWUser(headers = {'Authorization': `${requestHeaders.authorization}`});
user.loginName = await _getCurrentGWUser(headers = {'Authorization': `${requestHeaders.authorization}`}, region, logger);
logger.debug(`Authorized user is: ${user.loginName}`);
permissions = await _getCurrentGWPermissions(headers = {'Authorization': `${requestHeaders.authorization}`}, user.loginName);
permissions = await _getCurrentGWPermissions(headers = {'Authorization': `${requestHeaders.authorization}`}, user.loginName, region);
} else {
VIDUSR = _getCookie(requestHeaders.cookie, "VIDUSR");
if(!VIDUSR) {
Expand All @@ -78,15 +78,15 @@ async function lookupCurrentUser(params, options) {
}
logger.trace(`Trying to get current user based on VIDUSR cookie.`);
try {
user.loginName = await _getCurrentGWUser(headers = {'Cookie': requestHeaders.cookie});
user.loginName = await _getCurrentGWUser(headers = {'Cookie': requestHeaders.cookie}, region, logger);
} catch (err) {
// Might happen if the request has been sent to the wrong ANM by a Load-Balancer in between. (Session Stickyness not working as expected)
// Only mitigating the problem, but not really fully solving the issue - Load-Balanced request must be investigated
logger.warn(`Unexpected error while trying to get current user from the ANM. Using a Load-Balancer which is not sticky?! Try again at least once.`);
user.loginName = await _getCurrentGWUser(headers = {'Cookie': requestHeaders.cookie});
user.loginName = await _getCurrentGWUser(headers = {'Cookie': requestHeaders.cookie}, region, logger);
}
logger.trace(`Current user is: ${user.loginName}`);
permissions = await _getCurrentGWPermissions(headers = {'Cookie': requestHeaders.cookie, 'csrf-token': requestHeaders['csrf-token']}, user.loginName);
permissions = await _getCurrentGWPermissions(headers = {'Cookie': requestHeaders.cookie, 'csrf-token': requestHeaders['csrf-token']}, user.loginName, region);
}
if(unrestrictedPermissions.split(",").every( function(perm) { return permissions.includes(perm); })) {
user.gatewayManager.isUnrestricted = true;
Expand Down Expand Up @@ -120,7 +120,7 @@ async function lookupCurrentUser(params, options) {
}

async function lookupTopology(params, options) {
const { requestHeaders } = params;
const { requestHeaders, region } = params;
const logger = options.logger;
pluginConfig = options.pluginConfig;

Expand All @@ -131,7 +131,7 @@ async function lookupTopology(params, options) {
if(!requestHeaders.cookie && !requestHeaders.authorization) {
throw new Error('You must provide either the VIDUSR cookie + csrf-token or an HTTP-Basic Authorization header.');
}
let cacheKey = requestHeaders.host;
let cacheKey = `${requestHeaders.host}###region`;
if(!requestHeaders.host) {
logger.warn(`Host header not found, using static cache-key for the API-Gateway topology lookup.`);
cacheKey = "apigwTopology";
Expand All @@ -142,10 +142,10 @@ async function lookupTopology(params, options) {
var topology;
if(requestHeaders.authorization) {
logger.debug(`Trying to get API-Gateway topology based on Authorization header.`);
topology = await _getTopology(headers = {'Authorization': `${requestHeaders.authorization}`}, logger);
topology = await _getTopology(headers = {'Authorization': `${requestHeaders.authorization}`}, region, logger);
} else {
logger.trace(`Trying to get API-Gateway topology based on VIDUSR cookie.`);
topology = await _getTopology(headers = {'Cookie': requestHeaders.cookie, 'csrf-token': requestHeaders['csrf-token']}, logger);
topology = await _getTopology(headers = {'Cookie': requestHeaders.cookie, 'csrf-token': requestHeaders['csrf-token']}, region, logger);
}
if(topology.services) {
topology.services = topology.services.filter(function(service) {
Expand Down Expand Up @@ -465,34 +465,54 @@ async function _getGroupRegionFilename(basefilename, groupId, region) {
return result;
}

async function _getCurrentGWUser(requestHeaders) {
async function _getCurrentGWUser(requestHeaders, region, logger) {
var options = {
path: '/api/rbac/currentuser',
headers: requestHeaders,
agent: new https.Agent({ rejectUnauthorized: false })
};
var loginName = await sendRequest(pluginConfig.apigateway.url, options)
var anmConfig = await getANMConfig(pluginConfig.apigateway, region);
if(region) {
logger.debug(`Trying to read current user from Admin-Node-Manager: ${anmConfig.url} based on region: ${region}`);
} else {
logger.debug(`Trying to read current user from Admin-Node-Manager: ${anmConfig.url}`);
}
var loginName = await sendRequest(anmConfig.url, options)
.then(response => {
return response.body.result;
})
.catch(err => {
throw new Error(`Error getting current user. Request sent to: '${pluginConfig.apigateway.url}'. Response-Code: ${err.statusCode}`);
if(region) {
throw new Error(`Error getting current user. Request sent to: '${anmConfig.url}' based on given region: '${region}'. Response-Code: ${err.statusCode}`);
} else {
throw new Error(`Error getting current user. Request sent to: '${anmConfig.url}'. Response-Code: ${err.statusCode}`);
}
});
return loginName;
}

async function _getTopology(requestHeaders, logger) {
async function _getTopology(requestHeaders, region, logger) {
var options = {
path: '/api/topology',
headers: requestHeaders,
agent: new https.Agent({ rejectUnauthorized: false })
};
var topology = await sendRequest(pluginConfig.apigateway.url, options)
var anmConfig = await getANMConfig(pluginConfig.apigateway, region);
if(region) {
logger.debug(`Trying to read topology from Admin-Node-Manager: ${anmConfig.url} based on region: ${region}`);
} else {
logger.debug(`Trying to read topology from Admin-Node-Manager: ${anmConfig.url}`);
}
var topology = await sendRequest(anmConfig.url, options)
.then(response => {
return response.body.result;
})
.catch(err => {
logger.error(`Error getting API-Gateway topology from Admin-Node-Manager. Request sent to: '${pluginConfig.apigateway.url}'. Response-Code: ${err.statusCode}`);
if(region) {
logger.error(`Error getting API-Gateway topology from Admin-Node-Manager based on given region: ${region}. Request sent to: '${anmConfig.url}'. Response-Code: ${err.statusCode}`);
} else {
logger.error(`Error getting API-Gateway topology from Admin-Node-Manager. Request sent to: '${anmConfig.url}'. Response-Code: ${err.statusCode}`);
}
logger.error(`This error will cause the application to fail in a future release.`);
return {};
// During a grace period it not cause the entire application to fail - Just EMT will not include all services.
Expand Down Expand Up @@ -593,18 +613,19 @@ async function _getBackendBasePath(apiProxy, operationId) {
throw new Error('_getBackendBasePath with operationId not yet implemented.');
}

async function _getCurrentGWPermissions(requestHeaders, loginName) {
async function _getCurrentGWPermissions(requestHeaders, loginName, region) {
var options = {
path: '/api/rbac/permissions/currentuser',
headers: requestHeaders,
agent: new https.Agent({ rejectUnauthorized: false })
};
var result = await sendRequest(pluginConfig.apigateway.url, options)
var anmConfig = getANMConfig(pluginConfig.apigateway, region);
var result = await sendRequest(anmConfig.url, options)
.then(response => {
return response.body.result;
});
if(result.user!=loginName) {
throw new Error(`Error reading current permissions from API-Gateway Manager. Loginname: ${loginName} does not match to retrieved user: ${result.user}.`);
throw new Error(`Error reading current permissions from API-Gateway Manager (${anmConfig.url}). Loginname: ${loginName} does not match to retrieved user: ${result.user}.`);
}
return result.permissions;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ flow-nodes:
type: string
getApiManagerUser:
name: Get the API-Manager user?
description: If set to false, the flow-node does not tries to lookup the user on the API-Manager. Set it to false, when for instance user authorization is disabled as there might be no belonging user configured in the API-Manager.
description: If set to false, the flow-node does not try to lookup the user on the API-Manager. Set it to false, when for instance user authorization is disabled as there might be no belonging user configured in the API-Manager.
required: false
initialType: boolean
schema:
Expand All @@ -30,6 +30,12 @@ flow-nodes:
required: false
schema:
type: string
region:
name: Region
description: 'If the region is set, the corresponding admin node manager is determined based on this region. If no region is set, then the default Admin-Node-Manager is used.'
required: false
schema:
type: string
outputs:
next:
name: Next
Expand All @@ -54,6 +60,12 @@ flow-nodes:
required: true
schema:
type: string
region:
name: Region
description: 'If the region is set, the corresponding admin node manager is determined based on this region. If no region is set, then the default Admin-Node-Manager is used.'
required: false
schema:
type: string
outputs:
next:
name: Next
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { SDK } = require('@axway/api-builder-sdk');
const { lookupCurrentUser, lookupTopology, lookupAPIDetails, getCustomPropertiesConfig, isIgnoreAPI, lookupApplication } = require('./actions');
const { mergeCustomProperties } = require('./customProperties');
const NodeCache = require( "node-cache" );
const { checkAPIManagers, parseAPIManagerConfig } = require('./utils');
const { checkAPIManagers, parseAPIManagerConfig, parseANMConfig } = require('./utils');
const https = require('https');

/**
Expand Down Expand Up @@ -33,6 +33,7 @@ async function getPlugin(pluginConfig, options) {
if(!pluginConfig.apigateway.url) {
throw new Error(`Required parameter: apigateway.url is not set.`);
}
await parseANMConfig(pluginConfig, options);
await parseAPIManagerConfig(pluginConfig, options);
if(pluginConfig.validateConfig==true) {
var result = await checkAPIManagers(pluginConfig.apimanager, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,54 @@ function getManagerConfig(apiManagerConfig, groupId, region) {
}
}

async function parseANMConfig(pluginConfig, options) {
pluginConfig.apigateway.configs = {};
// Check, if multiple Admin-Node-Manager URLs based on the region are configured (Format: region|anmUrl)
if(pluginConfig.apigateway.url.indexOf('|')!=-1) {
pluginConfig.apigateway.perRegion = true;
options.logger.info(`Parse region based ADMIN_NODE_MANAGER: ${pluginConfig.apigateway.url}.`);
// Looks like ANM URLs are given based on regions
pluginConfig.apigateway.url.split(',').forEach(regionAndURL => {
regionAndURL = regionAndURL.trim().toLowerCase().split('|');
if(regionAndURL.length == 1) {
// The default Admin-Node-Manager
options.logger.debug(`Found default Admin-Node-Manager URL: ${regionAndURL[0]}`);
pluginConfig.apigateway.configs.default = { url: regionAndURL[0] }
} else if(regionAndURL.length == 2) {
// Only the Region is given
options.logger.debug(`Found Admin-Node-Manager URL: ${regionAndURL[1]} for region: ${regionAndURL[0]}`);
pluginConfig.apigateway.configs[`${regionAndURL[0]}`] = { url: regionAndURL[1] };
} else {
return Promise.reject(`Unexpected Admin-Node-Manager (ADMIN_NODE_MANAGER) format: ${regionAndURL}`);
}
});
} else { // If not, create a default Admin-Node-Manager config object
options.logger.info(`Using only default Admin-Node-Manager: ${pluginConfig.apigateway.url}.`);
pluginConfig.apigateway.configs.default = {
url: pluginConfig.apigateway.url
}
}
}

function getANMConfig(anmConfig, region) {
if(region == undefined) {
if(anmConfig.configs.default == undefined) {
throw new Error(`Cannot return Admin-Node-Manager config without a region as no default Admin-Node-Manager is configured.`);
} else {
return anmConfig.configs.default;
}
}
var key = region.toLowerCase();
if (anmConfig.configs && anmConfig.configs[key]) {
return anmConfig.configs[key];
} else {
if(!anmConfig.configs.default) {
throw new Error(`Cannot return Admin-Node-Manager config for region: '${region}' as no default Admin-Node-Manager is configured.`);
}
return anmConfig.configs.default;
}
}

async function checkAPIManagers(apiManagerConfig, options) {
var finalResult = { isValid: true };
for (const [key, config] of Object.entries(apiManagerConfig.configs)) {
Expand Down Expand Up @@ -223,6 +271,8 @@ module.exports = {
_getCookie,
isDeveloperMode,
getManagerConfig,
getANMConfig,
checkAPIManagers,
parseAPIManagerConfig
parseAPIManagerConfig,
parseANMConfig
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ describe('Tests Topology-Lookup', () => {
// Delete the cached module
decache('../config/axway-api-utils.default.js');
var pluginConfig = require('../config/axway-api-utils.default.js').pluginConfig['api-builder-plugin-axway-api-management'];
// Simulate a regional configuration, which is used in the regional test
pluginConfig.apigateway.url = "https://mocked-api-gateway:8190, dc1|https://mocked-dc1-api-gateway:8190, dc2|https://mocked-dc2-api-gateway:8190";

beforeEach(async () => {
plugin = await MockRuntime.loadPlugin(getPlugin,pluginConfig);
Expand Down Expand Up @@ -59,5 +61,18 @@ describe('Tests Topology-Lookup', () => {
expect(value.services).to.lengthOf(3); // We expect only 3 services, as the ANM is removed already
expect(output).to.equal('next');
});

it('should result into the API-Gateway topology', async () => {
nock('https://mocked-dc2-api-gateway:8190').get('/api/topology').replyWithFile(200, './test/testReplies/gateway/gatewayEMTTopology.json');

const { value, output } = await flowNode.lookupTopology({
requestHeaders: {"host":"api-gateway:8090","max-forwards":"20", "cookie":"VIDUSR=1597381095-XTawGDtJhBA7Zw==;", "csrf-token": "CF2796B3BD18C1B0B5AB1C8E95B75662E92FBC04BD799DEB97838FC5B9C39348"},
region: "DC2"
});

expect(value.emtEnabled).to.equal(true);
expect(value.services).to.lengthOf(3); // We expect only 3 services, as the ANM is removed already
expect(output).to.equal('next');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ describe('Tests User-Lookup with complete configuration parameters', () => {
// Delete the cached module
decache('../config/axway-api-utils.default.js');
var pluginConfig = require('../config/axway-api-utils.default.js').pluginConfig['api-builder-plugin-axway-api-management'];
// Simulate a regional configuration, which is used in the regional test
pluginConfig.apigateway.url = "https://mocked-api-gateway:8190, dc1|https://mocked-dc1-api-gateway:8190, dc2|https://mocked-dc2-api-gateway:8190";

beforeEach(async () => {
plugin = await MockRuntime.loadPlugin(getPlugin,pluginConfig);
Expand Down Expand Up @@ -114,6 +116,24 @@ describe('Tests User-Lookup with complete configuration parameters', () => {
expect(output).to.equal('next');
});

it('should result into an API-Gateway Admin-User based on the given region.', async () => {
nock('https://mocked-dc2-api-gateway:8190').get('/api/rbac/currentuser').reply(200, { "result": "dc2ApigatewayUser" });
nock('https://mocked-dc2-api-gateway:8190').get('/api/rbac/permissions/currentuser').replyWithFile(200, './test/testReplies/gateway/dc2-apiGatewayUser.json');

const { value, output } = await flowNode.lookupCurrentUser({
requestHeaders: {"host":"api-gateway:8090","max-forwards":"20", "cookie":"VIDUSR=1597381095-XTawGDtJhBA7Zw==;", "csrf-token": "CF2796B3BD18C1B0B5AB1C8E95B75662E92FBC04BD799DEB97838FC5B9C39348"},
region: "DC2"
});

expect(value).to.deep.equal({
"loginName": "dc2ApigatewayUser",
"gatewayManager": {
"isUnrestricted": true
}
});
expect(output).to.equal('next');
});

it('should result into an unrestricted API-Gateway User (based on permission: logs), which requires no lookup to the API-Manager', async () => {
nock('https://mocked-api-gateway:8190').get('/api/rbac/currentuser').reply(200, { "result": "operator" });
nock('https://mocked-api-gateway:8190').get('/api/rbac/permissions/currentuser').replyWithFile(200, './test/testReplies/gateway/gatewayLogsPermissions.json');
Expand Down
Loading

0 comments on commit f04147a

Please sign in to comment.