From ac477fb66776214e20d5e85cb18ea5a548aa41cf Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 6 Sep 2023 22:08:55 +0200 Subject: [PATCH] S3: add option to specify a custom CA cert bundle Users can: - set this option to the path to their CA cert bundle; - use the default value to use the default CA cert bundle used by botocore. We do not allow users to disable SSL certificates verification. Closes-Bug: #2030825 Change-Id: I852beab63512f2469f92181a2ba427cbaa172ec2 (cherry picked from commit f9db33edc78411a0d7cf82a19b01da24ad7304fd) (cherry picked from commit aeac1d7a0ac6dda16af9fe7312788a89255839ce) (cherry picked from commit f76d051190d24eb2cc5a1fc4e97eacebf53a494b) --- glance_store/_drivers/s3.py | 28 +++++++++++++++---- glance_store/tests/unit/test_multistore_s3.py | 19 +++++++++++++ glance_store/tests/unit/test_opts.py | 1 + glance_store/tests/unit/test_s3_store.py | 25 +++++++++++++++++ 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/glance_store/_drivers/s3.py b/glance_store/_drivers/s3.py index 8aff8f11..95d4c84b 100644 --- a/glance_store/_drivers/s3.py +++ b/glance_store/_drivers/s3.py @@ -230,7 +230,18 @@ Related Options: * s3_store_large_object_size * s3_store_large_object_chunk_size -""") +"""), + cfg.StrOpt('s3_store_cacert', + default='', + help=""" +The path to the CA cert bundle to use. The default value (an empty string) +forces the use of the default CA cert bundle used by botocore. + +Possible values: + * A path to the CA cert bundle to use + * An empty string to use the default CA cert bundle used by botocore + +"""), ] @@ -464,6 +475,8 @@ class Store(glance_store.driver.Store): return result if param == 's3_store_region_name': return result + if param == 's3_store_cacert': + return result reason = _("Could not find %s in configuration options.") % param LOG.error(reason) raise exceptions.BadStoreConfiguration(store_name="s3", @@ -500,11 +513,14 @@ class Store(glance_store.driver.Store): else: endpoint_url = s3_host - return session.client(service_name='s3', - endpoint_url=endpoint_url, - region_name=region_name, - use_ssl=(loc.scheme == 's3+https'), - config=config) + store_cacert = self._option_get('s3_store_cacert') + return session.client( + service_name='s3', + endpoint_url=endpoint_url, + region_name=region_name, + use_ssl=(loc.scheme == 's3+https'), + verify=None if store_cacert == '' else store_cacert, + config=config) def _operation_set(self, loc): """Objects and variables frequently used when operating S3 are diff --git a/glance_store/tests/unit/test_multistore_s3.py b/glance_store/tests/unit/test_multistore_s3.py index b35dca51..5747c29d 100644 --- a/glance_store/tests/unit/test_multistore_s3.py +++ b/glance_store/tests/unit/test_multistore_s3.py @@ -91,6 +91,7 @@ class TestMultiS3Store(base.MultiStoreBaseTest, s3_store_secret_key='key', s3_store_host='https://s3-region1.com', s3_store_region_name='custom_region_name', + s3_store_cacert='path/to/cert/bundle.pem', s3_store_bucket='glance', s3_store_large_object_size=S3_CONF[ 's3_store_large_object_size' @@ -147,6 +148,24 @@ class TestMultiS3Store(base.MultiStoreBaseTest, region_name='custom_region_name', service_name='s3', use_ssl=False, + verify='path/to/cert/bundle.pem', + ) + + @mock.patch('glance_store.location.Location') + @mock.patch.object(boto3.session.Session, "client") + def test_client_custom_ca_cert_bundle(self, mock_client, mock_loc): + """Test a custom s3_store_cacert in config""" + mock_loc.accesskey = 'abcd' + mock_loc.secretkey = 'efgh' + mock_loc.bucket = 'bucket1' + self.store._create_s3_client(mock_loc) + mock_client.assert_called_with( + config=mock.ANY, + endpoint_url='https://s3-region1.com', + region_name='custom_region_name', + service_name='s3', + use_ssl=False, + verify='path/to/cert/bundle.pem', ) @mock.patch.object(boto3.session.Session, "client") diff --git a/glance_store/tests/unit/test_opts.py b/glance_store/tests/unit/test_opts.py index 073b7825..b877d7ed 100644 --- a/glance_store/tests/unit/test_opts.py +++ b/glance_store/tests/unit/test_opts.py @@ -112,6 +112,7 @@ class OptsTestCase(base.StoreBaseTest): 's3_store_large_object_size', 's3_store_large_object_chunk_size', 's3_store_thread_pools', + 's3_store_cacert', 'swift_store_expire_soon_interval', 'swift_store_admin_tenants', 'swift_store_auth_address', diff --git a/glance_store/tests/unit/test_s3_store.py b/glance_store/tests/unit/test_s3_store.py index 583535ff..3e2e5c91 100644 --- a/glance_store/tests/unit/test_s3_store.py +++ b/glance_store/tests/unit/test_s3_store.py @@ -106,6 +106,31 @@ class TestStore(base.StoreBaseTest, region_name='regionOne', service_name='s3', use_ssl=False, + verify=None, + ) + + @mock.patch('glance_store.location.Location') + @mock.patch.object(boto3.session.Session, "client") + def test_client_custom_ca_cert_bundle(self, mock_client, mock_loc): + """Test a custom s3_store_cacert in config""" + self.config(s3_store_host='http://example.com') + self.config(s3_store_cacert='path/to/cert/bundle.pem') + self.config(s3_store_bucket_url_format='path') + self.store.configure() + + mock_loc.accesskey = 'abcd' + mock_loc.secretkey = 'efgh' + mock_loc.bucket = 'bucket1' + + self.store._create_s3_client(mock_loc) + + mock_client.assert_called_with( + config=mock.ANY, + endpoint_url='http://example.com', + region_name=None, + service_name='s3', + use_ssl=False, + verify='path/to/cert/bundle.pem', ) @mock.patch.object(boto3.session.Session, "client")