feat(web): add self-signed HTTPS mode for headless/LAN access
--tls generates a self-signed cert (SANs: localhost, 127.0.0.1, LAN IP) so a headless box is reachable directly over HTTPS, which browsers treat as a secure context (required for cross-origin isolation). make web now defaults to TLS.
This commit is contained in:
@@ -8,3 +8,5 @@ flash.bin
|
||||
result
|
||||
_scratch/
|
||||
sd.img
|
||||
.serve-cert.pem
|
||||
.serve-key.pem
|
||||
|
||||
@@ -29,8 +29,10 @@ qemu:
|
||||
qemu-wasm:
|
||||
QEMU_WASM_SRC=$(QEMU_WASM_SRC) WASM_OUT=$(WASM_OUT) JOBS=2 scripts/build-qemu-wasm.sh
|
||||
|
||||
# Serve over HTTPS by default so headless/LAN access is cross-origin isolated. Override with WEB_TLS=.
|
||||
WEB_TLS ?= --tls
|
||||
web: qemu-wasm
|
||||
python3 scripts/serve-web.py
|
||||
python3 scripts/serve-web.py $(WEB_TLS)
|
||||
|
||||
web-test:
|
||||
node scripts/test-web.mjs
|
||||
|
||||
+49
-3
@@ -1,6 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import ipaddress
|
||||
import socket
|
||||
import ssl
|
||||
import subprocess
|
||||
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class IsolatedHandler(SimpleHTTPRequestHandler):
|
||||
@@ -11,15 +16,56 @@ class IsolatedHandler(SimpleHTTPRequestHandler):
|
||||
super().end_headers()
|
||||
|
||||
|
||||
def local_ip():
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||
try:
|
||||
s.connect(("8.8.8.8", 80))
|
||||
return s.getsockname()[0]
|
||||
except OSError:
|
||||
return "127.0.0.1"
|
||||
|
||||
|
||||
def ensure_cert(cert: Path, key: Path):
|
||||
"""Generate a self-signed cert covering localhost and the LAN IP, so a
|
||||
headless box can be reached directly over HTTPS (secure context)."""
|
||||
if cert.exists() and key.exists():
|
||||
return
|
||||
ip = local_ip()
|
||||
san = f"subjectAltName=DNS:localhost,IP:127.0.0.1,IP:{ip}"
|
||||
subprocess.run(
|
||||
["openssl", "req", "-x509", "-newkey", "rsa:2048", "-sha256",
|
||||
"-days", "3650", "-nodes", "-keyout", str(key), "-out", str(cert),
|
||||
"-subj", "/CN=localhost", "-addext", san],
|
||||
check=True,
|
||||
)
|
||||
print(f"Generated self-signed cert for localhost and {ip} at {cert}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--port", type=int, default=8000)
|
||||
parser.add_argument("--host", default="0.0.0.0")
|
||||
parser.add_argument("--tls", action="store_true", help="serve over HTTPS with a self-signed cert")
|
||||
parser.add_argument("--cert", default=".serve-cert.pem")
|
||||
parser.add_argument("--key", default=".serve-key.pem")
|
||||
args = parser.parse_args()
|
||||
|
||||
server = ThreadingHTTPServer((args.host, args.port), IsolatedHandler)
|
||||
print(f"Serving on http://{args.host}:{args.port}/web/")
|
||||
print("Note: WASM pthreads need a secure context — use localhost or HTTPS; a"
|
||||
" bare LAN IP over HTTP is not cross-origin isolated.")
|
||||
scheme = "http"
|
||||
if args.tls:
|
||||
ensure_cert(Path(args.cert), Path(args.key))
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(args.cert, args.key)
|
||||
server.socket = context.wrap_socket(server.socket, server_side=True)
|
||||
scheme = "https"
|
||||
|
||||
print(f"Serving on {scheme}://{args.host}:{args.port}/web/")
|
||||
if args.tls:
|
||||
print(f"Reach it directly at {scheme}://{local_ip()}:{args.port}/web/ "
|
||||
"(accept the self-signed cert warning once).")
|
||||
else:
|
||||
print("Note: WASM pthreads need a secure context — use localhost, an SSH "
|
||||
"tunnel, or --tls; a bare LAN IP over HTTP is not cross-origin isolated.")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user