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.

Office 365 Data Group builder using F5 Python SDK

Problem this snippet solves:

Python script which pulls O365 URL's/IP's from microsoft published XML formatted list, parses into python dictionaries formatted for F5 data group, and uses F5 Python SDK to create or update data groups on BIG-IP for each MS product. The script will check if the data group already exists, and if so update based on changes. Once the data groups are installed they can used/referenced by any F5 feature/configuration that can reference a data group.

How to use this snippet:

Python version: 3.6

Python Modules Required: * f5-sdk * requests * f5-icontrol-rest * xmltodict

Update script variables for your environment: BIGIP_ADDRESS = ' ' BIGIP_USER = ' ' BIGIP_PASS = ' '

to execute: python3 o365_dg_builder.py

Code :

import requests
import xmltodict
from collections import OrderedDict
from f5.bigip import ManagementRoot
from icontrol.exceptions import iControlUnexpectedHTTPError

import argparse

MS_URL = 'https://support.content.office.net/en-us/static/O365IPAddresses.xml'
BIGIP_ADDRESS = '192.168.15.165'
BIGIP_USER = 'admin'
BIGIP_PASS = 'admin'

def load_bigip(f5_host, f5_user, f5_pass):
    bigip = ManagementRoot(f5_host, f5_user, f5_pass)

    return bigip


def get_o365_data():
    o365data = requests.get(MS_URL)
    records = []
    pdata = xmltodict.parse(o365data.content)
    for product in pdata['products']['product']:
        prod_name = product['@name']

        if isinstance(product['addresslist'],list):

            for addrlist in product['addresslist']:
                record = {}
                if 'address' in addrlist.keys():
                    record['name'] = "{}_{}_{}".format('MS',prod_name, addrlist['@type'])

                    if '@type' in addrlist.keys():
                        if addrlist['@type'] == 'URL':
                            record['type'] = 'string'
                        elif (addrlist['@type'] == 'IPv4' or addrlist['@type'] == 'IPv6'):
                            record['type'] = 'ip'

                    # MS XML has duplicate entries, unify the list before adding to record
                    new_addr_l = list(set(addrlist['address']))
                    record['records'] = new_addr_l
                    records.append(record)


        if isinstance(product['addresslist'],OrderedDict):
            if len(product['addresslist']['address']) > 0:
                record = {}
                record['name'] = "{}_{}_{}".format('MS', prod_name, product['addresslist']['@type'])
                if product['addresslist']['@type'] == 'URL':
                    record['type'] = 'string'
                elif product['addresslist']['@type'] == 'IPv4' or product['addresslist']['@type'] == 'IPv6':
                    record['type'] = 'ip'

                record['records'] = list(set(product['addresslist']['address']))
                records.append(record)

    return records


def compare_dg_entries(record, old_dg_l):
    # let's get the values to compare, and see what updates we need to make

    #compare lists
    addrs_to_rem = [x for x in old_dg_l if x not in record]
    addrs_to_add = [x for x in record if x not in old_dg_l]

    return addrs_to_rem, addrs_to_add




def create_dg(bigip, record):
    if not bigip.tm.ltm.data_group.internals.internal.exists(name=record['name']):
        bigip.tm.ltm.data_group.internals.internal.create(**record)
        create_status = 'SUCCESS'
    else:
        old_dg_entries = bigip.tm.ltm.data_group.internals.internal.load(name=record['name'])
        # pull into a list
        old_dg_l = []
        for entry in old_dg_entries.records:
            old_dg_l.append(entry['name'])

        addrs_to_rem, addrs_to_add = compare_dg_entries(record['records'], old_dg_l)
        if len(addrs_to_add)==0 and len(addrs_to_rem)==0:
            create_status = 'SKIPPED'
        else:
            if len(addrs_to_rem) > 0:
                for addr in addrs_to_rem:
                    old_dg_entries.records.remove(next(x for x in old_dg_entries.records if x['name'] == addr))
            if len(addrs_to_add) > 0:
                for addr in addrs_to_add:
                    old_dg_entries.records.append({'name':addr})

            old_dg_entries.update()
            # if you get time create log of updated entries
            create_status = 'UPDATED'


    return create_status


# -----
try:
    bigip = load_bigip(BIGIP_ADDRESS, BIGIP_USER, BIGIP_PASS)
except iControlUnexpectedHTTPError as e:
    print(e)
    exit(1)

if bigip:
    records = get_o365_data()
    for record in records:
        try:
            status = create_dg(bigip, record)
            print("BIGIP: {}, Data Group: {}, Create Status {}".format(BIGIP_ADDRESS, record['name'], status))
        except iControlUnexpectedHTTPError as e:
            status = 'FAILED'
            print("BIGIP: {}, Data Group: {}, Create Status {}, Error {}".format(BIGIP_ADDRESS, record['name'], status, e))

Tested this on version:

13.0
Published Mar 14, 2018
Version 1.0