Skip to content

Commit 7270e6d

Browse files
yinghsienwucopybara-github
authored andcommitted
feat: Support mTLS in custom client using google auth mtls.get_default_ssl_context
PiperOrigin-RevId: 951129864
1 parent 72caa9f commit 7270e6d

3 files changed

Lines changed: 74 additions & 24 deletions

File tree

google/genai/_api_client.py

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,8 @@ def __init__(
806806
append_library_version_headers(self._http_options.headers)
807807

808808
client_args, async_client_args = self._ensure_httpx_ssl_ctx(
809-
self._http_options
809+
self._http_options,
810+
vertexai=bool(self.vertexai),
810811
)
811812
self._async_httpx_client_args = async_client_args
812813
self._authorized_session: Optional[AuthorizedSession] = None
@@ -837,7 +838,10 @@ def __init__(
837838
import aiohttp # pylint: disable=g-import-not-at-top
838839
# Do it once at the genai.Client level. Share among all requests.
839840
self._async_client_session_request_args = (
840-
self._ensure_aiohttp_ssl_ctx(self._http_options)
841+
self._ensure_aiohttp_ssl_ctx(
842+
self._http_options,
843+
vertexai=bool(self.vertexai),
844+
)
841845
)
842846
if self._use_google_auth_async():
843847
self._async_client_session_request_args['ssl'] = True # type: ignore[no-untyped-call]
@@ -851,7 +855,10 @@ def __init__(
851855
pass
852856

853857
retry_kwargs = retry_args(self._http_options.retry_options)
854-
self._websocket_ssl_ctx = self._ensure_websocket_ssl_ctx(self._http_options)
858+
self._websocket_ssl_ctx = self._ensure_websocket_ssl_ctx(
859+
self._http_options,
860+
vertexai=bool(self.vertexai),
861+
)
855862
self._retry = tenacity.Retrying(**retry_kwargs)
856863
self._async_retry = tenacity.AsyncRetrying(**retry_kwargs)
857864

@@ -1014,13 +1021,15 @@ def __del__(self, _warnings: Any = warnings) -> None:
10141021
@staticmethod
10151022
def _ensure_httpx_ssl_ctx(
10161023
options: HttpOptions,
1024+
vertexai: bool = False,
10171025
) -> Tuple[_common.StringDict, _common.StringDict]:
10181026
"""Ensures the SSL context is present in the HTTPX client args.
10191027
10201028
Creates a default SSL context if one is not provided.
10211029
10221030
Args:
10231031
options: The http options to check for SSL context.
1032+
vertexai: Whether Vertex AI is enabled.
10241033
10251034
Returns:
10261035
A tuple of sync/async httpx client args.
@@ -1037,15 +1046,24 @@ def _ensure_httpx_ssl_ctx(
10371046
else None
10381047
)
10391048

1040-
if not ctx:
1049+
if ctx is None:
10411050
# Initialize the SSL context for the httpx client.
10421051
# Unlike requests, the httpx package does not automatically pull in the
10431052
# environment variables SSL_CERT_FILE or SSL_CERT_DIR. They need to be
10441053
# enabled explicitly.
1045-
ctx = ssl.create_default_context(
1046-
cafile=os.environ.get('SSL_CERT_FILE', certifi.where()),
1047-
capath=os.environ.get('SSL_CERT_DIR'),
1048-
)
1054+
if vertexai:
1055+
get_ctx_fn = getattr(mtls, 'get_default_ssl_context', None)
1056+
if get_ctx_fn is not None:
1057+
try:
1058+
ctx = get_ctx_fn()
1059+
except Exception as e: # pylint: disable=broad-except
1060+
logger.warning('Failed to get default SSL context from google-auth: %s', e)
1061+
1062+
if ctx is None:
1063+
ctx = ssl.create_default_context(
1064+
cafile=os.environ.get('SSL_CERT_FILE', certifi.where()),
1065+
capath=os.environ.get('SSL_CERT_DIR'),
1066+
)
10491067

10501068
def _maybe_set(
10511069
args: Optional[_common.StringDict],
@@ -1080,13 +1098,17 @@ def _maybe_set(
10801098
)
10811099

10821100
@staticmethod
1083-
def _ensure_aiohttp_ssl_ctx(options: HttpOptions) -> _common.StringDict:
1101+
def _ensure_aiohttp_ssl_ctx(
1102+
options: HttpOptions,
1103+
vertexai: bool = False,
1104+
) -> _common.StringDict:
10841105
"""Ensures the SSL context is present in the async client args.
10851106
10861107
Creates a default SSL context if one is not provided.
10871108
10881109
Args:
10891110
options: The http options to check for SSL context.
1111+
vertexai: Whether Vertex AI is enabled.
10901112
10911113
Returns:
10921114
An async aiohttp ClientSession._request args.
@@ -1095,11 +1117,20 @@ def _ensure_aiohttp_ssl_ctx(options: HttpOptions) -> _common.StringDict:
10951117
async_args = options.async_client_args
10961118
ctx = async_args.get(verify) if async_args else None
10971119

1098-
if not ctx:
1099-
ctx = ssl.create_default_context(
1100-
cafile=os.environ.get('SSL_CERT_FILE', certifi.where()),
1101-
capath=os.environ.get('SSL_CERT_DIR'),
1102-
)
1120+
if ctx is None:
1121+
if vertexai:
1122+
get_ctx_fn = getattr(mtls, 'get_default_ssl_context', None)
1123+
if get_ctx_fn is not None:
1124+
try:
1125+
ctx = get_ctx_fn()
1126+
except Exception as e: # pylint: disable=broad-except
1127+
logger.warning('Failed to get default SSL context from google-auth: %s', e)
1128+
1129+
if ctx is None:
1130+
ctx = ssl.create_default_context(
1131+
cafile=os.environ.get('SSL_CERT_FILE', certifi.where()),
1132+
capath=os.environ.get('SSL_CERT_DIR'),
1133+
)
11031134

11041135
def _maybe_set(
11051136
args: Optional[_common.StringDict],
@@ -1132,13 +1163,17 @@ def _maybe_set(
11321163
return _maybe_set(async_args, ctx)
11331164

11341165
@staticmethod
1135-
def _ensure_websocket_ssl_ctx(options: HttpOptions) -> _common.StringDict:
1166+
def _ensure_websocket_ssl_ctx(
1167+
options: HttpOptions,
1168+
vertexai: bool = False,
1169+
) -> _common.StringDict:
11361170
"""Ensures the SSL context is present in the async client args.
11371171
11381172
Creates a default SSL context if one is not provided.
11391173
11401174
Args:
11411175
options: The http options to check for SSL context.
1176+
vertexai: Whether Vertex AI is enabled.
11421177
11431178
Returns:
11441179
An async aiohttp ClientSession._request args.
@@ -1148,16 +1183,25 @@ def _ensure_websocket_ssl_ctx(options: HttpOptions) -> _common.StringDict:
11481183
async_args = options.async_client_args
11491184
ctx = async_args.get(verify) if async_args else None
11501185

1151-
if not ctx:
1186+
if ctx is None:
11521187
# Initialize the SSL context for the httpx client.
11531188
# Unlike requests, the aiohttp package does not automatically pull in the
11541189
# environment variables SSL_CERT_FILE or SSL_CERT_DIR. They need to be
11551190
# enabled explicitly. Instead of 'verify' at client level in httpx,
11561191
# aiohttp uses 'ssl' at request level.
1157-
ctx = ssl.create_default_context(
1158-
cafile=os.environ.get('SSL_CERT_FILE', certifi.where()),
1159-
capath=os.environ.get('SSL_CERT_DIR'),
1160-
)
1192+
if vertexai:
1193+
get_ctx_fn = getattr(mtls, 'get_default_ssl_context', None)
1194+
if get_ctx_fn is not None:
1195+
try:
1196+
ctx = get_ctx_fn()
1197+
except Exception as e: # pylint: disable=broad-except
1198+
logger.warning('Failed to get default SSL context from google-auth: %s', e)
1199+
1200+
if ctx is None:
1201+
ctx = ssl.create_default_context(
1202+
cafile=os.environ.get('SSL_CERT_FILE', certifi.where()),
1203+
capath=os.environ.get('SSL_CERT_DIR'),
1204+
)
11611205

11621206
def _maybe_set(
11631207
args: Optional[_common.StringDict],
@@ -1498,7 +1542,10 @@ async def _async_request_once(
14981542
logger.info('Retrying due to aiohttp error: %s' % e)
14991543
# Retrieve the SSL context from the session.
15001544
self._async_client_session_request_args = (
1501-
self._ensure_aiohttp_ssl_ctx(self._http_options)
1545+
self._ensure_aiohttp_ssl_ctx(
1546+
self._http_options,
1547+
vertexai=bool(self.vertexai),
1548+
)
15021549
)
15031550
# Instantiate a new session with the updated SSL context.
15041551
session = await self._get_aiohttp_session() # type: ignore[assignment]
@@ -1576,7 +1623,10 @@ async def _async_request_once(
15761623
logger.info('Retrying due to aiohttp error: %s' % e)
15771624
# Retrieve the SSL context from the session.
15781625
self._async_client_session_request_args = (
1579-
self._ensure_aiohttp_ssl_ctx(self._http_options)
1626+
self._ensure_aiohttp_ssl_ctx(
1627+
self._http_options,
1628+
vertexai=bool(self.vertexai),
1629+
)
15801630
)
15811631
# Instantiate a new session with the updated SSL context.
15821632
session = await self._get_aiohttp_session() # type: ignore[assignment]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ classifiers = [
2626
]
2727
dependencies = [
2828
"anyio>=4.8.0, <5.0.0",
29-
"google-auth[requests]>=2.48.1, <2.56.0",
29+
"google-auth[requests]>=2.56.0, <3.0.0",
3030
"httpx>=0.28.1, <1.0.0",
3131
"pydantic>=2.12.5, <3.0.0",
3232
"requests>=2.28.1, <3.0.0",

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ charset-normalizer==3.4.0
77
coverage==7.6.9
88
distro==1.9.0
99
httpx==0.28.1
10-
google-auth==2.47.0
10+
google-auth==2.56.0
1111
idna==3.10
1212
iniconfig==2.0.0
1313
packaging==24.2

0 commit comments

Comments
 (0)