diff --git a/requirements.txt b/requirements.txt index 3758ca584..fd61c1dee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ pytest pytest-mock requests mypy +trustme coverage # Documentation diff --git a/tests/conftest.py b/tests/conftest.py index 8d743e3fb..f396cb415 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,72 +1,34 @@ import pytest +import trustme -CERTIFICATE = b"""-----BEGIN CERTIFICATE----- -MIIEaDCCAtCgAwIBAgIRAPeU748qfVOTZJ7rj5DupbowDQYJKoZIhvcNAQELBQAw -fTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSkwJwYDVQQLDCBmcmFp -cjUwMEBmcmFpcjUwMC1QcmVjaXNpb24tNTUyMDEwMC4GA1UEAwwnbWtjZXJ0IGZy -YWlyNTAwQGZyYWlyNTAwLVByZWNpc2lvbi01NTIwMB4XDTE5MDEwOTIwMzQ1N1oX -DTI5MDEwOTIwMzQ1N1owVDEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNl -cnRpZmljYXRlMSkwJwYDVQQLDCBmcmFpcjUwMEBmcmFpcjUwMC1QcmVjaXNpb24t -NTUyMDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALahGo80UFExe7Iv -jPDulPP9Vu3mPVW/4XhrvmbwjHPSXk6nvK34kdDmGsS/UVgtSMH+sdMNFavkhyK/ -b6PW5dPy+febfxlnaOkrZ5ptYx5IG1l/CNY/QDpQKGljW9YGQDV2t9apgKgT1/Ob -JIKf/rfd2o94iyxlrRnbXXidyMa1E6loo1AzzaN/g17dnblIL7ZCZtflgbsgnytw -UtwS92kTsvMHvuzM7Paz2M0xx+RNtQ2rq51fwph55gn7HLlBFEbkrMsfFj7hEquC -vJYvyrIEvaQLMyIOf+6/OgmrG9Z5ioMV4WAW9FLSuzXuuJruQc7FwQl4XIuE8d0M -jPjRfIcCAwEAAaOBizCBiDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYB -BQUHAwEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBTfMtd0Al3Ly09elEje6jyl -b3EQmjAyBgNVHREEKzApgglsb2NhbGhvc3SHBAAAAACHBH8AAAGHEAAAAAAAAAAA -AAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBADLu7RSMVnUiRNyTqIM3aMmkUXmL -xSPB/SZRifqVwmp9R6ygAZWzC7Lw5BpX2WCde1jqWJZw1AjYbe4w5i8e9jaiUyYZ -eaLuQN7/+dyWeMIfFKx7thDxmati+OkSJSoojROA1v4NY7QAIM6ycfFkwTBRokPz -42srfR+XXrvdNmBRqjpvpr48SAn44uvqAkVr3kNgqs1xycPgjsFvMO7qZlU6w/ev -/7QFUgtyZS/Saa4s3yRXHZ++g3SpPinrzf8VqmovL/MoaqB/tYVjOA/1B3QAkli6 -DIl+99eKANlqARXzMeXvgLpcg+1oAw0hYjFpCtqKhovhQzqN6KlAbmJ9JWTk35x8 -81nOERZH5dh6JZoHzaaB/ZMEjWkmHnyi4bf5dXiPLzfXJslbQKHhnSt4nfZiSodS -brUVv/sux119zyUPe9iA6NNPFS/No1XOKcHrG19jiXTq/HIdJRoIrN6eRJDTRVK1 -HyJ6uTvTJDu4ceBp2J1gz7R5opWbGyytDGg3Tw== ------END CERTIFICATE----- -""" -PRIVATE_KEY = b"""-----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2oRqPNFBRMXuy -L4zw7pTz/Vbt5j1Vv+F4a75m8Ixz0l5Op7yt+JHQ5hrEv1FYLUjB/rHTDRWr5Ici -v2+j1uXT8vn3m38ZZ2jpK2eabWMeSBtZfwjWP0A6UChpY1vWBkA1drfWqYCoE9fz -mySCn/633dqPeIssZa0Z2114ncjGtROpaKNQM82jf4Ne3Z25SC+2QmbX5YG7IJ8r -cFLcEvdpE7LzB77szOz2s9jNMcfkTbUNq6udX8KYeeYJ+xy5QRRG5KzLHxY+4RKr -gryWL8qyBL2kCzMiDn/uvzoJqxvWeYqDFeFgFvRS0rs17ria7kHOxcEJeFyLhPHd -DIz40XyHAgMBAAECggEAZ1q7Liob/icz6r5wU/WhhIduB8qSEZI65qyLH7Sot+9p -Abh51jbjRsbChXAEeBOAppEeT+OKzTHSrH6MjrtSa+WJQ3DTuCvGupae1k1rl7qV -B8wV0zIOhjHQ/PuHAJOfCOK73ZclwXkhcLLvMaGcRLAgPaupj6GnGggEWPtqodDo -qBOcixT3/lMW5M1GklkqJqbD8g8qcx7SFBwORJjpwVX84Ynnursu0ZvTfK/CzZTk -D5t/UXyRV5Y5QBkzKIKzC0qUHv4eMIqkzlPBYx2PnAgrHokOm9/RS28yKT2DVPhw -t311ZM6+Z5AxfKamARWZbZdC8RG5Qo0ujLmgogNn2QKBgQDsqpwO+/yJlvF81nf9 -0Ye5o0OdOdD5q1ra46PyhQ56hIC5cRZx3s3E9hUFDcot81qj9nMTpSGJL5J6GqAY -W7p3PbpYxT27MDjthgHHcZy7hu1M9no65ZAK1ElxVhKMgl89RQu/HQoa6Uh3qjbF -X0edTBTBJoGOYQ1lVaoL8s307QKBgQDFjGtEKubolZ0OqFb361fDcYs0RDKNlNxy -RIMM6Dhl0tgGHxNFuFNlLdjKyPEltfNaK0L0W3i3Ndf5sUlr2MuXYgO6RRqWo/D2 -Tr2/jd6gsVKLK871WD7IS5SbCirCwuEsZQsZ2J2TWECoPqc8L3iZwyW6VGRkIW+K -o2Sl7P4cwwKBgQCnhAt6P7p82S6NInFEY28iYwGU5DuavUNN9BszqiKZbfh/SiCM -8RvM8jHmpeAZrkrWC7dgjF20cMvJSddP5n2RsUuZUeNj/7oLxfK0bSJ3SgXlmADk -d2EBiUmCw13VvuISyDCMUc25Rq5YpU6nXc2e9R8rqEnDscZ9l6kJVA+b8QKBgBAZ -coB6spjP4J3aMERCJMPj1AFtcWVCdXjGhpudrUL3HO3ayHpNHFbJlrpoB+cX3f5C -OlGpxru/optRzHcCkw0CSuV6TkFqmO+p2SLsT/Fuohh/eH1cNLmkFzdPa861jR5O -GcqAcc8ZSSOs/3oTMFPvqHp3+DqE0w9MY552Ivt7AoGATtJkMAg9M4U/5qIsCbRz -LplSCRvcarrg+czXW1re6y117rVjRHPCHgT//azsBDER0WpWSGv7XEnZwnz8U6Cn -FCXoiqqEJuD2wLwQlhb7QVXYTMdCwfPj5WV7ARJO1N4ty3g8x+jnTQCVoMpdhgxC -Sflxx+6bI4XMh0AsZhgtdW4= ------END PRIVATE KEY----- -""" +@pytest.fixture +def tls_certificate_authority() -> trustme.CA: + return trustme.CA() -@pytest.fixture(scope="function") -def certfile_and_keyfile(tmp_path): - certfile = str(tmp_path / "cert.pem") - with open(certfile, "bw") as fout: - fout.write(CERTIFICATE) +@pytest.fixture +def tls_certificate(tls_certificate_authority): + return tls_certificate_authority.issue_server_cert( + "localhost", + "127.0.0.1", + "::1", + ) - keyfile = str(tmp_path / "key.pem") - with open(keyfile, "bw") as fout: - fout.write(PRIVATE_KEY) - return certfile, keyfile +@pytest.fixture +def tls_ca_certificate_pem_path(tls_certificate_authority): + with tls_certificate_authority.cert_pem.tempfile() as ca_cert_pem: + yield ca_cert_pem + + +@pytest.fixture +def tls_ca_certificate_private_key_path(tls_certificate_authority): + with tls_certificate_authority.private_key_pem.tempfile() as private_key: + yield private_key + + +@pytest.fixture +def tls_certificate_pem_path(tls_certificate): + with tls_certificate.private_key_and_cert_chain_pem.tempfile() as cert_pem: + yield cert_pem diff --git a/tests/supervisors/test_watchgodreload.py b/tests/supervisors/test_watchgodreload.py index f8bc774fd..36a6e3261 100644 --- a/tests/supervisors/test_watchgodreload.py +++ b/tests/supervisors/test_watchgodreload.py @@ -11,7 +11,9 @@ def run(sockets): pass -def test_watchgodreload(certfile_and_keyfile): +def test_watchgodreload( + tls_ca_certificate_pem_path, tls_ca_certificate_private_key_path +): config = Config(app=None) reloader = WatchGodReload(config, target=run, sockets=[]) reloader.signal_handler(sig=signal.SIGINT, frame=None) diff --git a/tests/test_config.py b/tests/test_config.py index d8052dc2e..bd1c26873 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -84,9 +84,22 @@ def test_socket_bind(): assert isinstance(config.bind_socket(), socket.socket) -def test_ssl_config(certfile_and_keyfile): - certfile, keyfile = certfile_and_keyfile - config = Config(app=asgi_app, ssl_certfile=certfile, ssl_keyfile=keyfile) +def test_ssl_config(tls_ca_certificate_pem_path, tls_ca_certificate_private_key_path): + config = Config( + app=asgi_app, + ssl_certfile=tls_ca_certificate_pem_path, + ssl_keyfile=tls_ca_certificate_private_key_path, + ) + config.load() + + assert config.is_ssl is True + + +def test_ssl_config_combined(tls_certificate_pem_path): + config = Config( + app=asgi_app, + ssl_certfile=tls_certificate_pem_path, + ) config.load() assert config.is_ssl is True diff --git a/tests/test_ssl.py b/tests/test_ssl.py index b83d7dde3..e6b49367d 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -28,9 +28,42 @@ def no_ssl_verification(session=requests.Session): @pytest.mark.skipif( sys.platform.startswith("win"), reason="Skipping SSL test on Windows" ) -def test_run(certfile_and_keyfile): - certfile, keyfile = certfile_and_keyfile +def test_run(tls_ca_certificate_pem_path, tls_ca_certificate_private_key_path): + class App: + def __init__(self, scope): + if scope["type"] != "http": + raise Exception() + + async def __call__(self, receive, send): + await send({"type": "http.response.start", "status": 204, "headers": []}) + await send({"type": "http.response.body", "body": b"", "more_body": False}) + class CustomServer(Server): + def install_signal_handlers(self): + pass + + config = Config( + app=App, + loop="asyncio", + limit_max_requests=1, + ssl_keyfile=tls_ca_certificate_private_key_path, + ssl_certfile=tls_ca_certificate_pem_path, + ) + server = CustomServer(config=config) + thread = threading.Thread(target=server.run) + thread.start() + while not server.started: + time.sleep(0.01) + with no_ssl_verification(): + response = requests.get("https://127.0.0.1:8000") + assert response.status_code == 204 + thread.join() + + +@pytest.mark.skipif( + sys.platform.startswith("win"), reason="Skipping SSL test on Windows" +) +def test_run_chain(tls_certificate_pem_path): class App: def __init__(self, scope): if scope["type"] != "http": @@ -48,8 +81,7 @@ def install_signal_handlers(self): app=App, loop="asyncio", limit_max_requests=1, - ssl_keyfile=keyfile, - ssl_certfile=certfile, + ssl_certfile=tls_certificate_pem_path, ) server = CustomServer(config=config) thread = threading.Thread(target=server.run)