#!/usr/bin/env python3 """ Extract ipt-firewall configuration from a host and generate host_vars YAML. Reads /etc/ipt-firewall/{interfaces,main}_ipv{4,6}.conf via SSH, maps all variables to Ansible fw_* names, and writes a host_vars file. Usage: ./extract-fw-host-vars.py [--user USER] [--port PORT] [--dry-run] Example: ./extract-fw-host-vars.py cl-01.oopen.de ./extract-fw-host-vars.py cl-01.oopen.de --user root --dry-run """ import argparse import re import subprocess import sys from pathlib import Path # --------------------------------------------------------------------------- # Defaults matching roles/ipt-firewall/defaults/main.yml # Only values that differ from these will be emitted. # --------------------------------------------------------------------------- DEFAULTS = { "fw_do_not_firewall_bridged_traffic": False, "fw_do_not_firewall_lx_guest_systems": False, "fw_drop_icmp": False, "fw_drop_mndp": True, "fw_drop_mdns": True, "fw_allow_all_outgoing_traffic": False, "fw_blocked_ifs": "", "fw_unprotected_ifs": "", "fw_forward_private_ips_v4": "", "fw_forward_private_ips_v6": "", "fw_restrict_local_service_to_net_v4": "", "fw_restrict_local_service_to_net_v6": "", "fw_restrict_local_net_to_net_v4": "", "fw_restrict_local_net_to_net_v6": "", "fw_allow_ext_service_v4": "", "fw_allow_ext_service_v6": "", "fw_allow_ext_net_v4": "", "fw_allow_ext_net_v6": "", "fw_allow_local_service_v4": "", "fw_allow_local_service_v6": "", "fw_allow_local_service_from_networks_v4": "", "fw_allow_local_service_from_networks_v6": "", "fw_vpn_server_ips": "", "fw_forward_vpn_server_ips": "", "fw_vpn_ports": "$standard_vpn_port", "fw_wireguard_server_ips": "", "fw_forward_wireguard_server_ips": "", "fw_wireguard_server_ports": "$standard_wireguard_port", "fw_wireguard_out_ports": "$standard_wireguard_port", "fw_local_ntp_service": False, "fw_ntp_port": "$standard_ntp_port", "fw_ntp_allowed_net": "", "fw_dhcp_server_ifs": "", "fw_dhcp_client_ifs": "", "fw_dns_server_ips": "", "fw_forward_dns_server_ips": "", "fw_local_resolver_service": False, "fw_resolver_port": "$standard_dns_port", "fw_resolver_allowed_networks_v4": "", "fw_resolver_allowed_networks_v6": "", "fw_ssh_server_ips": "$ext_ips", "fw_forward_ssh_server_ips": "", "fw_ssh_ports": "$standard_ssh_port", "fw_http_server_ips": "", "fw_forward_http_server_ips": "", "fw_http_ports": "$standard_http_ports", "fw_log_cgi_traffic_out": False, "fw_cgi_script_users": "", "fw_mm_server_ips": "", "fw_forward_mm_server_ips": "", "fw_smtpd_ips": "", "fw_forward_smtpd_ips": "", "fw_smtpd_additional_listen_ports": "", "fw_smtpd_additional_outgoing_ports": "", "fw_mail_server_ips": "", "fw_forward_mail_server_ips": "", "fw_mail_user_ports": "$standard_mailuser_ports", "fw_mail_client_ips": "", "fw_forward_mail_client_ips": "", "fw_dovecot_auth_service": False, "fw_dovecot_auth_port": "$dovecot_external_auth_port", "fw_dovecot_auth_allowed_networks_v4": "", "fw_dovecot_auth_allowed_networks_v6": "", "fw_ftp_server_ips": "", "fw_forward_ftp_server_ips": "", "fw_ftp_passive_port_range": "50000:50400", "fw_xmpp_server_ips": "", "fw_forward_xmpp_server_ips": "", "fw_xmmp_tcp_in_ports": "5222 5223 5269", "fw_xmmp_tcp_out_ports": "5269", "fw_xmmp_remote_out_services_v4": "", "fw_xmmp_remote_out_services_v6": "", "fw_mumble_server_ips": "", "fw_forward_mumble_server_ips": "", "fw_mumble_ports": "$standard_mumble_port", "fw_jitsi_server_ips": "", "fw_forward_jitsi_server_ips": "", "fw_jitsi_dovecot_auth": False, "fw_jitsi_dovecot_host": "", "fw_jitsi_jibri_remote_auth": False, "fw_jitsi_jibri_remote_ips": "", "fw_jibri_server_ips": "", "fw_forward_jibri_server_ips": "", "fw_jibri_remote_jitsi_server": "", "fw_nc_turn_server_ips": "", "fw_forward_nc_turn_server_ips": "", "fw_nc_turn_ports": "$standard_turn_service_ports", "fw_nc_turn_udp_ports": "$standard_turn_service_udp_ports", "fw_tftp_server_ips": "", "fw_prometheus_local_server_ips": "", "fw_prometheus_local_client_ips": "", "fw_prometheus_remote_server_ips": "", "fw_munin_server_ips": "", "fw_forward_munin_server_ips": "", "fw_munin_remote_port": "$standard_munin_port", "fw_munin_local_port": "4949", "munin_remote_ipv4": "", "munin_remote_ipv6": "", "fw_xymon_server_ips": "", "fw_local_xymon_client": False, "fw_xymon_port": "$standard_xymon_port", "fw_rsync_out_ips": "", "fw_forward_rsync_out_ips": "", "fw_rsync_ports": "873", "fw_tcp_out_ports": "", "fw_forward_tcp_out_ports": "", "fw_udp_out_ports": "", "fw_forward_udp_out_ports": "", "fw_portforward_tcp_v4": "", "fw_portforward_udp_v4": "", "fw_portforward_tcp_v6": "", "fw_portforward_udp_v6": "", "fw_blocked_ips": "", "fw_block_tcp_ports": "111 113 135 137:139 445", "fw_block_udp_ports": "111 137:139", "fw_create_traffic_counter": True, "fw_create_iperf_rules": True, "fw_protection_against_syn_flooding": True, "fw_protection_against_port_scanning": True, "fw_protection_against_ssh_brute_force_attacks": True, "fw_limit_connections_per_source_IP": True, "fw_per_IP_connection_limit": "$default_per_IP_connection_limit", "fw_limit_new_tcp_connections_per_seconds_per_source_IP": True, "fw_limit_new_tcp_connections_per_seconds_ports": "", "fw_kernel_activate_forwarding": False, "fw_kernel_support_dynaddr": False, "fw_dynaddr_flag": "5", "fw_kernel_reduce_timeouts": True, "fw_kernel_tcp_syncookies": True, "fw_kernel_protect_against_icmp_bogus_messages": True, "fw_kernel_ignore_broadcast_ping": True, "fw_kernel_deactivate_source_route": True, "fw_kernel_dont_accept_redirects": True, "fw_kernel_activate_rp_filter": True, "fw_kernel_log_martians": False, "fw_kernel_forward_between_interfaces": False, "fw_vpn_ifs": "tun+", "fw_wg_ifs": "wg+", "fw_nat_devices": "", } # --------------------------------------------------------------------------- # Variable mapping: (bash_varname, source) → ansible_varname # source: 'iface_v4', 'iface_v6', 'main_v4', 'main_v6', 'main_shared' # --------------------------------------------------------------------------- # Shared service variables (read from main_ipv4.conf, same in both) MAIN_SHARED = { "do_not_firewall_bridged_traffic": "fw_do_not_firewall_bridged_traffic", "do_not_firewall_lx_guest_systems": "fw_do_not_firewall_lx_guest_systems", "drop_icmp": "fw_drop_icmp", "drop_mndp": "fw_drop_mndp", "drop_mdns": "fw_drop_mdns", "allow_all_outgoing_traffic": "fw_allow_all_outgoing_traffic", "blocked_ifs": "fw_blocked_ifs", "unprotected_ifs": "fw_unprotected_ifs", "vpn_server_ips": "fw_vpn_server_ips", "forward_vpn_server_ips": "fw_forward_vpn_server_ips", "vpn_ports": "fw_vpn_ports", "wireguard_server_ips": "fw_wireguard_server_ips", "forward_wireguard_server_ips": "fw_forward_wireguard_server_ips", "wireguard_server_ports": "fw_wireguard_server_ports", "wireguard_out_ports": "fw_wireguard_out_ports", "local_ntp_service": "fw_local_ntp_service", "ntp_port": "fw_ntp_port", "ntp_allowed_net": "fw_ntp_allowed_net", "dns_server_ips": "fw_dns_server_ips", "forward_dns_server_ips": "fw_forward_dns_server_ips", "local_resolver_service": "fw_local_resolver_service", "resolver_port": "fw_resolver_port", "ssh_server_ips": "fw_ssh_server_ips", "forward_ssh_server_ips": "fw_forward_ssh_server_ips", "ssh_ports": "fw_ssh_ports", "http_server_ips": "fw_http_server_ips", "forward_http_server_ips": "fw_forward_http_server_ips", "http_ports": "fw_http_ports", "log_cgi_traffic_out": "fw_log_cgi_traffic_out", "cgi_script_users": "fw_cgi_script_users", "mm_server_ips": "fw_mm_server_ips", "forward_mm_server_ips": "fw_forward_mm_server_ips", "smtpd_ips": "fw_smtpd_ips", "forward_smtpd_ips": "fw_forward_smtpd_ips", "smtpd_additional_listen_ports": "fw_smtpd_additional_listen_ports", "smtpd_additional_outgoung_ports": "fw_smtpd_additional_outgoing_ports", "mail_server_ips": "fw_mail_server_ips", "forward_mail_server_ips": "fw_forward_mail_server_ips", "mail_user_ports": "fw_mail_user_ports", "mail_client_ips": "fw_mail_client_ips", "forward_mail_client_ips": "fw_forward_mail_client_ips", "dovecot_auth_service": "fw_dovecot_auth_service", "dovecot_auth_port": "fw_dovecot_auth_port", "ftp_server_ips": "fw_ftp_server_ips", "forward_ftp_server_ips": "fw_forward_ftp_server_ips", "ftp_passive_port_range": "fw_ftp_passive_port_range", "xmpp_server_ips": "fw_xmpp_server_ips", "forward_xmpp_server_ips": "fw_forward_xmpp_server_ips", "xmmp_tcp_in_ports": "fw_xmmp_tcp_in_ports", "xmmp_tcp_out_ports": "fw_xmmp_tcp_out_ports", "mumble_server_ips": "fw_mumble_server_ips", "forward_mumble_server_ips": "fw_forward_mumble_server_ips", "mumble_ports": "fw_mumble_ports", "jitsi_server_ips": "fw_jitsi_server_ips", "forward_jitsi_server_ips": "fw_forward_jitsi_server_ips", "jitsi_tcp_ports": "fw_jitsi_tcp_ports", "jitsi_udp_port_range": "fw_jitsi_udp_port_range", "jitsi_tcp_ports_out": "fw_jitsi_tcp_ports_out", "jitsi_udp_ports_out": "fw_jitsi_udp_ports_out", "jitsi_dovecot_auth": "fw_jitsi_dovecot_auth", "jitsi_dovecot_host": "fw_jitsi_dovecot_host", "jitsi_jibri_remote_auth": "fw_jitsi_jibri_remote_auth", "jitsi_jibri_remote_ips": "fw_jitsi_jibri_remote_ips", "jibri_server_ips": "fw_jibri_server_ips", "forward_jibri_server_ips": "fw_forward_jibri_server_ips", "jibri_remote_jitsi_server": "fw_jibri_remote_jitsi_server", "nc_turn_server_ips": "fw_nc_turn_server_ips", "forward_nc_turn_server_ips": "fw_forward_nc_turn_server_ips", "nc_turn_ports": "fw_nc_turn_ports", "nc_turn_udp_ports": "fw_nc_turn_udp_ports", "tftp_server_ips": "fw_tftp_server_ips", "prometheus_local_server_ips": "fw_prometheus_local_server_ips", "prometheus_local_client_ips": "fw_prometheus_local_client_ips", "prometheus_remote_server_ips": "fw_prometheus_remote_server_ips", "munin_server_ips": "fw_munin_server_ips", "forward_munin_server_ips": "fw_forward_munin_server_ips", "munin_remote_port": "fw_munin_remote_port", "munin_local_port": "fw_munin_local_port", "xymon_server_ips": "fw_xymon_server_ips", "local_xymon_client": "fw_local_xymon_client", "xymon_port": "fw_xymon_port", "rsync_out_ips": "fw_rsync_out_ips", "forward_rsync_out_ips": "fw_forward_rsync_out_ips", "rsync_ports": "fw_rsync_ports", "tcp_out_ports": "fw_tcp_out_ports", "forward_tcp_out_ports": "fw_forward_tcp_out_ports", "udp_out_ports": "fw_udp_out_ports", "forward_udp_out_ports": "fw_forward_udp_out_ports", "blocked_ips": "fw_blocked_ips", "block_tcp_ports": "fw_block_tcp_ports", "block_udp_ports": "fw_block_udp_ports", "create_traffic_counter": "fw_create_traffic_counter", "create_iperf_rules": "fw_create_iperf_rules", "protection_against_syn_flooding": "fw_protection_against_syn_flooding", "protection_against_port_scanning": "fw_protection_against_port_scanning", "protection_against_ssh_brute_force_attacks": "fw_protection_against_ssh_brute_force_attacks", "limit_connections_per_source_IP": "fw_limit_connections_per_source_IP", "per_IP_connection_limit": "fw_per_IP_connection_limit", "limit_new_tcp_connections_per_seconds_per_source_IP": "fw_limit_new_tcp_connections_per_seconds_per_source_IP", "limit_new_tcp_connections_per_seconds_ports": "fw_limit_new_tcp_connections_per_seconds_ports", } # IPv4-only variables (from main_ipv4.conf) MAIN_V4_ONLY = { "forward_private_ips": "fw_forward_private_ips_v4", "restrict_local_service_to_net": "fw_restrict_local_service_to_net_v4", "restrict_local_net_to_net": "fw_restrict_local_net_to_net_v4", "allow_ext_service": "fw_allow_ext_service_v4", "allow_ext_net": "fw_allow_ext_net_v4", "allow_local_service": "fw_allow_local_service_v4", "allow_local_service_from_networks": "fw_allow_local_service_from_networks_v4", "portforward_tcp": "fw_portforward_tcp_v4", "portforward_udp": "fw_portforward_udp_v4", "munin_remote_ip": "munin_remote_ipv4", "dovecot_auth_allowed_networks": "fw_dovecot_auth_allowed_networks_v4", "xmmp_remote_out_services": "fw_xmmp_remote_out_services_v4", "resolver_allowed_networks": "fw_resolver_allowed_networks_v4", "dhcp_server_ifs": "fw_dhcp_server_ifs", "dhcp_client_ifs": "fw_dhcp_client_ifs", "kernel_activate_forwarding": "fw_kernel_activate_forwarding", "kernel_support_dynaddr": "fw_kernel_support_dynaddr", "dynaddr_flag": "fw_dynaddr_flag", "kernel_reduce_timeouts": "fw_kernel_reduce_timeouts", "kernel_tcp_syncookies": "fw_kernel_tcp_syncookies", "kernel_protect_against_icmp_bogus_messages": "fw_kernel_protect_against_icmp_bogus_messages", "kernel_ignore_broadcast_ping": "fw_kernel_ignore_broadcast_ping", "kernel_activate_rp_filter": "fw_kernel_activate_rp_filter", "kernel_log_martians": "fw_kernel_log_martians", "kernel_deactivate_source_route": "fw_kernel_deactivate_source_route", "kernel_dont_accept_redirects": "fw_kernel_dont_accept_redirects", } # IPv6-only variables (from main_ipv6.conf) MAIN_V6_ONLY = { "forward_private_ips": "fw_forward_private_ips_v6", "restrict_local_service_to_net": "fw_restrict_local_service_to_net_v6", "restrict_local_net_to_net": "fw_restrict_local_net_to_net_v6", "allow_ext_service": "fw_allow_ext_service_v6", "allow_ext_net": "fw_allow_ext_net_v6", "allow_local_service": "fw_allow_local_service_v6", "allow_local_service_from_networks": "fw_allow_local_service_from_networks_v6", "portforward_tcp": "fw_portforward_tcp_v6", "portforward_udp": "fw_portforward_udp_v6", "munin_remote_ip": "munin_remote_ipv6", "dovecot_auth_allowed_networks": "fw_dovecot_auth_allowed_networks_v6", "xmmp_remote_out_services": "fw_xmmp_remote_out_services_v6", "resolver_allowed_networks": "fw_resolver_allowed_networks_v6", "kernel_forward_between_interfaces": "fw_kernel_forward_between_interfaces", } # --------------------------------------------------------------------------- # Parsing # --------------------------------------------------------------------------- def parse_bash_config(text): """ Parse key=value pairs from a bash config file. Handles: var="value", var=value, var=true/false Multiline values (var="line1\n line2\n") are joined as a single string. Returns dict of {varname: value_string} """ result = {} warnings = [] # Collapse multiline quoted strings: "...\n ..." → "... ..." # Strategy: scan char by char for opening " after =, collect until closing " lines = text.splitlines() i = 0 while i < len(lines): line = lines[i].strip() # Skip comments and blank lines if not line or line.startswith('#'): i += 1 continue # Match assignment m = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)=(.*)', line) if not m: i += 1 continue varname = m.group(1) rest = m.group(2).strip() # Quoted value if rest.startswith('"'): # Collect until closing quote (may span multiple lines) collected = rest[1:] # strip opening " closed = False extra_lines = [] while True: # Check if closing " is in collected close_pos = collected.find('"') if close_pos != -1: value = collected[:close_pos].strip() if extra_lines: warnings.append(f" # {varname}: multiline value — verify manually") result[varname] = value closed = True break else: # Value continues on next line extra_lines.append(collected.strip()) i += 1 if i >= len(lines): break collected = lines[i].strip() if not closed: warnings.append(f" # {varname}: unterminated quoted string — skipped") else: # Unquoted value (true, false, $var_ref, number, etc.) # Strip trailing comment value = re.sub(r'\s+#.*$', '', rest).strip() result[varname] = value i += 1 return result, warnings def ssh_cat(host, user, port, path, sudo_password=None): """Read a file from a remote host via SSH. Returns file content or None.""" ssh_cmd = ["ssh"] if user: ssh_cmd += ["-l", user] if port: ssh_cmd += ["-p", str(port)] ssh_cmd += ["-o", "BatchMode=yes", "-o", "ConnectTimeout=10", host] if sudo_password is not None: # Use sudo -S to read password from stdin; -p '' suppresses the prompt ssh_cmd += [f"sudo -S -p '' cat {path}"] stdin_data = sudo_password + "\n" else: ssh_cmd += [f"cat {path}"] stdin_data = None try: result = subprocess.run( ssh_cmd, input=stdin_data, capture_output=True, text=True, timeout=30 ) if result.returncode != 0: print(f" WARNING: could not read {path}: {result.stderr.strip()}", file=sys.stderr) return None return result.stdout except subprocess.TimeoutExpired: print(f" ERROR: SSH timeout reading {path}", file=sys.stderr) return None def coerce_bool(value): """Convert bash true/false string to Python bool, or return string.""" if value.lower() in ("true", "yes", "1"): return True if value.lower() in ("false", "no", "0"): return False return value # keep as string (e.g. $standard_ssh_port) def yaml_value(v): """Format a Python value as a YAML-safe string.""" if isinstance(v, bool): return "true" if v else "false" if v == "": return '""' # Quote if contains special YAML characters if any(c in str(v) for c in [':', '#', '{', '}', '[', ']', ',', '&', '*', '?', '|', '-', '<', '>', '=', '!', '%', '@', '`', '"', "'"]): # Use double-quote with escaping escaped = str(v).replace('\\', '\\\\').replace('"', '\\"') return f'"{escaped}"' return str(v) def build_host_vars(parsed_iface_v4, parsed_iface_v6, parsed_main_v4, parsed_main_v6): """ Map parsed bash variables to Ansible fw_* variables. Returns dict of {ansible_var: value} containing only non-default values. """ result = {} # --- Interfaces: extract lists from numbered vars --- def extract_list(parsed, prefix, suffix="", count=3): items = [] for i in range(1, count + 1): v = parsed.get(f"{prefix}{i}{suffix}", "").strip() if v: items.append(v) return items fw_ext_interfaces = extract_list(parsed_iface_v4, "ext_if_") fw_ext_ips_v4 = extract_list(parsed_iface_v4, "ext_", suffix="_ip") # ext_1_ip, ext_2_ip, ext_3_ip fw_ext_ips_v6 = extract_list(parsed_iface_v6, "ext_", suffix="_ip") fw_local_interfaces = extract_list(parsed_iface_v4, "local_if_") fw_local_ips_v4 = extract_list(parsed_iface_v4, "local_", suffix="_ip") fw_local_ips_v6 = extract_list(parsed_iface_v6, "local_", suffix="_ip") fw_lxc_guest_ips_v4 = extract_list(parsed_iface_v4, "lxc_guest_", suffix="_ip", count=7) fw_lxc_guest_ips_v6 = extract_list(parsed_iface_v6, "lxc_guest_", suffix="_ip", count=7) if fw_ext_interfaces: result["fw_ext_interfaces"] = fw_ext_interfaces if fw_ext_ips_v4: result["fw_ext_ips_v4"] = fw_ext_ips_v4 if fw_ext_ips_v6: result["fw_ext_ips_v6"] = fw_ext_ips_v6 if fw_local_interfaces: result["fw_local_interfaces"] = fw_local_interfaces if fw_local_ips_v4: result["fw_local_ips_v4"] = fw_local_ips_v4 if fw_local_ips_v6: result["fw_local_ips_v6"] = fw_local_ips_v6 if fw_lxc_guest_ips_v4: result["fw_lxc_guest_ips_v4"] = fw_lxc_guest_ips_v4 if fw_lxc_guest_ips_v6: result["fw_lxc_guest_ips_v6"] = fw_lxc_guest_ips_v6 # vpn_ifs / wg_ifs / nat_devices (same in both interface files) for bash_var, ansible_var in [("vpn_ifs", "fw_vpn_ifs"), ("wg_ifs", "fw_wg_ifs"), ("nat_devices", "fw_nat_devices")]: v = parsed_iface_v4.get(bash_var, "") if v and v != DEFAULTS.get(ansible_var, ""): result[ansible_var] = v # --- Shared main variables (read from ipv4) --- for bash_var, ansible_var in MAIN_SHARED.items(): raw = parsed_main_v4.get(bash_var) if raw is None: continue v = coerce_bool(raw) if raw.lower() in ("true", "false") else raw default = DEFAULTS.get(ansible_var) if v != default: result[ansible_var] = v # --- IPv4-only main variables --- for bash_var, ansible_var in MAIN_V4_ONLY.items(): raw = parsed_main_v4.get(bash_var) if raw is None: continue v = coerce_bool(raw) if raw.lower() in ("true", "false") else raw default = DEFAULTS.get(ansible_var) if v != default: result[ansible_var] = v # --- IPv6-only main variables --- for bash_var, ansible_var in MAIN_V6_ONLY.items(): raw = parsed_main_v6.get(bash_var) if raw is None: continue v = coerce_bool(raw) if raw.lower() in ("true", "false") else raw default = DEFAULTS.get(ansible_var) if v != default: result[ansible_var] = v return result def render_yaml(hostname, host_vars, all_warnings): """Render the host_vars as YAML text.""" lines = [ "---", f"# ipt-firewall configuration for {hostname}", "# Generated by extract-fw-host-vars.py - review before committing!", "# Place in: host_vars//ipt_firewall.yml", "", ] if all_warnings: lines.append("# WARNINGS — manual review needed:") for w in all_warnings: lines.append(w) lines.append("") # Group output by section sections = [ ("Network", ["fw_ext_interfaces", "fw_ext_ips_v4", "fw_ext_ips_v6", "fw_local_interfaces", "fw_local_ips_v4", "fw_local_ips_v6", "fw_lxc_guest_ips_v4", "fw_lxc_guest_ips_v6", "fw_vpn_ifs", "fw_wg_ifs", "fw_nat_devices"]), ("Munin", ["munin_remote_ipv4", "munin_remote_ipv6", "fw_munin_local_port", "fw_munin_server_ips", "fw_forward_munin_server_ips", "fw_munin_remote_port"]), ("Bridged / LXC", ["fw_do_not_firewall_bridged_traffic", "fw_do_not_firewall_lx_guest_systems"]), ("Drop policies", ["fw_drop_icmp", "fw_drop_mndp", "fw_drop_mdns"]), ("Outgoing / interfaces", ["fw_allow_all_outgoing_traffic", "fw_blocked_ifs", "fw_unprotected_ifs"]), ("Forwarding", ["fw_forward_private_ips_v4", "fw_forward_private_ips_v6", "fw_kernel_activate_forwarding", "fw_kernel_forward_between_interfaces"]), ("Access control IPv4", ["fw_restrict_local_service_to_net_v4", "fw_restrict_local_net_to_net_v4", "fw_allow_ext_service_v4", "fw_allow_ext_net_v4", "fw_allow_local_service_v4", "fw_allow_local_service_from_networks_v4"]), ("Access control IPv6", ["fw_restrict_local_service_to_net_v6", "fw_restrict_local_net_to_net_v6", "fw_allow_ext_service_v6", "fw_allow_ext_net_v6", "fw_allow_local_service_v6", "fw_allow_local_service_from_networks_v6"]), ("SSH", ["fw_ssh_server_ips", "fw_forward_ssh_server_ips", "fw_ssh_ports"]), ("HTTP", ["fw_http_server_ips", "fw_forward_http_server_ips", "fw_http_ports", "fw_log_cgi_traffic_out", "fw_cgi_script_users"]), ("Mail", ["fw_smtpd_ips", "fw_forward_smtpd_ips", "fw_smtpd_additional_listen_ports", "fw_smtpd_additional_outgoing_ports", "fw_mail_server_ips", "fw_forward_mail_server_ips", "fw_mail_user_ports", "fw_mail_client_ips", "fw_forward_mail_client_ips", "fw_dovecot_auth_service", "fw_dovecot_auth_port", "fw_dovecot_auth_allowed_networks_v4", "fw_dovecot_auth_allowed_networks_v6"]), ("DNS", ["fw_dns_server_ips", "fw_forward_dns_server_ips", "fw_local_resolver_service", "fw_resolver_port", "fw_resolver_allowed_networks_v4", "fw_resolver_allowed_networks_v6"]), ("NTP", ["fw_local_ntp_service", "fw_ntp_port", "fw_ntp_allowed_net"]), ("DHCP", ["fw_dhcp_server_ifs", "fw_dhcp_client_ifs"]), ("VPN / WireGuard", ["fw_vpn_server_ips", "fw_forward_vpn_server_ips", "fw_vpn_ports", "fw_wireguard_server_ips", "fw_forward_wireguard_server_ips", "fw_wireguard_server_ports", "fw_wireguard_out_ports"]), ("FTP", ["fw_ftp_server_ips", "fw_forward_ftp_server_ips", "fw_ftp_passive_port_range"]), ("XMPP", ["fw_xmpp_server_ips", "fw_forward_xmpp_server_ips", "fw_xmmp_tcp_in_ports", "fw_xmmp_tcp_out_ports", "fw_xmmp_remote_out_services_v4", "fw_xmmp_remote_out_services_v6"]), ("Mumble", ["fw_mumble_server_ips", "fw_forward_mumble_server_ips", "fw_mumble_ports"]), ("Jitsi", ["fw_jitsi_server_ips", "fw_forward_jitsi_server_ips", "fw_jitsi_tcp_ports", "fw_jitsi_udp_port_range", "fw_jitsi_dovecot_auth", "fw_jitsi_dovecot_host", "fw_jitsi_jibri_remote_auth", "fw_jitsi_jibri_remote_ips", "fw_jibri_server_ips", "fw_forward_jibri_server_ips", "fw_jibri_remote_jitsi_server"]), ("TURN / STUN", ["fw_nc_turn_server_ips", "fw_forward_nc_turn_server_ips", "fw_nc_turn_ports", "fw_nc_turn_udp_ports"]), ("Mattermost", ["fw_mm_server_ips", "fw_forward_mm_server_ips"]), ("Prometheus", ["fw_prometheus_local_server_ips", "fw_prometheus_local_client_ips", "fw_prometheus_remote_server_ips"]), ("Xymon", ["fw_xymon_server_ips", "fw_local_xymon_client", "fw_xymon_port"]), ("Rsync", ["fw_rsync_out_ips", "fw_forward_rsync_out_ips", "fw_rsync_ports"]), ("Out ports", ["fw_tcp_out_ports", "fw_forward_tcp_out_ports", "fw_udp_out_ports", "fw_forward_udp_out_ports"]), ("Portforwarding", ["fw_portforward_tcp_v4", "fw_portforward_udp_v4", "fw_portforward_tcp_v6", "fw_portforward_udp_v6"]), ("Block", ["fw_blocked_ips", "fw_block_tcp_ports", "fw_block_udp_ports"]), ("Protection / limits", ["fw_protection_against_syn_flooding", "fw_protection_against_port_scanning", "fw_protection_against_ssh_brute_force_attacks", "fw_limit_connections_per_source_IP", "fw_per_IP_connection_limit", "fw_limit_new_tcp_connections_per_seconds_per_source_IP", "fw_limit_new_tcp_connections_per_seconds_ports"]), ("Kernel IPv4", ["fw_kernel_support_dynaddr", "fw_dynaddr_flag", "fw_kernel_reduce_timeouts", "fw_kernel_tcp_syncookies", "fw_kernel_protect_against_icmp_bogus_messages", "fw_kernel_ignore_broadcast_ping", "fw_kernel_deactivate_source_route", "fw_kernel_dont_accept_redirects", "fw_kernel_activate_rp_filter", "fw_kernel_log_martians"]), ("Special", ["fw_create_traffic_counter", "fw_create_iperf_rules"]), ] emitted = set() for section_name, keys in sections: section_lines = [] for k in keys: if k in host_vars: v = host_vars[k] if isinstance(v, list): section_lines.append(f"{k}:") for item in v: section_lines.append(f" - \"{item}\"") elif isinstance(v, bool): section_lines.append(f"{k}: {'true' if v else 'false'}") else: section_lines.append(f"{k}: {yaml_value(str(v))}") emitted.add(k) if section_lines: lines.append(f"# --- {section_name}") lines.extend(section_lines) lines.append("") # Anything not covered by sections remaining = {k: v for k, v in host_vars.items() if k not in emitted} if remaining: lines.append("# --- Other") for k, v in remaining.items(): if isinstance(v, list): lines.append(f"{k}:") for item in v: lines.append(f" - \"{item}\"") elif isinstance(v, bool): lines.append(f"{k}: {'true' if v else 'false'}") else: lines.append(f"{k}: {yaml_value(str(v))}") lines.append("") return "\n".join(lines) def main(): parser = argparse.ArgumentParser(description="Extract ipt-firewall host_vars from a remote host") parser.add_argument("hostname", help="Target hostname (must be in SSH config or known_hosts)") parser.add_argument("--user", "-u", default="chris", help="SSH user (default: chris)") parser.add_argument("--port", "-p", type=int, default=None, help="SSH port (default: 22)") parser.add_argument("--output", "-o", default=None, help="Output file (default: stdout)") parser.add_argument("--sudo", "-s", action="store_true", help="Read files via sudo (prompts for sudo password once)") parser.add_argument("--dry-run", action="store_true", help="Print SSH commands without executing") args = parser.parse_args() hostname = args.hostname conf_dir = "/etc/ipt-firewall" files = { "iface_v4": f"{conf_dir}/interfaces_ipv4.conf", "iface_v6": f"{conf_dir}/interfaces_ipv6.conf", "main_v4": f"{conf_dir}/main_ipv4.conf", "main_v6": f"{conf_dir}/main_ipv6.conf", } if args.dry_run: cmd = "sudo -S -p '' cat" if args.sudo else "cat" for key, path in files.items(): print(f"ssh {args.user}@{hostname} {cmd} {path}") return sudo_password = None if args.sudo: import getpass sudo_password = getpass.getpass(f"sudo password for {args.user}@{hostname}: ") print(f"Connecting to {hostname} as {args.user} ...", file=sys.stderr) contents = {} for key, path in files.items(): print(f" Reading {path} ...", file=sys.stderr) content = ssh_cat(hostname, args.user, args.port, path, sudo_password=sudo_password) contents[key] = content or "" all_warnings = [] parsed = {} for key, text in contents.items(): p, warnings = parse_bash_config(text) parsed[key] = p if warnings: all_warnings.extend([f" # [{key}] {w}" for w in warnings]) host_vars = build_host_vars( parsed["iface_v4"], parsed["iface_v6"], parsed["main_v4"], parsed["main_v6"], ) yaml_text = render_yaml(hostname, host_vars, all_warnings) if args.output: out_path = Path(args.output) out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(yaml_text) print(f"Written to {out_path}", file=sys.stderr) else: print(yaml_text) if __name__ == "__main__": main()