Skip to content

Commit c3c1836

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
fix: Add mTLS and telemetry endpoint configurations to preview AdkApp
Telemetry exports from preview Reasoning Engine ADK applications were failing with 401 Unauthorized because the preview template lacked mTLS support for telemetry requests. This fix ports the mTLS endpoint configuration logic (`_use_client_cert_effective()`, `_get_api_endpoint()`, and `configure_mtls_channel()`) from the production template, resolving telemetry issues for preview applications. PiperOrigin-RevId: 944004265
1 parent 62a8324 commit c3c1836

2 files changed

Lines changed: 164 additions & 10 deletions

File tree

tests/unit/vertex_adk/test_reasoning_engine_templates_adk.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from google.cloud.aiplatform import initializer
2727
from vertexai.agent_engines import _utils
2828
from vertexai.preview import reasoning_engines
29+
from vertexai.preview.reasoning_engines.templates import adk as adk_template
2930
from google.genai import types
3031
import pytest
3132
import uuid
@@ -894,6 +895,7 @@ def test_tracing_setup(
894895
):
895896
app.set_up()
896897

898+
import opentelemetry.sdk.version
897899
expected_attributes = {
898900
"cloud.account.id": _TEST_PROJECT_ID,
899901
"cloud.platform": "gcp.agent_engine",
@@ -906,8 +908,7 @@ def test_tracing_setup(
906908
"some-attribute": "some-value",
907909
"telemetry.sdk.language": "python",
908910
"telemetry.sdk.name": "opentelemetry",
909-
"telemetry.sdk.version": "1.39.0",
910-
"some-attribute": "some-value",
911+
"telemetry.sdk.version": opentelemetry.sdk.version.__version__,
911912
}
912913

913914
otlp_span_exporter_mock.assert_called_once_with(
@@ -1104,3 +1105,60 @@ async def test_bidi_stream_query_invalid_first_request(self):
11041105
):
11051106
async for _ in app.bidi_stream_query(request_queue):
11061107
pass
1108+
1109+
1110+
class TestAdkAppMtls:
1111+
"""Test cases for mTLS functionality in preview AdkApp."""
1112+
1113+
def test_use_client_cert_effective_with_should_use_client_cert(self):
1114+
"""Verifies that it respects the google-auth mTLS enablement check."""
1115+
with mock.patch.object(
1116+
adk_template.mtls,
1117+
"should_use_client_cert",
1118+
return_value=True,
1119+
create=True,
1120+
):
1121+
assert adk_template._use_client_cert_effective() is True
1122+
1123+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"})
1124+
def test_use_client_cert_effective_with_env_var_true(self):
1125+
"""Verifies that it falls back to the environment variable if google-auth check fails."""
1126+
with mock.patch.object(
1127+
adk_template.mtls,
1128+
"should_use_client_cert",
1129+
side_effect=AttributeError,
1130+
create=True,
1131+
):
1132+
assert adk_template._use_client_cert_effective() is True
1133+
1134+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"})
1135+
def test_use_client_cert_effective_with_env_var_false(self):
1136+
"""Verifies that it respects the environment variable being set to false."""
1137+
with mock.patch.object(
1138+
adk_template.mtls,
1139+
"should_use_client_cert",
1140+
side_effect=AttributeError,
1141+
create=True,
1142+
):
1143+
assert adk_template._use_client_cert_effective() is False
1144+
1145+
def test_get_api_endpoint_default(self):
1146+
"""Verifies the default telemetry endpoint is returned when no mTLS is configured."""
1147+
assert (
1148+
adk_template._get_api_endpoint() == adk_template._DEFAULT_TELEMETRY_ENDPOINT
1149+
)
1150+
1151+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"})
1152+
def test_get_api_endpoint_always_with_cert(self):
1153+
"""Verifies the mTLS endpoint is used when forced and a certificate is available."""
1154+
assert (
1155+
adk_template._get_api_endpoint(client_cert_source=b"cert")
1156+
== adk_template._DEFAULT_MTLS_TELEMETRY_ENDPOINT
1157+
)
1158+
1159+
@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
1160+
def test_get_api_endpoint_auto_no_cert(self):
1161+
"""Verifies it falls back to regular endpoint even if forced if no certificate is provided."""
1162+
assert (
1163+
adk_template._get_api_endpoint() == adk_template._DEFAULT_TELEMETRY_ENDPOINT
1164+
)

vertexai/preview/reasoning_engines/templates/adk.py

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@
2727
import asyncio
2828
from collections.abc import Awaitable
2929
import queue
30+
import os
3031
import sys
3132
import threading
33+
import enum
34+
from google.auth.transport import mtls
35+
from google.auth.transport import requests as requests_auth
3236

3337

3438
if TYPE_CHECKING:
@@ -97,6 +101,18 @@
97101

98102
_DEFAULT_APP_NAME = "default-app-name"
99103
_DEFAULT_USER_ID = "default-user-id"
104+
_DEFAULT_TELEMETRY_ENDPOINT = "https://telemetry.googleapis.com/v1/traces"
105+
_DEFAULT_MTLS_TELEMETRY_ENDPOINT = "https://telemetry.mtls.googleapis.com/v1/traces"
106+
107+
108+
class _MtlsEndpoint(enum.Enum):
109+
"""The mTLS endpoint setting."""
110+
111+
AUTO = "auto"
112+
ALWAYS = "always"
113+
NEVER = "never"
114+
115+
100116
_TELEMETRY_API_DISABLED_WARNING = (
101117
"Tracing integration for Agent Engine has migrated to a new API.\n"
102118
"The 'telemetry.googleapis.com' has not been enabled in project %s. \n"
@@ -250,6 +266,64 @@ def _warn(msg: str):
250266
_warn._LOGGER.warning(msg) # pyright: ignore[reportFunctionMemberAccess]
251267

252268

269+
def _get_api_endpoint(client_cert_source: bytes | None = None) -> str:
270+
"""Returns API endpoint based on mTLS configuration and cert availability.
271+
272+
Args:
273+
client_cert_source (bytes | None): The client certificate source.
274+
275+
Returns:
276+
str: The API endpoint to be used.
277+
"""
278+
use_mtls_endpoint_str = os.getenv(
279+
"GOOGLE_API_USE_MTLS_ENDPOINT", _MtlsEndpoint.AUTO.value
280+
).lower()
281+
282+
try:
283+
use_mtls_endpoint = _MtlsEndpoint(use_mtls_endpoint_str)
284+
except ValueError:
285+
_warn(
286+
f"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be one of "
287+
f"{[e.value for e in _MtlsEndpoint]}. Defaulting to"
288+
f" {_MtlsEndpoint.AUTO.value}."
289+
)
290+
use_mtls_endpoint = _MtlsEndpoint.AUTO
291+
292+
if (use_mtls_endpoint == _MtlsEndpoint.ALWAYS) or (
293+
use_mtls_endpoint == _MtlsEndpoint.AUTO and client_cert_source
294+
):
295+
return _DEFAULT_MTLS_TELEMETRY_ENDPOINT
296+
297+
return _DEFAULT_TELEMETRY_ENDPOINT
298+
299+
300+
def _use_client_cert_effective() -> bool:
301+
"""Returns whether client certificate should be used for mTLS.
302+
303+
This checks if the google-auth version supports should_use_client_cert
304+
automatic mTLS enablement. Alternatively, it reads from the
305+
GOOGLE_API_USE_CLIENT_CERTIFICATE env var.
306+
307+
Returns:
308+
bool: whether client certificate should be used for mTLS.
309+
"""
310+
# check if google-auth version supports should_use_client_cert for automatic
311+
# mTLS enablement
312+
try:
313+
return mtls.should_use_client_cert()
314+
except (ImportError, AttributeError):
315+
# if unsupported, fallback to reading from env var
316+
use_client_cert_str = os.getenv(
317+
"GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
318+
).lower()
319+
if use_client_cert_str not in ("true", "false"):
320+
_warn(
321+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
322+
" either `true` or `false`"
323+
)
324+
return use_client_cert_str == "true"
325+
326+
253327
async def _force_flush_otel(tracing_enabled: bool, logging_enabled: bool):
254328
try:
255329
import opentelemetry.trace
@@ -393,12 +467,24 @@ def _detect_cloud_resource_id(project_id: str) -> Optional[str]:
393467
otlp_http_version = opentelemetry.exporter.otlp.proto.http.version.__version__
394468
user_agent = f"Vertex-Agent-Engine/{vertex_sdk_version} OTel-OTLP-Exporter-Python/{otlp_http_version}"
395469

470+
session = requests_auth.AuthorizedSession(credentials=credentials)
471+
472+
use_client_cert = _use_client_cert_effective()
473+
if use_client_cert:
474+
client_cert_source = (
475+
mtls.default_client_cert_source()
476+
if mtls.has_default_client_cert_source()
477+
else None
478+
)
479+
session.configure_mtls_channel()
480+
endpoint = _get_api_endpoint(client_cert_source)
481+
else:
482+
endpoint = _DEFAULT_TELEMETRY_ENDPOINT
483+
396484
span_exporter = (
397485
opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter(
398-
session=google.auth.transport.requests.AuthorizedSession(
399-
credentials=credentials
400-
),
401-
endpoint="https://telemetry.googleapis.com/v1/traces",
486+
session=session,
487+
endpoint=endpoint,
402488
headers={"User-Agent": user_agent},
403489
)
404490
)
@@ -1620,10 +1706,20 @@ def _warn_if_telemetry_api_disabled(self):
16201706
except (ImportError, AttributeError):
16211707
return
16221708
credentials, project = google.auth.default()
1623-
session = google.auth.transport.requests.AuthorizedSession(
1624-
credentials=credentials
1625-
)
1626-
r = session.post("https://telemetry.googleapis.com/v1/traces", data=None)
1709+
session = requests_auth.AuthorizedSession(credentials=credentials)
1710+
1711+
use_client_cert = _use_client_cert_effective()
1712+
if use_client_cert:
1713+
client_cert_source = (
1714+
mtls.default_client_cert_source()
1715+
if mtls.has_default_client_cert_source()
1716+
else None
1717+
)
1718+
session.configure_mtls_channel()
1719+
endpoint = _get_api_endpoint(client_cert_source)
1720+
else:
1721+
endpoint = _DEFAULT_TELEMETRY_ENDPOINT
1722+
r = session.post(endpoint, data=None)
16271723
if "Telemetry API has not been used in project" in r.text:
16281724
_warn(_TELEMETRY_API_DISABLED_WARNING % (project, project))
16291725

0 commit comments

Comments
 (0)