From 525235ffcd3cca87b4d9b25bb93703003419357a Mon Sep 17 00:00:00 2001 From: Rodolfo Olivieri Date: Mon, 6 Jan 2025 12:53:53 -0300 Subject: [PATCH] [RSPEED-388] Add exception handling for SSL certificates (#87) * Add exception handling for SSL certificates Adding two exception handling for the SSLAdapter class. One for the certificate not being valid, and the second one for the certificate missing on the system. Without that, the user would receive a nasty error in the client and a not that helpful error in the journald logs. * Add unit tests --- command_line_assistant/daemon/http/session.py | 20 ++++++++++++---- tests/daemon/http/test_session.py | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/command_line_assistant/daemon/http/session.py b/command_line_assistant/daemon/http/session.py index 4a306fe..09ae341 100644 --- a/command_line_assistant/daemon/http/session.py +++ b/command_line_assistant/daemon/http/session.py @@ -1,6 +1,7 @@ """Handle the http sessions that the daemon issues to the backend.""" import logging +from ssl import SSLError import urllib3 from requests.sessions import Session @@ -8,6 +9,7 @@ from command_line_assistant.config import Config from command_line_assistant.constants import VERSION from command_line_assistant.daemon.http.adapters import RetryAdapter, SSLAdapter +from command_line_assistant.dbus.exceptions import RequestFailedError USER_AGENT = f"clad/{VERSION}" #: Define the custom user agent for clad @@ -46,10 +48,20 @@ def get_session(config: Config) -> Session: session.verify = False return session - ssl_adapter = SSLAdapter( - cert_file=config.backend.auth.cert_file, # type: ignore - key_file=config.backend.auth.key_file, # type: ignore - ) + try: + ssl_adapter = SSLAdapter( + cert_file=config.backend.auth.cert_file, # type: ignore + key_file=config.backend.auth.key_file, # type: ignore + ) + except SSLError as e: + raise RequestFailedError( + "Failed to load certificate in cert chain. If needed, regenerate the certificate with subscription-manager and try again." + ) from e + except FileNotFoundError as e: + raise RequestFailedError( + f"Couldn't find certificate files at '{config.backend.auth.cert_file.parent}' folder." # pyright: ignore[reportAttributeAccessIssue] + ) from e + # Mount the adapter for the given endpoint. session.mount(config.backend.endpoint, ssl_adapter) diff --git a/tests/daemon/http/test_session.py b/tests/daemon/http/test_session.py index 2bd2c19..faacab7 100644 --- a/tests/daemon/http/test_session.py +++ b/tests/daemon/http/test_session.py @@ -5,6 +5,7 @@ from command_line_assistant.constants import VERSION from command_line_assistant.daemon.http.session import get_session +from command_line_assistant.dbus.exceptions import RequestFailedError def test_session_headers(mock_config): @@ -73,3 +74,26 @@ def test_various_endpoints(mock_config, endpoint): # Verify that the endpoint is used for mounting adapters assert any(pattern == endpoint for pattern, _ in session.adapters.items()) + + +def test_session_with_corrupted_ssl_certificate(mock_config): + mock_config.backend.auth.cert_file.write_text("whatever pem") + mock_config.backend.auth.key_file.write_text("whatever secret") + mock_config.backend.auth.verify_ssl = True + + with pytest.raises( + RequestFailedError, match="Failed to load certificate in cert chain." + ): + get_session(mock_config) + + +def test_session_with_missing_ssl_certificate(tmp_path, mock_config): + cert_file = tmp_path / "missing" / "cert.pem" + key_file = tmp_path / "missing" / "key.pem" + + mock_config.backend.auth.cert_file = cert_file + mock_config.backend.auth.key_file = key_file + mock_config.backend.auth.verify_ssl = True + + with pytest.raises(RequestFailedError, match="Couldn't find certificate files at"): + get_session(mock_config)