From 9943ee588d5511a9fb9f4d17321be288948a1858 Mon Sep 17 00:00:00 2001 From: Ian Bucad Date: Wed, 19 Aug 2026 12:38:07 +1000 Subject: [PATCH 1/4] citrix_hypervisor: apply tls_verify to the XML-RPC session login Co-Authored-By: Claude Sonnet 5 --- citrix_hypervisor/datadog_checks/citrix_hypervisor/check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 {} From 195e1336aebc83cf0f86d80d76be0ce97bb4d44e Mon Sep 17 00:00:00 2001 From: Ian Bucad Date: Wed, 19 Aug 2026 12:38:46 +1000 Subject: [PATCH 2/4] citrix_hypervisor: add changelog entry for PR #24909 Co-Authored-By: Claude Sonnet 5 --- citrix_hypervisor/changelog.d/24909.fixed | 1 + 1 file changed, 1 insertion(+) create mode 100644 citrix_hypervisor/changelog.d/24909.fixed 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. From 38896c9383dd816b47c2baae6c4c39a5fe1fae46 Mon Sep 17 00:00:00 2001 From: Ian Bucad Date: Wed, 19 Aug 2026 12:43:50 +1000 Subject: [PATCH 3/4] citrix_hypervisor: add regression test for TLS context on session login Co-Authored-By: Claude Sonnet 5 --- citrix_hypervisor/tests/test_citrix_hypervisor.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/citrix_hypervisor/tests/test_citrix_hypervisor.py b/citrix_hypervisor/tests/test_citrix_hypervisor.py index d5782f74fd280..59888471929c8 100644 --- a/citrix_hypervisor/tests/test_citrix_hypervisor.py +++ b/citrix_hypervisor/tests/test_citrix_hypervisor.py @@ -2,6 +2,7 @@ # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) import logging +import ssl import mock import pytest @@ -35,6 +36,20 @@ def test_open_session(instance, side_effect, expected_session, tag): assert tag == check._additional_tags +def test_open_session_respects_tls_verify(instance): + # Regression test: the XML-RPC login used to ignore `tls_verify`, causing + # CERTIFICATE_VERIFY_FAILED against self-signed certs even when it was set to False. + instance = dict(instance, tls_verify=False) + with mock.patch( + 'datadog_checks.citrix_hypervisor.check.ServerProxy', side_effect=[mocked_xenserver('master')] + ) as server_proxy: + check = CitrixHypervisorCheck('citrix_hypervisor', {}, [instance]) + check.open_session() + + _, kwargs = server_proxy.call_args + assert kwargs['context'].verify_mode == ssl.CERT_NONE + + @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): From 47899756b54cf9d3e1d6d06a51bc6e2a385ab328 Mon Sep 17 00:00:00 2001 From: Ian Bucad Date: Wed, 19 Aug 2026 13:36:25 +1000 Subject: [PATCH 4/4] citrix_hypervisor: exercise real TLS handshake in session login test Replace the mocked ServerProxy assertion with a real self-signed HTTPS XML-RPC server, verifying open_session() actually fails by default and succeeds with tls_verify: false, per review feedback. Co-Authored-By: Claude Sonnet 5 --- citrix_hypervisor/tests/conftest.py | 53 +++++++++++++++++++ .../tests/test_citrix_hypervisor.py | 23 ++++---- 2 files changed, 64 insertions(+), 12 deletions(-) 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 59888471929c8..a062d1e505e31 100644 --- a/citrix_hypervisor/tests/test_citrix_hypervisor.py +++ b/citrix_hypervisor/tests/test_citrix_hypervisor.py @@ -2,7 +2,6 @@ # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) import logging -import ssl import mock import pytest @@ -36,18 +35,18 @@ def test_open_session(instance, side_effect, expected_session, tag): assert tag == check._additional_tags -def test_open_session_respects_tls_verify(instance): - # Regression test: the XML-RPC login used to ignore `tls_verify`, causing - # CERTIFICATE_VERIFY_FAILED against self-signed certs even when it was set to False. - instance = dict(instance, tls_verify=False) - with mock.patch( - 'datadog_checks.citrix_hypervisor.check.ServerProxy', side_effect=[mocked_xenserver('master')] - ) as server_proxy: - check = CitrixHypervisorCheck('citrix_hypervisor', {}, [instance]) - check.open_session() +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}]) - _, kwargs = server_proxy.call_args - assert kwargs['context'].verify_mode == ssl.CERT_NONE + assert check.open_session() == SESSION_MASTER @pytest.mark.usefixtures('mock_responses')