Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-google-auth for openSUSE:Factory checked in at 2026-06-02 16:01:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-google-auth (Old) and /work/SRC/openSUSE:Factory/.python-google-auth.new.1937 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-google-auth" Tue Jun 2 16:01:02 2026 rev:68 rq:1356350 version:2.53.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-google-auth/python-google-auth.changes 2026-05-24 19:35:22.190580323 +0200 +++ /work/SRC/openSUSE:Factory/.python-google-auth.new.1937/python-google-auth.changes 2026-06-02 16:01:27.075793136 +0200 @@ -1,0 +2,8 @@ +Mon Jun 1 09:53:55 UTC 2026 - John Paul Adrian Glaubitz <[email protected]> + +- Update to version 2.53.0 + * allowlist agents-nonprod trust domains for agent identity (#17155) + * fail-fast on invalid or non-workload certificate configs in agent + identity discovery (#17116) + +------------------------------------------------------------------- Old: ---- google_auth-2.52.0.tar.gz New: ---- google_auth-2.53.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-google-auth.spec ++++++ --- /var/tmp/diff_new_pack.7fJxaP/_old 2026-06-02 16:01:28.051833621 +0200 +++ /var/tmp/diff_new_pack.7fJxaP/_new 2026-06-02 16:01:28.051833621 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-google-auth -Version: 2.52.0 +Version: 2.53.0 Release: 0 Summary: Google Authentication Library License: Apache-2.0 ++++++ google_auth-2.52.0.tar.gz -> google_auth-2.53.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_auth-2.52.0/PKG-INFO new/google_auth-2.53.0/PKG-INFO --- old/google_auth-2.52.0/PKG-INFO 2026-05-07 21:27:57.138338600 +0200 +++ new/google_auth-2.53.0/PKG-INFO 2026-05-15 22:31:47.528021600 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: google-auth -Version: 2.52.0 +Version: 2.53.0 Summary: Google Authentication Library Home-page: https://github.com/googleapis/google-auth-library-python Author: Google Cloud Platform diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_auth-2.52.0/google/auth/_agent_identity_utils.py new/google_auth-2.53.0/google/auth/_agent_identity_utils.py --- old/google_auth-2.52.0/google/auth/_agent_identity_utils.py 2026-05-07 21:26:34.000000000 +0200 +++ new/google_auth-2.53.0/google/auth/_agent_identity_utils.py 2026-05-15 22:31:13.000000000 +0200 @@ -22,9 +22,7 @@ import time from urllib.parse import quote, urlparse -from google.auth import environment_vars -from google.auth import exceptions - +from google.auth import environment_vars, exceptions _LOGGER = logging.getLogger(__name__) @@ -37,6 +35,8 @@ _AGENT_IDENTITY_SPIFFE_TRUST_DOMAIN_PATTERNS = [ r"^agents\.global\.org-\d+\.system\.id\.goog$", r"^agents\.global\.proj-\d+\.system\.id\.goog$", + r"^agents-nonprod\.global\.org-\d+\.system\.id\.goog$", + r"^agents-nonprod\.global\.proj-\d+\.system\.id\.goog$", ] _WELL_KNOWN_CERT_PATH = "/var/run/secrets/workload-spiffe-credentials/certificates.pem" @@ -80,41 +80,86 @@ import json cert_config_path = os.environ.get(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG) - if not cert_config_path: + + # Check if the well-known workload directory is mounted. + well_known_dir = os.path.dirname(_WELL_KNOWN_CERT_PATH) + has_well_known_dir = os.path.exists(well_known_dir) + + # If we have neither a config path nor a well-known mount directory, exit immediately. + if not cert_config_path and not has_well_known_dir: return None - has_logged_warning = False + has_logged_config_warning = False + has_logged_cert_warning = False for interval in _POLLING_INTERVALS: try: - with open(cert_config_path, "r") as f: - cert_config = json.load(f) - cert_path = ( - cert_config.get("cert_configs", {}) - .get("workload", {}) - .get("cert_path") + # Path A: Config file is explicitly set + if cert_config_path: + with open(cert_config_path, "r") as f: + cert_config = json.load(f) + + cert_configs = ( + cert_config.get("cert_configs") + if isinstance(cert_config, dict) + else None + ) + workload_config = ( + cert_configs.get("workload") + if isinstance(cert_configs, dict) + else None ) + + if ( + not isinstance(workload_config, dict) + or "cert_path" not in workload_config + ): + return None + + cert_path = workload_config["cert_path"] if _is_certificate_file_ready(cert_path): return cert_path - except (IOError, ValueError, KeyError): - if not has_logged_warning: + + # The config was parsed, but the cert file is not ready yet + target_path = cert_path + + # Path B: Config is NOT set, fallback to the well-known path + else: + if _is_certificate_file_ready(_WELL_KNOWN_CERT_PATH): + return _WELL_KNOWN_CERT_PATH + + # The well-known cert file is not ready yet + target_path = _WELL_KNOWN_CERT_PATH + + # Log a warning on the first failed attempt to load the certificate file + if not has_logged_cert_warning: + _LOGGER.warning( + "Certificate file not ready at %s. Retrying until startup timeout (up to %s seconds total)...", + target_path, + _TOTAL_TIMEOUT, + ) + has_logged_cert_warning = True + + except (IOError, ValueError, KeyError) as e: + if cert_config_path and os.path.exists(cert_config_path): + # If the file exists but has invalid JSON or is unreadable, + # we assume it is in its final format and fail-fast by returning None. + return None + + if not has_logged_config_warning and cert_config_path: _LOGGER.warning( - "Certificate config file not found at %s (from %s environment " - "variable). Retrying for up to %s seconds.", - cert_config_path, + "Certificate config file not found or incomplete: %s (from %s " + "environment variable). Retrying until startup timeout (up to %s seconds total)...", + e, environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, _TOTAL_TIMEOUT, ) - has_logged_warning = True + has_logged_config_warning = True pass - # As a fallback, check the well-known certificate path. - if _is_certificate_file_ready(_WELL_KNOWN_CERT_PATH): - return _WELL_KNOWN_CERT_PATH - # A sleep is required in two cases: # 1. The config file is not found (the except block). - # 2. The config file is found, but the certificate is not yet available. + # 2. The config file/well-known path is found, but the certificate is not yet available. # In both cases, we need to poll, so we sleep on every iteration # that doesn't return a certificate. time.sleep(interval) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_auth-2.52.0/google/auth/version.py new/google_auth-2.53.0/google/auth/version.py --- old/google_auth-2.52.0/google/auth/version.py 2026-05-07 21:26:31.000000000 +0200 +++ new/google_auth-2.53.0/google/auth/version.py 2026-05-15 22:31:12.000000000 +0200 @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.52.0" +__version__ = "2.53.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_auth-2.52.0/google_auth.egg-info/PKG-INFO new/google_auth-2.53.0/google_auth.egg-info/PKG-INFO --- old/google_auth-2.52.0/google_auth.egg-info/PKG-INFO 2026-05-07 21:27:57.000000000 +0200 +++ new/google_auth-2.53.0/google_auth.egg-info/PKG-INFO 2026-05-15 22:31:47.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: google-auth -Version: 2.52.0 +Version: 2.53.0 Summary: Google Authentication Library Home-page: https://github.com/googleapis/google-auth-library-python Author: Google Cloud Platform diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_auth-2.52.0/tests/test_agent_identity_utils.py new/google_auth-2.53.0/tests/test_agent_identity_utils.py --- old/google_auth-2.52.0/tests/test_agent_identity_utils.py 2026-05-07 21:26:34.000000000 +0200 +++ new/google_auth-2.53.0/tests/test_agent_identity_utils.py 2026-05-15 22:31:11.000000000 +0200 @@ -21,9 +21,7 @@ from cryptography import x509 import pytest -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 # A mock PEM-encoded certificate without an Agent Identity SPIFFE ID. NON_AGENT_IDENTITY_CERT_BYTES = ( @@ -60,15 +58,22 @@ cert = _agent_identity_utils.parse_certificate(NON_AGENT_IDENTITY_CERT_BYTES) assert not _agent_identity_utils._is_agent_identity_certificate(cert) - def test__is_agent_identity_certificate_valid_spiffe(self): + @pytest.mark.parametrize( + "spiffe_id", + [ + "spiffe://agents.global.proj-12345.system.id.goog/workload", + "spiffe://agents.global.org-12345.system.id.goog/workload", + "spiffe://agents-nonprod.global.proj-12345.system.id.goog/workload", + "spiffe://agents-nonprod.global.org-12345.system.id.goog/workload", + ], + ) + def test__is_agent_identity_certificate_valid_spiffe(self, spiffe_id): mock_cert = mock.MagicMock() mock_ext = mock.MagicMock() mock_san_value = mock.MagicMock() mock_cert.extensions.get_extension_for_oid.return_value = mock_ext mock_ext.value = mock_san_value - mock_san_value.get_values_for_type.return_value = [ - "spiffe://agents.global.proj-12345.system.id.goog/workload" - ] + mock_san_value.get_values_for_type.return_value = [spiffe_id] assert _agent_identity_utils._is_agent_identity_certificate(mock_cert) def test__is_agent_identity_certificate_non_matching_spiffe(self): @@ -172,7 +177,7 @@ with pytest.raises(exceptions.RefreshError): _agent_identity_utils.get_agent_identity_certificate_path() - assert mock_sleep.call_count == 100 + assert mock_sleep.call_count == len(_agent_identity_utils._POLLING_INTERVALS) @mock.patch("time.sleep") def test_get_agent_identity_certificate_path_failure( @@ -191,7 +196,7 @@ environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES in str(excinfo.value) ) - assert mock_sleep.call_count == 100 + assert mock_sleep.call_count == len(_agent_identity_utils._POLLING_INTERVALS) @mock.patch("time.sleep") @mock.patch("os.path.exists") @@ -215,7 +220,149 @@ with pytest.raises(exceptions.RefreshError): _agent_identity_utils.get_agent_identity_certificate_path() - assert mock_sleep.call_count == 100 + assert mock_sleep.call_count == len(_agent_identity_utils._POLLING_INTERVALS) + + @mock.patch("time.sleep") + def test_get_agent_identity_certificate_path_non_workload_config( + self, mock_sleep, tmpdir, monkeypatch + ): + config_path = tmpdir.join("config.json") + config_path.write( + json.dumps({"cert_configs": {"pkcs11": {"module": "some_module"}}}) + ) + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) + ) + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + # Should return None immediately without polling + assert result is None + mock_sleep.assert_not_called() + + @mock.patch("time.sleep") + def test_get_agent_identity_certificate_path_invalid_json( + self, mock_sleep, tmpdir, monkeypatch + ): + config_path = tmpdir.join("config.json") + config_path.write("{invalid_json: true}") # Invalid JSON missing quotes + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) + ) + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + # Should return None immediately without polling/sleeping + assert result is None + mock_sleep.assert_not_called() + + @mock.patch("time.sleep") + def test_get_agent_identity_certificate_path_non_dict_json( + self, mock_sleep, tmpdir, monkeypatch + ): + config_path = tmpdir.join("config.json") + config_path.write(json.dumps(["not", "a", "dict"])) # Valid JSON list + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) + ) + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + # Should fail fast immediately and return None without polling + assert result is None + mock_sleep.assert_not_called() + + @mock.patch("time.sleep") + def test_get_agent_identity_certificate_path_workload_config_missing_cert_path( + self, mock_sleep, tmpdir, monkeypatch + ): + config_path = tmpdir.join("config.json") + config_path.write(json.dumps({"cert_configs": {"workload": {}}})) + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) + ) + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + # Should return None immediately without polling + assert result is None + mock_sleep.assert_not_called() + + @mock.patch("time.sleep") + @mock.patch("os.path.exists") + @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") + def test_get_agent_identity_certificate_path_no_config_but_has_well_known_dir( + self, mock_is_ready, mock_exists, mock_sleep, monkeypatch + ): + monkeypatch.delenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False + ) + + # Simulate that the well-known workload mount directory exists, and the cert is ready + mock_exists.return_value = True + mock_is_ready.return_value = True + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + # Should return the well-known path immediately + assert result == _agent_identity_utils._WELL_KNOWN_CERT_PATH + mock_sleep.assert_not_called() + + @mock.patch("time.sleep") + @mock.patch("os.path.exists") + def test_get_agent_identity_certificate_path_no_config_no_well_known_dir( + self, mock_exists, mock_sleep, monkeypatch + ): + monkeypatch.delenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False + ) + + # Simulate that the well-known mount directory does NOT exist + mock_exists.return_value = False + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + # Should return None immediately without polling + assert result is None + mock_sleep.assert_not_called() + + @mock.patch("time.sleep") + @mock.patch("os.path.exists") + @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") + def test_get_agent_identity_certificate_path_no_config_well_known_polling_success( + self, mock_is_ready, mock_exists, mock_sleep, monkeypatch + ): + monkeypatch.delenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False + ) + + # Simulate that the directory exists, file appears on 2nd try + mock_exists.return_value = True + mock_is_ready.side_effect = [False, True] + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + assert result == _agent_identity_utils._WELL_KNOWN_CERT_PATH + assert mock_sleep.call_count == 1 + + @mock.patch("time.sleep") + @mock.patch("os.path.exists") + @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") + def test_get_agent_identity_certificate_path_no_config_well_known_polling_timeout( + self, mock_is_ready, mock_exists, mock_sleep, monkeypatch + ): + monkeypatch.delenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False + ) + + # Simulate that the directory exists, but file never appears + mock_exists.return_value = True + mock_is_ready.return_value = False + + with pytest.raises(exceptions.RefreshError): + _agent_identity_utils.get_agent_identity_certificate_path() + + assert mock_sleep.call_count == len(_agent_identity_utils._POLLING_INTERVALS) @mock.patch("google.auth._agent_identity_utils.get_agent_identity_certificate_path") def test_get_and_parse_agent_identity_certificate_opted_out( @@ -261,27 +408,6 @@ mock_parse_certificate.assert_called_once_with(b"cert_bytes") assert result == mock_parse_certificate.return_value - @mock.patch("time.sleep", return_value=None) - @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") - def test_get_agent_identity_certificate_path_fallback_to_well_known_path( - self, mock_is_ready, mock_sleep, monkeypatch - ): - # Set a dummy config path that won't be found. - monkeypatch.setenv( - environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, "/dummy/config.json" - ) - - # First, the primary path from the (mocked) config is not ready. - # Then, the fallback well-known path is ready. - mock_is_ready.side_effect = [False, True] - - result = _agent_identity_utils.get_agent_identity_certificate_path() - - assert result == _agent_identity_utils._WELL_KNOWN_CERT_PATH - # The sleep should have been called once before the fallback is checked. - mock_sleep.assert_called_once() - assert mock_is_ready.call_count == 2 - def test_get_cached_cert_fingerprint_no_cert(self): with pytest.raises(ValueError, match="mTLS connection is not configured."): _agent_identity_utils.get_cached_cert_fingerprint(None)
