Using Apigeelint to require policies and shared flows in Proxy PreFlow

For an introduction to creating custom plugins, see @Eduardo Andrade's excellent article How to develop new plugins for apigeelint.

Now that Apigeelint (Git repo, npm) allows you to bring-your-own-plugins, you can easily extend it's functionality without the need to place the plugins in the official installation directory, for example, they could be a "shared library" in Jenkins.

One common request for extension to Apigeelint is to change the naming conventions and there is an example here. Other common requests are to require the use of specific policies by name or to require the use of a specific shared flow. The examples below require the use of a policy named FC-security that uses the security-v1 Shared Flow in the Proxy PreFlow.

To use these examples, place them in a folder and run Apigeelint using:

apigeelint -s ./apiproxy -x $SHARED_LOCATION/apigeelint_externalPlugins -f table.js 

NOTE 1: If your external plugin uses "require" you need to specify the path to the Apigeelint node_modules. This can be done by setting NODE_PATH=/usr/local/lib/node_modules/apigeelint/node_modules

NOTE 2: While developing your plugin you may want to add debug statements and then enable debugging output using DEBUG=extensions:EX-PF001 or DEBUG=extensions:*

Example command:

NODE_PATH=/usr/local/lib/node_modules/apigeelint/node_modules DEBUG=extensions:* apigeelint -x $HOME/work/apigeelint_externalPlugins -s ./apiproxy -f table.js 

Example Output:

10419-screen-shot-2020-10-05-at-55739-pm.png

Example plugin EX-PF001 - Require Named Policies in PreFlow Request
//EX-PF001-RequirePoliciesPreFlowRequest.js
var plugin = {
    ruleId: "EX-PF001",
    name: "Require Named Policies in PreFlow Request.",
    message: "Require one or more specific policies be used in the PreFlow.",
    fatal: false,
    severity: 2, // error, 1=warning
    nodeType: "ProxyEndpoint",
    enabled: true
  },
  debug = require("debug")("extensions:" + plugin.ruleId),
  requiredPolicies = [
    "VA-header",
    "AM-remove-x-apikey",
    "FC-security"
  ];

var onProxyEndpoint = function(ep, cb) {
  var proxyName = ep.getName();
  var missingPolicies = [], preFlowSteps = [];
  ep.getPreFlow() &&
  ep.getPreFlow().getFlowRequest() &&
  ep.getPreFlow().getFlowRequest().getSteps().forEach(function(step){
    preFlowSteps.push(step.getName());
  });
  
  debug( "ProxyName: " + proxyName);
  debug( "Required: " + requiredPolicies);
  debug( "PreFlowSteps: " + preFlowSteps);
  requiredPolicies.forEach(s => {
    if( !preFlowSteps.includes(s) ) {
      missingPolicies.push(s);
    }
  });

  var warnErr = missingPolicies.length > 0 ? true : false;
  if (warnErr) {
    ep.addMessage({
      plugin,
      message: 'ProxyEndpoint named: "' + proxyName + '" requires policies: "' + requiredPolicies + '" but is missing policies: "' + missingPolicies + '"'
    });
    warnErr = true;
  }
  if (typeof(cb) == 'function') {
    cb(null, warnErr);
  }
};

module.exports = {
  plugin,
  onProxyEndpoint
};
Example plugin EX-PF002 Require Shared Flow PreFlow Request
//EX-PF002-RequireSharedFlowsPreFlowRequest.js
var plugin = {
    ruleId: "EX-PF002",
    name: "Require Shared Flow PreFlow Request.",
    message: "Require Flow Callout policy with specific Shared Flow reference in PreFlow Request.",
    fatal: true,
    severity: 2, // error, 1=warning
    nodeType: "Bundle",
    enabled: true
  },
  debug = require("debug")("extensions:" + plugin.ruleId),
  xpath = require("xpath"),
  policyMap = new Map(),
  requiredSharedFlows = [
    "security-v1",
    "cors-v1"
  ];

var onBundle = function(bundle, cb) {
  // policyMap = new Map();
  bundle.getPolicies().forEach(function(p) {
    if( p.getType() == "FlowCallout" ) {
      sharedFlowName = xpath.select("string(/FlowCallout/SharedFlowBundle)",p.getElement());
      policyMap.set( sharedFlowName, p.getName() );
      debug( "Policy: " + p.getType() + " " + sharedFlowName + " used in: " + p.getName());
    }
  })
  
  if (typeof cb == "function") {
    cb(null, false);
  }
};

var onProxyEndpoint = function(ep, cb) {
  var proxyName = ep.getName();
  debug("ProxyEndpoint: " + ep.getName());
  var missingSharedFlows = [], preFlowSteps = [];
  
  ep.getPreFlow() &&
  ep.getPreFlow().getFlowRequest() &&
  ep.getPreFlow().getFlowRequest().getSteps().forEach(function(step){
    preFlowSteps.push(step.getName());
  });

  debug( "Required: " + requiredSharedFlows);
  requiredSharedFlows.forEach(sf => {
    debug( "forEach SF: " + sf + " " + policyMap.get(sf));
    if( !policyMap.has(sf) ) {
      missingSharedFlows.push(sf);
    }
  });
    
  var warnErr = missingSharedFlows.length > 0 ? true : false;
  if (warnErr) {
    ep.addMessage({
      plugin,
      message: 'ProxyEndpoint named: "' + proxyName + '" requires Shared Flow usage for: "' + requiredSharedFlows + '" but is missing Shared Flows: "' + missingSharedFlows + '"'
    });
    warnErr = true;
  }
  if (typeof(cb) == 'function') {
    cb(null, warnErr);
  }
};

module.exports = {
  plugin,
  onBundle,
  onProxyEndpoint
};

Enjoy and keep on linting!

Comments
dchiesa1
Staff

nice example!

Version history
Last update:
‎10-05-2020 02:55 PM
Updated by: