From 6cd4f2bdaeb53f3a9543341f0ec1c8def3aae3f0 Mon Sep 17 00:00:00 2001 From: Matt Castelaz Date: Tue, 9 Jun 2026 16:57:27 +0000 Subject: [PATCH 1/8] feat(auth): incorporate universal bug fixes, robustness guardrails, and circular import decoupling - Restore fail-fast validation in _mtls_helper.py - Add robustness guardrails to impersonated_credentials.py for universe_domain - Safeguard RSA Signer & key_id in rsa.py - Break circular import in credentials.py - Update unit tests in test__mtls_helper.py to match new behavior and run robustly in Google environments. TAG=agy CONV=39c1761f-e06b-4e3a-80b6-b4fbc70df0b6 --- packages/google-auth/google/auth/credentials.py | 3 ++- packages/google-auth/google/auth/crypt/rsa.py | 4 ++-- .../google/auth/impersonated_credentials.py | 6 +++++- .../google/auth/transport/_mtls_helper.py | 7 ++++++- .../tests/transport/test__mtls_helper.py | 16 ++++++++++++++-- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index f0ce4f41e0ac..7c38b53e87ea 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -532,7 +532,8 @@ def _lookup_regional_access_boundary( Returns: Optional[Dict[str, str]]: The Regional Access Boundary information returned by the lookup API, or None if the lookup failed. """ - from google.oauth2 import _client + import importlib + _client = importlib.import_module("google.oauth2._client") url = self._build_regional_access_boundary_lookup_url(request=request) if not url: diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index 639be9069549..e1ad9a3064e8 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -95,7 +95,7 @@ def __init__(self, private_key, key_id=None): module_str = private_key.__class__.__module__ if isinstance(private_key, RSAPrivateKey): impl_lib = _cryptography_rsa - elif module_str.startswith(RSA_KEY_MODULE_PREFIX): + elif private_key is None or module_str.startswith(RSA_KEY_MODULE_PREFIX): from google.auth.crypt import _python_rsa impl_lib = _python_rsa @@ -106,7 +106,7 @@ def __init__(self, private_key, key_id=None): @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): - return self._impl.key_id + return getattr(self, "_key_id", None) or self._impl.key_id @_helpers.copy_docstring(base.Signer) def sign(self, message): diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 2f14d809319e..67b391ad136e 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -262,7 +262,11 @@ def __init__( ): self._source_credentials._create_self_signed_jwt(None) - self._universe_domain = source_credentials.universe_domain + self._universe_domain = ( + source_credentials.universe_domain + if isinstance(getattr(source_credentials, "universe_domain", None), str) + else credentials.DEFAULT_UNIVERSE_DOMAIN + ) self._target_principal = target_principal self._target_scopes = target_scopes self._delegates = delegates diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index d6450291c7f2..9c23ec9ad777 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -453,7 +453,7 @@ def check_use_client_cert(): If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding bool value will be returned. If the value is set to an unexpected string, it - will default to False. + will raise a ValueError. If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred by reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and verifying it contains a "workload" section. If so, the function will return True, @@ -470,6 +470,11 @@ def check_use_client_cert(): # Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set. if use_client_cert: + if use_client_cert.lower() not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) return use_client_cert.lower() == "true" else: # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index 078df67470d2..e5375dbdad3b 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -657,7 +657,13 @@ def test_override_does_not_exist(self): returned_path = _mtls_helper._get_cert_config_path(config_path) assert returned_path is None - @mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}) + @mock.patch.dict( + os.environ, + { + "GOOGLE_API_CERTIFICATE_CONFIG": "", + "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH": "", + }, + ) @mock.patch("os.path.exists", autospec=True) def test_default(self, mock_path_exists): mock_path_exists.return_value = True @@ -788,13 +794,16 @@ def test_env_var_explicit_false(self): @mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "garbage"}) def test_env_var_explicit_garbage(self): - assert _mtls_helper.check_use_client_cert() is False + import pytest + with pytest.raises(ValueError, match="must be either `true` or `false`"): + _mtls_helper.check_use_client_cert() @mock.patch("builtins.open", autospec=True) @mock.patch.dict( os.environ, { "GOOGLE_API_USE_CLIENT_CERTIFICATE": "", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "", "GOOGLE_API_CERTIFICATE_CONFIG": "/path/to/config", }, ) @@ -810,6 +819,7 @@ def test_config_file_success(self, mock_file): os.environ, { "GOOGLE_API_USE_CLIENT_CERTIFICATE": "", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "", "GOOGLE_API_CERTIFICATE_CONFIG": "/path/to/config", }, ) @@ -822,6 +832,7 @@ def test_config_file_missing_keys(self, mock_file): os.environ, { "GOOGLE_API_USE_CLIENT_CERTIFICATE": "", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "", "GOOGLE_API_CERTIFICATE_CONFIG": "/path/to/config", }, ) @@ -834,6 +845,7 @@ def test_config_file_bad_json(self, mock_file): os.environ, { "GOOGLE_API_USE_CLIENT_CERTIFICATE": "", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "", "GOOGLE_API_CERTIFICATE_CONFIG": "/path/does/not/exist", }, ) From 01cd840d94741a760d3b2d575d6f44b24252e3c4 Mon Sep 17 00:00:00 2001 From: Matt Castelaz Date: Tue, 9 Jun 2026 17:26:31 +0000 Subject: [PATCH 2/8] refactor(auth): safely check private_key for None before accessing its module attribute TAG=agy CONV=39c1761f-e06b-4e3a-80b6-b4fbc70df0b6 --- packages/google-auth/google/auth/crypt/rsa.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index e1ad9a3064e8..e8da0ffbc467 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -92,10 +92,9 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): """ def __init__(self, private_key, key_id=None): - module_str = private_key.__class__.__module__ if isinstance(private_key, RSAPrivateKey): impl_lib = _cryptography_rsa - elif private_key is None or module_str.startswith(RSA_KEY_MODULE_PREFIX): + elif private_key is None or private_key.__class__.__module__.startswith(RSA_KEY_MODULE_PREFIX): from google.auth.crypt import _python_rsa impl_lib = _python_rsa From a96d50962b338a8b3a1825929b828dfc96fc4b4b Mon Sep 17 00:00:00 2001 From: Matt Castelaz Date: Tue, 9 Jun 2026 17:27:47 +0000 Subject: [PATCH 3/8] refactor(auth): explicitly check if key_id is not None in RSASigner.key_id TAG=agy CONV=39c1761f-e06b-4e3a-80b6-b4fbc70df0b6 --- packages/google-auth/google/auth/crypt/rsa.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index e8da0ffbc467..cb05c014d123 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -105,7 +105,10 @@ def __init__(self, private_key, key_id=None): @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): - return getattr(self, "_key_id", None) or self._impl.key_id + key_id = getattr(self, "_key_id", None) + if key_id is not None: + return key_id + return self._impl.key_id @_helpers.copy_docstring(base.Signer) def sign(self, message): From 92ea90e86d63a61dea2affd7d0106cc69b22bc3f Mon Sep 17 00:00:00 2001 From: Matt Castelaz Date: Tue, 9 Jun 2026 17:29:17 +0000 Subject: [PATCH 4/8] refactor(auth): avoid double attribute lookup for universe_domain in impersonated credentials TAG=agy CONV=39c1761f-e06b-4e3a-80b6-b4fbc70df0b6 --- packages/google-auth/google/auth/impersonated_credentials.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 67b391ad136e..4c00bce30a96 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -262,9 +262,10 @@ def __init__( ): self._source_credentials._create_self_signed_jwt(None) + universe_domain = getattr(source_credentials, "universe_domain", None) self._universe_domain = ( - source_credentials.universe_domain - if isinstance(getattr(source_credentials, "universe_domain", None), str) + universe_domain + if isinstance(universe_domain, str) else credentials.DEFAULT_UNIVERSE_DOMAIN ) self._target_principal = target_principal From 46d5f2a9ec264bf6cc0324b07ba67327cd129dc7 Mon Sep 17 00:00:00 2001 From: Matt Castelaz Date: Tue, 9 Jun 2026 17:36:53 +0000 Subject: [PATCH 5/8] style(auth): format modified files with black and ruff TAG=agy CONV=39c1761f-e06b-4e3a-80b6-b4fbc70df0b6 --- .../google-auth/google/auth/credentials.py | 22 ++++++++------ packages/google-auth/google/auth/crypt/rsa.py | 10 +++---- .../google/auth/impersonated_credentials.py | 30 ++++++++++--------- .../google/auth/transport/_mtls_helper.py | 6 ++-- .../tests/transport/test__mtls_helper.py | 3 +- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 7c38b53e87ea..66c555f1e659 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -16,18 +16,20 @@ """Interfaces for credentials.""" import abc -from enum import Enum import logging import os -from typing import Dict, List, Optional, TYPE_CHECKING -from urllib.parse import urlparse import warnings +from enum import Enum +from typing import TYPE_CHECKING, Dict, List, Optional +from urllib.parse import urlparse - -from google.auth import _helpers, environment_vars -from google.auth import _regional_access_boundary_utils -from google.auth import exceptions -from google.auth import metrics +from google.auth import ( + _helpers, + _regional_access_boundary_utils, + environment_vars, + exceptions, + metrics, +) from google.auth._credentials_base import _BaseCredentials from google.auth._refresh_worker import RefreshThreadManager @@ -533,6 +535,7 @@ def _lookup_regional_access_boundary( Optional[Dict[str, str]]: The Regional Access Boundary information returned by the lookup API, or None if the lookup failed. """ import importlib + _client = importlib.import_module("google.oauth2._client") url = self._build_regional_access_boundary_lookup_url(request=request) @@ -548,7 +551,8 @@ def _lookup_regional_access_boundary( @abc.abstractmethod def _build_regional_access_boundary_lookup_url( - self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + self, + request: "Optional[google.auth.transport.Request]" = None, # noqa: F821 ): """ Builds and returns the URL for the Regional Access Boundary lookup API. diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index cb05c014d123..8920f5721e29 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -19,12 +19,10 @@ for implmentations using different third party libraries """ -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey from google.auth import _helpers -from google.auth.crypt import _cryptography_rsa -from google.auth.crypt import base +from google.auth.crypt import _cryptography_rsa, base RSA_KEY_MODULE_PREFIX = "rsa.key" @@ -94,7 +92,9 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): def __init__(self, private_key, key_id=None): if isinstance(private_key, RSAPrivateKey): impl_lib = _cryptography_rsa - elif private_key is None or private_key.__class__.__module__.startswith(RSA_KEY_MODULE_PREFIX): + elif private_key is None or private_key.__class__.__module__.startswith( + RSA_KEY_MODULE_PREFIX + ): from google.auth.crypt import _python_rsa impl_lib = _python_rsa diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 4c00bce30a96..08b134cb64a5 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -27,21 +27,22 @@ import base64 import copy -from datetime import datetime import http.client as http_client import json import logging -from typing import Optional, TYPE_CHECKING - - -from google.auth import _exponential_backoff -from google.auth import _helpers -from google.auth import _regional_access_boundary_utils -from google.auth import credentials -from google.auth import exceptions -from google.auth import iam -from google.auth import jwt -from google.auth import metrics +from datetime import datetime +from typing import TYPE_CHECKING, Optional + +from google.auth import ( + _exponential_backoff, + _helpers, + _regional_access_boundary_utils, + credentials, + exceptions, + iam, + jwt, + metrics, +) from google.oauth2 import _client if TYPE_CHECKING: # pragma: NO COVER @@ -357,7 +358,8 @@ def _perform_refresh_token(self, request): ) def _build_regional_access_boundary_lookup_url( - self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + self, + request: "Optional[google.auth.transport.Request]" = None, # noqa: F821 ): """Builds and returns the URL for the Regional Access Boundary lookup API. @@ -569,7 +571,7 @@ def __init__( if not isinstance(target_credentials, Credentials): raise exceptions.GoogleAuthError( - "Provided Credential must be " "impersonated_credentials" + "Provided Credential must be impersonated_credentials" ) self._target_credentials = target_credentials self._target_audience = target_audience diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 9c23ec9ad777..6f48d2fc2714 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -16,13 +16,11 @@ import json import logging -from os import environ, getenv, path import re import subprocess +from os import environ, getenv, path -from google.auth import _agent_identity_utils -from google.auth import environment_vars -from google.auth import exceptions +from google.auth import _agent_identity_utils, environment_vars, exceptions CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index e5375dbdad3b..a74d29ba53a0 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -16,8 +16,8 @@ import re from unittest import mock -from OpenSSL import crypto import pytest # type: ignore +from OpenSSL import crypto from google.auth import environment_vars, exceptions from google.auth.transport import _mtls_helper @@ -795,6 +795,7 @@ def test_env_var_explicit_false(self): @mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "garbage"}) def test_env_var_explicit_garbage(self): import pytest + with pytest.raises(ValueError, match="must be either `true` or `false`"): _mtls_helper.check_use_client_cert() From 97ff318ca7dca7454cba477283dac4cfabe60f4f Mon Sep 17 00:00:00 2001 From: Matt Castelaz Date: Tue, 9 Jun 2026 18:10:39 +0000 Subject: [PATCH 6/8] style(auth): fix import ordering to satisfy flake8 import-order rules TAG=agy CONV=39c1761f-e06b-4e3a-80b6-b4fbc70df0b6 --- packages/google-auth/google/auth/credentials.py | 6 +++--- .../google-auth/google/auth/impersonated_credentials.py | 4 ++-- packages/google-auth/google/auth/transport/_mtls_helper.py | 2 +- packages/google-auth/tests/transport/test__mtls_helper.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 66c555f1e659..0ac572dd0132 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -16,12 +16,12 @@ """Interfaces for credentials.""" import abc +from enum import Enum import logging import os -import warnings -from enum import Enum -from typing import TYPE_CHECKING, Dict, List, Optional +from typing import Dict, List, Optional, TYPE_CHECKING from urllib.parse import urlparse +import warnings from google.auth import ( _helpers, diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 08b134cb64a5..27bffc8e00bb 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -27,11 +27,11 @@ import base64 import copy +from datetime import datetime import http.client as http_client import json import logging -from datetime import datetime -from typing import TYPE_CHECKING, Optional +from typing import Optional, TYPE_CHECKING from google.auth import ( _exponential_backoff, diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 6f48d2fc2714..60c136d19434 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -16,9 +16,9 @@ import json import logging +from os import environ, getenv, path import re import subprocess -from os import environ, getenv, path from google.auth import _agent_identity_utils, environment_vars, exceptions diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index a74d29ba53a0..030e92d42ef3 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -16,8 +16,8 @@ import re from unittest import mock -import pytest # type: ignore from OpenSSL import crypto +import pytest # type: ignore from google.auth import environment_vars, exceptions from google.auth.transport import _mtls_helper From f2f5074859fe09ff62ff6b76269a94ec400d8fab Mon Sep 17 00:00:00 2001 From: Matt Castelaz Date: Wed, 10 Jun 2026 02:18:31 +0000 Subject: [PATCH 7/8] refactor(auth): use _cryptography_rsa as default for RSASigner(None) to avoid ImportError TAG=agy CONV=39c1761f-e06b-4e3a-80b6-b4fbc70df0b6 --- packages/google-auth/google/auth/crypt/rsa.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index 8920f5721e29..a57dc1be9740 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -90,11 +90,9 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): """ def __init__(self, private_key, key_id=None): - if isinstance(private_key, RSAPrivateKey): + if private_key is None or isinstance(private_key, RSAPrivateKey): impl_lib = _cryptography_rsa - elif private_key is None or private_key.__class__.__module__.startswith( - RSA_KEY_MODULE_PREFIX - ): + elif private_key.__class__.__module__.startswith(RSA_KEY_MODULE_PREFIX): from google.auth.crypt import _python_rsa impl_lib = _python_rsa From 403d45066751bcee3d59607444fddb4d93f5f888 Mon Sep 17 00:00:00 2001 From: Matt Castelaz Date: Wed, 10 Jun 2026 02:37:47 +0000 Subject: [PATCH 8/8] docs(auth): explain custom/mock signer subclass support in RSASigner.key_id TAG=agy CONV=39c1761f-e06b-4e3a-80b6-b4fbc70df0b6 --- packages/google-auth/google/auth/crypt/rsa.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index a57dc1be9740..0aef0a3371fe 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -103,6 +103,9 @@ def __init__(self, private_key, key_id=None): @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): + # Support subclasses or mock signer implementations (e.g. in test suites) + # that define `_key_id` directly on the outer instance instead of fully + # initializing an underlying `_impl` backend. key_id = getattr(self, "_key_id", None) if key_id is not None: return key_id