Files
Mike Swanson 9ab36352ae Session log: Tunnel expansion + WHM fix (ix. grey-cloud)
Audited all 25 proxied zone records and expanded tunnel ingress to cover
9 hostnames total (azcomputerguru + analytics + community + radio +
git + plexrequest + rmm + rmm-api + sync). All verified HTTP 200.

Reverted 3 hostnames to original A records after discovering they
require backend work, not tunnel changes:
- plex/rustdesk: NPM on Jupiter has no vhost for these (returned
  'tls: unrecognized name' when tunneled)
- secure: Jupiter can't route to its backend subnet 172.16.1.0/24

Reverted ix.azcomputerguru.com to DNS-only A record after user
reported :2087 WHM access broken. Cloudflare Tunnel is hostname-bound,
not port-bound, so non-standard admin ports can't pass through. Direct
NAT to 72.194.62.5 restored WHM/cPanel access.

Adds four new helper scripts under clients/internal-infrastructure/
scripts/cloudflared-tunnel-setup/ (audit_proxied, discover_backends,
expand_tunnel, revert_broken). All use SOPS vault / env var for creds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 15:59:49 -07:00

69 lines
2.8 KiB
Python

"""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()