diff --git a/citrix_hypervisor/changelog.d/24909.fixed b/citrix_hypervisor/changelog.d/24909.fixed new file mode 100644 index 0000000000000..0dcffb64be43a --- /dev/null +++ b/citrix_hypervisor/changelog.d/24909.fixed @@ -0,0 +1 @@ +Apply ``tls_verify`` to the XML-RPC session login, previously only applied to the RRD metrics HTTP client. diff --git a/citrix_hypervisor/datadog_checks/citrix_hypervisor/check.py b/citrix_hypervisor/datadog_checks/citrix_hypervisor/check.py index 17f269fb9961c..bfd9c74347616 100644 --- a/citrix_hypervisor/datadog_checks/citrix_hypervisor/check.py +++ b/citrix_hypervisor/datadog_checks/citrix_hypervisor/check.py @@ -125,7 +125,7 @@ def _get_master_session(self, session): def open_session(self): # type: () -> Dict[str, str] try: - self.xenserver = ServerProxy(self._base_url) + self.xenserver = ServerProxy(self._base_url, context=self.get_tls_context()) except Exception as e: self.log.warning(str(e)) return {} diff --git a/citrix_hypervisor/tests/conftest.py b/citrix_hypervisor/tests/conftest.py index 9670622329d2e..df783ea211570 100644 --- a/citrix_hypervisor/tests/conftest.py +++ b/citrix_hypervisor/tests/conftest.py @@ -2,12 +2,18 @@ # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) import os +import ssl +import subprocess +import tempfile +import threading +from xmlrpc.server import SimpleXMLRPCServer import mock import pytest from datadog_checks.dev import docker_run from datadog_checks.dev.http import MockResponse +from datadog_checks.dev.utils import find_free_port from . import common @@ -52,3 +58,50 @@ def mock_requests_get(url, *args, **kwargs): def mock_responses(): with mock.patch('requests.Session.get', side_effect=mock_requests_get): yield + + +@pytest.fixture +def tls_xenserver(): + """Real HTTPS XML-RPC server backed by a fresh self-signed cert, for TLS behavior tests.""" + cert_file = tempfile.NamedTemporaryFile(delete=False, suffix='.crt') + key_file = tempfile.NamedTemporaryFile(delete=False, suffix='.key') + cert_file.close() + key_file.close() + subprocess.run( + [ + 'openssl', + 'req', + '-x509', + '-newkey', + 'rsa:2048', + '-keyout', + key_file.name, + '-out', + cert_file.name, + '-days', + '1', + '-nodes', + '-subj', + '/CN=localhost', + ], + check=True, + ) + + port = find_free_port('127.0.0.1') + server = SimpleXMLRPCServer(('127.0.0.1', port), logRequests=False) + server.register_function(lambda username, password: common.SESSION_MASTER, 'session.login_with_password') + + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.load_cert_chain(certfile=cert_file.name, keyfile=key_file.name) + server.socket = context.wrap_socket(server.socket, server_side=True) + + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + + yield 'https://localhost:{}'.format(port) + + server.shutdown() + server.server_close() + thread.join() + os.unlink(cert_file.name) + os.unlink(key_file.name) diff --git a/citrix_hypervisor/tests/test_citrix_hypervisor.py b/citrix_hypervisor/tests/test_citrix_hypervisor.py index d5782f74fd280..a062d1e505e31 100644 --- a/citrix_hypervisor/tests/test_citrix_hypervisor.py +++ b/citrix_hypervisor/tests/test_citrix_hypervisor.py @@ -35,6 +35,20 @@ def test_open_session(instance, side_effect, expected_session, tag): assert tag == check._additional_tags +def test_open_session_fails_on_self_signed_cert_by_default(tls_xenserver): + # Regression test: the XML-RPC login used to ignore `tls_verify`, always using Python's + # default verifying SSL context regardless of instance config. + check = CitrixHypervisorCheck('citrix_hypervisor', {}, [{'url': tls_xenserver}]) + + assert check.open_session() == {} + + +def test_open_session_succeeds_with_tls_verify_false(tls_xenserver): + check = CitrixHypervisorCheck('citrix_hypervisor', {}, [{'url': tls_xenserver, 'tls_verify': False}]) + + assert check.open_session() == SESSION_MASTER + + @pytest.mark.usefixtures('mock_responses') @pytest.mark.parametrize('server_type', [pytest.param('master'), pytest.param('slave')]) def test_check(aggregator, dd_run_check, instance, server_type):