Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
code share

Microsoft Office 365 IP intelligence

Problem this snippet solves:

This snippet adds Microsoft Office 365 IP intelligence. This snippet parses the O365IPAddresses.xml file that Microsoft supplies to help identify Microsoft URLs and IP address ranges. With this snippet you can check if an IP address belongs to Microsoft and let the BIG-IP decide to allow or deny traffic.

For more info see: Office 365 URLs and IP address ranges

DISCLAIMER: This code has never been tested outside a lab environment. Consider this snippet to be a proof-of-concept.

How to use this snippet:

Prepare BIG-IP

  • Create LX Workspace: office365_ipi
  • Add iRule: office365_ipi_irule
  • Add Extension: office365_ipi_extension
  • Add LX Plugin: office365_ipi_plugin -> From Workspace: office365_ipi

Install node.js modules

# cd /var/ilx/workspaces/Common/office365_ipi/extensions/office365_ipi_extension
# npm install xml2js https repeat lokijs ip-range-check --save

office365_ipi_irule

###
### Name   : office365_ipi_irule
### Author : Niels van Sluis, (niels@van-sluis.nl)
### Version: 0.1
### Date   : 2017-07-25
###
when RULE_INIT {
    # set table timeout to 1 hour
    set static::office365_ipi_timeout 3600
    set static::office365_ipi_lifetime 3600
}

when CLIENT_ACCEPTED {
        # Valid product names are:
        #     o365, LYO, Planner, Teams, ProPlus, OneNote,  Yammer,
        #     EXO, Identity, Office365Video, WAC, SPO, RCA, Sway,
        #     EX-Fed, OfficeMobile, CRLs, OfficeiPad, EOP
        #
        # Use 'any' to match an IP address in all products.
        set productName "o365"
        
        set ipAddress [IP::client_addr]
        set key $productName:$ipAddress
        set verdict [table lookup -notouch $key]
        
        if { $verdict eq "" } {
            log local0. "Need to retrieve verdict via iruleslx"
            set rpc_handle [ILX::init office365_ipi_plugin office365_ipi_extension]
            if {[catch {ILX::call $rpc_handle checkProductIP $productName $ipAddress} verdict]} {
                log local0.error  "Client - [IP::client_addr], ILX failure: $verdict"
                return
            }
            log local0. "The verdict for $ipAddress: $verdict";
            # cache verdict
            table set $key $verdict $static::office365_ipi_timeout $static::office365_ipi_lifetime
        }
            
        # verdict is 0 (reject) or 1 (allow)
        if { !($verdict) } {
            log local0. "rejected IP address: $ipAddress"
            reject
        }
        
}
Tested on Version:
13.0
Comments on this Snippet
Comment made 1 month ago by Andrew 296

Hi Niels,

Have you seen any unhandled errors during network outage events? I've seen a couple since implementing a solution based on your v0.3 iRule (obtained via Brett).

Error during network outage:

err sdmd[6365]: 018e0018:3: pid[12638]  plugin[/Common/office365_ipi_PROD_plugin.office365_ipi_extension] events.js:160
err sdmd[6365]: 018e0018:3: pid[12638]  plugin[/Common/office365_ipi_PROD_plugin.office365_ipi_extension]       throw er; // Unhandled 'error' event
err sdmd[6365]: 018e0018:3: pid[12638]  plugin[/Common/office365_ipi_PROD_plugin.office365_ipi_extension]       ^
err sdmd[6365]: 018e0018:3: pid[12638]  plugin[/Common/office365_ipi_PROD_plugin.office365_ipi_extension]
err sdmd[6365]: 018e0018:3: pid[12638]  plugin[/Common/office365_ipi_PROD_plugin.office365_ipi_extension] Error: connect EHOSTUNREACH 

Another variation of the last line. Presumably a DNS issue:

err sdmd[23042]: 018e0018:3: pid[812] plugin[/Common/office365_ipi_PROD_plugin.office365_ipi_extension] Error: getaddrinfo EAI_AGAIN endpoints.office.com:443

So the above issues ultimately end in the extension being terminated after it's maximum restart attempts. I'm looking at a few ways to improve this, however to begin with, your error handling actually looks fine, so I'm not sure why I get an unhandled error:

    res.on('error', function(e) {
        callback(e, null);
    }); 

Complete function:

// helper to call the webservice
function getJson(methodName, instanceName, clientRequestId, callback) {
    var ws = "https://endpoints.office.com";
    var requestPath = ws + '/' + methodName + '/' + instanceName + '?clientRequestId=' + clientRequestId;

    var req = https.get(requestPath, function(res) {
        var data = '';

        res.on('data', function(chunk) {
            data += 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) {

                callback(null, data);

            }
        });
    });
}

Any thoughts?

0
Comment made 4 weeks ago by Niels van Sluis 2591

Nice, so you are testing this code. Thanks for the feedback :-)

It seems the getJson function needs some extra error handling. This works for me:

// helper to call the webservice
function getJson(methodName, instanceName, clientRequestId, callback) {
    var ws = "https://endpoints.office.com";
    var requestPath = ws + '/' + methodName + '/' + instanceName + '?clientRequestId=' + clientRequestId;

    var req = https.get(requestPath, function(res) {
        var data = '';

        res.on('data', function(chunk) {
            data += 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) {
                callback(null, data);
            }
        });
    }).on('error', function(e) {
        console.log("Got error: " + e.message);
    });
}

Now the error message will be logged, but not throw an error.

Nov 13 14:41:54 nielsvs-bigip info sdmd[7798]: 018e0017:6: pid[5428]  plugin[/Common/office365_endpoints_plugin.office365_endpoints_extension] Got error: getaddrinfo EAI_AGAIN endpoints.office.com:443
Nov 13 14:41:54 nielsvs-bigip info sdmd[7798]: 018e0017:6: pid[5428]  plugin[/Common/office365_endpoints_plugin.office365_endpoints_extension] Got error: getaddrinfo EAI_AGAIN endpoints.office.com:443
Nov 13 14:41:54 nielsvs-bigip info sdmd[7798]: 018e0017:6: pid[5429]  plugin[/Common/office365_endpoints_plugin.office365_endpoints_extension] Got error: getaddrinfo EAI_AGAIN endpoints.office.com:443
Nov 13 14:41:54 nielsvs-bigip info sdmd[7798]: 018e0017:6: pid[5429]  plugin[/Common/office365_endpoints_plugin.office365_endpoints_extension] Got error: getaddrinfo EAI_AGAIN endpoints.office.com:443
0
Comment made 4 weeks ago by Niels van Sluis 2591

For anyone else that is interested in this code. See:

https://github.com/nvansluis/f5.office365_endpoints_extension

0
Comment made 4 weeks ago by Andrew 296

Looking good for me also. Thanks Niels!

0
Comment made 4 weeks ago by Andrew 296

In my TCL iRule, I also added a catch to the ILX::init command as when the extension terminates (like with the earlier unhandled exceptions) it results in a TCL error.

   if { [catch { set rpc_handle [ILX::init office365_ipi_DEV_plugin office365_ipi_extension] } ] } {
        log local0.error  "Host - $host, ILX::init failure. The ILX Extension may have been terminated. Try reloading the plugin or restarting the sdmd service if necessary."
        return
    }
0