From 3e6486c5b597e8cfa77a01077c2c5e9c83994ff1 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 3 Nov 2026 01:00:55 +0100 Subject: [PATCH] extract-fw-host-vars.py: abort if a conf file cannot be read; add --from-dir. Migrate cl-test. Previously an unreadable file (e.g. /etc/ipt-firewall is 0750 after the role ran) silently produced almost empty host_vars. The script now exits with an error and writes nothing. --from-dir reads the conf files from a local directory, e.g. fetched with ansible using the vaulted become password. Add host_vars for cl-test.oopen.de (fw_manage_config: true). Co-Authored-By: Claude Opus 5.5 --- extract-fw-host-vars.py | 42 +++++++++++++------ .../cl-test.oopen.de/cl-test-ipt-server.yml | 38 +++++++++++++++++ roles/ipt-server/MIGRATION.md | 16 ++++++- 3 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 host_vars/cl-test.oopen.de/cl-test-ipt-server.yml diff --git a/extract-fw-host-vars.py b/extract-fw-host-vars.py index 199fc3c..d7fcd26 100755 --- a/extract-fw-host-vars.py +++ b/extract-fw-host-vars.py @@ -6,7 +6,7 @@ Reads /etc/ipt-firewall/{interfaces,main}_ipv{4,6}.conf via SSH, maps all variables to Ansible fw_* names, and writes a host_vars file. Usage: - ./extract-fw-host-vars.py [--user USER] [--port PORT] [--dry-run] + ./extract-fw-host-vars.py [--user USER] [--port PORT] [--sudo] [--from-dir DIR] [--output FILE] [--dry-run] Example: ./extract-fw-host-vars.py cl-01.oopen.de @@ -749,6 +749,9 @@ def main(): 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("--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() @@ -767,18 +770,33 @@ def main(): 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 "" + 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 {args.user}@{hostname}: ") + + print(f"Connecting to {hostname} as {args.user} ...", 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 = {} diff --git a/host_vars/cl-test.oopen.de/cl-test-ipt-server.yml b/host_vars/cl-test.oopen.de/cl-test-ipt-server.yml new file mode 100644 index 0000000..56cf279 --- /dev/null +++ b/host_vars/cl-test.oopen.de/cl-test-ipt-server.yml @@ -0,0 +1,38 @@ +--- +# ipt-firewall configuration for cl-test.oopen.de +# Generated by extract-fw-host-vars.py - review before committing! +# Place in: host_vars//-ipt-server.yml + +fw_manage_config: true + +# --- Network +fw_ext_interfaces: + - "eth0" +fw_ext_ips_v4: + - "159.69.74.143" +fw_ext_ips_v6: + - "2a01:4f8:231:19a7::143" + +# --- Munin +munin_remote_ipv4: 37.27.121.227 +munin_remote_ipv6: "2a01:4f9:3070:2bda::227" + +# --- Access control IPv4 +fw_allow_ext_service_v4: "159.69.74.156:3478:tcp 159.69.74.156:3478:udp 159.69.74.156:3479:tcp 159.69.74.156:3479:udp 159.69.74.156:5349:tcp 159.69.74.156:5350:udp" +fw_allow_local_service_from_networks_v4: "46.90.86.195:80:tcp 46.90.86.195:443:tcp" + +# --- Access control IPv6 +fw_allow_ext_service_v6: "2a01:4f8:231:19a7::156,3478,tcp 2a01:4f8:231:19a7::156,3478,udp 2a01:4f8:231:19a7::156,3479,tcp 2a01:4f8:231:19a7::156,3479,udp 2a01:4f8:231:19a7::156,5349,tcp 2a01:4f8:231:19a7::156,5349,udp 2a01:4f8:231:19a7::156,5350,tcp 2a01:4f8:231:19a7::156,5350,udp" +fw_allow_local_service_from_networks_v6: "2003:ec:df11:4400::/56,80,tcp 2003:ec:df11:4400::/56,443,tcp" + +# --- HTTP +fw_http_server_ips: $ext_1_ip + +# --- Mail +fw_mail_client_ips: $ext_1_ip + +# --- TURN / STUN +fw_nc_turn_server_ips: $ext_1_ip + +# --- Block +fw_blocked_ips_v4: 222.184.0.0/13 61.160.0.0/16 116.8.0.0/14 diff --git a/roles/ipt-server/MIGRATION.md b/roles/ipt-server/MIGRATION.md index ef50e27..cbfc470 100644 --- a/roles/ipt-server/MIGRATION.md +++ b/roles/ipt-server/MIGRATION.md @@ -60,7 +60,21 @@ HOSTNAME= -o host_vars/${HOSTNAME}/${HOSTNAME%.*.*}-ipt-server.yml ``` -Das Skript fragt einmalig nach dem `sudo`-Passwort. +Das Skript fragt einmalig nach dem `sudo`-Passwort. Kann eine der Dateien nicht +gelesen werden, bricht es ab, ohne etwas zu schreiben. + +Alternativ ohne Passwortabfrage (sudo über `ansible_become_password` aus dem Vault): +die Dateien mit Ansible holen und lokal einlesen: + +```bash +for f in interfaces_ipv4 interfaces_ipv6 main_ipv4 main_ipv6 ; do + ansible ${HOSTNAME} -b -m fetch \ + -a "src=/etc/ipt-firewall/${f}.conf dest=/tmp/fw-${HOSTNAME}/ flat=yes" +done +./extract-fw-host-vars.py ${HOSTNAME} --from-dir /tmp/fw-${HOSTNAME} \ + -o host_vars/${HOSTNAME}/${HOSTNAME%.*.*}-ipt-server.yml +rm -rf /tmp/fw-${HOSTNAME} +``` **Ergebnis prüfen:**