Skip to content

Commit 3945c44

Browse files
Merge branch 'main' into add-support-for-async-authorized-session-api
2 parents 8c80aaa + c6d9903 commit 3945c44

File tree

13 files changed

+258
-76
lines changed

13 files changed

+258
-76
lines changed

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,36 @@
44

55
[1]: https://pypi.org/project/google-auth/#history
66

7+
## [2.34.0](https://github.com/googleapis/google-auth-library-python/compare/v2.33.0...v2.34.0) (2024-08-13)
8+
9+
10+
### Features
11+
12+
* **auth:** Update get_client_ssl_credentials to support X.509 workload certs ([#1558](https://github.com/googleapis/google-auth-library-python/issues/1558)) ([18c2ec1](https://github.com/googleapis/google-auth-library-python/commit/18c2ec1b571d506c0dbcffc483aa5e7b95e1b246))
13+
14+
15+
### Bug Fixes
16+
17+
* Retry token request on retryable status code ([#1563](https://github.com/googleapis/google-auth-library-python/issues/1563)) ([f858a15](https://github.com/googleapis/google-auth-library-python/commit/f858a151cb7e29d34578e03c9e3fd4110c6bc258))
18+
19+
## [2.33.0](https://github.com/googleapis/google-auth-library-python/compare/v2.32.0...v2.33.0) (2024-08-06)
20+
21+
22+
### Features
23+
24+
* Implement async `StaticCredentials` using access tokens ([#1559](https://github.com/googleapis/google-auth-library-python/issues/1559)) ([dc17dfc](https://github.com/googleapis/google-auth-library-python/commit/dc17dfc3fb65c87f2912300f0d11f79781240e78))
25+
* Implement base classes for credentials and request sessions ([#1551](https://github.com/googleapis/google-auth-library-python/issues/1551)) ([036dac4](https://github.com/googleapis/google-auth-library-python/commit/036dac43018b8cc26b5608e1bb21d6e3ee62a282))
26+
27+
28+
### Bug Fixes
29+
30+
* **metadata:** Enhance retry logic for metadata server access in _metadata.py ([#1545](https://github.com/googleapis/google-auth-library-python/issues/1545)) ([61c2432](https://github.com/googleapis/google-auth-library-python/commit/61c24321e52f6e017eecee211e11260d621c909b))
31+
32+
33+
### Documentation
34+
35+
* Update argument for Credentials initialization ([#1557](https://github.com/googleapis/google-auth-library-python/issues/1557)) ([40b9ed9](https://github.com/googleapis/google-auth-library-python/commit/40b9ed91a6b01948561cfc71edaaabdd7f362f17))
36+
737
## [2.32.0](https://github.com/googleapis/google-auth-library-python/compare/v2.31.0...v2.32.0) (2024-07-08)
838

939

google/auth/compute_engine/_metadata.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from google.auth import environment_vars
2929
from google.auth import exceptions
3030
from google.auth import metrics
31+
from google.auth import transport
3132
from google.auth._exponential_backoff import ExponentialBackoff
3233

3334
_LOGGER = logging.getLogger(__name__)
@@ -204,7 +205,17 @@ def get(
204205
for attempt in backoff:
205206
try:
206207
response = request(url=url, method="GET", headers=headers_to_use)
207-
break
208+
if response.status in transport.DEFAULT_RETRYABLE_STATUS_CODES:
209+
_LOGGER.warning(
210+
"Compute Engine Metadata server unavailable on "
211+
"attempt %s of %s. Response status: %s",
212+
attempt,
213+
retry_count,
214+
response.status,
215+
)
216+
continue
217+
else:
218+
break
208219

209220
except exceptions.TransportError as e:
210221
_LOGGER.warning(

google/auth/transport/_mtls_helper.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from google.auth import exceptions
2424

2525
CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json"
26-
_CERTIFICATE_CONFIGURATION_DEFAULT_PATH = "~/.config/gcloud/certificate_config.json"
26+
CERTIFICATE_CONFIGURATION_DEFAULT_PATH = "~/.config/gcloud/certificate_config.json"
2727
_CERTIFICATE_CONFIGURATION_ENV = "GOOGLE_API_CERTIFICATE_CONFIG"
2828
_CERT_PROVIDER_COMMAND = "cert_provider_command"
2929
_CERT_REGEX = re.compile(
@@ -48,21 +48,21 @@
4848
)
4949

5050

51-
def _check_dca_metadata_path(metadata_path):
52-
"""Checks for context aware metadata. If it exists, returns the absolute path;
51+
def _check_config_path(config_path):
52+
"""Checks for config file path. If it exists, returns the absolute path with user expansion;
5353
otherwise returns None.
5454
5555
Args:
56-
metadata_path (str): context aware metadata path.
56+
config_path (str): The config file path for either context_aware_metadata.json or certificate_config.json for example
5757
5858
Returns:
5959
str: absolute path if exists and None otherwise.
6060
"""
61-
metadata_path = path.expanduser(metadata_path)
62-
if not path.exists(metadata_path):
63-
_LOGGER.debug("%s is not found, skip client SSL authentication.", metadata_path)
61+
config_path = path.expanduser(config_path)
62+
if not path.exists(config_path):
63+
_LOGGER.debug("%s is not found.", config_path)
6464
return None
65-
return metadata_path
65+
return config_path
6666

6767

6868
def _load_json_file(path):
@@ -136,7 +136,7 @@ def _get_cert_config_path(certificate_config_path=None):
136136
if env_path is not None and env_path != "":
137137
certificate_config_path = env_path
138138
else:
139-
certificate_config_path = _CERTIFICATE_CONFIGURATION_DEFAULT_PATH
139+
certificate_config_path = CERTIFICATE_CONFIGURATION_DEFAULT_PATH
140140

141141
certificate_config_path = path.expanduser(certificate_config_path)
142142
if not path.exists(certificate_config_path):
@@ -279,14 +279,22 @@ def _run_cert_provider_command(command, expect_encrypted_key=False):
279279
def get_client_ssl_credentials(
280280
generate_encrypted_key=False,
281281
context_aware_metadata_path=CONTEXT_AWARE_METADATA_PATH,
282+
certificate_config_path=CERTIFICATE_CONFIGURATION_DEFAULT_PATH,
282283
):
283284
"""Returns the client side certificate, private key and passphrase.
284285
286+
We look for certificates and keys with the following order of priority:
287+
1. Certificate and key specified by certificate_config.json.
288+
Currently, only X.509 workload certificates are supported.
289+
2. Certificate and key specified by context aware metadata (i.e. SecureConnect).
290+
285291
Args:
286292
generate_encrypted_key (bool): If set to True, encrypted private key
287293
and passphrase will be generated; otherwise, unencrypted private key
288-
will be generated and passphrase will be None.
294+
will be generated and passphrase will be None. This option only
295+
affects keys obtained via context_aware_metadata.json.
289296
context_aware_metadata_path (str): The context_aware_metadata.json file path.
297+
certificate_config_path (str): The certificate_config.json file path.
290298
291299
Returns:
292300
Tuple[bool, bytes, bytes, bytes]:
@@ -297,7 +305,17 @@ def get_client_ssl_credentials(
297305
google.auth.exceptions.ClientCertError: if problems occurs when getting
298306
the cert, key and passphrase.
299307
"""
300-
metadata_path = _check_dca_metadata_path(context_aware_metadata_path)
308+
309+
# 1. Check for certificate config json.
310+
cert_config_path = _check_config_path(certificate_config_path)
311+
if cert_config_path:
312+
# Attempt to retrieve X.509 Workload cert and key.
313+
cert, key = _get_workload_cert_and_key(cert_config_path)
314+
if cert and key:
315+
return True, cert, key, None
316+
317+
# 2. Check for context aware metadata json
318+
metadata_path = _check_config_path(context_aware_metadata_path)
301319

302320
if metadata_path:
303321
metadata_json = _load_json_file(metadata_path)

google/auth/transport/grpc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def __init__(self):
302302
self._is_mtls = False
303303
else:
304304
# Load client SSL credentials.
305-
metadata_path = _mtls_helper._check_dca_metadata_path(
305+
metadata_path = _mtls_helper._check_config_path(
306306
_mtls_helper.CONTEXT_AWARE_METADATA_PATH
307307
)
308308
self._is_mtls = metadata_path is not None

google/auth/transport/mtls.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,19 @@ def has_default_client_cert_source():
2424
Returns:
2525
bool: indicating if the default client cert source exists.
2626
"""
27-
metadata_path = _mtls_helper._check_dca_metadata_path(
28-
_mtls_helper.CONTEXT_AWARE_METADATA_PATH
29-
)
30-
return metadata_path is not None
27+
if (
28+
_mtls_helper._check_config_path(_mtls_helper.CONTEXT_AWARE_METADATA_PATH)
29+
is not None
30+
):
31+
return True
32+
if (
33+
_mtls_helper._check_config_path(
34+
_mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH
35+
)
36+
is not None
37+
):
38+
return True
39+
return False
3140

3241

3342
def default_client_cert_source():

google/auth/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
__version__ = "2.32.0"
15+
__version__ = "2.34.0"

setup.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@
3232
"pyopenssl": ["pyopenssl>=20.0.0", "cryptography>=38.0.3"],
3333
"requests": "requests >= 2.20.0, < 3.0.0.dev0",
3434
"reauth": "pyu2f>=0.1.5",
35-
# Enterprise cert only works for OpenSSL 1.1.1. Newer versions of these
36-
# dependencies are built with OpenSSL 3.0 so we need to fix the version.
37-
"enterprise_cert": ["cryptography==36.0.2", "pyopenssl==22.0.0"],
35+
"enterprise_cert": ["cryptography", "pyopenssl"],
3836
}
3937

4038
with io.open("README.rst", "r") as fh:

system_tests/secrets.tar.enc

0 Bytes
Binary file not shown.

testing/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ grpcio
1717
pytest-asyncio; python_version > '3.0'
1818
aioresponses; python_version > '3.0'
1919
asynctest; python_version > '3.0'
20-
aiohttp; python_version > '3.0'
20+
aiohttp < 3.10.0; python_version > '3.0'

tests/compute_engine/test__metadata.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,74 @@ def test_get_universe_domain_not_found():
431431
assert universe_domain == "googleapis.com"
432432

433433

434+
def test_get_universe_domain_retryable_error_failure():
435+
# Test that if the universe domain endpoint returns a retryable error
436+
# we should retry.
437+
#
438+
# In this case, the error persists, and we still fail after retrying.
439+
request = make_request("too many requests", status=http_client.TOO_MANY_REQUESTS)
440+
441+
with pytest.raises(exceptions.TransportError) as excinfo:
442+
_metadata.get_universe_domain(request)
443+
444+
assert excinfo.match(r"Compute Engine Metadata server unavailable")
445+
446+
request.assert_called_with(
447+
method="GET",
448+
url=_metadata._METADATA_ROOT + "universe/universe_domain",
449+
headers=_metadata._METADATA_HEADERS,
450+
)
451+
assert request.call_count == 5
452+
453+
454+
def test_get_universe_domain_retryable_error_success():
455+
# Test that if the universe domain endpoint returns a retryable error
456+
# we should retry.
457+
#
458+
# In this case, the error is temporary, and we succeed after retrying.
459+
request_error = make_request(
460+
"too many requests", status=http_client.TOO_MANY_REQUESTS
461+
)
462+
request_ok = make_request(
463+
"fake_universe_domain", headers={"content-type": "text/plain"}
464+
)
465+
466+
class _RequestErrorOnce:
467+
"""This class forwards the request parameters to `request_error` once.
468+
469+
All subsequent calls are forwarded to `request_ok`.
470+
"""
471+
472+
def __init__(self, request_error, request_ok):
473+
self._request_error = request_error
474+
self._request_ok = request_ok
475+
self._call_index = 0
476+
477+
def request(self, *args, **kwargs):
478+
if self._call_index == 0:
479+
self._call_index += 1
480+
return self._request_error(*args, **kwargs)
481+
482+
return self._request_ok(*args, **kwargs)
483+
484+
request = _RequestErrorOnce(request_error, request_ok).request
485+
486+
universe_domain = _metadata.get_universe_domain(request)
487+
488+
request_error.assert_called_once_with(
489+
method="GET",
490+
url=_metadata._METADATA_ROOT + "universe/universe_domain",
491+
headers=_metadata._METADATA_HEADERS,
492+
)
493+
request_ok.assert_called_once_with(
494+
method="GET",
495+
url=_metadata._METADATA_ROOT + "universe/universe_domain",
496+
headers=_metadata._METADATA_HEADERS,
497+
)
498+
499+
assert universe_domain == "fake_universe_domain"
500+
501+
434502
def test_get_universe_domain_other_error():
435503
# Test that if the universe domain endpoint returns an error other than 404
436504
# we should throw the error

0 commit comments

Comments
 (0)