Skip to content

Commit 3b3471a

Browse files
IronPancopybara-github
authored andcommitted
fix: verify dedicated endpoint predict_async TLS against the certifi CA bundle
Endpoint.predict_async on dedicated endpoints used a bare aiohttp.ClientSession, which verifies TLS against the system CA store and fails with CERTIFICATE_VERIFY_FAILED in environments where that store is missing or incomplete (e.g. containers). This builds an SSLContext from certifi (matching the synchronous predict() path, which uses requests/certifi) so the async path trusts the same CAs. PiperOrigin-RevId: 927649578
1 parent 05234d1 commit 3b3471a

3 files changed

Lines changed: 41 additions & 1 deletion

File tree

google/cloud/aiplatform/models.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2811,10 +2811,20 @@ async def predict_async(
28112811
aiohttp.ClientTimeout(total=timeout) if timeout is not None else None
28122812
)
28132813

2814+
# Verify TLS against certifi, like the sync path (requests). aiohttp
2815+
# otherwise uses the system CA store, which is often missing in
2816+
# containers and fails with CERTIFICATE_VERIFY_FAILED.
2817+
import ssl
2818+
2819+
import certifi
2820+
2821+
ssl_context = ssl.create_default_context(cafile=certifi.where())
2822+
connector = aiohttp.TCPConnector(ssl=ssl_context)
2823+
28142824
# Use a per-call session so the underlying connector is always closed,
28152825
# avoiding leaked ClientSessions (Python has no async destructor to do
28162826
# this for a cached session).
2817-
async with aiohttp.ClientSession() as session:
2827+
async with aiohttp.ClientSession(connector=connector) as session:
28182828
async with session.post(
28192829
url=url,
28202830
data=data,

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@
324324
" <3.0.0,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*"
325325
),
326326
"google-auth >= 2.47.0, <3.0.0",
327+
"certifi >= 2023.7.22",
327328
"proto-plus >= 1.22.3, <2.0.0",
328329
"protobuf>=3.20.2,<7.0.0,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5",
329330
"packaging >= 14.3",

tests/unit/aiplatform/test_endpoints.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from datetime import datetime, timedelta
2020
from importlib import reload
2121
import json
22+
import ssl
2223
import aiohttp
2324
from aiohttp import web as aiohttp_web
2425
import requests
@@ -3809,6 +3810,34 @@ async def test_predict_async_dedicated_endpoint_closes_session(self):
38093810

38103811
close_mock.assert_awaited_once()
38113812

3813+
@pytest.mark.asyncio
3814+
@pytest.mark.usefixtures(
3815+
"get_dedicated_endpoint_mock", "predict_endpoint_aiohttp_mock"
3816+
)
3817+
async def test_predict_async_dedicated_endpoint_verifies_certifi(self):
3818+
"""The aiohttp session pins TLS verification to the certifi CA bundle.
3819+
3820+
Mirrors the synchronous predict() path (requests -> certifi), so the
3821+
async path does not fall back to the system CA store, which is commonly
3822+
missing in containers and raises CERTIFICATE_VERIFY_FAILED.
3823+
"""
3824+
import certifi
3825+
3826+
test_endpoint = models.Endpoint(_TEST_ENDPOINT_NAME)
3827+
3828+
sentinel_context = ssl.create_default_context(cafile=certifi.where())
3829+
with mock.patch.object(
3830+
ssl, "create_default_context", return_value=sentinel_context
3831+
) as create_context_mock, mock.patch.object(
3832+
aiohttp, "TCPConnector", wraps=aiohttp.TCPConnector
3833+
) as connector_mock:
3834+
await test_endpoint.predict_async(
3835+
instances=_TEST_INSTANCES, parameters={"param": 3.0}
3836+
)
3837+
3838+
create_context_mock.assert_called_once_with(cafile=certifi.where())
3839+
connector_mock.assert_called_once_with(ssl=sentinel_context)
3840+
38123841
@pytest.mark.asyncio
38133842
@pytest.mark.usefixtures("get_dedicated_endpoint_mock")
38143843
async def test_predict_async_dedicated_endpoint_integration(self):

0 commit comments

Comments
 (0)