fix: use mTLS transport for outbound A2A calls to Google endpoints#6370
Open
ShresthSamyak wants to merge 5 commits into
Open
fix: use mTLS transport for outbound A2A calls to Google endpoints#6370ShresthSamyak wants to merge 5 commits into
ShresthSamyak wants to merge 5 commits into
Conversation
AgentRegistry.get_remote_a2a_agent() handed RemoteA2aAgent a plain httpx.AsyncClient for the outbound A2A RPCs. Google context-aware access policies (Agent Identity) bind access tokens to a mutual-TLS channel, so presenting such a token over a plain connection is rejected with a 401 UNAUTHENTICATED. The mTLS work in 03671c6 only covered the AgentRegistry control-plane calls; the A2A data plane still used an unauthenticated, non-mTLS client. Standard Service Account bearer tokens are unbound and so worked either way, which is why the failure only surfaced with Agent Identity. Extract the async google-auth mTLS transport out of the MCP session manager into a shared utility (utils/_async_mtls_transport.py) and reuse it on the A2A path. RemoteA2aAgent gains an enable_google_auth_mtls flag; when set and no httpx_client is supplied, it builds its default client on a transport that presents the client certificate and attaches a channel-bound token. The flag is enabled automatically by get_remote_a2a_agent() for Google endpoints when a client certificate is configured and GOOGLE_API_USE_MTLS_ENDPOINT is not "never". A caller-supplied httpx_client is never overridden. When such a client would suppress required mTLS for a Google endpoint, a warning is logged so the 401 is diagnosable instead of silent. When mTLS cannot be negotiated (no client cert or google.auth.aio unavailable), it falls back to a plain client. Fixes google#6365
- _async_mtls_transport.py: mark the google.auth.aio import fallback shim with type: ignore and add __all__ so mypy treats the re-exported AsyncAuthorizedSession as an explicit export (fixes the attr-defined error in mcp_session_manager and the no-redef/misc errors on the moved shim). - test files: use example.com instead of googleapis.com URLs so the mTLS compliance check is not tripped by incidental host strings in tests.
Satisfies the end-of-file-fixer pre-commit hook (the file ended with a double newline).
Author
|
Thanks for taking a look, @rohityan, and for merging I've pushed two follow-up commits to address the CI failures from this change:
A few of the remaining red checks appear unrelated to this PR's diff, but please let me know if I'm missing something:
Happy to make any adjustments you'd like on the fix itself. Thanks again! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A2A remote calls made via
AgentRegistry.get_remote_a2a_agent(...)fail with401 UNAUTHENTICATEDwhen the orchestrator uses Agent Identity, whileworking fine with a standard Service Account. This routes outbound A2A traffic
to Google endpoints over a mutual-TLS channel so channel-bound credentials are
accepted.
Fixes #6365.
Root cause
get_remote_a2a_agent()builds aRemoteA2aAgentwith only an optionalhttpx_client, andRemoteA2aAgentcreates a plainhttpx.AsyncClientforthe actual A2A RPCs. Google context-aware access policies (used by Agent
Identity) issue access tokens that are cryptographically bound to a mutual-TLS
channel; presenting a bound token over a plain connection is rejected with
401 UNAUTHENTICATED.The mTLS work in 03671c6 hardened only the AgentRegistry control-plane calls
(
_make_requestviaAuthorizedSession). The A2A data-plane client nevercarried the client certificate or a channel-bound token, so:
get_mcp_toolset()already injects Google auth for Google MCP endpoints;get_remote_a2a_agent()had no equivalent for A2A. This aligns them.Changes
utils/_async_mtls_transport.py— the async google-auth mTLStransport (
_RefreshableAsyncCredentials,_GoogleAuthAsyncTransport,_SharedAsyncTransport,_GoogleAuthAsyncByteStream) extracted frommcp_session_manager.py, plus acreate_google_auth_mtls_transport(url)factory. Shared by both the MCP and A2A paths.
mcp_session_manager.py— imports the transport classes from the sharedmodule instead of defining them locally. No behavior change.
remote_a2a_agent.py— newenable_google_auth_mtlsflag; when set andno
httpx_clientis supplied, the default client is built on the mTLStransport, falling back to a plain client if mTLS cannot be negotiated.
agent_registry.py— enables the flag for Google A2A endpoints when aclient certificate is configured and
GOOGLE_API_USE_MTLS_ENDPOINT != "never".A caller-supplied
httpx_clientis never overridden; if it would suppressrequired mTLS, a warning is logged so the 401 is diagnosable.
Behavior / compatibility
httpx_clientis always respected (with a warning when itwould suppress required mTLS).
google.auth.aio(google-auth[aiohttp]); ifunavailable, it degrades gracefully to a plain client.
enable_google_auth_mtlsis an additive,default-
Falseparameter.Testing plan
Unit tests (
pytest), all passing (230 passed):tests/unittests/utils/test_async_mtls_transport.py— factory success /non-mTLS / exception / no-aio paths, and token host-scoping.
tests/unittests/integrations/agent_registry/test_agent_registry.py— mTLSenabled for Google endpoints; not enabled for non-Google, without a client
cert, with a caller-supplied client, or when the endpoint is set to
never;and that the suppressed-mTLS warning fires (and stays silent for non-Google).
tests/unittests/agents/test_remote_a2a_agent.py— default client is plainwhen disabled, uses the mTLS transport when enabled, and falls back when mTLS
is unavailable.
tests/unittests/tools/mcp_tool/test_mcp_session_manager.py— unchanged MCPmTLS behavior after the extraction.
Additionally verified locally that
create_google_auth_mtls_transport()negotiates a real mutual-TLS channel and presents the client certificate to a
server that requires it, with the bearer token attached over that channel
(returns 200; a cert-less client is rejected at the handshake).
Live validation against a deployed Agent Runtime + Agent Identity setup is
pending; the success path emits
Successfully configured mTLS using AsyncAuthorizedSessionat INFO.