Skip to content

Commit 62b9700

Browse files
google-genai-botcopybara-github
authored andcommitted
fix: implement dynamic mtls endpoint resolution for parameter manager
Replace hardcoded regional URLs with a helper function that prioritizes .mtls. endpoints when client certificates are present and enabled, fulfilling mTLS/CAA requirements. PiperOrigin-RevId: 937310286
1 parent 2f799d5 commit 62b9700

4 files changed

Lines changed: 215 additions & 3 deletions

File tree

src/google/adk/integrations/parameter_manager/parameter_client.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,17 @@
2626
from google.oauth2 import service_account
2727

2828
from ... import version
29+
from ...utils import mtls_utils
2930

3031
USER_AGENT = f"google-adk/{version.__version__}"
3132

33+
_DEFAULT_REGIONAL_ENDPOINT_TEMPLATE = (
34+
"parametermanager.{location}.rep.googleapis.com"
35+
)
36+
_DEFAULT_MTLS_REGIONAL_ENDPOINT_TEMPLATE = (
37+
"parametermanager.{location}.rep.mtls.googleapis.com"
38+
)
39+
3240

3341
class ParameterManagerClient:
3442
"""A client for interacting with Google Cloud Parameter Manager.
@@ -113,7 +121,11 @@ def __init__(
113121
client_options = None
114122
if location:
115123
client_options = {
116-
"api_endpoint": f"parametermanager.{location}.rep.googleapis.com"
124+
"api_endpoint": mtls_utils.get_api_endpoint(
125+
location,
126+
_DEFAULT_REGIONAL_ENDPOINT_TEMPLATE,
127+
_DEFAULT_MTLS_REGIONAL_ENDPOINT_TEMPLATE,
128+
)
117129
}
118130

119131
self._client = parametermanager_v1.ParameterManagerClient(

src/google/adk/utils/mtls_utils.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright 2026 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.
14+
15+
"""Utilities for mTLS regional endpoint resolution."""
16+
17+
from __future__ import annotations
18+
19+
import enum
20+
import os
21+
22+
from google.auth.transport import mtls
23+
24+
25+
class MtlsEndpoint(enum.Enum):
26+
"""Enum for the mTLS endpoint setting."""
27+
28+
AUTO = "auto"
29+
ALWAYS = "always"
30+
NEVER = "never"
31+
32+
33+
def use_client_cert_effective() -> bool:
34+
"""Returns whether client certificate should be used for mTLS."""
35+
try:
36+
return mtls.should_use_client_cert()
37+
except (ImportError, AttributeError):
38+
return (
39+
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower()
40+
== "true"
41+
)
42+
43+
44+
def get_api_endpoint(
45+
location: str, default_template: str, mtls_template: str
46+
) -> str:
47+
"""Returns API endpoint based on mTLS configuration and cert availability.
48+
49+
Args:
50+
location: The region location.
51+
default_template: Template for default regional endpoint (e.g.
52+
"secretmanager.{location}.rep.googleapis.com").
53+
mtls_template: Template for mTLS regional endpoint (e.g.
54+
"secretmanager.{location}.rep.mtls.googleapis.com").
55+
"""
56+
use_mtls_endpoint_str = os.getenv(
57+
"GOOGLE_API_USE_MTLS_ENDPOINT", MtlsEndpoint.AUTO.value
58+
).lower()
59+
try:
60+
use_mtls_endpoint = MtlsEndpoint(use_mtls_endpoint_str)
61+
except ValueError:
62+
use_mtls_endpoint = MtlsEndpoint.AUTO
63+
64+
if (use_mtls_endpoint == MtlsEndpoint.ALWAYS) or (
65+
use_mtls_endpoint == MtlsEndpoint.AUTO and use_client_cert_effective()
66+
):
67+
return mtls_template.format(location=location)
68+
return default_template.format(location=location)

tests/unittests/integrations/parameter_manager/test_parameter_client.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,14 @@ def test_init_with_auth_token(self, mock_pm_client_class):
122122
@patch(
123123
"google.adk.integrations.parameter_manager.parameter_client.default_service_credential"
124124
)
125+
@patch(
126+
"google.adk.integrations.parameter_manager.parameter_client.mtls_utils.get_api_endpoint"
127+
)
125128
def test_init_with_location(
126-
self, mock_default_service_credential, mock_pm_client_class
129+
self,
130+
mock_get_api_endpoint,
131+
mock_default_service_credential,
132+
mock_pm_client_class,
127133
):
128134
"""Test initialization with a specific location."""
129135
# Setup
@@ -133,6 +139,7 @@ def test_init_with_location(
133139
"test-project",
134140
)
135141
location = "us-central1"
142+
mock_get_api_endpoint.return_value = "resolved-endpoint"
136143

137144
# Execute
138145
ParameterManagerClient(location=location)
@@ -142,9 +149,14 @@ def test_init_with_location(
142149
call_kwargs = mock_pm_client_class.call_args.kwargs
143150
assert call_kwargs["credentials"] == mock_credentials
144151
assert call_kwargs["client_options"] == {
145-
"api_endpoint": f"parametermanager.{location}.rep.googleapis.com"
152+
"api_endpoint": "resolved-endpoint"
146153
}
147154
assert call_kwargs["client_info"].user_agent == USER_AGENT
155+
mock_get_api_endpoint.assert_called_once_with(
156+
location,
157+
"parametermanager.{location}.rep.googleapis.com",
158+
"parametermanager.{location}.rep.mtls.googleapis.com",
159+
)
148160

149161
@patch(
150162
"google.adk.integrations.parameter_manager.parameter_client.default_service_credential"
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Copyright 2026 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.
14+
15+
"""Unit tests for mtls_utils."""
16+
17+
import os
18+
from unittest.mock import MagicMock
19+
from unittest.mock import patch
20+
21+
from google.adk.utils import mtls_utils
22+
import pytest
23+
24+
_DEFAULT_TEMPLATE = "service.{location}.rep.googleapis.com"
25+
_MTLS_TEMPLATE = "service.{location}.rep.mtls.googleapis.com"
26+
_LOCATION = "us-central1"
27+
28+
29+
class TestMtlsUtils:
30+
"""Tests for mtls_utils functions."""
31+
32+
@patch("google.auth.transport.mtls.should_use_client_cert")
33+
def test_use_client_cert_effective_with_mtls_cert_true(
34+
self, mock_should_use_client_cert
35+
):
36+
mock_should_use_client_cert.return_value = True
37+
assert mtls_utils.use_client_cert_effective() is True
38+
mock_should_use_client_cert.assert_called_once()
39+
40+
@patch("google.auth.transport.mtls.should_use_client_cert")
41+
def test_use_client_cert_effective_with_mtls_cert_false(
42+
self, mock_should_use_client_cert
43+
):
44+
mock_should_use_client_cert.return_value = False
45+
assert mtls_utils.use_client_cert_effective() is False
46+
mock_should_use_client_cert.assert_called_once()
47+
48+
@patch("google.auth.transport.mtls.should_use_client_cert")
49+
@patch.dict("os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"})
50+
def test_use_client_cert_effective_fallback_true(
51+
self, mock_should_use_client_cert
52+
):
53+
mock_should_use_client_cert.side_effect = AttributeError
54+
assert mtls_utils.use_client_cert_effective() is True
55+
56+
@patch("google.auth.transport.mtls.should_use_client_cert")
57+
@patch.dict("os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"})
58+
def test_use_client_cert_effective_fallback_false(
59+
self, mock_should_use_client_cert
60+
):
61+
mock_should_use_client_cert.side_effect = AttributeError
62+
assert mtls_utils.use_client_cert_effective() is False
63+
64+
@patch("google.auth.transport.mtls.should_use_client_cert")
65+
@patch.dict("os.environ", {}, clear=True)
66+
def test_use_client_cert_effective_fallback_default_false(
67+
self, mock_should_use_client_cert
68+
):
69+
mock_should_use_client_cert.side_effect = AttributeError
70+
assert mtls_utils.use_client_cert_effective() is False
71+
72+
@patch("google.adk.utils.mtls_utils.use_client_cert_effective")
73+
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"})
74+
def test_get_api_endpoint_always(self, mock_use_client_cert):
75+
endpoint = mtls_utils.get_api_endpoint(
76+
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
77+
)
78+
assert endpoint == _MTLS_TEMPLATE.format(location=_LOCATION)
79+
mock_use_client_cert.assert_not_called()
80+
81+
@patch("google.adk.utils.mtls_utils.use_client_cert_effective")
82+
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"})
83+
def test_get_api_endpoint_never(self, mock_use_client_cert):
84+
endpoint = mtls_utils.get_api_endpoint(
85+
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
86+
)
87+
assert endpoint == _DEFAULT_TEMPLATE.format(location=_LOCATION)
88+
mock_use_client_cert.assert_not_called()
89+
90+
@patch("google.adk.utils.mtls_utils.use_client_cert_effective")
91+
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
92+
def test_get_api_endpoint_auto_with_cert(self, mock_use_client_cert):
93+
mock_use_client_cert.return_value = True
94+
endpoint = mtls_utils.get_api_endpoint(
95+
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
96+
)
97+
assert endpoint == _MTLS_TEMPLATE.format(location=_LOCATION)
98+
mock_use_client_cert.assert_called_once()
99+
100+
@patch("google.adk.utils.mtls_utils.use_client_cert_effective")
101+
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
102+
def test_get_api_endpoint_auto_without_cert(self, mock_use_client_cert):
103+
mock_use_client_cert.return_value = False
104+
endpoint = mtls_utils.get_api_endpoint(
105+
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
106+
)
107+
assert endpoint == _DEFAULT_TEMPLATE.format(location=_LOCATION)
108+
mock_use_client_cert.assert_called_once()
109+
110+
@patch("google.adk.utils.mtls_utils.use_client_cert_effective")
111+
@patch.dict("os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": "invalid_value"})
112+
def test_get_api_endpoint_invalid_fallback_to_auto(
113+
self, mock_use_client_cert
114+
):
115+
mock_use_client_cert.return_value = True
116+
endpoint = mtls_utils.get_api_endpoint(
117+
_LOCATION, _DEFAULT_TEMPLATE, _MTLS_TEMPLATE
118+
)
119+
assert endpoint == _MTLS_TEMPLATE.format(location=_LOCATION)
120+
mock_use_client_cert.assert_called_once()

0 commit comments

Comments
 (0)