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

Commit be10a50

Browse files
chore: Add dependencies and async function related wrapper
Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
1 parent 07d7818 commit be10a50

2 files changed

Lines changed: 53 additions & 18 deletions

File tree

google/auth/aio/transport/mtls.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
# limitations under the License.
1414

1515
"""
16-
Helper functions for mTLS in async.
16+
Helper functions for mTLS in async for discovery of certs.
1717
"""
1818

19+
import asyncio
1920
import logging
2021
from os import getenv, path
2122

@@ -42,6 +43,20 @@ def _check_config_path(config_path):
4243
return config_path
4344

4445

46+
async def _run_in_executor(func, *args):
47+
"""Run a blocking function in an executor to avoid blocking the event loop.
48+
49+
This implements the non-blocking execution strategy for disk I/O operations.
50+
"""
51+
try:
52+
# For python versions 3.9 and newer versions
53+
return await asyncio.to_thread(func, *args)
54+
except AttributeError:
55+
# Fallback for older Python versions
56+
loop = asyncio.get_running_loop()
57+
return await loop.run_in_executor(None, func, *args)
58+
59+
4560
def has_default_client_cert_source():
4661
"""Check if default client SSL credentials exists on the device.
4762
@@ -56,7 +71,7 @@ def has_default_client_cert_source():
5671
return False
5772

5873

59-
def get_client_ssl_credentials(
74+
async def get_client_ssl_credentials(
6075
generate_encrypted_key=False,
6176
certificate_config_path=None,
6277
):
@@ -84,16 +99,18 @@ def get_client_ssl_credentials(
8499
"""
85100

86101
# Attempt to retrieve X.509 Workload cert and key.
87-
cert, key = google.auth.transport._mtls_helper._get_workload_cert_and_key(
88-
certificate_config_path
102+
cert, key = await _run_in_executor(
103+
google.auth.transport._mtls_helper._get_workload_cert_and_key,
104+
certificate_config_path,
89105
)
106+
90107
if cert and key:
91108
return True, cert, key, None
92109

93110
return False, None, None, None
94111

95112

96-
def get_client_cert_and_key(client_cert_callback=None):
113+
async def get_client_cert_and_key(client_cert_callback=None):
97114
"""Returns the client side certificate and private key. The function first
98115
tries to get certificate and key from client_cert_callback; if the callback
99116
is None or doesn't provide certificate and key, the function tries application
@@ -117,5 +134,7 @@ def get_client_cert_and_key(client_cert_callback=None):
117134
cert, key = client_cert_callback()
118135
return True, cert, key
119136

120-
has_cert, cert, key, _ = get_client_ssl_credentials(generate_encrypted_key=False)
137+
has_cert, cert, key, _ = await get_client_ssl_credentials(
138+
generate_encrypted_key=False
139+
)
121140
return has_cert, cert, key

tests/transport/test_aio_mtls_helper.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1-
# Copyright 2024 Google LLC
2-
# Licensed under the Apache License, Version 2.0...
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
314

415
from unittest import mock
516

@@ -41,42 +52,46 @@ def test_has_default_client_cert_source_env_var(self, mock_getenv, mock_check):
4152

4253
assert mtls.has_default_client_cert_source() is True
4354

55+
@pytest.mark.asyncio
4456
@mock.patch("google.auth.transport._mtls_helper._get_workload_cert_and_key")
45-
def test_get_client_ssl_credentials_success(self, mock_workload):
57+
async def test_get_client_ssl_credentials_success(self, mock_workload):
4658
mock_workload.return_value = (CERT_DATA, KEY_DATA)
4759

48-
success, cert, key, passphrase = mtls.get_client_ssl_credentials()
60+
success, cert, key, passphrase = await mtls.get_client_ssl_credentials()
4961

5062
assert success is True
5163
assert cert == CERT_DATA
5264
assert key == KEY_DATA
5365
assert passphrase is None
5466

55-
def test_get_client_cert_and_key_callback(self):
67+
@pytest.mark.asyncio
68+
async def test_get_client_cert_and_key_callback(self):
5669
# The callback should be tried first and return immediately
5770
callback = mock.Mock(return_value=(CERT_DATA, KEY_DATA))
5871

59-
success, cert, key = mtls.get_client_cert_and_key(callback)
72+
success, cert, key = await mtls.get_client_cert_and_key(callback)
6073

6174
assert success is True
6275
assert cert == CERT_DATA
6376
assert key == KEY_DATA
6477
callback.assert_called_once()
6578

79+
@pytest.mark.asyncio
6680
@mock.patch("google.auth.aio.transport.mtls.get_client_ssl_credentials")
67-
def test_get_client_cert_and_key_default(self, mock_get_ssl):
81+
async def test_get_client_cert_and_key_default(self, mock_get_ssl):
6882
# If no callback, it should call get_client_ssl_credentials
6983
mock_get_ssl.return_value = (True, CERT_DATA, KEY_DATA, None)
7084

71-
success, cert, key = mtls.get_client_cert_and_key(None)
85+
success, cert, key = await mtls.get_client_cert_and_key(None)
7286

7387
assert success is True
7488
assert cert == CERT_DATA
7589
assert key == KEY_DATA
7690
mock_get_ssl.assert_called_with(generate_encrypted_key=False)
7791

92+
@pytest.mark.asyncio
7893
@mock.patch("google.auth.transport._mtls_helper._get_workload_cert_and_key")
79-
def test_get_client_ssl_credentials_error(self, mock_workload):
94+
async def test_get_client_ssl_credentials_error(self, mock_workload):
8095
"""Tests that ClientCertError is propagated correctly."""
8196
# Setup the mock to raise the specific google-auth exception
8297
mock_workload.side_effect = exceptions.ClientCertError(
@@ -85,10 +100,11 @@ def test_get_client_ssl_credentials_error(self, mock_workload):
85100

86101
# Verify that calling our function raises the same exception
87102
with pytest.raises(exceptions.ClientCertError, match="Failed to read metadata"):
88-
mtls.get_client_ssl_credentials()
103+
await mtls.get_client_ssl_credentials()
89104

105+
@pytest.mark.asyncio
90106
@mock.patch("google.auth.aio.transport.mtls.get_client_ssl_credentials")
91-
def test_get_client_cert_and_key_exception_propagation(self, mock_get_ssl):
107+
async def test_get_client_cert_and_key_exception_propagation(self, mock_get_ssl):
92108
"""Tests that get_client_cert_and_key propagates errors from its internal calls."""
93109
mock_get_ssl.side_effect = exceptions.ClientCertError(
94110
"Underlying credentials failed"
@@ -98,4 +114,4 @@ def test_get_client_cert_and_key_exception_propagation(self, mock_get_ssl):
98114
exceptions.ClientCertError, match="Underlying credentials failed"
99115
):
100116
# Pass None for callback so it attempts to call get_client_ssl_credentials
101-
mtls.get_client_cert_and_key(client_cert_callback=None)
117+
await mtls.get_client_cert_and_key(client_cert_callback=None)

0 commit comments

Comments
 (0)