#!/usr/bin/env python3 """Add exposedgays.com to Cloudflare tunnel remote ingress using best available token.""" import json import re import time import urllib.error import urllib.request import paramiko PASS = "Bbt9115xty9176!" ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda" TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472" CF = "https://api.cloudflare.com/client/v4" EG_RULES = [ { "hostname": "exposedgays.com", "service": "http://10.10.0.11:80", "originRequest": {"httpHostHeader": "exposedgays.com"}, }, { "hostname": "www.exposedgays.com", "service": "http://10.10.0.11:80", "originRequest": {"httpHostHeader": "www.exposedgays.com"}, }, ] def get_pfsense_token(): c = paramiko.SSHClient() c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect("10.10.0.1", username="admin", password=PASS, timeout=15) _, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30) o.channel.recv_exit_status() text = (o.read() + e.read()).decode() c.close() return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1) def cf(token, path, method="GET", data=None): req = urllib.request.Request( f"{CF}{path}", data=json.dumps(data).encode() if data is not None else None, method=method, headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, ) try: with urllib.request.urlopen(req, timeout=60) as r: return r.status, json.load(r) except urllib.error.HTTPError as e: return e.code, json.loads(e.read().decode()) def ssh(cmd, timeout=60): c = paramiko.SSHClient() c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30) _, o, e = c.exec_command(cmd, timeout=timeout) o.channel.recv_exit_status() out = (o.read() + e.read()).decode(errors="replace") c.close() return out def get_remote_ingress_from_journal(): raw = ssh( "sudo journalctl -u cloudflared --no-pager -n 200 | grep 'Updated to new configuration' | tail -1" ) start = raw.find('config="') if start == -1: raise RuntimeError("no remote config in journal") payload = raw[start + len('config="'):].rsplit('"', 1)[0] payload = payload.encode().decode("unicode_escape") return json.loads(payload).get("ingress", []) def main(): token = get_pfsense_token() code, verify = cf(token, "/user/tokens/verify") print(f"token verify: {code} {verify.get('success')}") code, body = cf(token, f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations") print(f"API GET config: {code} {body.get('errors')}") if body.get("success"): ingress = body["result"]["config"]["ingress"] source = "api" else: ingress = get_remote_ingress_from_journal() source = "journal" print(f"ingress source={source} rules={len(ingress)}") hostnames = {i.get("hostname") for i in ingress if i.get("hostname")} changed = False for rule in EG_RULES: if rule["hostname"] not in hostnames: catch = next((i for i, x in enumerate(ingress) if not x.get("hostname")), len(ingress)) ingress.insert(catch, rule) hostnames.add(rule["hostname"]) changed = True print("will add", rule["hostname"]) if not changed: print("already present in ingress list") elif source != "api": print("BLOCKED: cannot PUT without API token — ingress list prepared but not pushed") print("Prepared rules:") for rule in EG_RULES: print(" ", rule) return 1 put_code, put = cf( token, f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations", "PUT", {"config": {"ingress": ingress, "warp-routing": {"enabled": False}}}, ) print(f"PUT: {put_code} success={put.get('success')} errors={put.get('errors')}") if not put.get("success"): return 1 print(ssh(f"echo '{PASS}' | sudo -S systemctl restart cloudflared")) time.sleep(10) print(ssh( "curl -sSI --max-time 20 https://exposedgays.com/ | head -12; " "curl -sSL --max-time 20 https://exposedgays.com/ | head -c 250" )) return 0 if __name__ == "__main__": raise SystemExit(main())