Skip to content

Commit edc0423

Browse files
authored
fix(auth): centralize cert discovery logic and steps (#17696)
- Centralize mTLS configuration file path discovery by replacing disparate checks in `check_use_client_cert` and `has_default_client_cert_source` with a unified `_get_cert_config_path` helper. - Ensure X.509 Workload Identity Federation (WIF) takes precedence over SecureConnect by checking `_get_cert_config_path` before `CONTEXT_AWARE_METADATA_PATH` in `has_default_client_cert_source`. - Remove temporary Cloud Run certificate and key path overrides from `_get_workload_cert_and_key_paths`. - Properly honor `CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH` in availability checks, fixing an existing mismatch where auto-enablement would evaluate to true but the downstream source checker would miss the path. - Allow mTLS auto-enablement to properly trigger off the default `gcloud` configuration file (`~/.config/gcloud/certificate_config.json`), resolving previous contradictory behavior. - Ensure strict environment variable precedence: invalid paths supplied via environment variables now correctly result in failures rather than silently falling back to default disk locations. - Update tests to reflect the new centralized resolution. - fixes #17697
1 parent 61e795a commit edc0423

5 files changed

Lines changed: 187 additions & 222 deletions

File tree

packages/google-auth/google/auth/transport/_mtls_helper.py

Lines changed: 31 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from typing import cast, Generator, List, Optional, Tuple, Union
2727

2828
from google.auth import _agent_identity_utils
29+
from google.auth import _cloud_sdk
2930
from google.auth import environment_vars
3031
from google.auth import exceptions
3132

@@ -51,30 +52,10 @@
5152
_LOGGER = logging.getLogger(__name__)
5253

5354

54-
# A flag to track if we have already logged a warning about mTLS auto-enablement failures.
55-
# This prevents log spam when client libraries create transports or session instances
56-
# frequently within a single process.
57-
_has_logged_mtls_warning = False
58-
59-
6055
_PASSPHRASE_REGEX = re.compile(
6156
b"-----BEGIN PASSPHRASE-----(.+)-----END PASSPHRASE-----", re.DOTALL
6257
)
6358

64-
# Temporary patch to accomodate incorrect cert config in Cloud Run prod environment.
65-
_WELL_KNOWN_CLOUD_RUN_CERT_PATH = (
66-
"/var/run/secrets/workload-spiffe-credentials/certificates.pem"
67-
)
68-
_WELL_KNOWN_CLOUD_RUN_KEY_PATH = (
69-
"/var/run/secrets/workload-spiffe-credentials/private_key.pem"
70-
)
71-
_INCORRECT_CLOUD_RUN_CERT_PATH = (
72-
"/var/lib/volumes/certificate/workload-certificates/certificates.pem"
73-
)
74-
_INCORRECT_CLOUD_RUN_KEY_PATH = (
75-
"/var/lib/volumes/certificate/workload-certificates/private_key.pem"
76-
)
77-
7859

7960
class _MemfdCreationError(OSError):
8061
"""Raised when Linux in-memory virtual file creation (memfd) fails."""
@@ -436,22 +417,37 @@ def _get_cert_config_path(certificate_config_path=None, include_context_aware=Tr
436417
The absolute path of the certificate config file, and None if the file does not exist.
437418
"""
438419

420+
source = "function argument"
421+
is_explicit = True
439422
if certificate_config_path is None:
440423
env_path = environ.get(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, None)
441424
if env_path is not None and env_path != "":
442425
certificate_config_path = env_path
426+
source = (
427+
f"environment variable {environment_vars.GOOGLE_API_CERTIFICATE_CONFIG}"
428+
)
443429
else:
444430
env_path = environ.get(
445431
environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH,
446432
None,
447433
)
448434
if include_context_aware and env_path is not None and env_path != "":
449435
certificate_config_path = env_path
436+
source = f"environment variable {environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH}"
450437
else:
451-
certificate_config_path = CERTIFICATE_CONFIGURATION_DEFAULT_PATH
438+
certificate_config_path = os.path.join(
439+
_cloud_sdk.get_config_path(), "certificate_config.json"
440+
)
441+
is_explicit = False
452442

453443
certificate_config_path = path.expanduser(certificate_config_path)
454444
if not path.exists(certificate_config_path):
445+
if is_explicit:
446+
_LOGGER.debug(
447+
"Certificate configuration file explicitly specified via %s at %s does not exist",
448+
source,
449+
certificate_config_path,
450+
)
455451
return None
456452
return certificate_config_path
457453

@@ -489,25 +485,6 @@ def _get_workload_cert_and_key_paths(config_path, include_context_aware=True):
489485
cert_path = workload["cert_path"]
490486
key_path = workload["key_path"]
491487

492-
# == BEGIN Temporary Cloud Run PATCH ==
493-
# See https://github.com/googleapis/google-auth-library-python/issues/1881
494-
if (cert_path == _INCORRECT_CLOUD_RUN_CERT_PATH) and (
495-
key_path == _INCORRECT_CLOUD_RUN_KEY_PATH
496-
):
497-
if not path.exists(cert_path) and not path.exists(key_path):
498-
_LOGGER.debug(
499-
"Applying Cloud Run certificate path patch. "
500-
"Configured paths not found: %s, %s. "
501-
"Using well-known paths: %s, %s",
502-
cert_path,
503-
key_path,
504-
_WELL_KNOWN_CLOUD_RUN_CERT_PATH,
505-
_WELL_KNOWN_CLOUD_RUN_KEY_PATH,
506-
)
507-
cert_path = _WELL_KNOWN_CLOUD_RUN_CERT_PATH
508-
key_path = _WELL_KNOWN_CLOUD_RUN_KEY_PATH
509-
# == END Temporary Cloud Run PATCH ==
510-
511488
return cert_path, key_path
512489

513490

@@ -755,36 +732,33 @@ def check_use_client_cert():
755732
will default to False.
756733
If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred
757734
as True (auto-enabled) if a workload config file exists (pointed at by
758-
GOOGLE_API_CERTIFICATE_CONFIG) containing a "workload" section.
735+
GOOGLE_API_CERTIFICATE_CONFIG or CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH,
736+
or the default path like ~/.config/gcloud/certificate_config.json)
737+
containing a "workload" section.
759738
Otherwise, it returns False.
760739
761740
Returns:
762741
bool: Whether the client certificate should be used for mTLS connection.
763742
"""
764-
global _has_logged_mtls_warning
765743
env_override = _check_use_client_cert_env()
766744
if env_override is not None:
767745
return env_override
768746

769747
# Auto-enablement checks (when GOOGLE_API_USE_CLIENT_CERTIFICATE is not set)
770748

771-
# Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set.
772-
cert_path = getenv(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG) or getenv(
773-
environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH
774-
)
749+
# Check if a workload config file exists.
750+
cert_path = _get_cert_config_path(include_context_aware=True)
775751

776752
if cert_path:
777753
try:
778754
with open(cert_path, "r") as f:
779755
content = json.load(f)
780756
except (FileNotFoundError, OSError, json.JSONDecodeError) as e:
781-
if not _has_logged_mtls_warning:
782-
_LOGGER.warning(
783-
"mTLS auto-enablement failed: Could not read/parse certificate file at %s. Error: %s",
784-
cert_path,
785-
e,
786-
)
787-
_has_logged_mtls_warning = True
757+
_LOGGER.debug(
758+
"mTLS auto-enablement failed: Could not read/parse certificate file at %s. Error: %s",
759+
cert_path,
760+
e,
761+
)
788762
return False
789763

790764
# Structural validation
@@ -794,12 +768,10 @@ def check_use_client_cert():
794768
return True
795769

796770
# If we got here, the file exists but the expected structure is missing
797-
if not _has_logged_mtls_warning:
798-
_LOGGER.warning(
799-
"mTLS auto-enablement failed: Certificate configuration file at %s is missing the required ['cert_configs']['workload'] section.",
800-
cert_path,
801-
)
802-
_has_logged_mtls_warning = True
771+
_LOGGER.debug(
772+
"mTLS auto-enablement failed: Certificate configuration file at %s is missing the required ['cert_configs']['workload'] section.",
773+
cert_path,
774+
)
803775
return False
804776

805777

packages/google-auth/google/auth/transport/mtls.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from google.auth import exceptions
2525
from google.auth.transport import _mtls_helper
2626

27-
2827
_LOGGER = logging.getLogger(__name__)
2928

3029

@@ -44,25 +43,18 @@ def has_default_client_cert_source(include_context_aware=True):
4443
Returns:
4544
bool: indicating if the default client cert source exists.
4645
"""
46+
cert_path = _mtls_helper._get_cert_config_path(
47+
include_context_aware=include_context_aware
48+
)
49+
if cert_path is not None:
50+
return True
4751
if (
4852
include_context_aware
4953
and _mtls_helper._check_config_path(_mtls_helper.CONTEXT_AWARE_METADATA_PATH)
5054
is not None
5155
):
5256
return True
53-
if (
54-
_mtls_helper._check_config_path(
55-
_mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH
56-
)
57-
is not None
58-
):
59-
return True
60-
cert_config_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG")
61-
if (
62-
cert_config_path
63-
and _mtls_helper._check_config_path(cert_config_path) is not None
64-
):
65-
return True
57+
6658
return False
6759

6860

@@ -146,7 +138,9 @@ def should_use_client_cert():
146138
If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding
147139
bool value will be returned
148140
If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred by
149-
reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and verifying it
141+
reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG or
142+
CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH, or the default path
143+
like ~/.config/gcloud/certificate_config.json, and verifying it
150144
contains a "workload" section. If so, the function will return True,
151145
otherwise False.
152146

0 commit comments

Comments
 (0)