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

Filter by:
  • Solution
  • Technology
code share

bulk_iRule_apply

Problem this snippet solves:

Script to bulk apply an iRule to all virtual servers with an (any) http profile enabled. Example scenario is an iRule that mitigates a new application vulnerability for a BIG-IP with numerous virtual servers front-ending vulnerable application instances.

it uses ssh/tmsh (using the Python Paramiko module) to communicate with the BIG-IP rather than iControl REST to maximize applicability to BIG-IP software revisions. Tested solely with 11.5.4

Revised to add support for administrative partitions. Revised January 8 with fixes for config saving when partitions are used. Other minor updates.

How to use this snippet:

usage: bulk_irule_apply.py [-h] (--first | --last) --irulename IRULENAME --bigip BIGIP --user USER [--noprompt] [--partition PARTITION]

A tool to apply an iRule to all BIG-IP virtuals that have an http profile enabled

optional arguments: -h, --help show this help message and exit --first make new iRule first in list --last make new iRule last in list --irulename IRULENAME iRule name to add to virtuals - assumed to be in Common partition --bigip BIGIP IP or hostname of BIG-IP Management or Self IP --user USER username to use for authentication --noprompt Do not prompt to save config --partition PARTITION BIG-IP administrative partition for virtuals

Use this tool with caution; suggested use is to run it against Standby unit in a BIG-IP HA Pair and once proper changes are confirmed, sync it to Active unit

Tested on Version:
11.5

Code:

#!/usr/bin/python

## Bulk iRule Add
## Author: Chad Jenison (c.jenison@f5.com)
## This script adds an iRule to all virtual servers [as first or last iRule in list] that have an http profile
## January 4, 2018 - added some (maybe suspect) code to try to accomodate operation on BIG-IP administrative partitions; lightly tested


import argparse
import sys
import socket
import getpass
import paramiko
import time


# Taken from http://code.activestate.com/recipes/577058/
def query_yes_no(question, default="no"):
    valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
    if default == None:
        prompt = " [y/n] "
    elif default == "yes":
        prompt = " [Y/n] "
    elif default == "no":
        prompt = " [y/N] "
    else:
        raise ValueError("invalid default answer: '%s'" % default)
    while 1:
        ## Lines added per request from Oracle for "No Prompt" mode.
        if args.noprompt:
            return True
        else:
            sys.stdout.write(question + prompt)
            choice = raw_input().lower() 
            if default is not None and choice == '':
                return valid[default]
            elif choice in valid.keys():
                return valid[choice]
            else:
                sys.stdout.write("Please respond with 'yes' or 'no' (or 'y' or 'n').\n")

def determineShell():
    stdin, stdout, stderr = sshSession.exec_command('tmsh show sys version')
    output = ""
    for line in stderr.read().splitlines():
        output = output + line
    if output.find('Syntax Error') == -1:
        return 'bash'
    else:
        print ('Login shell for user %s is not bash; this script requires login shell of bash (Advanced Shell)')
        return 'tmsh'
        
        

parser = argparse.ArgumentParser(description='A tool to apply an iRule to all BIG-IP virtuals that have an http profile enabled', epilog='Use this tool with caution; suggested use is to run it against Standby unit in a BIG-IP HA Pair and once proper changes are confirmed, sync it to Active unit')
order = parser.add_mutually_exclusive_group(required=True)
order.add_argument('--first', action='store_true', help='make new iRule first in list')
order.add_argument('--last', action='store_true', help='make new iRule last in list')
parser.add_argument('--irulename', help='iRule name to add to virtuals - assumed to be in Common partition', required=True)
parser.add_argument('--bigip', help='IP or hostname of BIG-IP Management or Self IP', required=True)
parser.add_argument('--user', help='username to use for authentication', required=True)
parser.add_argument('--noprompt', action='store_true', help='Do not prompt to save config')
parser.add_argument('--partition', help='BIG-IP administrative partition for virtuals')

args = parser.parse_args()

passwd = getpass.getpass("Password for " + args.user + ":")

sshSession=paramiko.SSHClient()
sshSession.set_missing_host_key_policy(paramiko.AutoAddPolicy())
sshSession.connect(args.bigip, username=args.user, password=passwd, look_for_keys=False, allow_agent=False)
configChanged = False
if determineShell() == 'bash':
    loginShell = 'bash'
    commandPrefix = 'tmsh -c \"'
    commandPostfix = '\"'
else:
    loginShell = 'tmsh' 
    commandPrefix = ''
    commandPostfix = ''

if args.partition != None:
    partitionCommandPrefix = "cd /" + args.partition + ";"
else:
    partitionCommandPrefix = ""

stdin, stdout, stderr = sshSession.exec_command('%slist ltm rule %s%s' % (commandPrefix, args.irulename, commandPostfix))
exit_status = stdout.channel.recv_exit_status()
if exit_status == 0:
    print("iRule: %s found on system" % (args.irulename))
else:
    print("iRule: %s not found on system" % (args.irulename))
    print("Exit Status: %s" % (exit_status))
    sys.exit(1)

