9798ca9cd6
- 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.
727 lines
32 KiB
Python
Executable File
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()
|