Summary of the Issue
The current mTLS implementation for IAM Credentials API endpoints in google-auth is broken due to two distinct, related issues:
- Static import-time endpoint configuration: The library determines whether to use mTLS endpoints (e.g.,
iamcredentials.mtls.googleapis.com) once on module import. This prevents late-binding of certificates or runtime credential transitions from executing over the secure domain.
- Unconfigured internal helper transport: The actual HTTP transport adapter passed to internal IAM requests is an unconfigured helper session that lacks the mTLS client certificate, causing connection failures or silent security fallbacks to standard one-way TLS.
Part 1: Static Domain Evaluation at Import Time
In google/auth/iam.py, the domain hostname _IAM_DOMAIN is evaluated globally during import:
# google/auth/iam.py
if (
hasattr(_mtls_helper, "check_use_client_cert")
and _mtls_helper.check_use_client_cert()
):
_IAM_DOMAIN = f"iamcredentials.mtls.{credentials.DEFAULT_UNIVERSE_DOMAIN}"
else:
_IAM_DOMAIN = f"iamcredentials.{credentials.DEFAULT_UNIVERSE_DOMAIN}"
_IAM_BASE_URL = f"https://{_IAM_DOMAIN}/v1/projects/-/serviceAccounts/{{}}"
Evaluating this hostname statically at initial module load breaks two major flows:
- Late configuration bootstraps: Programmatic environment updates (e.g., setting
os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = "true") or dynamically mounting certificate configurations after import statements run are completely ignored.
- Runtime identity transitions: When identities transition at runtime (e.g., from standard service accounts to federated workload identities), the client cannot dynamically adapt its destination endpoints to match the certificate state.
Proposed Solution for Part 1:
Refactor google/auth/iam.py to resolve these endpoints dynamically per call (utilizing caching via @functools.lru_cache or similar helper strategies to prevent excessive lookup overhead).
In addition, no unit tests were added when the change to support mtls endpoints was added. Unit tests must be added to cover that the right endpoint is being triggered.
Part 2: Unconfigured Internal Helper Transport
Even if the IAM endpoints are resolved dynamically at runtime, requests to iamcredentials.mtls.googleapis.com will still fail to establish a valid mutual-TLS channel.
When a user calls configure_mtls_channel() on AuthorizedSession (in requests.py) or AuthorizedHttp (in urllib3.py), the client certificates are only mounted onto the outer client session:
# google/auth/transport/requests.py -> configure_mtls_channel()
if self._is_mtls:
mtls_adapter = _MutualTlsAdapter(cert, key)
self.mount("https://", mtls_adapter)
However, the credentials class delegates token refreshing and internal IAM requests (such as signBlob or generateAccessToken) to a separate internal helper session (self._auth_request_session wrapped by self._auth_request) to avoid infinite recursion:
# google/auth/transport/requests.py -> __init__()
if auth_request is None:
self._auth_request_session = requests.Session()
auth_request = Request(self._auth_request_session)
This internal helper session is never updated with the client certificate. When iam.py or impersonated_credentials.py dispatches a signing request, it utilizes this unconfigured internal transport:
# google/auth/iam.py -> _make_signing_request()
self._credentials.before_request(self._request, method, url, headers)
response = self._request(url=url, method=method, body=body, headers=headers)
Because the internal transport lacks the client certificate:
- If GFE allows optional client certificates, the TLS handshake succeeds but silently falls back to standard one-way TLS, bypassing the security properties of mTLS.
- If strict Certificate-Based Access (CBA) policies are enforced, the connection is blocked, resulting in runtime outages.
Proposed Solution for Part 2:
Ensure the client certificate configuration propagates to the inner helper transport during channel setup.
For Requests (google/auth/transport/requests.py):
if self._is_mtls:
mtls_adapter = _MutualTlsAdapter(cert, key)
self.mount("https://", mtls_adapter)
if self._auth_request_session is not None:
self._auth_request_session.mount("https://", mtls_adapter)
For Urllib3 (google/auth/transport/urllib3.py):
if found_cert_key:
self.http = _make_mutual_tls_http(cert, key)
self._request = Request(self.http)
Summary of the Issue
The current mTLS implementation for IAM Credentials API endpoints in
google-authis broken due to two distinct, related issues:iamcredentials.mtls.googleapis.com) once on module import. This prevents late-binding of certificates or runtime credential transitions from executing over the secure domain.Part 1: Static Domain Evaluation at Import Time
In
google/auth/iam.py, the domain hostname_IAM_DOMAINis evaluated globally during import:Evaluating this hostname statically at initial module load breaks two major flows:
os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = "true") or dynamically mounting certificate configurations after import statements run are completely ignored.Proposed Solution for Part 1:
Refactor
google/auth/iam.pyto resolve these endpoints dynamically per call (utilizing caching via@functools.lru_cacheor similar helper strategies to prevent excessive lookup overhead).In addition, no unit tests were added when the change to support mtls endpoints was added. Unit tests must be added to cover that the right endpoint is being triggered.
Part 2: Unconfigured Internal Helper Transport
Even if the IAM endpoints are resolved dynamically at runtime, requests to
iamcredentials.mtls.googleapis.comwill still fail to establish a valid mutual-TLS channel.When a user calls
configure_mtls_channel()onAuthorizedSession(in requests.py) orAuthorizedHttp(in urllib3.py), the client certificates are only mounted onto the outer client session:However, the credentials class delegates token refreshing and internal IAM requests (such as
signBloborgenerateAccessToken) to a separate internal helper session (self._auth_request_sessionwrapped byself._auth_request) to avoid infinite recursion:This internal helper session is never updated with the client certificate. When
iam.pyorimpersonated_credentials.pydispatches a signing request, it utilizes this unconfigured internal transport:Because the internal transport lacks the client certificate:
Proposed Solution for Part 2:
Ensure the client certificate configuration propagates to the inner helper transport during channel setup.
For Requests (
google/auth/transport/requests.py):For Urllib3 (
google/auth/transport/urllib3.py):