Ctrl K
ComunidadGuías…

scripts/test-check-public-tunnel-app.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""Exercise public application checks against an isolated local TLS server."""

import base64
import hashlib
import http.server
import importlib.util
from pathlib import Path
import socket
import ssl
import subprocess
import tempfile
import threading
import unittest
from unittest.mock import patch


spec = importlib.util.spec_from_file_location("public_app", Path(__file__).with_name("check-public-tunnel-app.py"))
check = importlib.util.module_from_spec(spec)
spec.loader.exec_module(check)


class Handler(http.server.BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"

    def do_GET(self):
        self.server.requests.append((self.path, dict(self.headers)))
        if self.path == "/app":
            body = b"fixture-a"
            self.send_response(self.server.http_status)
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return
        key = self.headers["Sec-WebSocket-Key"]
        accept = base64.b64encode(hashlib.sha1((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode()).digest()).decode()
        if self.server.bad_accept:
            accept = "wrong"
        self.send_response(101)
        self.send_header("Connection", "Upgrade")
        self.send_header("Upgrade", "websocket")
        self.send_header("Sec-WebSocket-Accept", accept)
        self.end_headers()
        if self.server.bad_accept:
            return
        first, size = check.read_exact(self.rfile, 2)
        if first != 0x81 or not size & 0x80:
            return
        mask = check.read_exact(self.rfile, 4)
        payload = check.read_exact(self.rfile, size & 0x7f)
        message = bytes(byte ^ mask[index % 4] for index, byte in enumerate(payload))
        if self.server.bad_echo:
            message = b"x" * len(message)
        self.wfile.write(bytes((0x81, len(message))) + message)
        self.close_connection = True

    def log_message(self, *args):
        pass


class PublicAppCheck(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.directory = tempfile.TemporaryDirectory()
        cert = Path(cls.directory.name) / "cert.pem"
        key = Path(cls.directory.name) / "key.pem"
        subprocess.run(["openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes",
                        "-keyout", str(key), "-out", str(cert), "-days", "1",
                        "-subj", "/CN=qa-a.firecrab.dev", "-addext", "subjectAltName=DNS:qa-a.firecrab.dev"],
                       check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        cls.context = ssl.create_default_context(cafile=str(cert))
        server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        server_context.load_cert_chain(cert, key)
        cls.server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler)
        cls.server.socket = server_context.wrap_socket(cls.server.socket, server_side=True)
        cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
        cls.thread.start()

    @classmethod
    def tearDownClass(cls):
        cls.server.shutdown()
        cls.server.server_close()
        cls.thread.join()
        cls.directory.cleanup()

    def setUp(self):
        self.server.requests = []
        self.server.http_status = 200
        self.server.bad_accept = False
        self.server.bad_echo = False
        original_connect = socket.create_connection

        def connect(address, *args, **kwargs):
            self.assertEqual(address[1], 443)
            return original_connect(("127.0.0.1", self.server.server_port), *args, **kwargs)

        self.connection_patch = patch.object(socket, "create_connection", side_effect=connect)
        self.connection_patch.start()
        self.addCleanup(self.connection_patch.stop)

    def test_http_and_websocket_round_trip_over_verified_tls(self):
        check.check_https("qa-a.firecrab.dev", "/app", b"fixture-a", self.context)
        check.check_websocket("qa-a.firecrab.dev", "/ws", self.context)
        self.assertEqual([item[0] for item in self.server.requests], ["/app", "/ws"])
        self.assertTrue(all("Authorization" not in headers for _, headers in self.server.requests))

    def test_unexpected_status_body_and_certificate_fail(self):
        self.server.http_status = 302
        with self.assertRaises(check.CheckError):
            check.check_https("qa-a.firecrab.dev", "/app", b"fixture-a", self.context)
        self.server.http_status = 200
        with self.assertRaises(check.CheckError):
            check.check_https("qa-a.firecrab.dev", "/app", b"fixture-b", self.context)
        with self.assertRaises(check.CheckError):
            check.check_https("qa-b.firecrab.dev", "/app", b"fixture-a", self.context)

    def test_invalid_upgrade_and_wrong_echo_fail(self):
        self.server.bad_accept = True
        with self.assertRaises(check.CheckError):
            check.check_websocket("qa-a.firecrab.dev", "/ws", self.context)
        self.server.bad_accept = False
        self.server.bad_echo = True
        with self.assertRaises(check.CheckError):
            check.check_websocket("qa-a.firecrab.dev", "/ws", self.context)

    def test_rejects_unowned_address_shapes_and_unsafe_paths(self):
        for label in ["console", "qa-", "qa-a.firecrab.dev", "qa-a\r\nX: b", "qa-A", "qa-" + "a" * 61]:
            with self.subTest(label=label), self.assertRaises(check.CheckError):
                check.validate_host(label)
        for path in ["//evil?token=a", "/ws#x", "/ws\r\nX: b", "ws"]:
            with self.subTest(path=path), self.assertRaises(check.CheckError):
                check.validate_path(path)
        self.assertEqual(check.validate_host("qa-test-1"), "qa-test-1.firecrab.dev")


if __name__ == "__main__":
    unittest.main()