From 16f1a5366b1e36f73220c27692249d230fed36f7 Mon Sep 17 00:00:00 2001 From: hellcat Date: Sun, 11 Oct 2026 16:21:59 +0800 Subject: [PATCH] ops: synchronize Cloudflare trust ranges for DPL vhost --- .../dpl-cloudflare-ips.service | 5 +++ .../node-onboarding/dpl-cloudflare-ips.timer | 8 ++++ scripts/node-onboarding/gateway-site.conf | 2 + scripts/node-onboarding/install-gateway.sh | 3 ++ .../node-onboarding/update-cloudflare-ips.py | 40 +++++++++++++++++++ 5 files changed, 58 insertions(+) create mode 100644 scripts/node-onboarding/dpl-cloudflare-ips.service create mode 100644 scripts/node-onboarding/dpl-cloudflare-ips.timer create mode 100644 scripts/node-onboarding/update-cloudflare-ips.py diff --git a/scripts/node-onboarding/dpl-cloudflare-ips.service b/scripts/node-onboarding/dpl-cloudflare-ips.service new file mode 100644 index 0000000..a10594f --- /dev/null +++ b/scripts/node-onboarding/dpl-cloudflare-ips.service @@ -0,0 +1,5 @@ +[Unit] +Description=Synchronize DPL vhost trusted Cloudflare ranges +[Service] +Type=oneshot +ExecStart=/usr/bin/python3 /opt/dpl-gateway/provision/update-cloudflare-ips.py diff --git a/scripts/node-onboarding/dpl-cloudflare-ips.timer b/scripts/node-onboarding/dpl-cloudflare-ips.timer new file mode 100644 index 0000000..aec8e1a --- /dev/null +++ b/scripts/node-onboarding/dpl-cloudflare-ips.timer @@ -0,0 +1,8 @@ +[Unit] +Description=Daily DPL Cloudflare IP range synchronization +[Timer] +OnCalendar=*-*-* 02:00:00 +RandomizedDelaySec=900 +Persistent=true +[Install] +WantedBy=timers.target diff --git a/scripts/node-onboarding/gateway-site.conf b/scripts/node-onboarding/gateway-site.conf index 7cb0020..d482184 100644 --- a/scripts/node-onboarding/gateway-site.conf +++ b/scripts/node-onboarding/gateway-site.conf @@ -1,12 +1,14 @@ server { listen 80; server_name c2.deeplink-subscribe.com; + include /etc/nginx/dpl-cloudflare-realip.conf; location ^~ /.well-known/acme-challenge/ { root /var/lib/dpl-sgw/acme; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name c2.deeplink-subscribe.com; + include /etc/nginx/dpl-cloudflare-realip.conf; ssl_certificate /etc/dpl-letsencrypt/live/c2.deeplink-subscribe.com/fullchain.pem; ssl_certificate_key /etc/dpl-letsencrypt/live/c2.deeplink-subscribe.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; diff --git a/scripts/node-onboarding/install-gateway.sh b/scripts/node-onboarding/install-gateway.sh index 9b09926..04f3365 100644 --- a/scripts/node-onboarding/install-gateway.sh +++ b/scripts/node-onboarding/install-gateway.sh @@ -23,6 +23,9 @@ chmod 755 renew-gateway.sh docker compose -f gateway.compose.yml config --quiet docker compose -f gateway.compose.yml up -d install -m 644 dpl-gateway-cert.service dpl-gateway-cert.timer /etc/systemd/system/ +install -m 644 dpl-cloudflare-ips.service dpl-cloudflare-ips.timer /etc/systemd/system/ +python3 update-cloudflare-ips.py systemctl daemon-reload systemctl enable --now dpl-gateway-cert.timer +systemctl enable --now dpl-cloudflare-ips.timer echo 'New gateway started; verify initialization before enabling the HTTPS host site.' diff --git a/scripts/node-onboarding/update-cloudflare-ips.py b/scripts/node-onboarding/update-cloudflare-ips.py new file mode 100644 index 0000000..9f422f6 --- /dev/null +++ b/scripts/node-onboarding/update-cloudflare-ips.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Update the DPL vhost's trusted ranges, retaining the last valid configuration on failure.""" +import ipaddress +import os +import pathlib +import subprocess + +target = pathlib.Path("/etc/nginx/dpl-cloudflare-realip.conf") +ranges = [] +for version in (4, 6): + result = subprocess.run(["curl", "-4", "--fail", "--silent", "--show-error", "--max-time", "30", + f"https://www.cloudflare.com/ips-v{version}"], check=True, capture_output=True, text=True) + nets = [ipaddress.ip_network(line.strip(), strict=True) for line in result.stdout.splitlines() if line.strip()] + assert 5 <= len(nets) <= 100 if version == 4 else 2 <= len(nets) <= 100, "Unexpected range count" + assert all(n.version == version and n.prefixlen >= (8 if version == 4 else 16) for n in nets), "Invalid trust range" + ranges.extend(str(n) for n in nets) +assert len(ranges) == len(set(ranges)), "Duplicate ranges" +data = ("# DPL only: synchronized from Cloudflare official lists.\n" + + "".join(f"set_real_ip_from {n};\n" for n in ranges) + + "real_ip_header CF-Connecting-IP;\nreal_ip_recursive off;\n").encode() +old = target.read_bytes() if target.exists() else None +if old == data: + print("Cloudflare trust ranges unchanged") + raise SystemExit +temporary = target.with_suffix(".conf.new") +temporary.write_bytes(data) +temporary.chmod(0o644) +os.replace(temporary, target) +try: + subprocess.run(["nginx", "-t"], check=True) + subprocess.run(["systemctl", "reload", "nginx"], check=True) +except Exception: + if old is None: + target.unlink() + else: + temporary.write_bytes(old) + temporary.chmod(0o644) + os.replace(temporary, target) + raise +print(f"DPL Cloudflare trust ranges updated: {len(ranges)} ranges")