Skip to content

Commit 0b9107d

Browse files
feat: Recognize workload certificate config in has_default_client_cert_source for mTLS for Agentic Identities (#1907)
feat: Recognize workload certificate config in `mTLS.has_default_client_cert_source` for mTLS for Agentic Identities. This is done as part of a fix for auto-enablement of mTLS, which depends on `mTLS.has_default_client_cert_source`. This method returned `False` because it only checked for gcloud cert configs and secure connect metadata, but not the workload-provided certificate_config.json. Thus, this change is necessary to help enable mTLS for Agentic Identities on GKE and Cloud Run Workloads. --------- Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
1 parent 59a5f58 commit 0b9107d

File tree

2 files changed

+89
-14
lines changed

2 files changed

+89
-14
lines changed

google/auth/transport/mtls.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
"""Utilites for mutual TLS."""
1616

17+
from os import getenv
18+
1719
from google.auth import exceptions
1820
from google.auth.transport import _mtls_helper
1921

@@ -36,6 +38,12 @@ def has_default_client_cert_source():
3638
is not None
3739
):
3840
return True
41+
cert_config_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG")
42+
if (
43+
cert_config_path
44+
and _mtls_helper._check_config_path(cert_config_path) is not None
45+
):
46+
return True
3947
return False
4048

4149

tests/transport/test_mtls.py

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,93 @@
2121
from google.auth.transport import mtls
2222

2323

24-
@mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True)
25-
def test_has_default_client_cert_source(check_config_path):
26-
def return_path_for_metadata(path):
27-
return mock.Mock() if path == _mtls_helper.CONTEXT_AWARE_METADATA_PATH else None
24+
@mock.patch("google.auth.transport._mtls_helper._check_config_path")
25+
def test_has_default_client_cert_source_with_context_aware_metadata(mock_check):
26+
"""
27+
Directly tests the logic: if CONTEXT_AWARE_METADATA_PATH is found, return True.
28+
"""
2829

29-
check_config_path.side_effect = return_path_for_metadata
30-
assert mtls.has_default_client_cert_source()
30+
# Setup: Return a path only for the Context Aware Metadata Path
31+
def side_effect(path):
32+
if path == _mtls_helper.CONTEXT_AWARE_METADATA_PATH:
33+
return "/path/to/context_aware_metadata.json"
34+
return None
35+
36+
mock_check.side_effect = side_effect
37+
38+
# Execute
39+
result = mtls.has_default_client_cert_source()
40+
41+
# Assert
42+
assert result is True
43+
mock_check.assert_any_call(_mtls_helper.CONTEXT_AWARE_METADATA_PATH)
44+
assert side_effect("non-matching-path") is None
45+
46+
47+
@mock.patch("google.auth.transport._mtls_helper._check_config_path")
48+
def test_has_default_client_cert_source_falls_back(mock_check):
49+
"""
50+
Tests that it skips CONTEXT_AWARE_METADATA_PATH if None, and checks the next path.
51+
"""
52+
53+
# Setup: First path is None, second path is valid
54+
def side_effect(path):
55+
if path == _mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH:
56+
return "/path/to/default_cert.json"
57+
return None
58+
59+
mock_check.side_effect = side_effect
3160

32-
def return_path_for_cert_config(path):
33-
return (
34-
mock.Mock()
35-
if path == _mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH
36-
else None
37-
)
61+
# Execute
62+
result = mtls.has_default_client_cert_source()
3863

39-
check_config_path.side_effect = return_path_for_cert_config
64+
# Assert
65+
assert result is True
66+
# Verify the sequence of calls
67+
expected_calls = [
68+
mock.call(_mtls_helper.CONTEXT_AWARE_METADATA_PATH),
69+
mock.call(_mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH),
70+
]
71+
mock_check.assert_has_calls(expected_calls)
72+
73+
74+
@mock.patch("google.auth.transport.mtls.getenv", autospec=True)
75+
@mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True)
76+
def test_has_default_client_cert_source_env_var_success(check_config_path, mock_getenv):
77+
# 1. Mock getenv to return our test path
78+
mock_getenv.side_effect = (
79+
lambda var: "path/to/cert.json"
80+
if var == "GOOGLE_API_CERTIFICATE_CONFIG"
81+
else None
82+
)
83+
84+
# 2. Mock _check_config_path side effect
85+
def side_effect(path):
86+
# Return None for legacy paths to ensure we reach the env var logic
87+
if path == "path/to/cert.json":
88+
return "/absolute/path/to/cert.json"
89+
return None
90+
91+
check_config_path.side_effect = side_effect
92+
93+
# 3. This should now return True
4094
assert mtls.has_default_client_cert_source()
4195

42-
check_config_path.side_effect = None
96+
# 4. Verify the env var path was checked
97+
check_config_path.assert_called_with("path/to/cert.json")
98+
99+
100+
@mock.patch("google.auth.transport.mtls.getenv", autospec=True)
101+
@mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True)
102+
def test_has_default_client_cert_source_env_var_invalid_config_path(
103+
check_config_path, mock_getenv
104+
):
105+
# Set the env var but make the check fail
106+
mock_getenv.side_effect = (
107+
lambda var: "invalid/path" if var == "GOOGLE_API_CERTIFICATE_CONFIG" else None
108+
)
43109
check_config_path.return_value = None
110+
44111
assert not mtls.has_default_client_cert_source()
45112

46113

0 commit comments

Comments
 (0)