Files
oopen-server/extract-fw-host-vars.py
T
chris 9798ca9cd6 Add ipt-server role with firewall configuration and management
- Created handlers for reloading systemd and restarting firewall services.
- Implemented tasks to ensure the existence of configuration directories and files.
- Deployed host-specific and shared configuration files using templates.
- Added scripts for managing IPv4 and IPv6 firewalls.
- Configured systemd service units for ipt-firewall and ip6t-firewall.
- Enabled and started firewall services on system boot.
2026-06-26 19:30:01 +02:00

727 lines
32 KiB
Python
Executable File

#!/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 <hostname> [--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/<hostname>/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()