tmos
1661 TopicsRadware config translation
Problem this snippet solves: This is a simple Python script that translates Radware text configuration file and outputs as config snippet and certificate files. $ ./rad2f5 Usage: rad2f5 <filename> [partition] Example: ./rad2f5 radwareconfig.txt MyPartition Using partition /MyPartition/ IP routes:46 Non-floating IP interfaces:26 Floating IP interfaces:19 Monitors:349 Virtual Servers:430 Pools:152 Certificates: 31 SSL profiles: 17 WARNING! Found L7 Regular Expression at "appdirector l7 farm-selection method-table setCreate l7Rule-Redirect" Layer 7 rules:12 Layer 7 policies:4 !! Copy all certificate files to BIG-IP /var/tmp and use load_certs.sh to load !! # Configuration #--------------------------------------# ltm policy /MyPartition/l7rule-Redirect { controls { forwarding } requires { http } rules { Redirect { actions { 0 { forward select pool SORRYPOOL } } ordinal 1 } } net route /MyPartition/10.20.30.40_32 { network 10.20.30.40/32 gateway 10.1.1.1 } How to use this snippet: This includes translation of routes and non-floating self-IPs, monitors, pools, virtual servers, certificates, SSL profiles and some layer 7 rules. To install, either download the file attached here, extract and run it or use pip: pip install rad2f5 To load the configuration to the f5 device, output to a file eg ./rad2f5 filename>newcfg.txt , remove the statistics header from the file with a text editor, upload the file into /var/tmp on the BIG-IP and test loading with tmsh load sys config merge file /var/tmp/<filename> verify . Fix any issues and load with tmsh load sys config merge file /var/tmp/<filename> The script will also output the certificates and keys which should be uploaded to the BIG-IP /var/tmp directory and run file check_certs.sh and load_certs.sh to check and load the certs and keys. Works with Python v2 and v3. If you want something to be added then message me with details of the text and i will try to add it. Feel free to add or change anything Code : #!/usr/bin/env python # v1.1 14/3/2018 Deal with lack of text in monitors ( line 157 ) # v2 3/1/2019 Updated after changes suggested by Avinash Piare # v3 6/11/2019 Updated to deal with \\r\n at the end of the line # v3.1 6/11/2019 Minor changes to handle VS names with spaces # 3.4 18/4/2020 Fixed minor issues # Peter White 6/11/2019 # usage ./rad2f5 [partition] import sys import re import textwrap import ipaddress print ("Running with Python v" + sys.version) # Set debug to true to output the workings of the script ie the arrays etc debug = False def parse_options(options): # Function to split options and return a dict containing option and value # Example: -pl 27 -v 123 -pa 172.28.1.234 # Return: { 'pl':'27', 'v':'123', 'pa':'172.28.1.234' } output = dict() # Deal with non-quotes eg -b 12345 for r in re.finditer(r'-(\w{1,3}) (\S+)',options): output[r.group(1)] = r.group(2) # Deal with quotes eg -u "User Name" for v in re.finditer(r'-(\w{1,3}) (".+")',options): output[v.group(1)] = v.group(2) return output def splitvars(vars): # Split a string on | and then =, return a dict of ABC=XYZ # eg HDR=User-Agent|TKN=Googlebot-Video/1.0|TMATCH=EQ| # Return is { 'HDR':'User-Agent','TKN': 'Googlebot-Video/1.0', 'TMATCH': 'EQ' } vlist = vars.split("|") o = {} for v in vlist: if v == '': continue w = v.split("=") if len(w) > 1: o[w[0]] = w[1] else: o[w[0]] = '' return o def array_add(a,d): # Function to safely add to an array #global a if not len(a): a = [ d ] else: a.append( d ) return a def setup_pools(name): global pools if not name in pools: pools[name] = { 'destinations': [], 'member-disabled': [], 'member-ids': [], 'priority-group': [] } else: if not 'destinations' in pools[name]: pools[name]['destinations'] = [] if not 'member-disabled' in pools[name]: pools[name]['member-disabled'] = [] if not 'member-ids' in pools[name]: pools[name]['member-ids'] = [] if not 'priority-group' in pools[name]: pools[name]['priority-group'] = [] def getVsFromPool(poolname): # This retrieves the names of virtual servers that use a specific pool global farm2vs if poolname in farm2vs: return farm2vs[poolname] else: return False def is_ipv6(ip): if ':' in ip: return True else: return False # Check there is an argument given if not len(sys.argv) > 1: exit("Usage: rad2f5 [partition]") if len(sys.argv) > 2: # Second command-line variable is the partition partition = "/" + sys.argv[2] + "/" print ("Using partition " + partition) else: partition = "/Common/" # Check input file fh = open(sys.argv[1],"r") if not fh: exit("Cannot open file " + argv[1]) rawfile = fh.read() # v2 # Remove any instances of slash at the end of line # eg health-monitoring check create\ # HC_DNS -id 5 -m DNS -p 53 -a \ #HOST=ns.domain.com|ADDR=1.1.1.1| -d 2.2.2.2 #file = re.sub('^security certificate table\\\\\n','',rawfile) file = re.sub('\\\\\n','',rawfile) file = re.sub('\\\\\r\n','',file) ################ LACP trunks ########################## # # # ############################################################# #net linkaggr trunks set T-1 -lap G-13,G-14 -lam Manual -lat Fast -law 3 -laa 32767 trunks = {} for r in re.finditer(r'net linkaggr trunks set (\S+) (.+)',file): name = r.group(1) options = parse_options(r.group(2)) if 'lap' in options: # Manage interfaces ints = options['lap'].split(',') trunks[name] = { 'interfaces': ints } print ("LACP trunks:" + str(len(trunks))) if debug: print('DEBUG LACP trunks: ' + str(trunks)) ################ IP routes ########################## # # # ############################################################# routes = {} for r in re.finditer(r'net route table create (\S+) (\S+) (\S+) (\S+)',file): if r.group(1) == '0.0.0.0': name = 'default' else: name = r.group(1) + "_" + r.group(2) routes[name] = { 'network': r.group(1), 'mask': r.group(2), 'gateway': r.group(3), 'interface': r.group(4)} print ("IP routes:" + str(len(routes))) if debug: print('DEBUG routes: ' + str(routes)) ################ Non-floating self-IPs ############### # # # # ############################################################# selfIpNonFloating = {} for s in re.finditer(r'net ip-interface create (\S+) (\S+) (.+)',file): output = { 'interface': s.group(2) } opts = parse_options(s.group(3)) if 'pl' in opts: output['mask'] = opts['pl'] if 'v' in opts: output['vlan'] = opts['v'] if 'pa' in opts: output['peerAddress'] = opts['pa'] selfIpNonFloating[s.group(1)] = output print ("Non-floating IP interfaces:" + str(len(selfIpNonFloating))) if debug: print("DEBUG non-floating IPs: " + str(selfIpNonFloating)) ################ Floating self-IPs ################### # # # ############################################################# selfIpFloating = {} for s in re.finditer(r'redundancy vrrp virtual-routers create (\S+) (\S+) (\S+) (.+)',file): output = { 'version': s.group(1),'interface': s.group(3), 'ipAddresses': []} opts = parse_options(s.group(4)) #if 'as' in opts: # Enabled - assume all are enabled if 'pip' in opts: output['peerIpAddress'] = opts['pip'] selfIpFloating[s.group(2)] = output # Retrieve IP addresses for Floating self-IPs # for s in re.finditer(r'redundancy vrrp associated-ip create (\S+) (\S+) (\S+) (\S+)',file): selfIpFloating[s.group(2)]['ipAddresses'] = array_add(selfIpFloating[s.group(2)]['ipAddresses'],s.group(4)) print ("Floating IP interfaces:" + str(len(selfIpFloating))) if debug: print("DEBUG Floating IPs: " + str(selfIpFloating)) ################ Monitors ########################### # # -m XYZ is the monitor type may or may not be present ( not present for icmp type ) # -id is the monitor ID, -p is the port, -d is the destination # ############################################################# monitors = {} for m in re.finditer(r'health-monitoring check create (\S+) (.+)',file): output = { 'name': m.group(1) } opts = parse_options(m.group(2)) if 'id' in opts: id = opts['id'] else: print ("No ID for monitor " + m.group(1)) continue if 'm' in opts: output['type'] = opts['m'] else: output['type'] = 'icmp' if 'a' in opts: output['text'] = opts['a'] else: # Added in v2 output['text'] = '' if 'p' in opts: output['port'] = opts['p'] else: output['port'] = '' monitors[id] = output print ("Monitors:" + str(len(monitors))) if debug: print("DEBUG Monitors:" + str(monitors)) ################ Virtual Servers ##################### # # 0 = name, 1= IP, 2=protocol, 3=port, 4=source 5=options # -ta = type, -ht = http policy, -fn = pool name, -sl = ssl profile name, -ipt = translation? # -po = policy name # ############################################################# farm2vs = {} virtuals = {} snatPools = {} for v in re.finditer(r'appdirector l4-policy table create (\S+) (\S+) (\S+) (\S+) (\S+) (.*)',file): # Puke if VS has quotes if v.group(1).startswith('"'): name = v.group(1).strip('"').replace(' ','_') + v.group(2).strip('"').replace(' ','_') address,protocol,port = v.group(3),v.group(4),v.group(5) source = v.group(6).split(' ')[0] options = ' '.join(v.group(6).split(' ')[1:]) else: name,address,protocol,port,source,options = v.group(1),v.group(2),v.group(3),v.group(4),v.group(5),v.group(6) opts = parse_options(options) # Get rid of ICMP virtual servers if protocol == 'ICMP': continue print ("Name " + name + " has quotes, address: " + address + " protocol: " + protocol) if port == 'Any': port = '0' output = {'source': source, 'destination': address + ":" + port, 'protocol': protocol, 'port': port } if 'fn' in opts: output['pool'] = opts['fn'] if not opts['fn'] in farm2vs: farm2vs[opts['fn']] = [] farm2vs[opts['fn']].append(name) output['port'] = port if 'po' in opts: output['policy'] = opts['po'] if 'ipt' in opts: output['snat'] = 'snatpool_' + opts['ipt'] if not 'snatpool_' + opts['ipt'] in snatPools: #Create snatpool snatPools['snatpool_' + opts['ipt']] = { 'members': [opts['ipt']] } # Set the correct profiles profiles = [] # Layer 4 if protocol == "TCP" or protocol == "UDP": profiles.append(protocol.lower()) else: profiles.append('ipother') # http if port == '80' or port == '8080': profiles.append('http') profiles.append('oneconnect') # ftp if port == '21': profiles.append('ftp') # RADIUS if port == '1812' or port == '1813': profiles.append('radius') # SSL if 'sl' in opts: profiles.append(opts['sl']) profiles.append('http') profiles.append('oneconnect') output['profiles'] = profiles # virtuals[name] = output print ("Virtual Servers:" + str(len(virtuals))) if debug: print("DEBUG Virtual Servers:" + str(virtuals)) #print("DEBUG Farm to VS Mapping:" + str(farm2vs)) ################ Pools ############################### # # Pools config options are distributed across multiple tables # This sets the global config such as load balancing algorithm # -dm This sets the distribution method. cyclic = Round Robin, Fewest Number of Users = least conn, Weighted Cyclic = ratio, Response Time = fastest # -as this is the admin state # -cm is the checking method ie monitor # -at is the activation time ############################################################# pools = {} for p in re.finditer(r'appdirector farm table setCreate (\S+) (.*)',file): name = p.group(1) setup_pools(name) opts = parse_options(p.group(2)) output = {} # Admin state output['poolDisabled'] = False if 'as' in opts: if opts['as'] == 'Disabled': output['poolDisabled'] = True # Deal with distribution methods method = 'round-robin' if 'dm' in opts: if opts['dm'] == '"Fewest Number of Users"': method = 'least-conn' elif opts['dm'] == 'Hashing': method = 'hash' else: method = 'round-robin' output['lbMethod'] = method if 'at' in opts: output['slowRamp'] = opts['at'] pools[name] = output # This sets the pool members # 0=name, 1=node, 2=node address, 3=node port, 4=? -id =id -sd ? -as admin state eg Disable, -om operation mode eg Backup ( fallback server ), -rt backup server address 0.0.0.0 for p in re.finditer(r'appdirector farm server table create (\S+) (\S+) (\S+) (\S+) (\S+) (.*)',file): name,node,node_address,node_port,node_hostname = p.group(1),p.group(2),p.group(3),p.group(4),p.group(5) opts = parse_options(p.group(6)) setup_pools(name) if node_port == 'None': node_port = '0' pools[name]['destinations'] = array_add(pools[name]['destinations'],node_address + ":" + node_port) # Manage monitor if node_port == '80' or node_port == '8080': monitor = 'http' elif node_port == '443': monitor = 'https' elif node_port == '53': monitor = 'dns' elif node_port == '21': monitor = 'ftp' elif node_port == '0': monitor = 'gateway-icmp' else: monitor = 'tcp' pools[name]['monitor'] = monitor # Retrieve pool member ID if 'id' in opts: pools[name]['member-ids'] = array_add(pools[name]['member-ids'],opts['id']) else: print ("ID not found for pool " + name) # Check if member is disabled if 'as' in opts and opts['as'] == 'Disabled': # This pool member is disabled pools[name]['member-disabled'] = array_add(pools[name]['member-disabled'],True) else: pools[name]['member-disabled'] = array_add(pools[name]['member-disabled'],False) # Check if member is backup ie Priority Groups if 'om' in opts and opts['om'] == 'Backup': pools[name]['priority-group'] = array_add(pools[name]['priority-group'],20) else: pools[name]['priority-group'] = array_add(pools[name]['priority-group'],0) print ("Pools:" + str(len(pools)) ) if debug: print("DEBUG pools: " + str(pools)) ################ SSL certificates ################### # # #Name: certificate_name \ #Type: certificate \ #-----BEGIN CERTIFICATE----- \ #MIIEcyFCA7agAwIAAgISESFWs9QGF # ############################################################# certs = {} for r in re.finditer(r'Name: (\S+) Type: (\S+) (-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----)',file): name,type,text = r.group(1),r.group(2),r.group(3) certs[name] = { 'type': type,'text': text.replace(' \\\r\n','') } # Print out to file or something print ("SSL Certificates: " + str(len(certs))) if debug: print("DEBUG certs: " + str(certs)) ################ SSL Keys ################### # # #Name: New_Root_Cert Type: key Passphrase: -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: [key] [key] -----END RSA PRIVATE KEY----- \ # ############################################################# keys = {} # Keys with passphrase for r in re.finditer(r'Name: (\S+) Type: key Passphrase: (\S+) (-----BEGIN RSA PRIVATE KEY-----.+?-----END RSA PRIVATE KEY-----)',file): name,passphrase,text = r.group(1),r.group(2),r.group(3) keys[name] = { 'passphrase': passphrase,'text': text.replace(' \\\r\n','') } # Keys without passphrase for r in re.finditer(r'Name: (\S+) Type: key (-----BEGIN RSA PRIVATE KEY-----.+?-----END RSA PRIVATE KEY-----)',file): name,text = r.group(1),r.group(2) keys[name] = { 'text': text.replace(' \\\r\n','') } print ("SSL Keys: " + str(len(keys))) if debug: print("DEBUG SSL keys: " + str(keys)) ################ SSL profiles ######################## # # -c is cert, -u is ciphers, -t is chain cert, -fv - versions ############################################################## sslProfiles = {} for s in re.finditer(r'^appdirector l4-policy ssl-policy create (\S+) (.+)',file.replace('\\\r\n',''),re.MULTILINE): name = s.group(1) output = {} opts = parse_options(s.group(2)) if 'c' in opts: # Certificate output['certificate'] = opts['c'] if 't' in opts: # Chain cert output['chaincert'] = opts['t'] if 'fv' in opts: # TLS Version output['version'] = opts['fv'] if 'u' in opts: # User-defined Ciphers output['cipher'] = opts['u'] if 'lp' in opts: # ? output['lp'] = opts['lp'] if 'pa' in opts: # ? output['pa'] = opts['pa'] if 'cb' in opts: # ? output['cb'] = opts['cb'] if 'i' in opts: # Backend SSL Cipher -> Values: Low, Medium, High, User Defined output['i'] = opts['i'] if 'bs' in opts: # Backend SSL in use ie serverSSL output['serverssl'] = opts['bs'] sslProfiles[name] = output print ("SSL profiles: " + str(len(sslProfiles))) if debug: print("DEBUG SSL Profiles: " + str(sslProfiles)) ################ SNAT pools ########################## # # Note that this only works for addresses in the same /24 subnet ############################################################# for s in re.finditer(r'appdirector nat client address-range create (\S+) (\S+)',file): name = s.group(1) if sys.version.startswith('2'): start = ipaddress.ip_address(unicode(name,'utf_8')) end = ipaddress.ip_address(unicode(s.group(2),'utf_8')) else: start = ipaddress.ip_address(name) end = ipaddress.ip_address(s.group(2)) current = start ipAddresses = [] while current <= end: ipAddresses.append(str(current)) current += 1 snatPools['snatpool_' + name] = { 'members': ipAddresses } print ("SNAT Pools:" + str(len(snatPools))) if debug: print("DEBUG SNAT Pools: " + str(snatPools)) #################################################################### ################ Layer 7 functions ########################## # # #################################################################### l7rules = { } for r in re.finditer(r'appdirector l7 farm-selection method-table setCreate (\S+) (.+)',file): name = r.group(1) l7rules[name] = { 'match': [], 'action': [] } opts = parse_options(r.group(2)) if 'cm' in opts and opts['cm'] == '"Header Field"' and 'ma' in opts: # This is a rule to insert a header params = splitvars(opts['ma']) if 'HDR' in params and 'TKN' in params: if params['TKN'] == "$Client_IP": params['TKN'] = '[IP::client_addr]' l7rules[name]['action'].append("http-header\ninsert\nname " + params['HDR'] + "\nvalue " + params['TKN']) if 'cm' in opts and opts['cm'] == 'URL' and 'ma' in opts: # This does a match on Host and URL params = splitvars(opts['ma']) if 'HN' in params: l7rules[name]['match'].append("http-host\nhost\nvalues { " + params['HN'] + " }") if 'P' in params: l7rules[name]['match'].append("http-uri\npath\nvalues { " + params['P'] + " }") if 'cm' in opts and opts['cm'] == '"Regular Expression"' and 'ma' in opts: params = splitvars(opts['ma']) if 'EXP' in params and params['EXP'] == '.': # This is a regex which matches everything print ("REGEX: " + name) else: print ("WARNING! Found L7 Regular Expression at \"appdirector l7 farm-selection method-table setCreate " + name + "\". Manually set the match in output config.") # Note that there can be multiple entries ie multiple rules per policy l7policies = {} for p in re.finditer(r'appdirector l7 farm-selection policy-table setCreate (\S+) (\d+) (.+)',file): name = p.group(1) precedence = p.group(2) opts = parse_options(p.group(3)) if 'fn' in opts: farm = opts['fn'] else: farm = '' if 'm1' in opts: rule = opts['m1'] if 'pa' in opts: # Retain HTTP Persistency (PRSST)-If the argument is ON (or undefined), AppDirector maintains HTTP 1.1 # HTTP Redirect To (RDR)-Performs HTTP redirection to the specified name or IP address. # HTTPS Redirect To (RDRS)-AppDirector redirects the HTTP request to the specified name or IP address and modifies the request to a HTTPS request. # Redirection Code (RDRC)-Defines the code to be used for redirection. # RDRC=PRMN stand for Permanent I assume , as in HTTP 301 # SIP Redirect To (RDRSIP)-Performs SIP redirection to the specified name or IP address. params = splitvars(opts['pa']) if 'RDR' in params: url = 'http://' + params['RDR'] elif 'RDRS' in params: url = 'https://' + params['RDRS'] else: url = '' if not name in l7policies: l7policies[name] = [] if rule in l7rules: if farm != '': l7rules[rule]['action'].append("forward\nselect\npool " + farm) elif url != '': l7rules[rule]['action'].append("http-redirect\nhost " + url) l7policies[name].append({ 'precedence': precedence, 'farm': farm, 'rule': rule }) print ("Layer 7 rules:" + str(len(l7rules))) print ("Layer 7 policies:" + str(len(l7policies))) if debug: print("DEBUG L7 rules: " + str(l7rules)) print("DEBUG L7 policies: " + str(l7policies)) # # We have retrieved the required configuration from the input file # Now start outputting the config ################# output SSL certificates import script ###################### # # ######################################################################## if len(certs): print ("-- Creating SSL certs and keys --") with open("load_certs.sh",'w') as loadScript: loadScript.write("#!/bin/bash\n# Script to load SSL certs and keys from /var/tmp\n") ##### Manage Certificates ######## for cert in certs: # Write certs to load_certs.sh if certs[cert]['type'] == 'certificate' or certs[cert]['type'] == 'interm': loadScript.write("tmsh install sys crypto cert " + partition + cert + ".crt from-local-file /var/tmp/" + cert + ".crt\n") # Create the certificate files with open(cert + ".crt","w") as certFile: for m in re.finditer(r'(-----BEGIN CERTIFICATE-----)\s?(.+)\s?(-----END CERTIFICATE-----)',certs[cert]['text']): certFile.write(m.group(1) + "\n") certFile.write(textwrap.fill(m.group(2),64) + "\n") certFile.write(m.group(3) + "\n") print ("Created SSL certificate file " + cert + ".crt") ##### Manage Keys ################# for key in keys: # Write keys to load_certs.sh loadScript.write("tmsh install sys crypto key " + partition + key + ".key") if 'passphrase' in keys[key]: loadScript.write(" passphrase " + keys[key]['passphrase']) loadScript.write(" from-local-file /var/tmp/" + key + ".key\n") # Create the key files with open(key + ".key","w") as keyFile: for n in re.finditer(r'(-----BEGIN RSA PRIVATE KEY-----)(.+)(-----END RSA PRIVATE KEY-----)',keys[key]['text']): keyFile.write(n.group(1) + "\n") if 'Proc-Type:' in n.group(2) and 'DEK-Info:' in n.group(2): # File is encrypted, separate the first lines for o in re.finditer(r'(Proc-Type: \S+) (DEK-Info: \S+) (.+)',n.group(2)): keyFile.write(o.group(1) + "\n") keyFile.write(o.group(2) + "\n\n") keyFile.write(textwrap.fill(o.group(3),64) + "\n") else: # File is not encrypted, output as it is keyFile.write(textwrap.fill(n.group(2),64) + "\n") keyFile.write(n.group(3) + "\n") print ("Created SSL key file " + key + ".key") print ("-- Finished creating SSL certs and keys --") print ("!! Copy all .crt and .key SSL files to BIG-IP /var/tmp and use load_certs.sh to load !!" ) ####################################################################### ################# output SSL certificates checking script ###################### # # ######################################################################## with open("check_certs.sh",'w') as checkScript: checkScript.write("#!/bin/bash\n# Script to check SSL certs and keys\n") checkScript.write("\n# Check SSL Certs\n") for cert in certs: if certs[cert]['type'] == 'certificate' or certs[cert]['type'] == 'interm': checkScript.write("openssl x509 -in " + cert + ".crt -noout || echo \"Error with file " + cert + ".crt\"\n") checkScript.write("\n# Check SSL Keys\n") for key in keys: checkScript.write("openssl rsa -in " + key + ".key -check || echo \"Error with file " + key + ".key\"\n") print ("!! Run the check_certs.sh script to check the certificates are valid !!" ) ####################################################################### # # # # # # # # print ("\n\n\n\n\n\n# Configuration \n#--------------------------------------#") ################# output policy config ##################### # # ############################################################# #print ("L7 rules: " + str(l7rules)) for i in l7policies.keys(): output = "ltm policy " + partition + i + " {\n\tcontrols { forwarding }\n\trequires { http }\n\t" output += "rules {\n\t" ordinal = 1 for j in l7policies[i]: output += "\t" + j['rule'] + " { \n" if len(l7rules[j['rule']]['match']): # Deal with conditions output += "\t\t\tconditions { \n" l = 0 for k in l7rules[j['rule']]['match']: output += "\t\t\t\t" + str(l) + " { \n\t\t\t\t\t" + k.replace('\n','\n\t\t\t\t\t') l += 1 output += "\n\t\t\t\t }\n" output += "\n\t\t\t }\n" if len(l7rules[j['rule']]['action']): # Deal with actions output += "\t\t\tactions { \n" m = 0 for n in l7rules[j['rule']]['action']: output += "\t\t\t\t" + str(m) + " { \n\t\t\t\t\t" + n.replace('\n','\n\t\t\t\t\t') m += 1 output += "\n\t\t\t\t }\n" output += "\n\t\t\t }\n" output += "\t\tordinal " + str(ordinal) output += "\n\t\t}\n\t" ordinal += 1 output += "\n\t}\n}\n" print (output) ################# output LACP trunk config ############## # # ############################################################# for trunk in trunks.keys(): output = "net trunk " + partition + trunk + " {\n" if 'interfaces' in trunks[trunk]: output += "\tinterfaces {\n" for int in trunks[trunk]['interfaces']: output += "\t\t" + int + "\n" output += "\t}\n" output += "}\n" print (output) ################# output vlan config ############## # # ############################################################# for ip in selfIpNonFloating.keys(): output = "" if 'vlan' in selfIpNonFloating[ip]: vlanName = "VLAN-" + selfIpNonFloating[ip]['vlan'] output += "net vlan " + partition + vlanName + " {\n" if 'interface' in selfIpNonFloating[ip]: output += "\tinterfaces {\n" output += "\t\t" + selfIpNonFloating[ip]['interface'] + " {\n" output += "\t\t\ttagged\n\t\t}\n" output += "\t}\n" output += "\ttag " + selfIpNonFloating[ip]['vlan'] + "\n}\n" print(output) ################# output non-floating self-ip config ####### # # ############################################################# for ip in selfIpNonFloating.keys(): output = "" if 'mask' in selfIpNonFloating[ip]: mask = "/" + selfIpNonFloating[ip]['mask'] else: if is_ipv6(ip): mask = "/64" else: mask = "/32" if 'vlan' in selfIpNonFloating[ip]: vlanName = "VLAN-" + selfIpNonFloating[ip]['vlan'] else: continue output += "net self " + partition + "selfip_" + ip + " {\n\taddress " + ip + mask + "\n\t" output += "allow-service none\n\t" output += "traffic-group traffic-group-local-only\n" output += "\tvlan " + vlanName + "\n" output += "}\n" print (output) ################# output network route config ############## # # ############################################################# for route in routes.keys(): network,mask,gateway = routes[route]['network'],routes[route]['mask'],routes[route]['gateway'] output = "net route " + partition + route + " {\n\tnetwork " + network + "/" + mask + "\n\t" output += "gateway " + gateway + "\n}" print (output) ################# output SSL profiles config ########################## # # ######################################################################## for s in sslProfiles: #print (str(sslProfiles[s])) output = "ltm profile client-ssl " + partition + s + " {\n" output += "\tcert-key-chain { " + s + " { cert " output += sslProfiles[s]['certificate'] + ".crt " output += " key " output += sslProfiles[s]['certificate'] + ".key " if 'chaincert' in sslProfiles[s]: output += "chain " + sslProfiles[s]['chaincert'] output += " }\n\tdefaults-from /Common/clientssl\n}\n" print (output) if 'serverssl' in sslProfiles[s]: print("WARNING: VSs using Client SSL profile " + s + " should have Server SSL profile assigned") ################# output monitor config #################### # # ############################################################# for m in monitors.keys(): if monitors[m]['type'] == '"TCP Port"': output = "ltm monitor tcp " + partition + monitors[m]['name'] + " {\n\tdefaults-from /Common/tcp\n}\n" if monitors[m]['type'] == '"UDP Port"': output = "ltm monitor udp " + partition + monitors[m]['name'] + " {\n\tdefaults-from /Common/udp\n}\n" if monitors[m]['type'] == 'HTTP': ''' https://webhelp.radware.com/AppDirector/v214/214Traffic%20Management%20and%20Application%20Acceleration.03.001.htm https://webhelp.radware.com/AppDirector/v214/HM_Checks_Table.htm Arguments: Host name - HOST=10.10.10.53 path - PATH=/hc.htm HTTP Header HTTP method - MTD=G (G/H/P) send HTTP standard request or proxy request - PRX=N use of no-cache - NOCACHE=N text for search within a HTTP header and body, and an indication whether the text appears Username and Password for basic authentication NTLM authentication option - AUTH=B Up to four valid HTTP return codes - C1=200 -a PATH=/hc.htm|C1=200|MEXIST=Y|MTD=G|PRX=N|NOCACHE=N|AUTH=B| ''' output = "ltm monitor http " + partition + monitors[m]['name'] + " {\n\tdefaults-from /Common/http\n" vars = splitvars(monitors[m]['text']) output += "\tsend \"" if 'MTD' in vars: if vars['MTD'] == 'G': output += "GET " elif vars['MTD'] == 'P': output += "POST " elif vars['MTD'] == 'H': output += "HEAD " else: output += "GET " if 'PATH' in vars: output += vars['PATH'] + ' HTTP/1.0\\r\\n\\r\\n"\n' if 'C1' in vars: output += "\trecv \"^HTTP/1\.. " + vars['C1'] + "\"\n" output += "}\n" if monitors[m]['type'] == 'HTTPS': output = "ltm monitor https " + partition + monitors[m]['name'] + " {\n\tdefaults-from /Common/https\n" vars = splitvars(monitors[m]['text']) output += "\tsend \"" if 'MTD' in vars: if vars['MTD'] == 'G': output += "GET " elif vars['MTD'] == 'P': output += "POST " elif vars['MTD'] == 'H': output += "HEAD " else: output += "GET " if 'PATH' in vars: output += vars['PATH'] + ' HTTP/1.0\\r\\n\\r\\n"\n' output += "}\n" if monitors[m]['type'] == 'LDAP': ''' The Health Monitoring module enhances the health checks for LDAP servers by allowing performing searches in the LDAP server. Before Health Monitoring performs the search, it issues a Bind request command to the LDAP server. After performing the search, it closes the connection with the Unbind command. A successful search receives an answer from the server that includes a "searchResultEntry" message. An unsuccessful search receives an answer that only includes only a "searchResultDone" message. Arguments: Username A user with privileges to search the LDAP server. Password The password of the user. Base Object The location in the directory from which the LDAP search begins. Attribute Name The attribute to look for. For example, CN - Common Name. Search Value The value to search for. Search scope baseObject, singleLevel, wholeSubtree Search Deref Aliases neverDerefAliases, dereflnSearching, derefFindingBaseObj, derefAlways USER=cn=healthcheck,dc=domain|PASS=1234|BASE=dc=domain|ATTR=cn|VAL=healthcheck|SCP=1|DEREF=3| ''' output = "ltm monitor ldap " + partition + monitors[m]['name'] + " {\n\tdefaults-from /Common/ldap\n" vars = splitvars(monitors[m]['text']) if 'BASE' in vars: output += "\tbase \"" + vars['BASE'] + "\"\n" if 'USER' in vars: output += "\tusername \"" + vars['USER'] + "\"\n" if 'PASS' in vars: output += "\tpassword \"" + vars['PASS'] + "\"\n" output += "}\n" if monitors[m]['type'] == 'icmp': output = "ltm monitor gateway-icmp " + partition + monitors[m]['name'] + " {\n\tdefaults-from /Common/gateway-icmp\n}\n" if monitors[m]['type'] == 'DNS': output = "ltm monitor dns " + partition + monitors[m]['name'] + " {\n\tdefaults-from /Common/dns\n" vars = splitvars(monitors[m]['text']) if 'HOST' in vars: output += "\tqname \"" + vars['HOST'] + "\"\n" if 'ADDR' in vars: output += "\trecv \"" + vars['ADDR'] + "\"\n" output += "}\n" if monitors[m]['type'] == '"Radius Accounting"': output = "ltm monitor radius-accounting " + partition + monitors[m]['name'] + " {\n\tdefaults-from /Common/radius-accounting\n" vars = splitvars(monitors[m]['text']) if 'USER' in vars: output += "\tusername \"" + vars['USER'] + "\"\n" #if 'PASS' in vars: if 'SECRET' in vars: output += "\tsecret \"" + vars['SECRET'] + "\"\n" output += "}\n" if monitors[m]['type'] == '"Radius Authentication"': output = "ltm monitor radius " + partition + monitors[m]['name'] + " {\n\tdefaults-from /Common/radius\n" vars = splitvars(monitors[m]['text']) if 'USER' in vars: output += "\tusername \"" + vars['USER'] + "\"\n" #if 'PASS' in vars: if 'SECRET' in vars: output += "\tsecret \"" + vars['SECRET'] + "\"\n" output += "}\n" print (output) ################# output SNAT pool config ###################### # # ############################################################# for s in snatPools.keys(): output = "ltm snatpool " + partition + s + " {\n\t" output += "members {\n" #run through pool range and add members as individual IP addresses below each other for member in snatPools[s]['members']: output += "\t " + member + "\n" output += "\t}" output += "\n}" print (output) ################# output pool config ###################### # # ############################################################# for p in pools.keys(): output = "ltm pool " + partition + p + " {\n\t" if 'monitor' in pools[p]: output += "monitor min 1 of { " + pools[p]['monitor'] + " }\n\t" if 'lbMethod' in pools[p]: output += "load-balancing-mode " + pools[p]['lbMethod'] + "\n\t" output += "members {\n" if 'destinations' in pools[p] and len(pools[p]['destinations']): for i,v in enumerate(pools[p]['destinations']): d = re.sub(':\d+$','',v) output += "\t\t" + v + " {\n\t\t\taddress " + d + "\n" if pools[p]['member-disabled'][i]: output += "\t\t\tstate down\n" output += "\t\t\tpriority-group " + str(pools[p]['priority-group'][i]) + "\n" output += "\t\t}\n" output += "\t}\n}" print (output) ################# output virtual server config ###################### # # ###################################################################### for v in virtuals: output = "ltm virtual " + partition + v + " {\n" output += "\tdestination " + virtuals[v]['destination'] + "\n" output += "\tip-protocol " + virtuals[v]['protocol'].lower() + "\n" if 'pool' in virtuals[v] and virtuals[v]['pool']: output += "\tpool " + virtuals[v]['pool'] + "\n" if 'profiles' in virtuals[v] and len(virtuals[v]['profiles']): output += "\tprofiles {\n" for p in virtuals[v]['profiles']: output += "\t\t" + p + " { }\n" output += "\t}\n" if 'snat' in virtuals[v]: output += "\tsource-address-translation {\n\t" if virtuals[v]['snat'] == 'automap': output += "\ttype automap\n\t" else: output += "\ttype snat\n\t" output += "\tpool " + virtuals[v]['snat'] + "\n\t" output += "}\n" if 'policy' in virtuals[v]: output += "\tpolicies {\n\t" output += "\t" + virtuals[v]['policy'] + " { }\n\t}\n" output += "}" print (output) Tested this on version: 12.03KViews0likes31CommentsSNI Routing with BIG-IP
In the previous article, The Three HTTP Routing Patterns, Lori MacVittie covers 3 methods of routing. Today we will look at Server Name Indication (SNI) routing as an additional method of routing HTTPS or any protocol that uses TLS and SNI. Using SNI we can route traffic to a destination without having to terminate the SSL connection. This enables several benefits including: Reduced number of Public IPs Simplified configuration More intelligent routing of TLS traffic Terminating SSL Connections When you have a SSL certificate and key you can perform the cryptographic actions required to encrypt traffic using TLS. This is what I refer to as “terminating the SSL connection” throughout this article. When you want to route traffic this is a chicken and an egg problem, because for TLS traffic you want to be able to route the traffic by being able to inspect the contents, but this normally requires being able to “terminate the SSL connection”. The goal of this article is to layer in traffic routing for TLS traffic without having to require having/knowing the original SSL certificate and key. Server Name Indication (SNI) SNI is a TLS extension that makes it possible to "share" certificates on a single IP address. This is possible due to a client using a TLS extension that requests a specific name before the server responds with a SSL certificate. Prior to SNI, the other options would be a wildcard certificate or Subject Alternative Name (SAN) that allows you to specify multiple names with a single certificate. SNI with Virtual Servers It has been possible to use SNI on F5 BIG-IP since TMOS 11.3.0. The following KB13452 outlines how it can be configured. In this scenario (from the KB article) the BIG-IP is terminating the SSL connection. Not all clients support SNI and you will always need to specify a “fallback” profile that will be used if a SNI name is not used or matched. The next example will look at how to use SNI without terminating the SSL connection. SNI Routing Occasionally you may have the need to have a hybrid configuration of terminating SSL connections on the BIG-IP and sending connections without terminating SSL. One method is to create two separate virtual servers, one for SSL connections that the BIG-IP will handle (using clientssl profile) and one that it will not handle SSL (just TCP). This works OK for a small number of backends, but does not scale well if you have many backends (run out of Public IP addresses). Using SNI Routing we can handle everything on a single virtual server / Public IP address. There are 3 methods for performing SNI Routing with BIG-IP iRule with binary scan a. Article by Colin Walker code attribute to Joel Moses b. Code Share by Stanislas Piron iRule with SSL::extensions Local Traffic Policy Option #1 is for folks that prefer complete control of the TLS protocol. It only requires the use of a TCP profile. Options #2 and #3 only require the use of a SSL persistence profile and TCP profile. SNI Routing with Local Traffic Policy We will skip option #1 and #2 in this article and look at using a Local Traffic Policy for SNI Routing. For a review of Local Traffic Policies check out the following DevCentral articles: LTM Policy Jan 2015 Simplifying Local Traffic Policies in BIG-IP 12.1 June 2016 In previous articles about Local Traffic Policies the focus was on routing HTTP traffic, but today we will use it to route SSL connections using SNI. In the following example, using a Local Traffic Policy named “sni_routing”, we are setting a condition on the SSL Extension “servername” and sending the traffic to a pool without terminating the SSL connection. The pool member could be another server or another BIG-IP device. The next example will forward the traffic to another virtual server that is configured with a clientssl profile. This uses VIP targeting to send traffic to another virtual server on the same device. In both examples it is important to note that the “condition”/“action” has been changed from occurring on “request” (that maps to a HTTP L7 request) to “ssl client hello”. By performing the action prior to any L7 functions occurring, we can forward the traffic without terminating the SSL connection. The previous example policy, “sni_routing”, can be attached to a Virtual Server that only has a TCP profile and SSL persistence profile. No HTTP or clientssl profile is required! This method can also be used to solve the issue of how to consolidate multiple SSL virtual servers behind a single virtual server that have different APM and/or ASM policies. This is similar to the architecture that is used by the Container Connector for Cloud Foundry; in creating a two-tier load balancing solution on a single device. Routed Correctly? TLS 1.3 has interesting proposals on how to obscure the servername (TLS in TLS?), but for now this is a useful and practical method of handling multiple SSL certs on a single IP. In the future this may still be possible as well with TLS 1.3. For example the use of a HTTP Fronting service could be a tier 1 virtual server (this is just my personal speculation, I have not tried, at the time of publishing this was still a draft proposal). In other news it has been demonstrated that a combination of using SNI and a different host header can be used for “domain fronting”. A method to enforce consistent policy (prevent domain fronting) would be to layer in additional conditions that match requested SNI servername (TLS extension) with requested HOST header (L7 HTTP header). This would help enforce that a tenant is using a certificate that is associated with their application and not “borrowing” the name and certificate that is being used by an adjacent service. We don’t think of a TLS extension as an attribute that can be used to route application traffic, but it is useful and possible on BIG-IP.30KViews0likes17CommentsVerify change in behavior for (major) software updates
Dear all, I can remember there was a cool feature within ihealth, where you could perform some kind of simulation for a software update and the output was telling you, which of your configuration items needs to be adjusted before/after the change and where is a change in behavior. This was removed I think already several years ago 😞 Therefor my question, is there something similar available or what's the latest recommendation from F5 for a major software update (e.g. from 13.1.5 to 16.x) to check any "conflicts" with the existing configuration. Thank you! Regards Stefan 🙂726Views3likes4CommentsCookie persistence and source address fallback
I wonder what will be result of such setup: LB set to Round Robin Default Persistence Profile: Cookie (Cookie Insert) Fallback Persistence Profile: Source Address Source IP same for all requests (SNATed) My assumption is: First new TCP connection established, no cookie present Fallback Persistence used, no Persistence Record (PR) found No persistence is applied because none exist so connection will be directed to first member What will happen then? Persistence Record for source IP will be created pointing to first server? In HTTP response cookie is inserted pointing to first pool member Then second connection from the same IP comes, assuming that PR was created and did not time out then LB will be ignored and connection will be directed to first server In HTTP response again cookie pointing to first server will be inserted Then all returning connections (with cookies set) will be directed to first server, LB in fact will not be used, except for situation when there is enough period of inactivity between connections to allow PR to expire, but will then new connection be send to second server according to RR or not necessarily? Is above correct? PiotrSolved1.3KViews0likes19CommentsQuestion regarding K22301343, extend /var/ folder after upgrade..
Hi all! We´ve been looking to extend the space of /var/ as it´s causing all types of problems for us when it´s gets full (which happens all the the time). So I´ve been reading and found this article, https://my.f5.com/manage/s/article/K22301343. We´re doing an upgrade at the same this so does anyone know if the /var/ needs to have been extended previously before doing this?? /Kim169Views0likes2CommentsHappy 20th Birthday, BIG-IP TMOS!
I wasn’t in the waiting room with the F5 family, ears and eyes perked for the release announcement of BIG-IP version 9.0. I was a customer back in 2004, working on a government contract at Scott AFB, Illinois. I shared ownership of the F5 infrastructure, pairs of BIG-IPs running version 4.5 on Dell PowerEdge 2250 servers with one other guy. But maybe a month or two before the official first release of TMOS, my F5 account manager dropped off some shiny new hardware. And it was legit purpose-built and snazzy, not some garage-style hacked Frankenstein of COTS parts like the earlier stuff. And you wonder why we chose Dell servers! Anyway, I was a hard-core network engineer at this time, with very little exposure to anything above layer four, and even there, my understanding was limited to ports and ACLs and maybe a little high-level clarity around transport protocols. But application protocols? Nah. No idea. So with this new hardware and an entirely new full-proxy architecture (what’s a proxy, again?) I was overwhelmed. And honestly, I was frustrated with it for the first few days because I didn’t know what I didn’t know and so I struggled to figure out what to do with it, even to replicate my half-proxy configuration in the “new way”. But I’m a curious person. Given enough time and caffeine, I can usually get to the bottom of a problem, at least well enough to arrive at a workable solution. And so I did. My typical approach to anything is to make it work, make it work better, make it work reliably better, then finally make it work reliably and more performantly better. And the beauty here with this new TMOS system is that I was armed with a treasure trove of new toys. The short list I dug into during my beta trial, which lasted for a couple of weeks: The concept of a profile. When you support a few applications, this is no big deal. When you support hundreds, being able to macro configuration snippets within your application and across applications was revolutionary. Not just for the final solution, but also for setting up and executing your test plans. iRules. Yes, technically they existed in 4.x, but they were very limited in scope. With TMOS, F5 introduced the Tcl-based and F5 extended live-traffic scripting environment that unleashed tremendous power and flexibility for network and application teams. I dabbled with this, and thought I understood exactly how useful this was. More on this a little later. A host operating system. I was a router, switch, and firewall guy. Nothing I worked on had this capability. I mean, a linux system built in to my networking device? YES!!! Two things I never knew I always needed during my trial: 1) tcpdump ON BOX. Seriously--mind blown; and 2) perl scripting against config and snmp. Yeah, I know, I laugh about perl now. But 20 years ago, it was the cats pajamas. A fortunate job change Shortly after my trial was over, I interviewed for an accepted a job offer from a major rental car company that was looking to hire an engineer to redesign their application load balancing infrastructure and select the next gear purchase for the effort. We evaluated Cisco, Nortel/Alteon, Radware, and F5 on my recommendation. With our team’s resident architect we drafted the rubric with which we’d evaluate all the products, and whereas there were some layer two performance issues in some packet sizes that were arguably less than real-world, the BIG-IP blew away the competitors across the board. Particularly, though, in configurability and instrumentation. Tcpdump on box was such a game-changer for us. Did we have issues with TMOS version 9? For sure. My first year with TMOS was also TMOS's first year. Bugs are going to happen with any release, but a brand new thing is guaranteed. But F5 support was awesome, and we worked through all the issues in due time. Anyway, I want to share three wins in my first year with TMOS. Win #1 Our first production rollout was in the internet space, on BIG-IP version 9.0.5. That’s right, a .0 release. TMOS was a brand new baby, and we had great confidence throughout our testing. During our maintenance, once we flipped over the BIG-IPs, our rental transaction monitors all turned red and the scripted rental process had increased by 50%! Not good. “What is this F5 stuff? Send it back!!” But it was new, and we knew we had a gem here. We took packet captures on box, of course, then rolled back and took more packet captures, this time through taps because our old stuff didn’t have tcpdump on box. This is where Jason started to really learn about the implications of both a full proxy architecture and the TCP protocol. It turns our our application servers had a highly-tuned TCP stack on them specific to the characteristics of the rental application. We didn’t know this, of course. But since we implemented a proxy that terminates clients at the BIG-IP and starts a new session to the servers, all those customizations for WAN traffic were lost. Once we built a TCP profile specifically for the rental application servers and tested it under WAN emulation, we not only reached parity with the prior performance but beat it by 10%. Huzzah! Go BIG-IP custom protocol stack configuration! Win #2 For the next internal project, I had to rearchitect the terminal server farm. We had over 700 servers in two datacenters supporting over 60,000 thin clients around the world for rental terminals. Any failures meant paper tickets and unhappy staff and customers. One thing that was problematic with the existing solution is that sometimes clients would detach and upon reconnect would connect directly to the server, which skewed the load balancers view of the world and frequently overloaded some servers to the point all sessions on that server would hang until metrics (but usually angry staff) would notify. Remember my iRules comment earlier on differentiators? Well, iRules architect David Hansen happened to be a community hero and was very helpful to me in the DevCentral forums and really opened my eyes to the art of possible with iRules. He was able to take the RDP session token that was being returned by the client, read it, translate it from its Microsoft encoding format, and then forward the session on to the correct server in the backend so that all sessions continued to be accounted for in our load balancing tier. This was formative for me as a technologist and as a member of the DevCentral community. Win #3 2004-2005 was the era before security patching was as visible a responsibility as it is today, but even then we had a process and concerns when there were obstacles. We had an internal application that had a plugin for the web tier that managed all the sessions to the app tier, and this plugin was no longer supported. We were almost a year behind on system and application patches because we had no replacement for this. Enter, again, iRules. I was able to rebuild the logic of the plugin in an iRule that IIRC wasn’t more than 30 lines. So the benefits ended up not only being a solution to that problem, but the ability to remove that web tier altogether, saving on equipment, power, and complexity costs. And that was just the beginning... TMOS was mature upon arrival, but it got better every year. iControl added REST-based API access; clustered multi-processing introduced tremendous performance gains; TMOS got virtualized, and all the home-lab technologists shouted with joy; a plugin architecture allowed for product modules like ASM and APM; solutions that began as iRules like AFM and SSLO became products. It’s crazy how much innovation has taken place on this platform! The introduction of TMOS didn’t just introduce me to applications and programmability. It did that and I’m grateful, but it did so much more. It unlocked in me that fanboy level that fans of sports teams, video game platforms, Taylor Swift, etc, experience. It helped me build an online community at DevCentral, long before I was an employee. Happy 20th Birthday, TMOS! We celebrate and salute you!719Views10likes1Comment201 Recommendations for Study
Hello mates, I am a new member of the forum 😀 I have to retake my 201 exam again in February and it´s been a while since the last time I touch a F5 device. I tried to look for the PDF which, as I remember, it was pretty solid material for the exam and the last time, I was able to pass the exam at the first attempt by only using the study guide and a F5 device I had on the lab. But I´ve seen recently that the guide is not longer available where it was: https://clouddocs.f5.com/training/community/f5cert/html/class3/class3.html May you kindly recommend me documentation and good material for my study. Much appreciate it! Regards,3KViews0likes9CommentsiHealth Upgrade Advisor: Making upgrades a little easier
Whether it is upgrading the firmware on a switch, the OS on a server, an important business application or the software on a BIG-IP, performing upgrades is something that makes almost all IT Admins and Network Engineers nervous. We’ve learned from (sometimes painful) experience that things don’t always go as planned. Good preparation greatly increases the likelihood that an upgrade will be successful, which is why F5 has created the iHealth Upgrade Advisor. Its goal is to provide an additional service from F5 that will complement your existing upgrade preparations, increasing the predictability of the upgrade while reducing your upgrade time. The iHealth Upgrade Advisor service provides a way for users to gain insight into potential issues with a BIG-IP upgrade before they attempt the upgrade. It provides guidance that is specific to a BIG-IP based on its configuration, the version of software it is currently running and the version you are planning to upgrade to. When an issue can be avoided by making a configuration change prior to upgrading, the Upgrade Advisor will tell you exactly what to change. For some issues, it will list the corrective actions to take after the upgrade. Demo Video This short video demonstrating the Upgrade Advisor shows you how to use it and some examples of the guidance it provides. Accessing the Upgrade Advisor The next time you are preparing to upgrade a BIG-IP, login to ihealth.f5.com, upload a .qkview file from that BIG-IP and then view the qkview after iHealth has analyzed it. The Upgrade Advisor can be accessed by clicking on its tab in the left-hand menu. Simply select the version of BIG-IP you are planning to upgrade to in the advisor and review the results. Here is a screenshot of the Upgrade Advisor: Give it a Try Try out the F5 upgrade Advisor today and let us know what you think using the feedback option (circled in red on the right side of the screenshot above).4.5KViews1like8CommentsTLS server_name extension based routing without clientssl profile
Problem this snippet solves: Some configuration requires to not decrypt SSL traffic on F5 appliances to select pool based on HTTP Host header. I found a useful irule and this code keeps the structure and most of binary commands of it. I'm not sure if the first author was Kevin Stewart or Colin Walker. thanks both of them to have provided such code. I worked to understand it reading TLS 1.2 RFC 5246 and TLS 1.3 draft-23 and provided some enhancements and following description with irule variables references. According to TLS 1.3 draft-23, this code will still be valid with next TLS version. the following network diagram shows one use cases where this code will help. This diagram show how this code works based on the tls_servername_routing_dg Datagroup values and detected server name and TLS versions detected in the CLIENT_HELLO packet. For performances reasons, only the first TCP data packet is analyzed. Versions : 1.1 : Updated to support TLS version detection and SSL offload feature. (05/03/2018) 1.2 : Updated to support TLS Handshake Failure Messages instead of reject. (09/03/2018) 1.3 : Updated to support node forwarding, logs only for debug (disabled with static variable), and changed the Datagroup name to tls_servername_routing_dg . (16/03/2018) 1.4 : Added 16K handshake length limit defined in RFC 1.2 in variable payload. (13/04/2018) 1.5 : Added supported version extension recursion, to bypass unknown TLS version if a known and allowed version is in the list. This correct an issue with Google chrome which include not documented TLS version on top of the list. (30/04/2018) How to use this snippet: create a virtual server with following configuration: type : Standard SSL Profile (client) : Only if you want to enable SSL offload for some pools irule : code bellow create all objects used in following datagroup (virtual servers, pools) create a data-group named tls_servername_routing_dg. if you want to forward to pool, add the value pool NameOfPool if you want to forward to pool and enable SSL Offload (ClientSSL profile must be enabled on virtual server), add the value pool NameOfPool ssl_offload if you want to forward to virtual server, add the value virtual NameOfVirtual if you want to forward to an IP address, add the value node IPOfServer , backend server will not be translated if you want to reject the connection with RFC compliant handshake_failure message, add the value handshake_failure if you want to reject the connection, add the value reject if you want to drop the connection, add the value drop The default value keyword is search if there is no TLS server name extension or if TLS server name extension is not found in the data group. here is an example: ltm data-group internal tls_servername_routing_dg { records { app1.company.com { data "virtual vs_app1.company.com" } app2.company.com { data "pool p_app2" } app3.company.com { data "pool p_app3 ssl_offload" } app4.company.com { reject } default { data "handshake_failure" } } type string } Code : when RULE_INIT { set static::sni_routing_debug 0 } when CLIENT_ACCEPTED { if { [PROFILE::exists clientssl] } { # We have a clientssl profile attached to this VIP but we need # to find an SNI record in the client handshake. To do so, we'll # disable SSL processing and collect the initial TCP payload. set ssldisable "SSL::disable" set sslenable "SSL::enable" eval $ssldisable } TCP::collect set default_pool [LB::server pool] set tls_servername "" set tls_handshake_prefered_version "0000" } when CLIENT_DATA { # Store TCP Payload up to 2^14 + 5 bytes (Handshake length is up to 2^14) set payload [TCP::payload 16389] set payloadlen [TCP::payload length] # - Record layer content-type (1 byte) --> variable tls_record_content_type # Handshake value is 22 (required for CLIENT_HELLO packet) # - SSLv3 / TLS version. (2 byte) --> variable tls_version # SSLv3 value is 0x0300 (doesn't support SNI, not valid in first condition) # TLS_1.0 value is 0x0301 # TLS_1.1 value is 0x0302, 0x0301 in CLIENT_HELLO handskake packet for backward compatibility (not specified in RFC, that's why the value 0x0302 is allowed in condition) # TLS_1.2 value is 0x0303, 0x0301 in CLIENT_HELLO handskake packet for backward compatibility (not specified in RFC, that's why the value 0x0303 is allowed in condition) # TLS_1.3 value is 0x0304, 0x0301 in CLIENT_HELLO handskake packet for backward compatibility (explicitly specified in RFC) # TLS_1.3 drafts values are 0x7FXX (XX is the hexadecimal encoded draft version), 0x0301 in CLIENT_HELLO handskake packet for backward compatibility (explicitly specified in RFC) # - Record layer content length (2 bytes) : must match payload length --> variable tls_recordlen # - TLS Hanshake protocol (length defined by Record layer content length value) # - Handshake action (1 byte) : CLIENT_HELLO = 1 --> variable tls_handshake_action # - handshake length (3 bytes) # - SSL / TLS handshake version (2 byte) # In TLS 1.3 CLIENT_HELLO handskake packet, TLS hanshake version is sent whith 0303 (TLS 1.2) version for backward compatibility. a new TLS extension add version negociation. # - hanshake random (32 bytes) # - handshake sessionID length (1 byte) --> variable tls_handshake_sessidlen # - handshake sessionID (length defined by sessionID length value, max 32-bit) # - CipherSuites length (2 bytes) --> variable tls_ciphlen # - CipherSuites (length defined by CipherSuites length value) # - Compression length (2 bytes) --> variable tls_complen # - Compression methods (length defined by Compression length value) # - Extensions # - Extension length (2 bytes) --> variable tls_extension_length # - list of Extensions records (length defined by extension length value) # - extension record type (2 bytes) : server_name = 0, supported_versions = 43--> variable tls_extension_type # - extension record length (2 bytes) --> variable tls_extension_record_length # - extension data (length defined by extension record length value) # # TLS server_name extension data format: # - SNI record length (2 bytes) # - SNI record data (length defined by SNI record length value) # - SNI record type (1 byte) # - SNI record value length (2 bytes) # - SNI record value (length defined by SNI record value length value) --> variable tls_servername # # TLS supported_version extension data format (added in TLS 1.3): # - supported version length (1 bytes) --> variable tls_supported_versions_length # - List of supported versions (2 bytes per version) --> variable tls_supported_versions # If valid TLS 1.X CLIENT_HELLO handshake packet if { [binary scan $payload cH4Scx3H4x32c tls_record_content_type tls_version tls_recordlen tls_handshake_action tls_handshake_version tls_handshake_sessidlen] == 6 && \ ($tls_record_content_type == 22) && \ ([string match {030[1-3]} $tls_version]) && \ ($tls_handshake_action == 1) && \ ($payloadlen == $tls_recordlen+5)} { # store in a variable the handshake version set tls_handshake_prefered_version $tls_handshake_version # skip past the session id set record_offset [expr {44 + $tls_handshake_sessidlen}] # skip past the cipher list binary scan $payload @${record_offset}S tls_ciphlen set record_offset [expr {$record_offset + 2 + $tls_ciphlen}] # skip past the compression list binary scan $payload @${record_offset}c tls_complen set record_offset [expr {$record_offset + 1 + $tls_complen}] # check for the existence of ssl extensions if { ($payloadlen > $record_offset) } { # skip to the start of the first extension binary scan $payload @${record_offset}S tls_extension_length set record_offset [expr {$record_offset + 2}] # Check if extension length + offset equals payload length if {$record_offset + $tls_extension_length == $payloadlen} { # for each extension while { $record_offset < $payloadlen } { binary scan $payload @${record_offset}SS tls_extension_type tls_extension_record_length if { $tls_extension_type == 0 } { # if it's a servername extension read the servername # SNI record value start after extension type (2 bytes), extension record length (2 bytes), record type (2 bytes), record type (1 byte), record value length (2 bytes) = 9 bytes binary scan $payload @[expr {$record_offset + 9}]A[expr {$tls_extension_record_length - 5}] tls_servername set record_offset [expr {$record_offset + $tls_extension_record_length + 4}] } elseif { $tls_extension_type == 43 } { # if it's a supported_version extension (starting with TLS 1.3), extract supported version in a list binary scan $payload @[expr {${record_offset} + 4}]cS[expr {($tls_extension_record_length -1)/2}] tls_supported_versions_length tls_supported_versions set tls_handshake_prefered_version [list] foreach version $tls_supported_versions { lappend tls_handshake_prefered_version [format %04X [expr { $version & 0xffff }] ] } if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : prefered version list : $tls_handshake_prefered_version"} set record_offset [expr {$record_offset + $tls_extension_record_length + 4}] } else { # skip over other extensions set record_offset [expr {$record_offset + $tls_extension_record_length + 4}] } } } } } elseif { [binary scan $payload cH4 ssl_record_content_type ssl_version] == 2 && \ ($tls_record_content_type == 22) && \ ($tls_version == 0300)} { # SSLv3 detected set tls_handshake_prefered_version "0300" } elseif { [binary scan $payload H2x1H2 ssl_version handshake_protocol_message] == 2 && \ ($ssl_version == 80) && \ ($handshake_protocol_message == 01)} { # SSLv2 detected set tls_handshake_prefered_version "0200" } unset -nocomplain payload payloadlen tls_record_content_type tls_recordlen tls_handshake_action tls_handshake_sessidlen record_offset tls_ciphlen tls_complen tls_extension_length tls_extension_type tls_extension_record_length tls_supported_versions_length tls_supported_versions foreach version $tls_handshake_prefered_version { switch -glob -- $version { "0200" { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : SSLv2 ; connection is rejected"} reject return } "0300" - "0301" { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : SSL/TLS ; connection is rejected (0x$version)"} # Handshake Failure packet format: # # - Record layer content-type (1 byte) --> variable tls_record_content_type # Alert value is 21 (required for Handshake Failure packet) # - SSLv3 / TLS version. (2 bytes) --> from variable tls_version # - Record layer content length (2 bytes) : value is 2 for Alert message # - TLS Message (length defined by Record layer content length value) # - Level (1 byte) : value is 2 (fatal) # - Description (1 bytes) : value is 40 (Handshake Failure) TCP::respond [binary format cH4Scc 21 $tls_version 2 2 40] after 10 TCP::close #drop #reject return } "030[2-9]" - "7F[0-9A-F][0-9A-F]" { # TLS version allowed, do nothing break } "0000" { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : No SSL/TLS protocol detected ; connection is rejected (0x$version)"} reject return } default { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : Unknown CLIENT_HELLO TLS handshake prefered version : 0x$version"} } } } if { $tls_servername equals "" || ([set sni_dg_value [class match -value [string tolower $tls_servername] equals tls_servername_routing_dg]] equals "")} { set sni_dg_value [class match -value "default" equals tls_servername_routing_dg] } switch [lindex $sni_dg_value 0] { "virtual" { if {[catch {virtual [lindex $sni_dg_value 1]}]} { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : TLS server_name value = ${tls_servername} ; TLS prefered version = 0x${tls_handshake_prefered_version} ; Virtual server [lindex $sni_dg_value 1] doesn't exist"} } else { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : TLS server_name value = ${tls_servername} ; TLS prefered version = 0x${tls_handshake_prefered_version} ; forwarded to Virtual server [lindex $sni_dg_value 1]"} } } "pool" { if {[catch {pool [lindex $sni_dg_value 1]}]} { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : TLS server_name value = ${tls_servername} ; TLS prefered version = 0x${tls_handshake_prefered_version} ; Pool [lindex $sni_dg_value 1] doesn't exist"} } else { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : TLS server_name value = ${tls_servername} ; TLS prefered version = 0x${tls_handshake_prefered_version} ; forwarded to Pool [lindex $sni_dg_value 1]"} } if {[lindex $sni_dg_value 2] equals "ssl_offload" && [info exists sslenable]} { eval $sslenable } } "node" { if {[catch {node [lindex $sni_dg_value 1]}]} { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : TLS server_name value = ${tls_servername} ; TLS prefered version = 0x${tls_handshake_prefered_version} ; Invalid Node value [lindex $sni_dg_value 1]"} } else { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : TLS server_name value = ${tls_servername} ; TLS prefered version = 0x${tls_handshake_prefered_version} ; forwarded to Node [lindex $sni_dg_value 1]"} } } "handshake_failure" { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : TLS server_name value = ${tls_servername} ; TLS prefered version = 0x${tls_handshake_prefered_version} ; connection is rejected (with Handshake Failure message)"} TCP::respond [binary format cH4Scc 21 $tls_handshake_prefered_version 2 2 40] after 10 TCP::close return } "reject" { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : TLS server_name value = ${tls_servername} ; TLS prefered version = 0x${tls_handshake_prefered_version} ; connection is rejected"} reject return } "drop" { if {$static::sni_routing_debug} {log local0. "[IP::remote_addr] : TLS server_name value = ${tls_servername} ; TLS prefered version = 0x${tls_handshake_prefered_version} ; connection is dropped"} drop return } } TCP::release }4.7KViews6likes10Comments