Technical World: Fortigate config parser

Thursday, April 24, 2025

Fortigate config parser

this python script will take fortigate config file as input with name "fortigate_config.conf" and generate the excel file ipsec_tunnels.xlsx with all the ipsec tunnel details. save and run the python sccript below: import re import pandas as pd from pathlib import Path from openpyxl import Workbook from openpyxl.utils import get_column_letter def parse_phase1_config(config_text): """Parse config vpn ipsec phase1-interface section.""" tunnels = {} current_tunnel = None phase1_patterns = { "Interface": r"set interface \"(\S+)\"", "Type": r"set type (\S+)", "Remote Gateway": r"set remote-gw (\S+)", "Local Gateway": r"set local-gw (\S+)", "Proposal": r"set proposal (\S+)", "NAT Traversal": r"set nattraversal (\S+)", "DPD": r"set dpd (\S+)", "DPD Retry Count": r"set dpd-retrycount (\d+)", "DPD Retry Interval": r"set dpd-retryinterval (\d+)", "Mode": r"set mode (\S+)", # For dial-up (main/aggressive) "Mode CFG": r"set mode-cfg (\S+)", # For dial-up IP assignment "Auth User Group": r"set authusrgrp \"(\S+)\"", # For dial-up "Client IP Range": r"set ipv4-name \"(\S+)\"", # For dial-up # Note: PSK Secret omitted for security; add if needed } phase1_section = re.search(r"config vpn ipsec phase1-interface.*?end", config_text, re.DOTALL) if not phase1_section: return tunnels for line in phase1_section.group(0).splitlines(): line = line.strip() if line.startswith("edit "): current_tunnel = line.split('"')[1] tunnels[current_tunnel] = {} elif line.startswith("set ") and current_tunnel: for key, pattern in phase1_patterns.items(): match = re.match(pattern, line) if match: tunnels[current_tunnel][key] = match.group(1) elif line == "next": current_tunnel = None # Set defaults for missing fields for tunnel in tunnels.values(): for key in phase1_patterns: tunnel.setdefault(key, "N/A") return tunnels def parse_phase2_config(config_text): """Parse config vpn ipsec phase2-interface section.""" tunnels = {} current_tunnel = None phase2_patterns = { "Phase 1 Name": r"set phase1name \"(\S+)\"", "Source Subnet": r"set src-subnet (\S+ \S+)", "Destination Subnet": r"set dst-subnet (\S+ \S+)", "Source Port": r"set src-port (\d+)", "Destination Port": r"set dst-port (\d+)", "Protocol": r"set protocol (\d+)", "Proposal": r"set proposal (\S+)" } phase2_section = re.search(r"config vpn ipsec phase2-interface.*?end", config_text, re.DOTALL) if not phase2_section: return tunnels for line in phase2_section.group(0).splitlines(): line = line.strip() if line.startswith("edit "): current_tunnel = line.split('"')[1] tunnels[current_tunnel] = {} elif line.startswith("set ") and current_tunnel: for key, pattern in phase2_patterns.items(): match = re.match(pattern, line) if match: tunnels[current_tunnel][key] = match.group(1) elif line == "next": current_tunnel = None # Set defaults for missing fields for tunnel in tunnels.values(): for key in phase2_patterns: tunnel.setdefault(key, "N/A") return tunnels def parse_firewall_policies(config_text, tunnel_names): """Parse config firewall policy section for policies referencing tunnels.""" policies = {name: [] for name in tunnel_names} current_policy = None policy_patterns = { "Policy ID": r"edit (\d+)", "Source Interface": r"set srcintf \"(\S+)\"", "Destination Interface": r"set dstintf \"(\S+)\"", "Source Address": r"set srcaddr \"(\S+)\"", "Destination Address": r"set dstaddr \"(\S+)\"", "Service": r"set service \"(\S+)\"", "Action": r"set action (\S+)", "Schedule": r"set schedule \"(\S+)\"" } policy_section = re.search(r"config firewall policy.*?end", config_text, re.DOTALL) if not policy_section: return policies for line in policy_section.group(0).splitlines(): line = line.strip() if line.startswith("edit "): current_policy = {"Policy ID": line.split()[1]} elif line.startswith("set ") and current_policy is not None: for key, pattern in policy_patterns.items(): match = re.match(pattern, line) if match: current_policy[key] = match.group(1) elif line == "next" and current_policy: for tunnel_name in tunnel_names: if (current_policy.get("Source Interface") == tunnel_name or current_policy.get("Destination Interface") == tunnel_name): policies[tunnel_name].append(current_policy) current_policy = None return policies def parse_address_objects(config_text): """Parse config firewall address section for address objects (e.g., client IP pools).""" addresses = {} current_address = None address_patterns = { "Type": r"set type (\S+)", "Start IP": r"set start-ip (\S+)", "End IP": r"set end-ip (\S+)", "Subnet": r"set subnet (\S+ \S+)" } address_section = re.search(r"config firewall address.*?end", config_text, re.DOTALL) if not address_section: return addresses for line in address_section.group(0).splitlines(): line = line.strip() if line.startswith("edit "): current_address = line.split('"')[1] addresses[current_address] = {} elif line.startswith("set ") and current_address: for key, pattern in address_patterns.items(): match = re.match(pattern, line) if match: addresses[current_address][key] = match.group(1) elif line == "next": current_address = None return addresses def create_excel_from_configs(phase1_configs, phase2_configs, firewall_policies, address_objects, output_file="ipsec_tunnels.xlsx"): """Create an Excel file from parsed IPsec configurations with an index sheet using openpyxl.""" # Initialize workbook workbook = Workbook() # Remove default sheet default_sheet = workbook.active workbook.remove(default_sheet) # Dictionary to store sheet names for index tunnel_sheets = {} # Create sheets for each tunnel for tunnel_name in phase1_configs: try: rows = [] # Phase 1 Configuration for key, value in phase1_configs[tunnel_name].items(): rows.append({"Category": "Phase 1 Configuration", "Field": key, "Value": value}) # Phase 2 Configuration for phase2_name, phase2_data in phase2_configs.items(): if phase2_data.get("Phase 1 Name") == tunnel_name: for key, value in phase2_data.items(): rows.append({"Category": f"Phase 2: {phase2_name}", "Field": key, "Value": value}) # Firewall Policies for policy in firewall_policies.get(tunnel_name, []): for key, value in policy.items(): rows.append({"Category": "Firewall Policy", "Field": key, "Value": value}) # Address Objects (for dial-up VPNs) client_ip_range = phase1_configs[tunnel_name].get("Client IP Range", "N/A") if client_ip_range != "N/A" and client_ip_range in address_objects: for key, value in address_objects[client_ip_range].items(): rows.append({"Category": "Client IP Range", "Field": key, "Value": value}) # Create DataFrame df = pd.DataFrame(rows) # Create sheet with truncated name sheet_name = tunnel_name[:31] # Excel sheet name limit worksheet = workbook.create_sheet(sheet_name) tunnel_sheets[tunnel_name] = sheet_name # Write DataFrame to worksheet using openpyxl # Write headers headers = df.columns.tolist() for col_idx, header in enumerate(headers, 1): worksheet[f"{get_column_letter(col_idx)}1"] = header # Write data for row_idx, row in enumerate(df.itertuples(index=False), 2): for col_idx, value in enumerate(row, 1): worksheet[f"{get_column_letter(col_idx)}{row_idx}"] = str(value) except Exception as e: print(f"Error processing tunnel {tunnel_name}: {e}") # Create index sheet index_sheet = workbook.create_sheet("Index", 0) # Place at the beginning index_sheet.append(["Tunnel Name", "Link"]) # Add hyperlinks to each tunnel sheet row = 2 for tunnel_name, sheet_name in tunnel_sheets.items(): cell = index_sheet[f"A{row}"] cell.value = tunnel_name link_cell = index_sheet[f"B{row}"] # Escape single quotes in sheet name for Excel HYPERLINK formula escaped_sheet_name = sheet_name.replace("'", "''") link_cell.value = f'=HYPERLINK("#\'{escaped_sheet_name}\'!A1", "{sheet_name}")' row += 1 # Save workbook try: workbook.save(output_file) print(f"Excel file created with index sheet: {output_file}") except Exception as e: print(f"Error saving Excel file: {e}") def main(): # Input file config_file = "fortigate_config.conf" # Replace with your config file output_file = "ipsec_tunnels.xlsx" try: # Read config file with open(config_file, "r") as f: config_text = f.read() # Parse configurations phase1_configs = parse_phase1_config(config_text) phase2_configs = parse_phase2_config(config_text) firewall_policies = parse_firewall_policies(config_text, phase1_configs.keys()) address_objects = parse_address_objects(config_text) if not phase1_configs: print("No IPsec tunnels found in the configuration file.") return # Create Excel with index sheet create_excel_from_configs(phase1_configs, phase2_configs, firewall_policies, address_objects, output_file) except FileNotFoundError: print(f"Config file {config_file} not found.") except Exception as e: print(f"Error: {e}") if __name__ == "__main__": main()

No comments: