Skip to content

Commit d831ee6

Browse files
xuanyang15copybara-github
authored andcommitted
fix: support unencrypted workload certificates for mTLS in GoogleApiToolset
Co-authored-by: Xuan Yang <xygoogle@google.com> PiperOrigin-RevId: 945407876
1 parent 5e3fa3b commit d831ee6

4 files changed

Lines changed: 90 additions & 2 deletions

File tree

src/google/adk/tools/google_api_tool/google_api_toolset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def __init__(
8888
if use_client_cert:
8989
self._mtls_certs = MtlsClientCerts()
9090
cert_path, key_path, passphrase = self._mtls_certs.get_certs()
91-
if cert_path and key_path and passphrase:
91+
if cert_path and key_path:
9292

9393
def client_factory() -> httpx.AsyncClient:
9494
if passphrase:

src/google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def fetch_google_api_spec(self) -> None:
7979

8080
if self._use_client_cert and self._mtls_certs:
8181
cert_path, key_path, passphrase = self._mtls_certs.get_certs()
82-
if cert_path and key_path and passphrase:
82+
if cert_path and key_path:
8383
# Set default HTTP timeout similar to googleapiclient.http.build_http()
8484
http_timeout = socket.getdefaulttimeout() or 60
8585
http_client = httplib2.Http(timeout=http_timeout)

tests/unittests/tools/google_api_tool/test_google_api_toolset.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,3 +564,47 @@ async def test_mtls_cleanup_on_close(
564564

565565
mock_openapi_toolset_instance.close.assert_called_once()
566566
mock_mtls_certs_instance.close.assert_called_once()
567+
568+
@mock.patch(
569+
"google.adk.tools.google_api_tool.google_api_toolset.httpx.AsyncClient"
570+
)
571+
@mock.patch(
572+
"google.adk.tools.google_api_tool.google_api_toolset.OpenAPIToolset"
573+
)
574+
@mock.patch(
575+
"google.adk.tools.google_api_tool.google_api_toolset.GoogleApiToOpenApiConverter"
576+
)
577+
@mock.patch(
578+
"google.adk.tools.google_api_tool.google_api_toolset.MtlsClientCerts"
579+
)
580+
@mock.patch(
581+
"google.adk.tools.google_api_tool.google_api_toolset.use_client_cert_effective"
582+
)
583+
async def test_mtls_no_passphrase(
584+
self,
585+
mock_use_client_cert,
586+
mock_mtls_certs_class,
587+
mock_converter_class,
588+
mock_openapi_toolset_class,
589+
mock_async_client_class,
590+
mock_converter_instance,
591+
mock_openapi_toolset_instance,
592+
):
593+
"""Test that mTLS is configured even if key passphrase is None."""
594+
mock_converter_class.return_value = mock_converter_instance
595+
mock_openapi_toolset_class.return_value = mock_openapi_toolset_instance
596+
597+
mock_use_client_cert.return_value = True
598+
mock_mtls_certs_instance = mock.MagicMock()
599+
mock_mtls_certs_instance.get_certs.return_value = ("cert", "key", None)
600+
mock_mtls_certs_class.return_value = mock_mtls_certs_instance
601+
602+
tool_set = GoogleApiToolset(
603+
api_name=TEST_API_NAME, api_version=TEST_API_VERSION
604+
)
605+
606+
assert tool_set._httpx_client_factory is not None
607+
608+
client = tool_set._httpx_client_factory()
609+
assert client is not None
610+
mock_async_client_class.assert_called_once_with(cert=("cert", "key"))

tests/unittests/tools/google_api_tool/test_googleapi_to_openapi_converter.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,50 @@ def test_fetch_google_api_spec_with_mtls(
333333
== "https://www.mtls.googleapis.com/discovery/v1/apis/{api}/{apiVersion}/rest"
334334
)
335335

336+
def test_fetch_google_api_spec_with_mtls_no_passphrase(
337+
self, monkeypatch, mock_api_resource, calendar_api_spec
338+
):
339+
"""Test fetching Google API specification with mTLS enabled but no passphrase."""
340+
mock_build = MagicMock(return_value=mock_api_resource)
341+
monkeypatch.setattr(
342+
"google.adk.tools.google_api_tool.googleapi_to_openapi_converter.build",
343+
mock_build,
344+
)
345+
346+
# Enable mTLS
347+
monkeypatch.setattr(
348+
"google.auth.transport.mtls.should_use_client_cert",
349+
lambda: True,
350+
)
351+
monkeypatch.setattr(
352+
"google.auth.transport.mtls.has_default_client_cert_source",
353+
lambda: True,
354+
)
355+
356+
# Return None for passphrase
357+
mock_cert_source = MagicMock(
358+
return_value=("/path/to/cert", "/path/to/key", None)
359+
)
360+
monkeypatch.setattr(
361+
"google.auth.transport.mtls.default_client_encrypted_cert_source",
362+
lambda c, k: mock_cert_source,
363+
)
364+
365+
converter = GoogleApiToOpenApiConverter("calendar", "v3")
366+
converter.fetch_google_api_spec()
367+
368+
assert converter._google_api_spec == calendar_api_spec
369+
370+
# Verify build was called with the http parameter set and mtls url
371+
mock_build.assert_called_once()
372+
_, kwargs = mock_build.call_args
373+
assert "http" in kwargs
374+
assert kwargs["http"] is not None
375+
assert (
376+
kwargs["discoveryServiceUrl"]
377+
== "https://www.mtls.googleapis.com/discovery/v1/apis/{api}/{apiVersion}/rest"
378+
)
379+
336380
def test_fetch_google_api_spec_error(self, monkeypatch, converter):
337381
"""Test error handling when fetching Google API specification."""
338382
# Create a mock that raises an error

0 commit comments

Comments
 (0)