Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.

Commit 79b5dd2

Browse files
feat: add mtls support
1 parent 41849b1 commit 79b5dd2

3 files changed

Lines changed: 54 additions & 4 deletions

File tree

google/cloud/storage/_http.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,19 @@ class Connection(_http.JSONConnection):
2929
3030
:type client_info: :class:`~google.api_core.client_info.ClientInfo`
3131
:param client_info: (Optional) instance used to generate user agent.
32+
33+
:type api_endpoint: str or None
34+
:param api_endpoint: (Optional) api endpoint to use.
3235
"""
3336

3437
DEFAULT_API_ENDPOINT = "https://storage.googleapis.com"
38+
DEFAULT_API_MTLS_ENDPOINT = "https://storage.mtls.googleapis.com"
3539

36-
def __init__(self, client, client_info=None, api_endpoint=DEFAULT_API_ENDPOINT):
40+
def __init__(self, client, client_info=None, api_endpoint=None):
3741
super(Connection, self).__init__(client, client_info)
38-
self.API_BASE_URL = api_endpoint
42+
self.API_BASE_URL = api_endpoint or self.DEFAULT_API_ENDPOINT
43+
self.API_BASE_MTLS_URL = self.DEFAULT_API_MTLS_ENDPOINT
44+
self.ALLOW_AUTO_SWITCH_TO_MTLS_URL = api_endpoint is None
3945
self._client_info.client_library_version = __version__
4046

4147
# TODO: When metrics all use gccl, this should be removed #9552

google/cloud/storage/client.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from google.cloud.client import ClientWithProject
3131
from google.cloud.exceptions import NotFound
3232
from google.cloud.storage._helpers import _get_storage_host
33+
from google.cloud.storage._helpers import _DEFAULT_STORAGE_HOST
3334
from google.cloud.storage._helpers import _bucket_bound_hostname_url
3435
from google.cloud.storage._http import Connection
3536
from google.cloud.storage._signing import (
@@ -121,7 +122,13 @@ def __init__(
121122

122123
kw_args = {"client_info": client_info}
123124

124-
kw_args["api_endpoint"] = _get_storage_host()
125+
# `api_endpoint` should be only set by the user via `client_options`,
126+
# or if the _get_storage_host() returns a non-default value.
127+
# `api_endpoint` plays an important role for mTLS, if it is not set,
128+
# then mTLS logic will be applied to decide which endpoint will be used.
129+
storage_host = _get_storage_host()
130+
if storage_host != _DEFAULT_STORAGE_HOST:
131+
kw_args["api_endpoint"] = storage_host
125132

126133
if client_options:
127134
if type(client_options) == dict:

tests/system/test_system.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class Config(object):
8181

8282
CLIENT = None
8383
TEST_BUCKET = None
84+
TESTING_MTLS = False
8485

8586

8687
def setUpModule():
@@ -91,6 +92,10 @@ def setUpModule():
9192
Config.TEST_BUCKET = Config.CLIENT.bucket(bucket_name)
9293
Config.TEST_BUCKET.versioning_enabled = True
9394
retry_429_503(Config.TEST_BUCKET.create)()
95+
# mTLS testing uses the system test as well. For mTLS testing,
96+
# GOOGLE_API_USE_CLIENT_CERTIFICATE env var will be set to "true"
97+
# explicitly.
98+
Config.TESTING_MTLS = (os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE") == "true")
9499

95100

96101
def tearDownModule():
@@ -101,6 +106,14 @@ def tearDownModule():
101106

102107

103108
class TestClient(unittest.TestCase):
109+
@classmethod
110+
def setUpClass(cls):
111+
if (
112+
type(Config.CLIENT._credentials)
113+
is not google.oauth2.service_account.Credentials
114+
):
115+
cls.skipTest(self=cls, reason="These tests require a service account credential")
116+
104117
def setUp(self):
105118
self.case_hmac_keys_to_delete = []
106119

@@ -563,6 +576,15 @@ def tearDown(self):
563576
class TestStorageWriteFiles(TestStorageFiles):
564577
ENCRYPTION_KEY = "b23ff11bba187db8c37077e6af3b25b8"
565578

579+
@classmethod
580+
def setUpClass(cls):
581+
if (
582+
type(Config.CLIENT._credentials)
583+
is not google.oauth2.service_account.Credentials
584+
):
585+
cls.skipTest(self=cls, reason="These tests require a service account credential")
586+
587+
566588
def test_large_file_write_from_stream(self):
567589
blob = self.bucket.blob("LargeFile")
568590

@@ -1264,7 +1286,7 @@ def setUpClass(cls):
12641286
type(Config.CLIENT._credentials)
12651287
is not google.oauth2.service_account.Credentials
12661288
):
1267-
cls.skipTest("Signing tests requires a service account credential")
1289+
cls.skipTest(self=cls, reason="Signing tests requires a service account credential")
12681290

12691291
bucket_name = "gcp-signing" + unique_resource_id()
12701292
cls.bucket = retry_429_503(Config.CLIENT.create_bucket)(bucket_name)
@@ -1825,6 +1847,11 @@ class TestStorageNotificationCRUD(unittest.TestCase):
18251847
CUSTOM_ATTRIBUTES = {"attr1": "value1", "attr2": "value2"}
18261848
BLOB_NAME_PREFIX = "blob-name-prefix/"
18271849

1850+
@classmethod
1851+
def setUpClass(cls):
1852+
if Config.TESTING_MTLS:
1853+
cls.skipTest(self=cls, reason="Skip pubsub tests for mTLS testing")
1854+
18281855
@property
18291856
def topic_path(self):
18301857
return "projects/{}/topics/{}".format(Config.CLIENT.project, self.TOPIC_NAME)
@@ -1987,6 +2014,8 @@ def _kms_key_name(self, key_name=None):
19872014

19882015
@classmethod
19892016
def setUpClass(cls):
2017+
if Config.TESTING_MTLS:
2018+
cls.skipTest(self=cls, reason="Skip kms tests for mTLS testing")
19902019
super(TestKMSIntegration, cls).setUpClass()
19912020
_empty_bucket(cls.bucket)
19922021

@@ -2441,6 +2470,14 @@ def test_ubla_set_unset_preserves_acls(self):
24412470

24422471

24432472
class TestV4POSTPolicies(unittest.TestCase):
2473+
@classmethod
2474+
def setUpClass(cls):
2475+
if (
2476+
type(Config.CLIENT._credentials)
2477+
is not google.oauth2.service_account.Credentials
2478+
):
2479+
cls.skipTest(self=cls, reason="These tests require a service account credential")
2480+
24442481
def setUp(self):
24452482
self.case_buckets_to_delete = []
24462483

0 commit comments

Comments
 (0)