Skip to content

Commit 7b0c995

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: Add mTLS support for telemetry endpoint in adk.py.
This change enables the telemetry exporter to use mTLS endpoints when configured, by dynamically determining the correct endpoint and configuring the requests session accordingly. It introduces helper functions to handle client certificate source management. PiperOrigin-RevId: 912220336
1 parent 3ce4b02 commit 7b0c995

2 files changed

Lines changed: 411 additions & 44 deletions

File tree

tests/unit/vertex_adk/test_agent_engine_templates_adk.py

Lines changed: 269 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,30 @@
1616
import importlib
1717
import json
1818
import os
19-
import cloudpickle
20-
import sys
2119
import re
22-
from unittest import mock
20+
import sys
2321
from typing import Optional
22+
from unittest import mock
23+
import uuid
2424

25+
import cloudpickle
2526
from google import auth
27+
from google.api_core import operation as ga_operation
2628
from google.auth import credentials as auth_credentials
29+
from google.auth.transport import mtls
2730
from google.cloud import storage
28-
import vertexai
2931
from google.cloud import aiplatform
30-
from google.cloud.aiplatform_v1 import types as aip_types
31-
from google.cloud.aiplatform_v1.services import reasoning_engine_service
32+
import vertexai
3233
from google.cloud.aiplatform import base
3334
from google.cloud.aiplatform import initializer
34-
from vertexai.agent_engines import _utils
35+
from google.cloud.aiplatform_v1 import types as aip_types
36+
from google.cloud.aiplatform_v1.services import reasoning_engine_service
3537
from vertexai import agent_engines
36-
from vertexai.agent_engines.templates import adk as adk_template
3738
from vertexai.agent_engines import _agent_engines
38-
from google.api_core import operation as ga_operation
39+
from vertexai.agent_engines import _utils
40+
from vertexai.agent_engines.templates import adk as adk_template
3941
from google.genai import types
4042
import pytest
41-
import uuid
4243

4344

4445
try:
@@ -1066,6 +1067,7 @@ def update_agent_engine_mock():
10661067

