Skip to content

Commit e096127

Browse files
authored
fix: fail-fast on missing ECP config file to avoid 30s hang (#17377)
This PR resolves two issues in the `google-auth` package: First, it adds a fast-fail check for ECP configuration. When the `GOOGLE_API_CERTIFICATE_CONFIG` environment variable is set but the configuration file is missing (common on corporate workstations or clean sandbox test runners), the SDK was falling through to the well-known SPIFFE path and waiting on a 30-second retry loop. We now check if the config path is set but missing, and if we are not in a workload environment (the well-known credentials directory is absent), we immediately return `None` to fallback to unbound tokens. (Fixes b/512912028) Second, it fixes an incorrect mock in `test_mtls.py`. `test_default_client_encrypted_cert_source` was mocking `open` in the test namespace instead of the target module namespace. This caused the test to write actual `cert_path` and `key_path` files to the local disk during test runs. This is fixed by patching `google.auth.transport.mtls.open`. Unit tests have been added to verify the fast-fail behavior, and existing retry tests have been updated to mock the workload directory. All tests now pass without writing files to disk.
1 parent 3f70b2f commit e096127

3 files changed

Lines changed: 44 additions & 3 deletions

File tree

packages/google-auth/google/auth/_agent_identity_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ def get_agent_identity_certificate_path():
8989
if not cert_config_path and not has_well_known_dir:
9090
return None
9191

92+
# If ECP config path is specified but does not exist, and we are on a workstation, fail-fast immediately.
93+
if (
94+
cert_config_path
95+
and not has_well_known_dir
96+
and not os.path.exists(cert_config_path)
97+
):
98+
return None
99+
92100
has_logged_config_warning = False
93101
has_logged_cert_warning = False
94102

packages/google-auth/tests/test_agent_identity_utils.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,29 +165,47 @@ def test_get_agent_identity_certificate_path_success(self, tmpdir, monkeypatch):
165165
assert result == str(cert_path)
166166

167167
@mock.patch("time.sleep")
168+
@mock.patch("google.auth._agent_identity_utils.os.path.exists")
168169
def test_get_agent_identity_certificate_path_retry(
169-
self, mock_sleep, tmpdir, monkeypatch
170+
self, mock_exists, mock_sleep, tmpdir, monkeypatch
170171
):
171172
config_path = tmpdir.join("config.json")
172173
monkeypatch.setenv(
173174
environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path)
174175
)
175176

177+
# Simulate workload env (well_known_dir exists) to avoid fail-fast
178+
def exists_side_effect(path):
179+
if path == "/var/run/secrets/workload-spiffe-credentials":
180+
return True
181+
return False
182+
183+
mock_exists.side_effect = exists_side_effect
184+
176185
# File doesn't exist initially
177186
with pytest.raises(exceptions.RefreshError):
178187
_agent_identity_utils.get_agent_identity_certificate_path()
179188

180189
assert mock_sleep.call_count == len(_agent_identity_utils._POLLING_INTERVALS)
181190

182191
@mock.patch("time.sleep")
192+
@mock.patch("google.auth._agent_identity_utils.os.path.exists")
183193
def test_get_agent_identity_certificate_path_failure(
184-
self, mock_sleep, tmpdir, monkeypatch
194+
self, mock_exists, mock_sleep, tmpdir, monkeypatch
185195
):
186196
config_path = tmpdir.join("non_existent_config.json")
187197
monkeypatch.setenv(
188198
environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path)
189199
)
190200

201+
# Simulate workload env (well_known_dir exists) to avoid fail-fast
202+
def exists_side_effect(path):
203+
if path == "/var/run/secrets/workload-spiffe-credentials":
204+
return True
205+
return False
206+
207+
mock_exists.side_effect = exists_side_effect
208+
191209
with pytest.raises(exceptions.RefreshError) as excinfo:
192210
_agent_identity_utils.get_agent_identity_certificate_path()
193211

@@ -198,6 +216,19 @@ def test_get_agent_identity_certificate_path_failure(
198216
)
199217
assert mock_sleep.call_count == len(_agent_identity_utils._POLLING_INTERVALS)
200218

219+
def test_get_agent_identity_certificate_path_workstation_fail_fast(
220+
self, tmpdir, monkeypatch
221+
):
222+
config_path = tmpdir.join("non_existent_config.json")
223+
monkeypatch.setenv(
224+
environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path)
225+
)
226+
227+
# On a workstation, well_known_dir does not exist, and config file is missing.
228+
# It should fail-fast and return None immediately.
229+
result = _agent_identity_utils.get_agent_identity_certificate_path()
230+
assert result is None
231+
201232
@mock.patch("time.sleep")
202233
@mock.patch("os.path.exists")
203234
def test_get_agent_identity_certificate_path_cert_not_found(

packages/google-auth/tests/transport/test_mtls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,10 @@ def test_default_client_encrypted_cert_source(
154154
# Test good callback.
155155
get_client_ssl_credentials.return_value = (True, b"cert", b"key", b"passphrase")
156156
callback = mtls.default_client_encrypted_cert_source("cert_path", "key_path")
157-
with mock.patch("{}.open".format(__name__), return_value=mock.MagicMock()):
157+
with mock.patch("google.auth.transport.mtls.open", mock.mock_open()) as mock_file:
158158
assert callback() == ("cert_path", "key_path", b"passphrase")
159+
mock_file.assert_any_call("cert_path", "wb")
160+
mock_file.assert_any_call("key_path", "wb")
159161

160162
# Test bad callback which throws exception.
161163
get_client_ssl_credentials.side_effect = exceptions.ClientCertError()

0 commit comments

Comments
 (0)