"""Discover internal backends for each proxied hostname by tracing NAT rules. For each public IP in the 72.194.62.x block, pull pfSense port forwards on 443 (and other ports if visible) and map them to internal LAN IPs:ports. Also pull NPM hosts from Jupiter to map hostnames -> backend services. """ import json, os, re, subprocess import paramiko, yaml def _pwd(vault_path): r = subprocess.run(['sops','-d',vault_path], capture_output=True, text=True, timeout=30, check=True) return yaml.safe_load(r.stdout)['credentials']['password'] def ssh(host, user, pwd, port=22): c = paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect(host, port=port, username=user, password=pwd, timeout=30, look_for_keys=False, allow_agent=False) return c def run(c, cmd, to=60): _, o, _ = c.exec_command(cmd, timeout=to) return o.read().decode('utf-8','replace') # ----------------------------------------------------------------- print('=== [1] pfSense NAT rules: public 72.194.62.x -> internal ===') pf_pwd = _pwd('D:/vault/infrastructure/pfsense-firewall.sops.yaml') pf = ssh('172.16.0.1', 'admin', pf_pwd, port=2248) # Pull rdr rules referencing each public IP on :443 out = run(pf, r'pfctl -s nat 2>/dev/null | grep -E "rdr on igc0 .*tcp.*72\.194\.62\.[0-9]+ port = (https|2083|2087|3389|3000|8000)" | sort -u | head -40') print(out.strip()) print() # ----------------------------------------------------------------- print('=== [2] Jupiter docker ps + NPM inspection for :4 traffic ===') j_pwd = _pwd('D:/vault/infrastructure/jupiter-unraid-primary.sops.yaml') j = ssh('172.16.3.20', 'root', j_pwd) # NPM container: find its config file out = run(j, 'docker ps --format "{{.Names}}\\t{{.Image}}\\t{{.Ports}}" | grep -iE "npm|nginx-proxy|proxy"') print('-- NPM container --') print(out.strip()) print() # Find NPM hosts config (usually /data/nginx/proxy_host or in database) out = run(j, 'ls /mnt/user/appdata/NginxProxyManager*/data/nginx/proxy_host/ 2>/dev/null | head') print('-- NPM proxy_host configs --') print(out.strip()) print() # Show the first few proxy_host configs to extract hostname -> upstream mappings out = run(j, r''' for f in /mnt/user/appdata/NginxProxyManager-v3/data/nginx/proxy_host/*.conf /mnt/user/appdata/NginxProxyManager/data/nginx/proxy_host/*.conf 2>/dev/null; do if [ -f "$f" ]; then srv=$(grep -oP "server_name \K[^;]+" "$f" | head -1) ups=$(grep -oP "(proxy_pass|set \$server) \K[^;\"]+" "$f" | head -2 | tr '\n' '|') echo "$(basename $f): server=$srv upstream=$ups" fi done 2>/dev/null ''', to=60) print('-- server_name -> upstream --') print(out.strip()) print() # Also dump docker ps for the services themselves out = run(j, 'docker ps --format "{{.Names}}\\t{{.Ports}}" | head -30') print('-- all docker containers + ports --') print(out.strip()) pf.close(); j.close()