10671068
@pytest.mark.usefixtures("google_auth_mock")
10681069
class TestAgentEngines:
1070+
10691071
def setup_method(self):
10701072
importlib.reload(initializer)
10711073
importlib.reload(aiplatform)
@@ -1167,3 +1169,260 @@ def test_update_default_telemetry_enablement(
11671169
assert _utils.to_dict(deployment_spec)["env"] == [
11681170
{"name": key, "value": value} for key, value in expected_env_vars.items()
11691171
]
1172+
1173+
1174+
class TestAdkAppMtls:
1175+
"""Test cases for mTLS functionality in AdkApp."""
1176+
1177+
def test_use_client_cert_effective_with_should_use_client_cert(self):
1178+
"""Verifies that it respects the google-auth mTLS enablement check."""
1179+
with mock.patch.object(
1180+
mtls,
1181+
"should_use_client_cert",
1182+
return_value=True,
1183+
create=True,
1184+
):
1185+
assert adk_template._use_client_cert_effective() is True
1186+
1187+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"})
1188+
def test_use_client_cert_effective_with_env_var_true(self):
1189+
"""Verifies that it falls back to the environment variable if google-auth check fails."""
1190+
with mock.patch.object(
1191+
mtls,
1192+
"should_use_client_cert",
1193+
side_effect=AttributeError,
1194+
create=True,
1195+
):
1196+
assert adk_template._use_client_cert_effective() is True
1197+
1198+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"})
1199+
def test_use_client_cert_effective_with_env_var_false(self):
1200+
"""Verifies that it respects the environment variable being set to false."""
1201+
with mock.patch.object(
1202+
mtls,
1203+
"should_use_client_cert",
1204+
side_effect=AttributeError,
1205+
create=True,
1206+
):
1207+
assert adk_template._use_client_cert_effective() is False
1208+
1209+
def test_get_api_endpoint_default(self):
1210+
"""Verifies the default telemetry endpoint is returned when no mTLS is configured."""
1211+
assert (
1212+
adk_template._get_api_endpoint() == adk_template._DEFAULT_TELEMETRY_ENDPOINT
1213+
)
1214+
1215+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"})
1216+
def test_get_api_endpoint_always_with_cert(self):
1217+
"""Verifies the mTLS endpoint is used when forced and a certificate is available."""
1218+
assert (
1219+
adk_template._get_api_endpoint(client_cert_source=b"cert")
1220+
== adk_template._DEFAULT_MTLS_TELEMETRY_ENDPOINT
1221+
)
1222+
1223+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
1224+
def test_get_api_endpoint_auto_no_cert(self):
1225+
"""Verifies it falls back to regular endpoint even if forced if no certificate is provided."""
1226+
assert (
1227+
adk_template._get_api_endpoint() == adk_template._DEFAULT_TELEMETRY_ENDPOINT
1228+
)
1229+
1230+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"})
1231+
def test_get_api_endpoint_never(self):
1232+
"""Verifies the regular endpoint is used when mTLS is explicitly disabled."""
1233+
assert (
1234+
adk_template._get_api_endpoint(client_cert_source=b"cert")
1235+
== adk_template._DEFAULT_TELEMETRY_ENDPOINT
1236+
)
1237+
1238+
@mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT))
1239+
@mock.patch.object(adk_template.requests_auth, "AuthorizedSession")
1240+
@mock.patch(
1241+
"opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter"
1242+
)
1243+
def test_default_instrumentor_builder_with_mtls(
1244+
self,
1245+
mock_exporter,
1246+
mock_session_cls,
1247+
mock_auth_default,
1248+
):
1249+
"""Integration test for the instrumentor builder with mTLS enabled."""
1250+
# Mocking to enable mTLS
1251+
with mock.patch.object(
1252+
adk_template, "_use_client_cert_effective", return_value=True
1253+
):
1254+
with mock.patch.object(
1255+
mtls, "has_default_client_cert_source", return_value=True
1256+
):
1257+
with mock.patch.object(
1258+
mtls,
1259+
"default_client_cert_source",
1260+
return_value=lambda: b"cert",
1261+
):
1262+
adk_template._default_instrumentor_builder(
1263+
_TEST_PROJECT_ID, enable_tracing=True
1264+
)
1265+
1266+
# Verify the session was configured for mTLS
1267+
mock_session_cls.return_value.configure_mtls_channel.assert_called_once()
1268+
# Verify the exporter was initialized with the mTLS endpoint
1269+
mock_exporter.assert_called_once()
1270+
assert (
1271+
mock_exporter.call_args.kwargs["endpoint"]
1272+
== adk_template._DEFAULT_MTLS_TELEMETRY_ENDPOINT
1273+
)
1274+
1275+
@mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT))
1276+
@mock.patch.object(adk_template.requests_auth, "AuthorizedSession")
1277+
def test_warn_if_telemetry_api_disabled_with_mtls(
1278+
self,
1279+
mock_session_cls,
1280+
mock_auth_default,
1281+
):
1282+
"""Integration test for the telemetry API check with mTLS enabled."""
1283+
mock_session = mock_session_cls.return_value
1284+
mock_session.post.return_value = mock.Mock(text="")
1285+
1286+
# Mocking to enable mTLS
1287+
with mock.patch.object(
1288+
adk_template, "_use_client_cert_effective", return_value=True
1289+
):
1290+
with mock.patch.object(
1291+
mtls, "has_default_client_cert_source", return_value=True
1292+
):
1293+
with mock.patch.object(
1294+
mtls,
1295+
"default_client_cert_source",
1296+
return_value=lambda: b"cert",
1297+
):
1298+
adk_template._warn_if_telemetry_api_disabled()
1299+
1300+
# Verify mTLS channel was configured for the check request
1301+
mock_session.configure_mtls_channel.assert_called_once()
1302+
# Verify the check was performed against the mTLS endpoint
1303+
mock_session.post.assert_called_once_with(
1304+
adk_template._DEFAULT_MTLS_TELEMETRY_ENDPOINT, data=None
1305+
)
1306+
1307+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "invalid_value"})
1308+
def test_get_api_endpoint_invalid_env(self):
1309+
"""Verifies it defaults to AUTO and warns on invalid env var."""
1310+
with mock.patch.object(adk_template, "_warn") as mock_warn:
1311+
assert (
1312+
adk_template._get_api_endpoint()
1313+
== adk_template._DEFAULT_TELEMETRY_ENDPOINT
1314+
)
1315+
mock_warn.assert_called_once()
1316+
1317+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "not_a_bool"})
1318+
def test_use_client_cert_effective_invalid_env(self):
1319+
"""Verifies it warns on invalid boolean env var."""
1320+
with mock.patch.object(
1321+
mtls,
1322+
"should_use_client_cert",
1323+
side_effect=AttributeError,
1324+
create=True,
1325+
):
1326+
with mock.patch.object(adk_template, "_warn") as mock_warn:
1327+
assert adk_template._use_client_cert_effective() is False
1328+
mock_warn.assert_called_once()
1329+
1330+
def test_use_client_cert_effective_with_should_use_client_cert_false(self):
1331+
"""Verifies that it respects google-auth returning False for mTLS."""
1332+
with mock.patch.object(
1333+
mtls,
1334+
"should_use_client_cert",
1335+
return_value=False,
1336+
create=True,
1337+
):
1338+
assert adk_template._use_client_cert_effective() is False
1339+
1340+
def test_get_api_endpoint_auto_with_cert(self):
1341+
"""Verifies the mTLS endpoint is used in AUTO mode when a cert is available."""
1342+
# AUTO is the default, so we just pass a cert
1343+
assert (
1344+
adk_template._get_api_endpoint(client_cert_source=b"cert")
1345+
== adk_template._DEFAULT_MTLS_TELEMETRY_ENDPOINT
1346+
)
1347+
1348+
@mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT))
1349+
@mock.patch.object(adk_template.requests_auth, "AuthorizedSession")
1350+
@mock.patch(
1351+
"opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter"
1352+
)
1353+
def test_default_instrumentor_builder_no_mtls(
1354+
self,
1355+
mock_exporter,
1356+
mock_session_cls,
1357+
mock_auth_default,
1358+
):
1359+
"""Integration test for the instrumentor builder with mTLS disabled."""
1360+
with mock.patch.object(
1361+
adk_template, "_use_client_cert_effective", return_value=False
1362+
):
1363+
adk_template._default_instrumentor_builder(
1364+
_TEST_PROJECT_ID, enable_tracing=True
1365+
)
1366+
1367+
# Verify mTLS channel was NOT configured
1368+
mock_session_cls.return_value.configure_mtls_channel.assert_not_called()
1369+
# Verify the exporter was initialized with the regular endpoint
1370+
mock_exporter.assert_called_once()
1371+
assert (
1372+
mock_exporter.call_args.kwargs["endpoint"]
1373+
== adk_template._DEFAULT_TELEMETRY_ENDPOINT
1374+
)
1375+
1376+
@mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT))
1377+
@mock.patch.object(adk_template.requests_auth, "AuthorizedSession")
1378+
def test_warn_if_telemetry_api_disabled_no_mtls(
1379+
self,
1380+
mock_session_cls,
1381+
mock_auth_default,
1382+
):
1383+
"""Integration test for the telemetry API check with mTLS disabled."""
1384+
mock_session = mock_session_cls.return_value
1385+
mock_session.post.return_value = mock.Mock(text="")
1386+
1387+
with mock.patch.object(
1388+
adk_template, "_use_client_cert_effective", return_value=False
1389+
):
1390+
adk_template._warn_if_telemetry_api_disabled()
1391+
1392+
# Verify mTLS channel was NOT configured
1393+
mock_session.configure_mtls_channel.assert_not_called()
1394+
# Verify the check was performed against the regular endpoint
1395+
mock_session.post.assert_called_once_with(
1396+
adk_template._DEFAULT_TELEMETRY_ENDPOINT, data=None
1397+
)
1398+
1399+
@mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT))
1400+
@mock.patch.object(adk_template.requests_auth, "AuthorizedSession")
1401+
@mock.patch(
1402+
"opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter"
1403+
)
1404+
def test_default_instrumentor_builder_mtls_no_cert_source(
1405+
self,
1406+
mock_exporter,
1407+
mock_session_cls,
1408+
mock_auth_default,
1409+
):
1410+
"""Tests that it falls back to regular endpoint if mTLS is on but no cert is found."""
1411+
with mock.patch.object(
1412+
adk_template, "_use_client_cert_effective", return_value=True
1413+
):
1414+
with mock.patch.object(
1415+
mtls,
1416+
"has_default_client_cert_source",
1417+
return_value=False,
1418+
):
1419+
adk_template._default_instrumentor_builder(
1420+
_TEST_PROJECT_ID, enable_tracing=True
1421+
)
1422+
1423+
# Channel is configured, but endpoint remains default due to missing cert source
1424+
mock_session_cls.return_value.configure_mtls_channel.assert_called_once()
1425+
assert (
1426+
mock_exporter.call_args.kwargs["endpoint"]
1427+
== adk_template._DEFAULT_TELEMETRY_ENDPOINT
1428+
)

0 commit comments

Comments
 (0)