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
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))
Tested this on version:
11.5