From 365333d425423d1f0cd96b2ae0c1e68983d13dde Mon Sep 17 00:00:00 2001 From: Smittix Date: Mon, 23 Feb 2026 21:26:33 +0000 Subject: [PATCH] feat: add HTTPS support via INTERCEPT_HTTPS config Auto-generates a self-signed certificate into data/certs/ when INTERCEPT_HTTPS=true, or accepts custom cert/key paths via INTERCEPT_SSL_CERT and INTERCEPT_SSL_KEY. Resolves 400 errors from browsers sending TLS ClientHello to the plain HTTP server. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 1 + app.py | 50 +++++++++++++++++++++++++++++++++++++++++++++- config.py | 5 +++++ docker-compose.yml | 10 ++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c6045db..b5c8904 100644 --- a/Dockerfile +++ b/Dockerfile @@ -250,6 +250,7 @@ RUN mkdir -p /app/data /app/data/weather_sat # Expose web interface port EXPOSE 5050 +EXPOSE 5443 # Environment variables with defaults ENV INTERCEPT_HOST=0.0.0.0 \ diff --git a/app.py b/app.py index fa75223..0754fff 100644 --- a/app.py +++ b/app.py @@ -869,6 +869,36 @@ def kill_all() -> Response: return jsonify({'status': 'killed', 'processes': killed}) +def _ensure_self_signed_cert(cert_dir: str) -> tuple: + """Generate a self-signed certificate if one doesn't already exist. + + Returns (cert_path, key_path) tuple. + """ + cert_path = os.path.join(cert_dir, 'intercept.crt') + key_path = os.path.join(cert_dir, 'intercept.key') + + if os.path.exists(cert_path) and os.path.exists(key_path): + print(f"Using existing SSL certificate: {cert_path}") + return cert_path, key_path + + os.makedirs(cert_dir, exist_ok=True) + print("Generating self-signed SSL certificate...") + + import subprocess + result = subprocess.run([ + 'openssl', 'req', '-x509', '-newkey', 'rsa:2048', + '-keyout', key_path, '-out', cert_path, + '-days', '365', '-nodes', + '-subj', '/CN=intercept/O=INTERCEPT/C=US', + ], capture_output=True, text=True) + + if result.returncode != 0: + raise RuntimeError(f"Failed to generate SSL certificate: {result.stderr}") + + print(f"SSL certificate generated: {cert_path}") + return cert_path, key_path + + def main() -> None: """Main entry point.""" import argparse @@ -895,6 +925,12 @@ def main() -> None: default=config.DEBUG, help='Enable debug mode' ) + parser.add_argument( + '--https', + action='store_true', + default=config.HTTPS, + help='Enable HTTPS with self-signed certificate' + ) parser.add_argument( '--check-deps', action='store_true', @@ -1020,7 +1056,18 @@ def main() -> None: except ImportError as e: print(f"WebSocket waterfall disabled: {e}") - print(f"Open http://localhost:{args.port} in your browser") + # Configure SSL if HTTPS is enabled + ssl_context = None + if args.https: + cert_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'certs') + if config.SSL_CERT and config.SSL_KEY: + ssl_context = (config.SSL_CERT, config.SSL_KEY) + print(f"Using provided SSL certificate: {config.SSL_CERT}") + else: + ssl_context = _ensure_self_signed_cert(cert_dir) + + protocol = 'https' if ssl_context else 'http' + print(f"Open {protocol}://localhost:{args.port} in your browser") print() print("Press Ctrl+C to stop") print() @@ -1032,4 +1079,5 @@ def main() -> None: debug=args.debug, threaded=True, load_dotenv=False, + ssl_context=ssl_context, ) diff --git a/config.py b/config.py index b4ea276..89d383b 100644 --- a/config.py +++ b/config.py @@ -280,6 +280,11 @@ PORT = _get_env_int('PORT', 5050) DEBUG = _get_env_bool('DEBUG', False) THREADED = _get_env_bool('THREADED', True) +# HTTPS / SSL settings +HTTPS = _get_env_bool('HTTPS', False) +SSL_CERT = _get_env('SSL_CERT', '') +SSL_KEY = _get_env('SSL_KEY', '') + # Default RTL-SDR settings DEFAULT_GAIN = _get_env('DEFAULT_GAIN', '40') DEFAULT_DEVICE = _get_env('DEFAULT_DEVICE', '0') diff --git a/docker-compose.yml b/docker-compose.yml index b6318ba..9253075 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,8 @@ services: container_name: intercept ports: - "5050:5050" + # Uncomment for HTTPS support (set INTERCEPT_HTTPS=true below) + # - "5443:5443" # Privileged mode required for USB SDR device access privileged: true # USB device mapping for all USB devices @@ -32,6 +34,9 @@ services: - INTERCEPT_HOST=0.0.0.0 - INTERCEPT_PORT=5050 - INTERCEPT_LOG_LEVEL=INFO + # HTTPS support (auto-generates self-signed cert) + # - INTERCEPT_HTTPS=true + # - INTERCEPT_PORT=5443 # ADS-B history is disabled by default # To enable, use: docker compose --profile history up -d # - INTERCEPT_ADSB_HISTORY_ENABLED=true @@ -70,6 +75,8 @@ services: - adsb_db ports: - "5050:5050" + # Uncomment for HTTPS support (set INTERCEPT_HTTPS=true below) + # - "5443:5443" # Privileged mode required for USB SDR device access privileged: true # USB device mapping for all USB devices @@ -81,6 +88,9 @@ services: - INTERCEPT_HOST=0.0.0.0 - INTERCEPT_PORT=5050 - INTERCEPT_LOG_LEVEL=INFO + # HTTPS support (auto-generates self-signed cert) + # - INTERCEPT_HTTPS=true + # - INTERCEPT_PORT=5443 - INTERCEPT_ADSB_HISTORY_ENABLED=true - INTERCEPT_ADSB_DB_HOST=adsb_db - INTERCEPT_ADSB_DB_PORT=5432