import datetime; import re; # Get a date: https://www.geeksforgeeks.org/get-current-timestamp-using-python/ ct = datetime.datetime.now() #print("current time:-", ct) #The file that contains the firewall rules shorthand filePath = '/home/jhydeman/Documents/Test/Zones.txt' #The file that will this script will output to, {ct} is the timestamp variable writeFilePath = '/home/jhydeman/Documents/Test/Rules_{ct}.txt'.format(ct=(ct)) sourceZoneName = "" #Functions: https://www.guru99.com/functions-in-python.html #Check if word count of string = 1, if so this is the source zone #https://www.geeksforgeeks.org/python-program-to-count-words-in-a-sentence/ def funcFindSourceZone(line, lineNum): #count the numbers of words on a line by splitting them by space #res = len(line.split()) # word count is 1 then this is the source zone name #if res == 1: #Take the expected ZONENAME: and split it at : then select the first value #https://stackoverflow.com/questions/43162309/how-can-you-read-just-the-first-word-of-every-line-of-file-using-python #https://stackoverflow.com/questions/41228115/how-to-extract-the-first-and-final-words-from-a-string sourceZoneName = line.split(':')[0] #print ("source zone name is: " + sourceZoneName) print("\nSource Zone Name is \"{sourceZoneName}\" from line {lineNum}".format(sourceZoneName=(sourceZoneName),lineNum=(lineNum))) #Count the length of zoneNameLength: https://www.kite.com/python/answers/how-to-count-the-number-of-characters-in-a-string-except-spaces-in-python zoneNameLength = len(sourceZoneName) if zoneNameLength > 13: ERROR = ("ERROR: Zone: \"{sourceZoneName}\" (char count {zoneNameLength}) exceeds 14 characters at line {lineNum}\n".format(sourceZoneName=(sourceZoneName),lineNum=(lineNum), zoneNameLength=(zoneNameLength))) funcError(ERROR) allowedCharactersZoneName = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '_'] if any(x not in allowedCharactersZoneName for x in sourceZoneName): ERROR = ("ERROR: invalid character in zone name {sourceZoneName} at line {lineNum}.\nAllowed characters are: A-Z, 0-9, - and _.\nPlease check the syntax of your firewall rules.\n".format(lineNum=(lineNum), sourceZoneName=(sourceZoneName))) funcError(ERROR) return sourceZoneName def funcFindDestinationZone(line,lineNum): #set first word to destinationZoneName: https://stackoverflow.com/questions/43162309/how-can-you-read-just-the-first-word-of-every-line-of-file-using-python destinationZoneName = line.split(':')[0] destinationZoneName = destinationZoneName.split()[-1] #print ("destination zone name is: " + destinationZoneName) print("Destination Zone Name is \"{destinationZoneName}\" from line {lineNum}".format(destinationZoneName=(destinationZoneName),lineNum=(lineNum))) zoneNameLength = len(destinationZoneName) if zoneNameLength > 13: ERROR = ("ERROR: Zone: \"{destinationZoneName}\" (char count {zoneNameLength}) exceeds 13 characters at line {lineNum}\n".format(destinationZoneName=(destinationZoneName),lineNum=(lineNum), zoneNameLength=(zoneNameLength))) funcError(ERROR) #Allow the destination zone line to end only with certain characters to help prevent typos: https://stackoverflow.com/questions/46798641/how-to-only-allow-digits-letters-and-certain-characters-in-a-string-in-python/46799212 allowed_characters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '}', ')', ']', '>'] last_char = line[-1] if any(x not in allowed_characters for x in last_char): ERROR = ("ERROR: invalid character \"{last_char}\" at end of line {lineNum}.\nAllowed characters are: A-Z, 0-9, ), ], {bracket} and >.\nPlease check the syntax of your firewall rules.\n".format(last_char=(last_char), lineNum=(lineNum), bracket=('}'))) funcError(ERROR) allowedCharactersZoneName = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '_'] if any(x not in allowedCharactersZoneName for x in destinationZoneName): ERROR = ("ERROR: invalid character in zone name {destinationZoneName} at line {lineNum}.\nAllowed characters are: A-Z, 0-9, - and _.\nPlease check the syntax of your firewall rules.\n".format(last_char=(last_char), lineNum=(lineNum), destinationZoneName=(destinationZoneName))) funcError(ERROR) return destinationZoneName def funcFindRules(line,sourceZoneName,destinationZoneName,lineNum): valRuleList = "" rulecount = 100 #valportProtoRule is here to keep the protocol the same despite successive runs through the values, error checking will be enabled to ensure protocols match if 2 port rules exist valPortProtoRule = "" allowedCharactersRuleName = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P','Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7','8', '9', '0', '-', '_', '!'] #split the line after the first : "(':', (1))" and use the last value [-1] as the value rules = line.split(':', (1))[-1] #remove all spaces from string: https://www.journaldev.com/23763/python-remove-spaces-from-string rules = rules.replace(" ", '') #split the line by "," into values rules = rules.split(",") #Check each value for specific characters: https://stackoverflow.com/questions/18294534/is-there-a-foreach-function-in-python-3 for val in rules: #Was trying here to create a list of rules per zone and to check if any of the rules was a duplicate of a previous rule, but I got tired. #valRuleList.extend(val) #print(valRuleList) #funcRuleListDupeDetect(valRuleList) #valDashCount is used to determine the number of "-" characters in a rule, if more than 1 then error #valDashCount = 0 #create rulecode to determine if a rule within some type of bracket is found; it can also differentiate between combinations of rule code types rulecode = 0 valDestIP = "" valDestIPRules = "" valDestPort = "" valDestPortRules = "" valSourceIP = "" valSourceIPRules = "" valSourcePort = "" valSourcePortRules = "" #valPortProtocolCheck is the value of the protocol, tcp, udp or tcp_udp and is used to check against the previous protocol value to ensure source and destination are the same protocol valPortProtocolCheck = "" #valOptions are the per rule options that override the defaults, such and setting the action to drop instead of the default of accept valOptions = "" #This is used for error checking so that only 1 set of options is specific per rule valOptionsCount = 0 # Set the default log value, it can be overriden by setting options per rule log = "enable" # Set the default action value, it can be overriden by setting options per rule action = "accept" # Set the default and multiple state values (each rule can have 4 different states values), these are set by options per rule state_default = "new enable" state1 = "" state2 = "" state3 = "" state4 = "" ipsec_opt = "" #Check if brackets exist in a rule, if so run it through the bracket check function to do basic syntax validation: https://stackoverflow.com/questions/3389574/check-if-multiple-strings-exist-in-another-string/3389611 brackets = ["(", ")", "{", "}", "[", "]", "<", ">"] if any(x in val for x in brackets): funcBracketRulesCheck(val, lineNum) #check if a value contains a specific character: https://www.codegrepper.com/code-examples/python/check+if+a+string+contains+a+character+python #https://stackoverflow.com/questions/11427138/python-wildcard-search-in-string #check if a character is present in a value and do something: https://www.codegrepper.com/code-examples/python/check+if+a+string+contains+a+character+python #run through each set of brackets and if they exist extract the contained value and classify it as source/destination/port/ip group # < indicates a destination port value if "<" in (val) and ">" in (val): #rulecode is used to create # by assigning a value to each rule type then adding them together to get a rulecode, \ #this can then used for more granular per rule functions rulecode = 1 # find value between "<>" brackets: https://stackoverflow.com/questions/4894069/regular-expression-to-return-text-between-parenthesis valDestPort = val[val.find("<") + 1:val.find(">")] #if a "|" is present this means there are options specified for this rule, run the options if "|" in (valDestPort): #split valDestPort by "|" then read the first 2 values, if there is a 3rd value exit on error split = valDestPort.split("|") #if there are more than 2 "|" characters per rule then exit on error, theres should only be \ #one "|" and additional options are split by the ";" character if (len(split)) > 2: ERROR = ("ERROR: There are more than one \"|\" characters for rule {val} at line {lineNum}.".format(val=(val), lineNum=(lineNum))) funcError(ERROR) #split[0] gives the first value after the "|" split, this value contains the firewall rule name of the destination port group valDestPort = split[0] #split[1] gives the second value after the "|" split, this value contains the options for the firewall rule valOptions = split[1] #this options counter helps ensure options are set only once per rule, for values greater than one the script exits on error, \ #for values of exactly 1 the script will process the options in a separate function valOptionsCount = valOptionsCount + 1 print("Rule \"{valDestPort}\" has options \"{valOptions}\" at line {lineNum}.".format(valDestPort=(valDestPort), valOptions=(valOptions), lineNum=(lineNum))) #Only certain characters are allowed for rule names, check and exit on error if violations are found #valDashCount = valDestPort.count('-') #if valDashCount > 1: #ERROR = ("ERROR: more than one \"-\" character found in rule \"{valDestPort}\" at line {lineNum}.".format(valDestPort=(valDestPort), lineNum=(lineNum))) #funcError(ERROR) if any(x not in allowedCharactersRuleName for x in valDestPort): ERROR = ("ERROR: invalid character in rule name \"{valDestPort}\" at line {lineNum}.\nAllowed characters are: A-Z, 0-9, -, _ and !.\nPlease check the syntax of your firewall rules.\n".format(lineNum=(lineNum), valDestPort=(valDestPort))) funcError(ERROR) # Count the number of characters of the firewall group ruleCharCount = len(valDestPort) # If the name of the firewall group exceeds 31 characters then exit with error if ruleCharCount > 31: ERROR = ("ERROR: Firewall group rule name \"{valDestPort}\" on line {lineNum} exceeds 31 characters,\n\ Shorten the firewall group name, remember to do so for this rule summary and the commands used to create the group.".format(valDestPort=(valDestPort), lineNum=(lineNum))) funcError(ERROR) #Determine the port protocol used by running valDestPort through the function and check that the \ #protocols are the same if both source ports and destination ports are specified in the same rule, \ #this is done via the valPortProtocolCheck value. val and lineNum are for more description error messages valPortProtocol, valPortProtocolCheck = funcFindPortProtocol(valDestPort, valPortProtocolCheck, val, lineNum) #Insert port group into a string that will be used by a VyOS/EdgeOS firewall and assign it to value valDestPortRules #This will later be written to a file in a particular order valDestPortRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} destination group port-group {valDestPort}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valDestPort=(valDestPort), valPortProtocol=(valPortProtocol))) # Insert port protocol type into a string that will be used by a VyOS/EdgeOS firewall and assign it to value valPortProtoRule #This will later be written to a file in a particular order valPortProtoRule = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} protocol {valPortProtocol}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valPortProtocol=(valPortProtocol))) # ( indicates a destination ip value if "(" in (val) and ")" in (val): rulecode = (rulecode) + 2 valDestIP = val[val.find("(") + 1:val.find(")")] if "|" in (valDestIP): #split valDestIP by "|" then read the first 2 values, if there is a 3rd value exit on error split = valDestIP.split("|") if (len(split)) > 2: ERROR = ("ERROR: There are more than one \"|\" characters for rule {val} at line {lineNum}.".format(val=(val), lineNum=(lineNum))) funcError(ERROR) valDestIP = split[0] valOptions = split[1] valOptionsCount = valOptionsCount + 1 print("Rule \"{valDestIP}\" has options \"{valOptions}\" at line {lineNum}.".format(valDestIP=(valDestIP), valOptions=(valOptions), lineNum=(lineNum))) #valDashCount = valDestIP.count('-') #if valDashCount > 1: #ERROR = ("ERROR: more than one \"-\" character found in rule \"{valDestIP}\" at line {lineNum}.".format(valDestIP=(valDestIP), lineNum=(lineNum))) #funcError(ERROR) if any(x not in allowedCharactersRuleName for x in valDestIP): ERROR = ( "ERROR: invalid character in rule name \"{valDestIP}\" at line {lineNum}.\nAllowed characters are: A-Z, 0-9, -, _ and !.\nPlease check the syntax of your firewall rules.\n".format(lineNum=(lineNum), valDestIP=(valDestIP))) funcError(ERROR) ruleCharCount = len(valDestIP) # If the name of the firewall group exceeds 31 characters then exit with error if ruleCharCount > 31: ERROR = ("ERROR: Firewall group rule name \"{valDestIP}\" on line {lineNum} exceeds 31 characters,\n\ Shorten the firewall group name, remember to do so for this rule summary and the commands used to create the group.".format( valDestIP=(valDestIP), lineNum=(lineNum))) funcError(ERROR) valAddressGroupType = funcFindAddressType(valDestIP, val, lineNum) # https://stackoverflow.com/questions/10112614/how-do-i-create-a-multiline-python-string-with-inline-variables valDestIPRules= ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} destination group {valAddressGroupType} {valDestIP}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valDestIP=(valDestIP), valAddressGroupType=(valAddressGroupType))) # [ indicates a source ip value if "[" in (val) and "]" in (val): rulecode = (rulecode) + 4 valSourceIP = val[val.find("[") + 1:val.find("]")] if "|" in (valDestIP): # split valDestIP by "|" then read the first 2 values, if there is a 3rd value exit on error split = valDestIP.split("|") if (len(split)) > 2: ERROR = ("ERROR: There are more than one \"|\" characters for rule {val} at line {lineNum}.".format( val=(val), lineNum=(lineNum))) funcError(ERROR) valDestIP = split[0] valOptions = split[1] valOptionsCount = valOptionsCount + 1 print("Rule \"{valDestIP}\" has options \"{valOptions}\" at line {lineNum}.".format( valDestIP=(valDestIP), valOptions=(valOptions), lineNum=(lineNum))) if "|" in (valSourceIP): # split valSourceIP by "|" then read the first 2 values, if there is a 3rd value exit on error split = valSourceIP.split("|") if (len(split)) > 2: ERROR = ("ERROR: There are more than one \"|\" characters for rule {val} at line {lineNum}.".format( val=(val), lineNum=(lineNum))) funcError(ERROR) valSourceIP = split[0] valOptions = split[1] valOptionsCount = valOptionsCount + 1 print("Rule \"{valSourceIP}\" has options \"{valOptions}\" at line {lineNum}.".format( valSourceIP=(valSourceIP), valOptions=(valOptions), lineNum=(lineNum))) #valDashCount = valSourceIP.count('-') #if valDashCount > 1: #ERROR = ("ERROR: more than one \"-\" character found in rule \"{valSourceIP}\" at line {lineNum}.".format(valSourceIP=(valSourceIP), lineNum=(lineNum))) #funcError(ERROR) if any(x not in allowedCharactersRuleName for x in valSourceIP): ERROR = ( "ERROR: invalid character in rule name \"{valSourceIP}\" at line {lineNum}.\nAllowed characters are: A-Z, 0-9, -, _ and !.\nPlease check the syntax of your firewall rules.\n".format( lineNum=(lineNum), valSourceIP=(valSourceIP))) funcError(ERROR) ruleCharCount = len(valSourceIP) # If the name of the firewall group exceeds 31 characters then exit with error if ruleCharCount > 31: ERROR = ("ERROR: Firewall group rule name \"{valSourceIP}\" on line {lineNum} exceeds 31 characters,\n\ Shorten the firewall group name, remember to do so for this rule summary and the commands used to create the group.".format( valSourceIP=(valSourceIP), lineNum=(lineNum))) funcError(ERROR) #Find the address group type, NET for subnet or IP for single/range address valAddressGroupType = funcFindAddressType(valSourceIP, val, lineNum) valSourceIPRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} source group {valAddressGroupType} {valSourceIP}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=(valSourceIP), valAddressGroupType=(valAddressGroupType))) # f.close() # { indicates a source port value if "{" in (val) and "}" in (val): rulecode = (rulecode) + 8 valSourcePort = val[val.find("{") + 1:val.find("}")] if "|" in (valSourcePort): #split valSourcePort by "|" then read the first 2 values, if there is a 3rd value exit on error split = valSourcePort.split("|") if (len(split)) > 2: ERROR = ("ERROR: There are more than one \"|\" characters for rule {val} at line {lineNum}.".format(val=(val), lineNum=(lineNum))) funcError(ERROR) valSourcePort = split[0] valOptions = split[1] valOptionsCount = valOptionsCount + 1 print("Rule \"{valSourcePort}\" has options \"{valOptions}\" at line {lineNum}.".format(valSourcePort=(valSourcePort), valOptions=(valOptions), lineNum=(lineNum))) #valDashCount = valSourcePort.count('-') #if valDashCount > 1: #ERROR = ("ERROR: more than one \"-\" character found in rule \"{valSourcePort}\" at line {lineNum}.".format(valSourcePort=(valSourcePort), lineNum=(lineNum))) #funcError(ERROR) if any(x not in allowedCharactersRuleName for x in valSourcePort): ERROR = ( "ERROR: invalid character in rule name \"{valSourcePort}\" at line {lineNum}.\nAllowed characters are: A-Z, 0-9, -, _ and !.\nPlease check the syntax of your firewall rules.\n".format( lineNum=(lineNum), valSourcePort=(valSourcePort))) funcError(ERROR) ruleCharCount = len(valSourcePort) # If the name of the firewall group exceeds 31 characters then exit with error if ruleCharCount > 31: ERROR = ("ERROR: Firewall group rule name \"{valSourcePort}\" on line {lineNum} exceeds 31 characters,\n\ Shorten the firewall group name, remember to do so for this rule summary and the commands used to create the group.".format( valSourcePort=(valSourcePort), lineNum=(lineNum))) funcError(ERROR) valPortProtocol, valPortProtocolCheck = funcFindPortProtocol(valSourcePort, valPortProtocolCheck, val, lineNum) valSourcePortRules= ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} source group port-group {valSourcePort}\n".format( sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourcePort=(valSourcePort),valPortProtocol=(valPortProtocol))) valPortProtoRule = ( "set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} protocol {valPortProtocol}\n".format( sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount), valPortProtocol=(valPortProtocol))) if valOptionsCount > 1: ERROR = ("ERROR: Multiple rule OPTIONS entries exist for rule \"{val}\" on line {lineNum}.\n\ Put all rule OPTIONS together in any one of the rule groups instead of splitting them among 2 or more groups.".format( val=(val), lineNum=(lineNum))) funcError(ERROR) if valOptionsCount == 1: # Determine the options for the rule and return the options (log, action, state1, state2, state3, state4) to valOptionsList # then if the returned value for any (array/item/value/list... I don't know the terminology) is not blank then assign it the value on the left # if you add new option types be sure to add them to the list on the "valOptionsList = funcDetermineOptions" line valOptionsList = funcDetermineOptions(valOptions, log, action, state1, state2, state3, state4, ipsec_opt, lineNum) #If the value in a returned variable isn't blank then assign it to a variable #Be sure to add new assignments for any new options you add in your firewall shorthand if valOptionsList[0] != "": log = valOptionsList[0] #print("Log carried = " + log) if valOptionsList[1] != "": action = valOptionsList[1] #print("action carried = " + action) if valOptionsList[2] != "": state1 = valOptionsList[2] #print("state1 carried = " + state1) if valOptionsList[3] != "": state2 = valOptionsList[3] #print("state2 carried = " + state2) if valOptionsList[4] != "": state3 = valOptionsList[4] #print("state3 carried = " + state3) if valOptionsList[5] != "": state4 = valOptionsList[5] #print("state4 carried = " + state4) if valOptionsList[6] != "": ipsec_opt = valOptionsList[6] # print("ipsec_opt carried = " + ipsec_opt) if (rulecode) != 0: valDescription = (val) if rulecode == 1: valDescription = ("to {valDestPort}".format(valDestPort=(valDestPort))) if rulecode == 2: valDescription = ("to {valDestIP}".format(valDestIP=(valDestIP))) if rulecode == 3: valDescription = ("to {valDestPort} to {valDestIP}".format(valDestPort=(valDestPort), valDestIP=(valDestIP))) if rulecode == 4: valDescription = ("from {valSourceIP}".format(valSourceIP=(valSourceIP))) if rulecode == 5: valDescription = ("from {valSourceIP} to {valDestPort}".format(valDestPort=(valDestPort), valSourceIP=(valSourceIP))) if rulecode == 6: valDescription = ("from {valSourceIP} to {valDestIP}".format(valDestIP=(valDestIP), valSourceIP=(valSourceIP))) if rulecode == 7: valDescription = ("from {valSourceIP} to {valDestPort} to {valDestIP}".format(valSourceIP=(valSourceIP), valDestPort=(valDestPort), valDestIP=(valDestIP))) if rulecode == 8: valDescription = ("from {valSourcePort}".format(valSourcePort=(valSourcePort))) if rulecode == 9: valDescription = ("from {valSourcePort} to {valDestPort}".format(valSourcePort=(valSourcePort), valDestPort=(valDestPort))) if rulecode == 10: valDescription = ("from {valSourcePort} to {valDestIP}".format(valSourcePort=(valSourcePort), valDestIP=(valDestIP))) if rulecode == 11: valDescription = ("from {valSourcePort} to {valDestPort} to {valDestIP}".format(valSourcePort=(valSourcePort), valDestPort=(valDestPort), valDestIP=(valDestIP))) if rulecode == 12: valDescription = ("from {valSourceIP} from {valSourcePort}".format(valSourceIP=(valSourceIP), valSourcePort=(valSourcePort))) if rulecode == 13: valDescription = ("from {valSourceIP} from {valSourcePort} to {valDestPort}".format(valSourceIP=(valSourceIP), valSourcePort=(valSourcePort), valDestPort=(valDestPort))) if rulecode == 14: valDescription = ("from {valSourceIP} from {valSourcePort} to {valDestIP}".format(valSourceIP=(valSourceIP), valSourcePort=(valSourcePort), valDestIP=(valDestIP))) if rulecode == 15: valDescription = ("from {valSourceIP} from {valSourcePort} to {valDestPort} to {valDestIP}".format(valSourceIP=(valSourceIP), valSourcePort=(valSourcePort), valDestPort=(valDestPort), valDestIP=(valDestIP))) #print(val, rulecode) with open((writeFilePath), "a+") as f: #Write the firewall description f.write("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"{valDescription}\"\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=("from " +valSourceIP), valSourcePort=("from " +valSourcePort), valDestPort=("to " +valDestPort), valDestIP=("to " +valDestIP), val=(val), valDescription=(valDescription))) #Write the firewall action f.write("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action {action}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=(valSourceIP), valSourcePort=(valSourcePort), valDestPort=(valDestPort), valDestIP=(valDestIP), action=(action))) #Write firewall source IP rule f.write(valSourceIPRules) #Write firewall source port rule f.write(valSourcePortRules) #Write firewall destination port rule f.write(valDestPortRules) #Check if the value that indicates the protocol type (currently TCP or UDP or TCP_UDP) is present, #if it is present then write it to the firewall rule if valPortProtoRule != "": f.write(valPortProtoRule) #Blank out the protocol value after it was written in order to clear the value for the next run valPortProtoRule = "" #Write firewall destination IP rule f.write(valDestIPRules) #The following "if" statements check if there are options variable value present, if there are then write them to the firewall rule #If you create a new type of option for your firewall shorthand you'll need to add a new line for writing it below (be sure to use the correct firewall command syntax) if ipsec_opt != "": f.write("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} ipsec {ipsec_opt}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=(valSourceIP), valSourcePort=(valSourcePort),valDestPort=(valDestPort), valDestIP=(valDestIP), ipsec_opt=(ipsec_opt))) if state1 == "" and state2 == "" and state3 == "" and state4 == "": f.write("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state {state_default}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=(valSourceIP), valSourcePort=(valSourcePort), valDestPort=(valDestPort), valDestIP=(valDestIP), state_default=(state_default))) if state1 != "": f.write("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state {state1}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=(valSourceIP), valSourcePort=(valSourcePort),valDestPort=(valDestPort), valDestIP=(valDestIP), state1=(state1))) if state2 != "": f.write("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state {state2}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=(valSourceIP), valSourcePort=(valSourcePort),valDestPort=(valDestPort), valDestIP=(valDestIP), state2=(state2))) if state3 != "": f.write("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state {state3}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=(valSourceIP), valSourcePort=(valSourcePort),valDestPort=(valDestPort), valDestIP=(valDestIP), state3=(state3))) if state4 != "": f.write("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state {state4}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=(valSourceIP), valSourcePort=(valSourcePort),valDestPort=(valDestPort), valDestIP=(valDestIP), state4=(state4))) f.write("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log {log}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName),rulecount=(rulecount), valSourceIP=(valSourceIP), valSourcePort=(valSourcePort), valDestPort=(valDestPort), valDestIP=(valDestIP), log=(log))) f.close() rulecount = (rulecount) + 5 if (rulecode) == 0: #This is for rules where they don't match the basic tcp/udp/ip source/destination groups # Some rules will have a static rule number, for these rules we don't want to the rulecount to be incremented; for rules where this needs to be true the flag will be set to True \ # the value will be returned and the parent function will increment the rulecount incrementRule = False print ("Basic TCP/UDP/IP/NET rule variations not found; checking for pre-defined/custom rule for \"{val}\" from line {lineNum}.".format(val=(val), lineNum=(lineNum))) valDefinedRules = funcDefinedRules(val,sourceZoneName,destinationZoneName,rulecount,incrementRule,lineNum) with open((writeFilePath), "a+") as f: # This grabs the 1st value "[0]" returned from "valDefinedRules" which got it's values from the return values of "funcDefinedRules" f.write(valDefinedRules[0]) f.close() #This grabs the 3rd value "[2]" returned from "valDefinedRules" which got it's values from the return values of "funcDefinedRules" #If the value is true it increments the rulecount incrementRule = valDefinedRules[2] rulecount = valDefinedRules[1] #print (incrementRule) #print (rulecount) if incrementRule == "True": rulecount = (rulecount) + 5 #print (rulecount) def funcFindPortProtocol(valPortRule, valPortProtocolCheck, val, lineNum): #print("Destination Port Value is : " +valDestPort) #Split the rule on the last "-" character: https://stackoverflow.com/questions/15012228/splitting-on-last-delimiter-in-python-string valPortProtocol = valPortRule.rsplit("-", 1) #print(valDestPortProtocol) #Set the value to the last value in the variable, # which is the TCP or UDP or TCP_UDP suffix: https://www.codespeedy.com/get-the-last-word-from-a-string-in-python/ valPortProtocolCurrent = (valPortProtocol[-1]) #Check if the suffix matches any of the following values, if so then set the port protocol value if valPortProtocolCurrent == "TCP": valPortProtocol = "tcp" #print("TCP_UDP found") #print(valDestPortProtocol) if valPortProtocolCurrent == "UDP": valPortProtocol = "udp" #print("TCP_UDP found") #print(valDestPortProtocol) if valPortProtocolCurrent == "TCP_UDP": valPortProtocol = "tcp_udp" #print("TCP found") #print(valDestPortProtocol) #Check if the suffix matches any of the pre-defined protocols TCP, UDP or TCP_UDP, \ #if it doesn't then exit with error if valPortProtocolCurrent not in ["TCP_UDP", "TCP", "UDP"]: print("TCP_UDP,TCP or UDP not found") ERROR = ("ERROR: value \"{valPortProtocolCurrent}\" is not a TCP or UDP protocol or the syntax is wrong at line {lineNum}".format(valPortProtocolCurrent=(valPortProtocolCurrent), lineNum=(lineNum))) funcError(ERROR) #Check that both values aren't blank; these are the current port protocol and the previously \ #found port protocol. Two protocols would be found if a source and a destination port are specified. if valPortProtocolCheck != "" and valPortProtocol != "": #Check if both values match, if they do this means the source and destination protocols specified in the rules #use different protocols; exit with error. if valPortProtocolCheck != valPortProtocol: ERROR = ("ERROR: src and dest protocol mismatch: protocol \"{valPortProtocol}\" does not match protocol \"{valPortProtocolCheck}\" for rule \"{val}\" on line {lineNum}".format(valPortProtocol=(valPortProtocol), valPortProtocolCheck=(valPortProtocolCheck), val=(val), lineNum=(lineNum))) funcError(ERROR) #After all error checks have passed set the protocol found in this run of this function #to a value that can be used to check against the value found in the next run, this happens #only when a source and a destination port are specified in the same rule #. valPortProtocolCheck = valPortProtocol #Return the values to the point where the function was called return valPortProtocol, valPortProtocolCheck #Function to find if a firewall group is a network or address group def funcFindAddressType(valAddress, val, lineNum): valAddressType = valAddress.rsplit("-", 1) valAddressType = (valAddressType[-1]) if valAddressType == "IP": valAddressGroupType = "address-group" if valAddressType == "NET": valAddressGroupType = "network-group" if valAddressType not in ["IP", "NET"]: ERROR = ("ERROR: value \"{valAddressType}\" doesn't match the expected values of \"IP\" or \"NET\" at line {lineNum}\n\ \"IP\" is for single addresses or address ranges; \"NET\" is for subnets.".format(valAddressType=(valAddressType), lineNum=(lineNum))) funcError(ERROR) return valAddressGroupType def funcDefinedRules(val,sourceZoneName,destinationZoneName,rulecount,incrementRule,lineNum): #This runs checks against a pre-defined rules list for items like "allow establed/related", "ICMP", "OSPF" or other items that aren't strictly IP or TCP/UDP protocols. valFound = "False" ### STOCK rule = allow established/related but don't log; drop and log invalid if val == "STOCK": #print (rulecount) rulecount5 = rulecount + 5 valFound = "True" valDefinedRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"Allow established/related\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state established enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state related enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log disable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} description \"Drop invalid\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} action drop\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} state invalid enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} log enable\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount), rulecount5=(rulecount5))) rulecount = (rulecount5) incrementRule = "True" #print("Value is STOCK") ### AER_DI rule = allow established/related but don't log; drop and log invalid if val == "EST-REL": # print (rulecount) rulecount5 = rulecount + 5 valFound = "True" valDefinedRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"Allow established/related\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state established enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state related enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log disable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} description \"Drop invalid\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} action drop\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} state invalid enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} log enable\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount),rulecount5=(rulecount5))) rulecount = (rulecount5) incrementRule = "True" # print("Value is EST-REL") if val == "DENY_ALL": # print (rulecount) valFound = "True" valDefinedRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"Drop Everything\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action drop\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log enable\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount))) incrementRule = "True" # print("Value is DENY_ALL") if val == "DROP": # print (rulecount) valFound = "True" valDefinedRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"Drop Everything\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action drop\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log enable\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount))) incrementRule = "True" # print("Value is DROP") # IPSEC-IKE-ESP rule = Allow port 500/udp, protocol esp, port 4500/udp; log all if val == "IPSEC-IKE-ESP": # print (rulecount) rulecount5 = rulecount + 5 rulecount10 = rulecount + 10 valFound = "True" valDefinedRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"IPSEC IKE/ESP/NAT-T Group; Accept IKE\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} destination port 500\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} protocol udp\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} description \"Accept ESP\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} protocol esp\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} log enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} description \"Accept NAT-T\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} destination port 4500\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} protocol udp\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} log enable\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount),rulecount5=(rulecount5), rulecount10=(rulecount10))) rulecount = rulecount10 incrementRule = "True" # print("Value is IPSEC-IKE-ESP") if val == "PING": valFound = "True" valDefinedRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"Allow ICMP Ping\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} icmp type-name echo-request\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} protocol icmp\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state new enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log disable\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount))) #print("Value is PING") incrementRule = "True" if val == "OSPF": valFound = "True" valDefinedRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"Allow OSPF\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} protocol ospf\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state new enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log disable\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount))) #print("Value is OSPF") incrementRule = "True" if val == "ALL": rulecount5 = rulecount + 5 rulecount10 = rulecount + 10 valFound = "True" valDefinedRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"Allow Established\Related\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state established enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state related enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log disable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} description \"Drop invalid\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} action drop\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} state invalid enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} log disable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} description \"Allow New\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} state new enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} log enable\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount), rulecount5=(rulecount5), rulecount10=(rulecount10))) #This is to assign the value to the last rule count for this internally incremented if routine back to the rulecount value that is used by other functions rulecount = rulecount10 incrementRule = "True" if val == "ALLOW_ALL": rulecount5 = rulecount + 5 rulecount10 = rulecount + 10 valFound = "True" valDefinedRules = ("set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} description \"Allow Established\Related\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state established enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} state related enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount} log disable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} description \"Drop invalid\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} action drop\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} state invalid enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount5} log disable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} description \"Allow New\"\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} action accept\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} state new enable\n\ set firewall name {sourceZoneName}.{destinationZoneName} rule {rulecount10} log enable\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), rulecount=(rulecount),rulecount5=(rulecount5), rulecount10=(rulecount10))) # This is to assign the value to the last rule count for this internally incremented if routine back to the rulecount value that is used by other functions rulecount = rulecount10 incrementRule = "True" if valFound == "False": ERROR = ("ERROR: firewall rule \"{val}\" is undefined at line {lineNum}.\n\ If this is a pre-defined rule, options are not allowed,\n\ define all options in the pre-defined rule area instead...\n\ If this is not a pre-defind rule, check your syntax.".format(val=(val),lineNum=(lineNum))) funcError(ERROR) if valFound == "True": #print(rulecount) return valDefinedRules, rulecount, incrementRule #funcDetermineOptions is called from ################ and is used to determine if any options have been added to firewall rule, such as disabling the log, #reject or accept actions, ipsec options, etc. #Be sure to add the variable of any new options you add in the "for opt in options" group to item list on the "def funcDetermineOptions" line. def funcDetermineOptions(options, log, action, state1, state2, state3, state4, ipsec_opt, lineNum): #The following values are counters and are used for error checking to make sure only 1 of any particular option type in included per rule logCount = 0 actionCount = 0 st_estbCount = 0 st_rltdCount = 0 st_invldCount = 0 st_newCount = 0 ipsec_Count = 0 #split the line by ";" into values options = options.split(";") #Check each value for specific characters: https://stackoverflow.com/questions/18294534/is-there-a-foreach-function-in-python-3 for opt in options: optFound = "False" if opt == "NOLOG": log = "disable" logCount = logCount + 1 optFound = "True" if opt == "LOG": log = "enable" logCount = logCount + 1 optFound = "True" if opt == "DROP": action = "drop" actionCount = actionCount + 1 optFound = "True" if opt == "REJECT": action = "reject" actionCount = actionCount + 1 optFound = "True" if opt == "ACCEPT": action = "accept" actionCount = actionCount + 1 optFound = "True" if opt == "ST_ESTB_ENBL": state4 = "established enable" st_estbCount = st_estbCount + 1 optFound = "True" if opt == "ST_ESTB_DSBL": state4 = "established disable" st_estbCount = st_estbCount + 1 optFound = "True" if opt == "ST_RLTD_ENBL": state3 = "related enable" st_rltdCount = st_rltdCount + 1 optFound = "True" if opt == "ST_RLTD_DSBL": state3 = "related disable" st_rltdCount = st_rltdCount + 1 optFound = "True" if opt == "ST_INVLD_ENBL": state2 = "related enable" st_invldCount = st_invldCount + 1 optFound = "True" if opt == "ST_INVLD_DSBL": state2 = "related disable" st_invldCount = st_invldCount + 1 optFound = "True" if opt == "ST_NEW_ENBL": state1 = "new enable" st_newCount = st_newCount + 1 optFound = "True" if opt == "ST_NEW_DSBL": state1 = "new disable" st_newCount = st_newCount + 1 optFound = "True" if opt == "MATCH_IPSEC": ipsec_opt = "match-ipsec" ipsec_Count = ipsec_Count + 1 optFound = "True" #print("OPT found is: " + opt) #print("log = " +log) #print("action = " +action) #print("state1 = " +state1) #print("state2 = " +state2) #print("state3 = " +state3) #print("state4 = " +state4) #If no options are found and this function had been run then there is likely a syntax issue in the firewall rules shorthand if optFound == "False": ERROR = ("Error: option \"{opt}\" doesn't match pre-defined options; line {lineNum}.\n\ Check that your options match the pre-defined options.".format(opt=(opt), lineNum=(lineNum))) funcError(ERROR) #Check the counts of each type of options, anything over 1 indicates a duplicate or conflicting option in the firewall rule shorthand, this will cause the firewall script to exit with error #Be sure to add a new error check group for each new option type added above, error checking is CRITICAL to firewall rule integrity! if logCount > 1: ERROR = ("Error: log related option was set more than once in options group \"{options}\" at line {lineNum}.".format(options=(options), lineNum=(lineNum))) funcError(ERROR) if actionCount > 1: ERROR = ("Error: action related option was set more than once in options group \"{options}\" at line {lineNum}.".format(options=(options), lineNum=(lineNum))) funcError(ERROR) if st_estbCount > 1: ERROR = ("Error: state establised related option was set more than once in options group \"{options}\" at line {lineNum}.".format(options=(options), lineNum=(lineNum))) funcError(ERROR) if st_rltdCount > 1: ERROR = ("Error: state related related option was set more than once in options group \"{options}\" at line {lineNum}.".format(options=(options), lineNum=(lineNum))) funcError(ERROR) if st_invldCount > 1: ERROR = ("Error: state invalid related option was set more than once in options group \"{options}\" at line {lineNum}.".format(options=(options), lineNum=(lineNum))) funcError(ERROR) if st_newCount > 1: ERROR = ("Error: state new related option was set more than once in options group \"{options}\" at line {lineNum}.".format(options=(options), lineNum=(lineNum))) funcError(ERROR) if ipsec_Count > 1: ERROR = ("Error: ipsec related option was set more than once in options group \"{options}\" at line {lineNum}.".format(options=(options), lineNum=(lineNum))) funcError(ERROR) #Return the values to the ???????? that called this function, be sure to add additional values to the return group if you've added new option types return(log, action, state1, state2, state3, state4, ipsec_opt) #Function called when an error is encountered, a copy of the error output will be printed to the console and \ #the same error output will overwrite whatever was in output file (the file with the firewall commands being written) def funcError(ERROR): print(ERROR) with open((writeFilePath), "w") as f: f.write(ERROR) f.close() exit(1) def funcRuleListDupeDetect(): pass #funcBracketRulesCheck will do a basic check of the syntax for bracket based rules #it will ensure that no extra characters exist outside of an open-close bracket set \ #that the rules inside a bracket set contain only allowed characters \ #that each bracket type occurs only once #valid rules are: [SOURCE-IP]{SOURCE-TCP}(DESTINATION-NET|NOLOG;OPTION_A;OPTION_B) #invalid rules are: [SOURCE-IP]OSPF or [SOURCE-IP]OSPF[DESTINATION-IP] def funcBracketRulesCheck (val, lineNum): #valRule is the value of a rule as it's found between bracket pairs valRule = "" #duplicate_bracket will be set to True if duplicates are found which will result in an error condition duplicate_bracket = False #Set the allowed characters for rules between bracket pairs allowed_characters_bracket_rules = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '!', '_', '-', '|', ';'] #Check that bracket pairs come in opening and closing pairs #Each pair will be found and deleted, any spaces will be deleted, afterwards the value should be empty, if not then the pairs weren't complete or there were extra characters outside the pairs which means the syntax is incorrect #Use regex to match the bracket set and remove everything between the brackets: https://stackoverflow.com/questions/8784396/how-to-delete-the-words-between-two-delimiters #Find string from < to > and delete it valCheck = val valCheck = re.sub('\<[^\>]+\>', '', valCheck) #Find string from ( to ) and delete it valCheck = re.sub('\([^\)]+\)', '', valCheck) #Find string from [ to ] and delete it valCheck = re.sub('\[[^\]]+\]', '', valCheck) #Find string from { to } and delete it valCheck = re.sub('\{[^\}]+\}', '', valCheck) #Remove space characters valCheck = valCheck.replace(" ", "") #Now that all valid brack pairs have been identified and remove \ #and any space characters removed then the resulting value should \ #be empty. If it's not then it means invalid or extra characters are \ #present which will is considered a voilation of the syntax if valCheck != "": ERROR = ("ERROR: invalid syntax for rule \"{val}\" on line {lineNum}.".format(lineNum=(lineNum), val=(val))) funcError(ERROR) else: #Count the different brackets, if any value is more than 1 then error valCount = val.count('(') if valCount > 1: duplicate_bracket = True valCount = val.count(')') if valCount > 1: duplicate_bracket = True valCount = val.count('{') if valCount > 1: duplicate_bracket = True valCount = val.count('}') if valCount > 1: duplicate_bracket = True valCount = val.count('[') if valCount > 1: duplicate_bracket = True valCount = val.count(']') if valCount > 1: duplicate_bracket = True valCount = val.count('<') if valCount > 1: duplicate_bracket = True valCount = val.count('>') if valCount > 1: duplicate_bracket = True if duplicate_bracket == True: ERROR = ("ERROR: duplicate bracket found in rule \"{val}\" on line {lineNum}.".format(lineNum=(lineNum), val=(val))) funcError(ERROR) else: #Check that characters between each bracket contain only allow characters if "<" in (val) and ">" in (val): valRule = val[val.find("<") + 1:val.find(">")] if any(x not in allowed_characters_bracket_rules for x in valRule): ERROR = ("ERROR: invalid character found in rule \"{valRule}\" on line {lineNum}.".format(lineNum=(lineNum), valRule=(valRule))) funcError(ERROR) if "(" in (val) and ")" in (val): valRule = val[val.find("(") + 1:val.find(")")] if any(x not in allowed_characters_bracket_rules for x in valRule): ERROR = ("ERROR: invalid character found in rule \"{valRule}\" on line {lineNum}.".format(lineNum=(lineNum), valRule=(valRule))) funcError(ERROR) if "[" in (val) and "]" in (val): valRule = val[val.find("[") + 1:val.find("]")] if any(x not in allowed_characters_bracket_rules for x in valRule): ERROR = ("ERROR: invalid character found in rule \"{valRule}\" on line {lineNum}.".format(lineNum=(lineNum), valRule=(valRule))) funcError(ERROR) if "{" in (val) and "}" in (val): valRule = val[val.find("{") + 1:val.find("}")] if any(x not in allowed_characters_bracket_rules for x in valRule): ERROR = ("ERROR: invalid character found in rule \"{valRule}\" on line {lineNum}.".format(lineNum=(lineNum), valRule=(valRule))) funcError(ERROR) #print("funcBracketRulesCheck passed without error") ''' NOLOG = log disable LOG = log enable DROP = action drop REJECT = action reject ACCEPT = action accept ST_ESTB_ENBL = state established enable ST_ESTB_DISBL = state established disable ST_RLTD_ENBL = state related enable ST_RLTD_DSBL = state related disable ST_INVLD_ENBL = state invalid enable ST_INVLD_DSBL = state invalid disable ST_NEW_ENBL = state new disable ST_NEW_DSBL = state new disable ''' ''' The gist of this script is to read a set of firewall rules from human readable shorthand and generate the commands to program an EdgeOS or VyOS firewall.\ An example of the shorthand is: SOURCE_ZONE_A: DESTINATION_ZONE_C: STOCK_RULE, (SERVER-IP), [CLIENT-IP]{CLIENT-UDP}(SERVER-IP), OSPF, PING DESTINATION_ZONE_X: STOCK_RULE, (SERVER-IP) DESTINATION_ZONE_P: STOCK_RULE, [CLIENT-IP]{CLIENT-UDP}(SERVER-IP), OSPF, PING The line with a single word is the source zone, all other lines are destinations zones along with each destination zones rule sets The script will open a file and read it line by line to determine if the line is a source zone or a destination zone and once the pair has be found it will evaluate the rules associated with the destination zone. For the rules there are functions that will determine the protocol (tcp,udp,tcp_udp) if the rule type is set as such. Once all the zones, rules and protocols are identified the values are written to a file together with the commands needed by VyOS or EdgeOS to program a zone based firewall. ''' #Open file and read it line by line: https://stackabuse.com/read-a-file-line-by-line-in-python/ with open(filePath) as fp: lineNum = 0 #The next 2 values set that the source and desination zones haven't been found, \ #This is to keep the program reading new lines until both source and destination zones are found sourceZoneFound = False destinationZoneFound = False #This is to keep track of the # of sequential source zones found \ #and to error out if two source zones are found before destination zone is found sourceZoneCount = 0 for index, line in enumerate(fp): first_char = "0" not_blank = False is_comment = False #Increment the lineNum value by 1 for each new line read lineNum = lineNum + 1 # remove the extra blank line between lines line = ("{}".format(line.strip())) #If the line is not blank if line != "": #Set the first character of "line" to the value "first_char" first_char = line[0] not_blank = True #check for the # or = characters at the beginning of the line, if they are present this means the line is a comment if line[0] == "#": print(line) is_comment = True if line[0] == "=": print(line) is_comment = True #If line is NOT blank and the first character of the line isn't "#" or "=" then... if not_blank == True and is_comment == False: #Non-comment or blank lines should be zone lines and have a ":" character present \ #If this character isn't present exit with error if ":" not in (line): ERROR = ("ERROR: Line {lineNum} doesn't contain expected values.\n\ Expected values are comment lines that begin with the \"#\" or \"=\" characters or zone lines which contain the \":\" character.\n\ Line value in question is: \"{line}\"".format(lineNum=(lineNum), line=(line))) funcError(ERROR) #Check if the line contains the character ":", if so continue the evaluation if ":" in (line): #Check that a line has only one ":" character since this character is strictly \ #used for designating zone names: https://www.geeksforgeeks.org/python-count-occurrences-of-a-character-in-string/ if 1 < line.count(':'): ERROR = ("ERROR: more than 1 \":\" character found at line {lineNum}.\n\ Only one \":\" character should be used per line.\nThe zone name should preceed the \":\" character.".format(lineNum=(lineNum))) funcError(ERROR) #Split the line by the ":" character, source zones should have a blank value for the 2nd value, \ #destination zones should have a non-blank value for the 2nd value res = line.split(':') #if the second value "res[1]" is blank then this is considered a source zone name, run the function to get the exact name if res[1] == "": sourceZoneName = funcFindSourceZone(line,lineNum) #print("Source Zone Name is " +sourceZoneName) sourceZoneFound = True #This is added so that when a new sourceZone is found it resets the search for the destinationZone destinationZoneFound = False #Add 1 to sourceZoneCount sourceZoneCount = sourceZoneCount + 1 #If sourceZoneCount equals 2 this means 2 source zones were found before a destination zone was found #This will cause an error since the script depends on destination zones always following a source zone if sourceZoneCount == 2: ERROR = ("ERROR: 2 sequential source zones found. Zone name \"{sourceZoneName}\" at line {lineNum} is the 2nd source zone.".format(sourceZoneName=(sourceZoneName), lineNum=(lineNum))) funcError(ERROR) #if the second value "res[1]" is NOT blank then this is considered the destination zone line with rules, run the function to get the exact name if res[1] != "": destinationZoneName = funcFindDestinationZone(line,lineNum) #print("Destination Zone Name is " +destinationZoneName) destinationZoneFound = True #if the destination zone is found before the source zone then error out as the source zone always needs to be defined first if sourceZoneFound == False and destinationZoneFound == True: ERROR = ("ERROR: destination zone found before source zone at line {lineNum}.\n\ Ensure that source zones are always listed first and then followed by list of associated destination zones.".format(lineNum=(lineNum))) funcError(ERROR) #if both source and destination zones are found then run function: https://pythonexamples.org/python-if-and/ if sourceZoneFound == True and destinationZoneFound == True: if sourceZoneName == destinationZoneName: ERROR = ("ERROR: destination zone name \"{destinationZoneName}\" on line {lineNum} is the same as the source zone name \"{sourceZoneName}\".".format(lineNum=(lineNum), destinationZoneName=(destinationZoneName), sourceZoneName=(sourceZoneName))) funcError(ERROR) #Write to the file 1 new line and a title that begins with "echo" so it's visually easier to read on the output and will be harmless if entered as a command on the firewall with open((writeFilePath), "a+") as f: f.write("\necho ====={sourceZoneName} to {destinationZoneName}=====\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName))) f.write("echo delete firewall name {sourceZoneName}.{destinationZoneName}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName))) f.write("set firewall name {sourceZoneName}.{destinationZoneName} description \"{sourceZoneName} {line} ###Rules Generated {ct}###\"\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), line=(line), ct=(ct))) f.close() print("Source and Destination Zones are: {sourceZoneName} and {destinationZoneName}".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName))) funcFindRules(line, sourceZoneName, destinationZoneName, lineNum) with open((writeFilePath), "a+") as f: f.write("set firewall name {sourceZoneName}.{destinationZoneName} enable-default-log\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName))) f.write("set zone-policy zone {destinationZoneName} from {sourceZoneName} firewall name {sourceZoneName}.{destinationZoneName}\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName))) f.write("echo ====={sourceZoneName}.{destinationZoneName} Rules Generated {ct}=====\n\n".format(sourceZoneName=(sourceZoneName), destinationZoneName=(destinationZoneName), ct=(ct))) f.close() #Reset sourceZoneCount to zero now that a zone pair has been established. \ #This allows script to continue after the current zone pair is processed sourceZoneCount = 0 # call another function: https://stackoverflow.com/questions/20526905/calling-function-inside-if-statement # remove ":" character from a string https://www.askpython.com/python/string/remove-character-from-string-python #Zone Names must be 13 characters or less; the must be all CAPS and be comprised of A-Z, 0-9 and - or _ characters