For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

Microsoft Office 365 IP Intelligence - V0.2

Problem this snippet solves:

Update to V0.1 - Microsoft Office 365 IP intelligence

Added a new method to check if a URL is part of Office365 IPI

How to use this snippet:

Refer to this Article: Intelligent Proxy Steering - Office365

Code :

/**
*** Name   : office365_ipi_extension
*** Author : Niels van Sluis, 
*** Modified by: Brett Smith @f5
*** Version: 0.2
*** Date   : 2018-02-25
**/
 
'use strict';
 
// Import the f5-nodejs module and others.
var f5 = require('f5-nodejs');
var parseString = require('xml2js').parseString;
var https = require('https');
var repeat = require('repeat');
var loki = require('lokijs');
var ipRangeCheck = require('ip-range-check');
 
// Create (in-memory) LokiJS database.
var db = new loki('db.json');
var products = db.addCollection('products');
 
// Create a new rpc server for listening to TCL iRule calls.
var ilx = new f5.ILXServer();
 
// URL to Microsoft Office 365 XML file.
var url = "https://support.content.office.net/en-us/static/O365IPAddresses.xml";
 
// Function to get XML file and convert to JSON object.
function xmlToJson(url, callback) {
    var req = https.get(url, function(res) {
        var xml = '';
 
        res.on('data', function(chunk) {
            xml += chunk;
        });
 
        res.on('error', function(e) {
            callback(e, null);
        }); 
 
        res.on('timeout', function(e) {
            callback(e, null);
        }); 
 
        res.on('end', function() {
            if(res.statusCode == 200) {
                parseString(xml, function(err, result) {
                    callback(null, result);
                });
            }
        });
    });
}
 
// Function that uses the data in the XML file to create a database
// that can be used to perform IP address and URL lookups.
function getOffice365() {
    xmlToJson(url, function(err,data) {
        if(err) {
            console.log("Error: xmlToJson failed");
            return;
        }
        
        // if xml happens to be empty due to an error, do not continue.
        if(!data) {
            console.log("Error: No data in XML file");
            return;
        }
        
        // Get date updated: 7/13/2017
        var productsUpdated = data.products.$.updated;
        
        // Only update if version changed
        var versionCheck = products.findObject({'name':'any'});
        if(versionCheck && versionCheck.version == productsUpdated) {
            console.log("Info: product version didn't changed; No update required");
            return;
        }
        
        var allIpAdresses = [];
        var allUrls = [];
 
        data.products.product.forEach(function (product) {
            var ipAddresses = [];
            var urls = [];
 
            product.addresslist.forEach(function (addresslist) {
                if(addresslist.$.type == "IPv4" || addresslist.$.type == "IPv6") {
                     if ( typeof addresslist.address !== 'undefined' && addresslist.address ) {
                        addresslist.address.forEach(function (address) {
                            ipAddresses.push(address);
                            allIpAdresses.push(address);
                        });
                    }
                } else if(addresslist.$.type == "URL") {
                    if ( typeof addresslist.address !== 'undefined' && addresslist.address ) {
                        addresslist.address.forEach(function (address) {
                            if(address.indexOf('*') !== -1) {
                                // Start Version 0.2 - Added by Brett Smith @f5.com
                                // remove the wilcard (*) from the URL
                                address = address.substr(address.indexOf('*')+1);
                                // End Version 0.2
                                urls.push(address);
                                allUrls.push(address);
                            } else {    
                                urls.push(address);
                                allUrls.push(address);
                            }
                        });
                    }
                }
            });
 
            var p = products.findObject({'name':product.$.name.toLowerCase()});
            if(!p) {
                products.insert({ name: product.$.name.toLowerCase(), ipAddresses: ipAddresses, urls: urls, version: productsUpdated });
            } else {
                p.ipAddresses = ipAddresses;
                p.urls = urls;
                p.version = productsUpdated;
            }
        });
        
        var p = products.findObject({'name':'any'});
        if(!p) {
            products.insert({ name: 'any', ipAddresses: allIpAdresses, urls: allUrls, version: productsUpdated });
        } else {
            p.ipAddresses = Array.from(new Set(allIpAdresses));
            p.urls = Array.from(new Set(allUrls));
            p.version = productsUpdated;
        }
        console.log("Info: update finished; " + products.data.length + " product records in database.");
    });
}
 
// refresh Microsoft Office 365 XML every hour
repeat(getOffice365).every(1, 'hour').start.now();
 
// Start Version 0.2 - Added by Brett Smith @f5.com
// ILX::call to check if an URL is part of Office365
ilx.addMethod('checkProductURL', function(objArgs, objResponse) {
    var productName = objArgs.params()[0];
    var hostName = objArgs.params()[1];
    
    var verdict = false;
    
    var req = products.findObject( { 'name':productName.toLowerCase()});
    if(Array.isArray(req.urls)) {
        for (let url of req.urls) {
            if(hostName.indexOf(url) !== -1) {
                console.log("match found:" + url);
                verdict = true;
                break;
            }
        }
    }

    // return verdict to Tcl iRule
    objResponse.reply(verdict);
});
// End Version 0.2

// ILX::call to check if an IP address is part of Office365
ilx.addMethod('checkProductIP', function(objArgs, objResponse) {
    var productName = objArgs.params()[0];
    var ipAddress = objArgs.params()[1];
    
    // fail-open = true, fail-close = false
    var verdict = true;
    
    var req = products.findObject( { 'name':productName.toLowerCase()});
    if(req) {
        verdict = ipRangeCheck(ipAddress, req.ipAddresses);
    }
 
    // return AuthnRequest to Tcl iRule
    objResponse.reply(verdict);
});
 
// Start listening for ILX::call and ILX::notify events.
ilx.listen();

Tested this on version:

13.0
Published Apr 20, 2018
Version 1.0

3 Comments