# Obtain list of http profiles and make a Python set with them
stdin, stdout, stderr = sshSession.exec_command('%slist ltm profile http one-line%s' % (commandPrefix, commandPostfix))
exit_status = stdout.channel.recv_exit_status()
if exit_status == 0:
    httpProfiles = set()
    for line in stdout.read().splitlines():
        if line.lstrip().startswith('ltm profile http '):
            httpProfile = line.lstrip().split(' ')[3].rstrip()		
            httpProfiles.add(httpProfile)
else:
    print("Error obtaining http profiles (none found?)", exit_status)
if args.partition != '':
    stdin, stdout, stderr = sshSession.exec_command('%s%slist ltm profile http one-line%s' % (commandPrefix, partitionCommandPrefix, commandPostfix))
    exit_status = stdout.channel.recv_exit_status()
    if exit_status == 0:
        for line in stdout.read().splitlines():
            if line.lstrip().startswith('ltm profile http '):
                httpProfile = line.lstrip().split(' ')[3].rstrip()		
                httpProfiles.add(httpProfile)


# Obtain list of virtuals and make a Python list with them
stdin, stdout, stderr = sshSession.exec_command('%s%slist ltm virtual one-line%s' % (commandPrefix, partitionCommandPrefix, commandPostfix))
exit_status = stdout.channel.recv_exit_status()
if exit_status == 0:
    virtuals = []
    for line in stdout.read().splitlines():
        if line.lstrip().startswith('ltm virtual '):
            virtual = line.lstrip().split(' ')[2].rstrip()
            virtuals.append(virtual)
else:
    print("Error obtaining virtuals (none found?)", exit_status)

# Iterate through Virtuals
for virtual in virtuals:
    httpProfileRequirementMet = False
    # List profiles for a virtual and build Python list virtualProfiles
    virtualProfiles = []
    stdin, stdout, stderr = sshSession.exec_command('%s%slist ltm virtual %s profiles%s' % (commandPrefix, partitionCommandPrefix, virtual, commandPostfix))
    exit_status = stdout.channel.recv_exit_status()
    if exit_status == 0:
        inProfiles = False
        for line in stdout.read().splitlines():
            if inProfiles:
                if inProfile:
                    if line.lstrip().rstrip() == '}':
                        inProfile = False
                elif line.lstrip().rstrip().endswith('{'):
                    virtualProfileRaw = line.lstrip().split(' ')[0]
                    #this weird code below removes partition prefix from profiles in virtual config because we built a list of http profiles without partition prefixes
                    virtualProfile = virtualProfileRaw.split('/')[-1]
                    virtualProfiles.append(virtualProfile)
                    inProfile = True
                else:
                    if line.lstrip().rstrip() == '}':
                        inProfiles = False
            elif line.lstrip().rstrip() == 'profiles {':
                inProfiles = True
                inProfile = False
        for virtualProfile in virtualProfiles:
            if virtualProfile in httpProfiles:
                httpProfileRequirementMet = True
    else:
        print("Error obtaining virtual profiles (none found?)", exit_status)
    
    if httpProfileRequirementMet:
        # List iRules and Build a Python list virtualRules
        stdin, stdout, stderr = sshSession.exec_command('%s%slist ltm virtual %s rules%s' % (commandPrefix, partitionCommandPrefix, virtual, commandPostfix))
        exit_status = stdout.channel.recv_exit_status()
        if exit_status == 0:
            inRules = False
            virtualRules = []
            for line in stdout.read().splitlines():
                if inRules:
                    if line.lstrip().rstrip() == '}':
                        inRules = False
                    else:
                        ruleRaw = line.lstrip().rstrip()
                        rule = ruleRaw.split('/')[-1]
                        virtualRules.append(rule)
                elif line.lstrip().rstrip() == 'rules {':
                    inRules = True
        else:
            print("Error obtaining rules (none found?)", exit_status)
        if args.irulename in virtualRules:
            print("Virtual: %s already has rule: %s enabled" % (virtual, args.irulename))
        else:
            if args.first:
                newRules = [args.irulename] + virtualRules
            elif args.last:
                newRules = virtualRules + [args.irulename]
            newRuleString = " ".join(str(rule) for rule in newRules)
            print("Virtual: %s - newRuleString: %s" % (virtual, newRuleString))
            stdin, stdout, stderr = sshSession.exec_command('%s%smodify ltm virtual %s rules { %s }%s' % (commandPrefix, partitionCommandPrefix, virtual, newRuleString, commandPostfix))
            exit_status = stdout.channel.recv_exit_status()
            if exit_status != 0:
                print('Possible problem modifying virtual - STDOUT: %s ; STDERR: %s' % (stdout, stderr)) 
            configChanged = True
    else:
        print("Virtual: %s doesn't have mandatory http profile enabled" % (virtual))



if configChanged:
    if args.noprompt:
        stdin, stdout, stderr = sshSession.exec_command('%s%ssave sys config%s' % (commandPrefix, partitionCommandPrefix, commandPostfix))
    else:
        queryString = 'Do you want to save changes to configuration files?'
        if query_yes_no(queryString, default="yes"):
            stdin, stdout, stderr = sshSession.exec_command('%s%ssave sys config%s' % (commandPrefix, partitionCommandPrefix, commandPostfix))