#!/usr/bin/python3## Copyright (C) 2026 Red Hat, Inc.# SPDX-License-Identifier: LGPL-2.1-or-later## TLS termination proxy for Anaconda Web UI remote installation.## Terminates TLS on port 443 and forwards plaintext TCP to cockpit-ws# on 127.0.0.1:80. Uses the certificate generated by# cockpit-certificate-ensure.import asyncioimport globimport sslimport sysLISTEN_ADDRESS = "0.0.0.0"LISTEN_PORT = 443BACKEND_ADDRESS = "127.0.0.1"BACKEND_PORT = 80CERT_DIR = "/etc/cockpit/ws-certs.d"async def pipe(reader, writer):"""Forward data from reader to writer until EOF or connection error."""try:while data := await reader.read(8192):writer.write(data)await writer.drain()except (ConnectionResetError, BrokenPipeError, OSError):passfinally:writer.close()async def handle(client_reader, client_writer):"""Handle a TLS client by opening a plaintext connection to the backend and piping both directions."""try:backend_reader, backend_writer = await asyncio.open_connection(BACKEND_ADDRESS, BACKEND_PORT)await asyncio.gather(pipe(client_reader, backend_writer),pipe(backend_reader, client_writer),)except (ConnectionRefusedError, OSError) as e:print(f"Backend connection failed: {e}", file=sys.stderr)client_writer.close()def create_ssl_context():"""Load the certificate generated by cockpit-certificate-ensure."""ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)certs = glob.glob(f"{CERT_DIR}/*.cert")if not certs:print(f"No certificates found in {CERT_DIR}", file=sys.stderr)sys.exit(1)cert_file = certs[0]key_file = cert_file.rsplit(".", 1)[0] + ".key"ctx.load_cert_chain(certfile=cert_file, keyfile=key_file)return ctxasync def main():ssl_ctx = create_ssl_context()server = await asyncio.start_server(handle, LISTEN_ADDRESS, LISTEN_PORT, ssl=ssl_ctx)print(f"TLS proxy listening on {LISTEN_ADDRESS}:{LISTEN_PORT} -> "f"{BACKEND_ADDRESS}:{BACKEND_PORT}",file=sys.stderr,)async with server:await server.serve_forever()asyncio.run(main())