Previously "-l chris" was always passed, which overrode per-host users in ~/.ssh/config (e.g. the so36 hosts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
853 lines
38 KiB
Python
Executable File
853 lines
38 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
|
|
(default: host_vars/<hostname>/<short-hostname>-ipt-server.yml next to this
|
|
script; an existing file is only overwritten with --force).
|
|
|
|
Usage:
|
|
./extract-fw-host-vars.py <hostname> [--user USER] [--port PORT] [--sudo] [--from-dir DIR]
|
|
[--output FILE|-] [--force] [--dry-run]
|
|
|
|
Example:
|
|
./extract-fw-host-vars.py cl-01.oopen.de # -> host_vars/cl-01.oopen.de/cl-01-ipt-server.yml
|
|
./extract-fw-host-vars.py cl-01.oopen.de -o - # print to stdout
|
|
./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-server/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_set_max_new_http_conn_per_ip_per_second": True,
|
|
"fw_max_http_new_conn_per_ip_per_second": "$default_max_http_new_conn_per_ip_per_second",
|
|
"fw_set_max_concurrent_http_conn_per_ip": True,
|
|
"fw_max_concurrent_http_conn_per_ip": "$default_max_concurrent_http_conn_per_ip",
|
|
"fw_set_max_new_http_conn_per_second": True,
|
|
"fw_max_new_http_conn_per_second": "$default_max_new_http_conn_per_second",
|
|
"fw_throttle_tcp6_mss_for_https": False,
|
|
"fw_tcp6_mss_for_https_val": "$min_tcp6_mss_for_https_val",
|
|
"fw_mm_server_ips": "",
|
|
"fw_forward_mm_server_ips": "",
|
|
"fw_mm_udp_ports_in": "$standard_mattermost_udp_ports_in",
|
|
"fw_mm_tcp_ports_in": "$standard_mattermost_tcp_ports_in",
|
|
"fw_mm_udp_ports_out": "$standard_mattermost_udp_ports_out",
|
|
"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_tcp_ports": "$standard_jitsi_tcp_ports",
|
|
"fw_jitsi_udp_port_range": "$standard_jitsi_udp_port_range",
|
|
"fw_jitsi_tcp_ports_out": "$standard_turn_service_ports,4443,4444,4445,4446",
|
|
"fw_jitsi_udp_ports_out": "$standard_http_ports,$standard_turn_service_ports,4443,4444,4445,4446",
|
|
"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_v4": "",
|
|
"fw_blocked_ips_v6": "",
|
|
"fw_ban_ipv6_mask": "$default_ban_ipv6_mask",
|
|
"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",
|
|
"set_max_new_http_conn_per_ip_per_second": "fw_set_max_new_http_conn_per_ip_per_second",
|
|
"max_http_new_conn_per_ip_per_second": "fw_max_http_new_conn_per_ip_per_second",
|
|
"set_max_concurrent_http_conn_per_ip": "fw_set_max_concurrent_http_conn_per_ip",
|
|
"max_concurrent_http_conn_per_ip": "fw_max_concurrent_http_conn_per_ip",
|
|
"set_max_new_http_conn_per_second": "fw_set_max_new_http_conn_per_second",
|
|
"max_new_http_conn_per_second": "fw_max_new_http_conn_per_second",
|
|
"mm_server_ips": "fw_mm_server_ips",
|
|
"forward_mm_server_ips": "fw_forward_mm_server_ips",
|
|
"mm_udp_ports_in": "fw_mm_udp_ports_in",
|
|
"mm_tcp_ports_in": "fw_mm_tcp_ports_in",
|
|
"mm_udp_ports_out": "fw_mm_udp_ports_out",
|
|
"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",
|
|
"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",
|
|
"blocked_ips": "fw_blocked_ips_v4",
|
|
}
|
|
|
|
# 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",
|
|
"throttle_tcp6_mss_for_https": "fw_throttle_tcp6_mss_for_https",
|
|
"tcp6_mss_for_https_val": "fw_tcp6_mss_for_https_val",
|
|
"ban_ipv6_mask": "fw_ban_ipv6_mask",
|
|
"blocked_ips": "fw_blocked_ips_v6",
|
|
}
|
|
|
|
# Renamed bash variables: old name → new name (applied to $var references)
|
|
RENAMED_BASH_VARS = {
|
|
"stansard_mattermost_udp_ports_in": "standard_mattermost_udp_ports_in",
|
|
"stansard_mattermost_udp_ports_out": "standard_mattermost_udp_ports_out",
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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 (single or double quotes, may span multiple lines)
|
|
if rest and rest[0] in ('"', "'"):
|
|
quote_char = rest[0]
|
|
collected = rest[1:] # strip opening quote
|
|
parts = []
|
|
closed = False
|
|
|
|
while True:
|
|
close_pos = collected.find(quote_char)
|
|
if close_pos != -1:
|
|
parts.append(collected[:close_pos])
|
|
# join all parts; split() collapses whitespace and drops empty lines
|
|
value = ' '.join(' '.join(parts).split())
|
|
result[varname] = value
|
|
closed = True
|
|
break
|
|
else:
|
|
parts.append(collected)
|
|
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 normalize_value(value):
|
|
"""Normalize bash variable references: ${var} → $var, renamed vars → new name."""
|
|
value = re.sub(r'\$\{([A-Za-z_][A-Za-z0-9_]*)\}', r'$\1', value)
|
|
for old, new in RENAMED_BASH_VARS.items():
|
|
value = re.sub(r'\$' + old + r'\b', '$' + new, value)
|
|
return value
|
|
|
|
|
|
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,
|
|
list of warnings).
|
|
"""
|
|
result = {}
|
|
warnings = []
|
|
|
|
# --- 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
|
|
|
|
# IPv6 interface lists that differ from IPv4 -> fw_*_interfaces_v6 override
|
|
for prefix, ansible_var, v4_list in [("ext_if_", "fw_ext_interfaces", fw_ext_interfaces),
|
|
("local_if_", "fw_local_interfaces", fw_local_interfaces)]:
|
|
v6_list = extract_list(parsed_iface_v6, prefix)
|
|
if v6_list != v4_list:
|
|
result[f"{ansible_var}_v6"] = v6_list
|
|
|
|
# --- Shared main variables (read from ipv4, fall back to ipv6) ---
|
|
# If IPv4 and IPv6 differ, the IPv6 value goes into <ansible_var>_v6.
|
|
def to_value(raw):
|
|
raw = normalize_value(raw)
|
|
return coerce_bool(raw) if raw.lower() in ("true", "false") else raw
|
|
|
|
for bash_var, ansible_var in MAIN_SHARED.items():
|
|
raw_v4 = parsed_main_v4.get(bash_var)
|
|
raw_v6 = parsed_main_v6.get(bash_var)
|
|
raw = raw_v4 if raw_v4 is not None else raw_v6
|
|
if raw is None:
|
|
continue
|
|
v = to_value(raw)
|
|
default = DEFAULTS.get(ansible_var)
|
|
if v != default:
|
|
result[ansible_var] = v
|
|
if raw_v4 is not None and raw_v6 is not None:
|
|
v6 = to_value(raw_v6)
|
|
if v6 != v:
|
|
result[f"{ansible_var}_v6"] = v6
|
|
|
|
# --- 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
|
|
raw = normalize_value(raw)
|
|
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
|
|
raw = normalize_value(raw)
|
|
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, warnings
|
|
|
|
|
|
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>/<short-hostname>-ipt-server.yml",
|
|
"",
|
|
]
|
|
|
|
lines.append("fw_manage_config: false")
|
|
lines.append("")
|
|
|
|
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",
|
|
"fw_set_max_new_http_conn_per_ip_per_second", "fw_max_http_new_conn_per_ip_per_second",
|
|
"fw_set_max_concurrent_http_conn_per_ip", "fw_max_concurrent_http_conn_per_ip",
|
|
"fw_set_max_new_http_conn_per_second", "fw_max_new_http_conn_per_second",
|
|
"fw_throttle_tcp6_mss_for_https", "fw_tcp6_mss_for_https_val"]),
|
|
("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_tcp_ports_out", "fw_jitsi_udp_ports_out",
|
|
"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",
|
|
"fw_mm_udp_ports_in", "fw_mm_tcp_ports_in", "fw_mm_udp_ports_out"]),
|
|
("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_v4", "fw_blocked_ips_v6", "fw_ban_ipv6_mask", "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"]),
|
|
]
|
|
|
|
# IPv6-only overrides of shared variables follow their base variable
|
|
sections = [(name, [x for k in keys for x in (k, f"{k}_v6")
|
|
if x == k or x not in keys])
|
|
for name, keys in sections]
|
|
|
|
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) and not v:
|
|
section_lines.append(f"{k}: []")
|
|
elif 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) and not v:
|
|
lines.append(f"{k}: []")
|
|
elif 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=None,
|
|
help="SSH user (default: as configured in ~/.ssh/config)")
|
|
parser.add_argument("--port", "-p", type=int, default=None, help="SSH port (default: 22)")
|
|
parser.add_argument("--output", "-o", default=None,
|
|
help="Output file, '-' for stdout "
|
|
"(default: host_vars/<hostname>/<short-hostname>-ipt-server.yml)")
|
|
parser.add_argument("--force", "-f", action="store_true", help="Overwrite an existing output file")
|
|
parser.add_argument("--sudo", "-s", action="store_true",
|
|
help="Read files via sudo (prompts for sudo password once)")
|
|
parser.add_argument("--from-dir", "-d", default=None,
|
|
help="Read the conf files from this local directory instead of via SSH "
|
|
"(e.g. fetched with: ansible HOST -b -m fetch ...)")
|
|
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",
|
|
}
|
|
|
|
target = f"{args.user}@{hostname}" if args.user else hostname
|
|
|
|
if args.dry_run:
|
|
cmd = "sudo -S -p '' cat" if args.sudo else "cat"
|
|
for key, path in files.items():
|
|
print(f"ssh {target} {cmd} {path}")
|
|
return
|
|
|
|
# Determine the output file first, so an existing file aborts before any SSH
|
|
out_path = None
|
|
if args.output != "-":
|
|
if args.output:
|
|
out_path = Path(args.output)
|
|
else:
|
|
# Same as ${HOSTNAME%.*.*} in the docs: a.mx.oopen.de -> a.mx
|
|
short = hostname.rsplit(".", 2)[0] if hostname.count(".") >= 2 else hostname
|
|
out_path = Path(__file__).resolve().parent / "host_vars" / hostname / f"{short}-ipt-server.yml"
|
|
|
|
if out_path.exists() and not args.force:
|
|
print(f"ERROR: {out_path} already exists — use --force to overwrite "
|
|
"or -o - to print to stdout", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
contents = {}
|
|
if args.from_dir:
|
|
for key, path in files.items():
|
|
local = Path(args.from_dir) / Path(path).name
|
|
print(f" Reading {local} ...", file=sys.stderr)
|
|
try:
|
|
contents[key] = local.read_text()
|
|
except OSError as e:
|
|
print(f" WARNING: could not read {local}: {e}", file=sys.stderr)
|
|
contents[key] = None
|
|
else:
|
|
sudo_password = None
|
|
if args.sudo:
|
|
import getpass
|
|
sudo_password = getpass.getpass(f"sudo password for {target}: ")
|
|
|
|
print(f"Connecting to {target} ...", file=sys.stderr)
|
|
|
|
for key, path in files.items():
|
|
print(f" Reading {path} ...", file=sys.stderr)
|
|
contents[key] = ssh_cat(hostname, args.user, args.port, path, sudo_password=sudo_password)
|
|
|
|
# An unreadable file would silently produce (nearly) empty host_vars
|
|
if any(c is None for c in contents.values()):
|
|
print("ERROR: not all config files could be read — nothing written "
|
|
"(try --sudo or --from-dir)", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
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, map_warnings = build_host_vars(
|
|
parsed["iface_v4"], parsed["iface_v6"],
|
|
parsed["main_v4"], parsed["main_v6"],
|
|
)
|
|
all_warnings.extend(map_warnings)
|
|
|
|
yaml_text = render_yaml(hostname, host_vars, all_warnings)
|
|
|
|
if out_path is None:
|
|
print(yaml_text)
|
|
return
|
|
|
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
out_path.write_text(yaml_text)
|
|
print(f"Written to {out_path}", file=sys.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|