From 1537ebf65c2835d2b5d16d9385bfa1abba16507b Mon Sep 17 00:00:00 2001 From: hellcat Date: Sun, 11 Oct 2026 16:32:29 +0800 Subject: [PATCH] ops: verify AnyTLS relay and allow validated certificate chains --- scripts/node-onboarding/gateway-nginx.conf | 3 ++ scripts/node-onboarding/gateway-protect.conf | 1 + scripts/node-onboarding/gateway-proxy.conf | 1 + scripts/node-onboarding/verify-node.py | 55 ++++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 scripts/node-onboarding/verify-node.py diff --git a/scripts/node-onboarding/gateway-nginx.conf b/scripts/node-onboarding/gateway-nginx.conf index b76d271..0bd5ca3 100644 --- a/scripts/node-onboarding/gateway-nginx.conf +++ b/scripts/node-onboarding/gateway-nginx.conf @@ -27,6 +27,9 @@ http { ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; + proxy_ssl_verify on; + proxy_ssl_verify_depth 3; + proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; include /etc/nginx/subscribe/blacklist.conf; include /etc/nginx/subscribe/protect.conf; location / { return 404; } diff --git a/scripts/node-onboarding/gateway-protect.conf b/scripts/node-onboarding/gateway-protect.conf index 48f2a9d..d5a36bf 100644 --- a/scripts/node-onboarding/gateway-protect.conf +++ b/scripts/node-onboarding/gateway-protect.conf @@ -17,6 +17,7 @@ location ^~ /api/v1/client/subscribe { proxy_ssl_server_name on; proxy_ssl_name ${V2B_HOST}; proxy_ssl_verify on; + proxy_ssl_verify_depth 3; proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; proxy_http_version 1.1; proxy_connect_timeout 10s; diff --git a/scripts/node-onboarding/gateway-proxy.conf b/scripts/node-onboarding/gateway-proxy.conf index a14a030..e92eec3 100644 --- a/scripts/node-onboarding/gateway-proxy.conf +++ b/scripts/node-onboarding/gateway-proxy.conf @@ -6,6 +6,7 @@ proxy_http_version 1.1; proxy_ssl_server_name on; proxy_ssl_name c2.deeplink-subscribe.com; proxy_ssl_verify on; +proxy_ssl_verify_depth 3; proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; proxy_connect_timeout 10s; proxy_read_timeout 60s; diff --git a/scripts/node-onboarding/verify-node.py b/scripts/node-onboarding/verify-node.py new file mode 100644 index 0000000..93a80fc --- /dev/null +++ b/scripts/node-onboarding/verify-node.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Run an isolated SOCKS client with strict TLS; never print QA credentials.""" +import argparse +import json +import os +import pathlib +import socket +import subprocess +import tempfile +import time + +p = argparse.ArgumentParser() +p.add_argument('--qa', required=True) +p.add_argument('--binary', required=True) +p.add_argument('--expected-exit', required=True) +p.add_argument('--port', type=int, default=19080) +a = p.parse_args() +q = json.loads(pathlib.Path(a.qa).read_text()) +c = {'log': {'level': 'error'}, 'dns': {'servers': [{'type': 'local', 'tag': 'local'}]}, + 'inbounds': [{'type': 'mixed', 'listen': '127.0.0.1', 'listen_port': a.port}], + 'outbounds': [{'type': 'anytls', 'tag': 'qa', 'server': q['host'], 'server_port': q['port'], + 'password': q['uuid'], 'tls': {'enabled': True, 'server_name': q['sni'], 'insecure': False}}], + 'route': {'default_domain_resolver': 'local', 'final': 'qa'}} +os.umask(0o077) +with tempfile.TemporaryDirectory(prefix='dpl-client-') as d: + config = pathlib.Path(d) / 'client.json' + config.write_text(json.dumps(c)) + env = dict(os.environ, GOMEMLIMIT='64MiB', GOGC='75') + with (pathlib.Path(d) / 'client.log').open('wb') as log: + proc = subprocess.Popen([a.binary, 'run', '-c', str(config)], env=env, stdout=log, stderr=log) + try: + for _ in range(50): + assert proc.poll() is None, 'Client exited during startup' + try: + with socket.create_connection(('127.0.0.1', a.port), 0.1): + break + except OSError: + time.sleep(0.1) + command = ['curl', '-q', '--fail', '--silent', '--show-error', '--max-time', '30', + '--socks5-hostname', f'127.0.0.1:{a.port}'] + result = subprocess.run(command + ['https://api.ipify.org'], capture_output=True, text=True, check=True) + ip = result.stdout.strip() + assert ip == a.expected_exit, 'Unexpected exit IP' + result = subprocess.run(command + ['https://www.cloudflare.com/cdn-cgi/trace'], capture_output=True, text=True, check=True) + trace = dict(line.split('=', 1) for line in result.stdout.splitlines() if '=' in line) + assert trace.get('ip') == a.expected_exit, 'Second exit check mismatch' + print(json.dumps({'anytls': True, 'relay': q['host'], 'port': q['port'], 'strict_tls': True, + 'exit_ip': ip, 'country': trace.get('loc'), 'https_tls': trace.get('tls')}, ensure_ascii=False)) + finally: + proc.terminate() + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait()