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.5Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)