diff --git a/doc/changes/changes_0.2.9.md b/doc/changes/changes_0.2.9.md index 5332d98..874e133 100644 --- a/doc/changes/changes_0.2.9.md +++ b/doc/changes/changes_0.2.9.md @@ -12,4 +12,6 @@ Post-release fixes. * #89: Connecting a new AI-Lab container to the Docker DB network when the latter container restarts. * #93: Refactoring the ITDE manager interface. * #94: Adding an integration test for restart_itde() in a container. -* #95: Adding an integration test for get_itde_status() in a container. \ No newline at end of file +* #95: Adding an integration test for get_itde_status() in a container. +* #99: Setting the correct protocol and TLS certificate verification option when creating a + connection object with BucketFS credentials. \ No newline at end of file diff --git a/exasol/nb_connector/extension_wrapper_common.py b/exasol/nb_connector/extension_wrapper_common.py index d2a6d04..34ced2e 100644 --- a/exasol/nb_connector/extension_wrapper_common.py +++ b/exasol/nb_connector/extension_wrapper_common.py @@ -1,3 +1,6 @@ +from __future__ import annotations +from typing import Optional + from exasol.nb_connector.connections import open_pyexasol_connection from exasol.nb_connector.secret_store import Secrets from exasol.nb_connector.utils import optional_str_to_bool @@ -40,19 +43,30 @@ def encapsulate_bucketfs_credentials( Path identifying a location in the bucket. connection_name: Name for the connection object to be created. + + A note about handling the TLS certificate verification settings. + If the server certificate verification is turned on, either through + reliance of the default https request settings or by setting the cert_vld + configuration parameter to True, this intention will be passed to + the connection object. However, if the user specifies a custom CA list + file or directory, which also implies the certificate verification, + the connection object will instead turn the verification off. This is + because there is no guarantee that the consumer of the connection object, + i.e. a UDF, would have this custom CA list, and even if it would, its location + is unknown. """ bfs_host = conf.get(CKey.bfs_host_name, conf.get(CKey.db_host_name)) - # For now, just use the http. Once the exasol.bucketfs is capable of using - # the https without validating the server certificate choose between the - # http and https depending on the bfs_encryption setting, like this: - # bfs_protocol = "https" if str_to_bool(conf, CKey.bfs_encryption, True) - # else "http" - bfs_protocol = "http" + bfs_protocol = "https" if str_to_bool(conf, CKey.bfs_encryption, True) else "http" bfs_dest = ( f"{bfs_protocol}://{bfs_host}:{conf.get(CKey.bfs_port)}/" f"{conf.get(CKey.bfs_bucket)}/{path_in_bucket};{conf.get(CKey.bfs_service)}" ) + # TLS certificate verification option shall be provided in the fragment field. + verify: Optional[bool] = (False if conf.get(CKey.trusted_ca) + else optional_str_to_bool(conf.get(CKey.cert_vld))) + if verify is not None: + bfs_dest += f'#{verify}' sql = f""" CREATE OR REPLACE CONNECTION [{connection_name}] diff --git a/test/unit/test_extension_wrapper_common.py b/test/unit/test_extension_wrapper_common.py new file mode 100644 index 0000000..779d11a --- /dev/null +++ b/test/unit/test_extension_wrapper_common.py @@ -0,0 +1,80 @@ +import unittest.mock +import pytest +import tempfile + +from exasol.nb_connector.secret_store import Secrets +from exasol.nb_connector.ai_lab_config import AILabConfig as CKey +from exasol.nb_connector.extension_wrapper_common import encapsulate_bucketfs_credentials + + +@pytest.fixture +def filled_secrets(secrets) -> Secrets: + secrets.save(CKey.db_host_name, 'localhost') + secrets.save(CKey.db_port, '8888') + secrets.save(CKey.db_user, 'user') + secrets.save(CKey.db_password, 'password') + secrets.save(CKey.bfs_port, '6666') + secrets.save(CKey.bfs_encryption, 'True') + secrets.save(CKey.bfs_service, 'bfsdefault') + secrets.save(CKey.bfs_bucket, 'default') + secrets.save(CKey.bfs_user, 'user'), + secrets.save(CKey.bfs_password, 'password') + return secrets + + +@unittest.mock.patch("pyexasol.connect") +def test_bucketfs_credentials_default(mock_connect, filled_secrets): + + path_in_bucket = 'location' + + mock_connection = unittest.mock.MagicMock() + mock_connection.__enter__.return_value = mock_connection + mock_connect.return_value = mock_connection + + encapsulate_bucketfs_credentials(filled_secrets, path_in_bucket=path_in_bucket, + connection_name='whatever') + expected_url = f"https://localhost:6666/default/{path_in_bucket};bfsdefault" + + mock_connection.execute.assert_called_once() + query = mock_connection.execute.call_args_list[0].kwargs['query'] + assert f"TO '{expected_url}'" in query + + +@unittest.mock.patch("pyexasol.connect") +def test_bucketfs_credentials_verify(mock_connect, filled_secrets): + + path_in_bucket = 'location' + filled_secrets.save(CKey.cert_vld, 'yes') + + mock_connection = unittest.mock.MagicMock() + mock_connection.__enter__.return_value = mock_connection + mock_connect.return_value = mock_connection + + encapsulate_bucketfs_credentials(filled_secrets, path_in_bucket=path_in_bucket, + connection_name='whatever') + expected_url = f"https://localhost:6666/default/{path_in_bucket};bfsdefault#True" + + mock_connection.execute.assert_called_once() + query = mock_connection.execute.call_args_list[0].kwargs['query'] + assert f"TO '{expected_url}'" in query + + +@unittest.mock.patch("pyexasol.connect") +def test_bucketfs_credentials_ca(mock_connect, filled_secrets): + + with tempfile.NamedTemporaryFile() as f: + path_in_bucket = 'location' + filled_secrets.save(CKey.trusted_ca, f.name) + filled_secrets.save(CKey.cert_vld, 'yes') + + mock_connection = unittest.mock.MagicMock() + mock_connection.__enter__.return_value = mock_connection + mock_connect.return_value = mock_connection + + encapsulate_bucketfs_credentials(filled_secrets, path_in_bucket=path_in_bucket, + connection_name='whatever') + expected_url = f"https://localhost:6666/default/{path_in_bucket};bfsdefault#False" + + mock_connection.execute.assert_called_once() + query = mock_connection.execute.call_args_list[0].kwargs['query'] + assert f"TO '{expected_url}'" in query