Changeset: 27b6861a2493 for MonetDB URL: https://dev.monetdb.org/hg/MonetDB/rev/27b6861a2493 Modified Files: clients/mapilib/Tests/systemcertificates.py Branch: Dec2023 Log Message:
Properly test system certificates Depends on environment variables TLSTEST_URL and TLSTEST_ALTURL diffs (166 lines): diff --git a/clients/mapilib/Tests/systemcertificates.py b/clients/mapilib/Tests/systemcertificates.py --- a/clients/mapilib/Tests/systemcertificates.py +++ b/clients/mapilib/Tests/systemcertificates.py @@ -7,39 +7,134 @@ # Copyright 1997 - July 2008 CWI, August 2008 - 2023 MonetDB B.V. -import subprocess -import sys - # Test that certificates from the system trust store are used when no explicit # certificate is given. # -# # If all goes well, this will give a protocol error (MAPI != HTTP), -# not a TLS error +# This test tries to connect to an existing TLS+MAPI server that has a publicly +# trusted certificate. This needs to be configured externally through +# the environment variables TLSTEST_URL and TLSTEST_ALTURL. +# +# These must point to the same server so they receive the same certificate. +# However, the host name in TLSTEST_URL must match the certificate while the +# hostname in TLSTEST_ALT doesn't match. + + +import logging +import os +import shlex +import socket +import subprocess +import ssl +import sys +import urllib +from urllib.parse import urlparse + +level = logging.WARNING +# if sys.platform == 'win32': +# level=logging.DEBUG +if '-v' in sys.argv: + level = logging.DEBUG +# level = logging.DEBUG +logging.basicConfig(level=level) + +logger = logging.root -# Ideally we'd use www.monetdb.org but, but at the time of writing www.monetdb.org -# only returns an error after a 20 second time out. -# -# python.org on the other hand uses a CDN which kicks us out instantly. -HOST = 'python.org' +##################################################################### +# Look for the URL configuration variables, exit if not present + +def getvar(name): + value = os.environ.get(name) + if value: + logger.debug(f"Environment variable {name}={value}") + return value + else: + logger.debug(f"Environment variable {name} is not set, exiting early") + exit(0) + +url = getvar('TLSTEST_URL') +alturl = getvar('TLSTEST_ALTURL') + + +##################################################################### +# Before trying with mclient, connect directly to see if the server is +# up and behaves the way we need for this test + +def try_connect(url, check_hostname, expected_error): + logger.info(f"Attempting to connect to {url}, check_hostname={check_hostname}") + parsed = urlparse(url) + assert parsed.scheme == 'monetdbs', f'Expected scheme monetdbs, not {parsed.scheme}' + + host = parsed.hostname + port = parsed.port or 50000 + logger.debug(f"Connecting to host {host!r} port {port!r}") + + with socket.create_connection((host, port)) as sock: + logger.debug(f"Connection established") -# Run mclient -cmd = ['mclient', '-L-', '-d', f"monetdbs://{HOST}:443/demo"] -proc = subprocess.run(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) -if proc.returncode != 2: - msg = str(proc.stderr, 'utf-8') - print(f"mclient is supposed to exit with status 2, not {proc.returncode}.\n--- stderr ---\n{msg}\n---end stderr ---", file=sys.stderr) - exit(1) + logger.debug(f"Verifying TLS") + ctx = ssl.create_default_context() + ctx.check_hostname = check_hostname + try: + ssl_connection = ctx.wrap_socket(sock, server_hostname=host) + # if we get here it succeeded + if expected_error: + raise Exception(f"Verification succeeded unexpectedly") + else: + logger.debug(f"Verification succeeded as expected") + logger.debug("Closing") + ssl_connection.close() + except ssl.SSLError as e: + if expected_error and expected_error in str(e): + logger.debug(f"Verification failed as expected: {e}") + elif expected_error: + logger.error(f"Verification failed but the error did not match {expected_error!r}: {e}") + else: + logger.error(f"Verification failed unexpectedly: {e}") + raise e + + +# both urls should be reachable and present a TLS certificate +try_connect(url, check_hostname=False, expected_error=None) +try_connect(alturl, check_hostname=False, expected_error=None) + +# url should have the expected hostname so this should succeed +try_connect(url, check_hostname=True, expected_error=None) + +# alturl should have a hostname that makes the verification fail +try_connect(alturl, check_hostname=True, expected_error='Hostname mismatch') + -# After the TLS handshake succeeds we expect the server to send something like -# 'HTTP/1.1 400 Bad Request' because we're sending \x00\x00 instead of an HTTP -# request. libmapi will interpret the first two bytes 'H' and 'T' as an invalid -# block header. -# -# In ASCII, 'H' + 256 * 'T' == 72 + 256 * 84 == 21576. -tls_works_but_mapi_fails = b'21576' in proc.stderr +##################################################################### +# The URLs and the server are configured correctly. +# Check if mclient notices the wrong host name in alturl. + +def run_mclient(url, should_pass): + cmd = ['mclient', '-L-', '-s', 'SELECT 42', '-d', url] + display_cmd = ' '.join(shlex.quote(w) for w in cmd) + logger.info(f"Running {display_cmd}") + proc = subprocess.run(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + + status = proc.returncode + logger.debug(f"Mclient exited with status {status}") -if not tls_works_but_mapi_fails: - msg = str(proc.stderr, 'utf-8') - print(f"Expected mclient to print an error message containing the number 21576, got:\n--- stderr ---\n{msg}\n---end stderr ---", file=sys.stderr) - exit(1) + err = proc.stderr + logger.debug('--- stderr ---') + try: + for line in str(err, 'utf-8').splitlines(): + logger.debug(line) + except UnicodeDecodeError: + logger.debug(err) + logger.debug('--- end of stderr ---') + passed_verification = (status == 0 or b'Sorry, this is not a real' in err) + msg = f"passed_verification={passed_verification}, should_pass={should_pass}" + + if passed_verification == should_pass: + logger.debug(f"{msg}, as expected") + else: + logger.error(f"{msg}, unexpected!") + raise Exception(msg) + +run_mclient(url, should_pass=True) + +run_mclient(alturl, should_pass=False) _______________________________________________ checkin-list mailing list -- checkin-list@monetdb.org To unsubscribe send an email to checkin-list-le...@monetdb.org