Skip to content

Commit 614a3d0

Browse files
fix(auth): use requests transport for GCE MDS (#16480)
Fixes #16090 We were seeing errors on GCE environments, because the library would use _http_client to communicate to the metadata server, even when mTLS is enabled. This resulted in failures, because _http_client doesn't support https. This PR addresses the issue by using requests as the transport instead. This depends on the optional `requests` dependency, but requests is already [required by compute engine](https://github.com/googleapis/google-cloud-python/blob/51a96200324965f071dd9542d25b907c1420b3f6/packages/google-auth/google/auth/compute_engine/_metadata.py#L27), so there are no changes there. I was able to reproduce and verify this test on a GCE VM --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent dea6a1b commit 614a3d0

File tree

3 files changed

+47
-10
lines changed

3 files changed

+47
-10
lines changed

packages/google-auth/google/auth/_default.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@
2727

2828
from google.auth import environment_vars
2929
from google.auth import exceptions
30-
import google.auth.transport._http_client
3130

3231
if TYPE_CHECKING: # pragma: NO COVER
33-
from google.auth.credentials import Credentials # noqa: F401
34-
from google.auth.transport import Request # noqa: F401
32+
import google.auth.credentials.Credentials # type: ignore
33+
import google.auth.transport.Request # type: ignore
3534

3635
_LOGGER = logging.getLogger(__name__)
3736

@@ -390,22 +389,19 @@ def _get_gae_credentials():
390389

391390
def _get_gce_credentials(request=None, quota_project_id=None):
392391
"""Gets credentials and project ID from the GCE Metadata Service."""
393-
# Ping requires a transport, but we want application default credentials
394-
# to require no arguments. So, we'll use the _http_client transport which
395-
# uses http.client. This is only acceptable because the metadata server
396-
# doesn't do SSL and never requires proxies.
397-
398392
# While this library is normally bundled with compute_engine, there are
399393
# some cases where it's not available, so we tolerate ImportError.
394+
# Compute Engine requires optional `requests` dependency.
400395
try:
401396
from google.auth import compute_engine
402397
from google.auth.compute_engine import _metadata
398+
import google.auth.transport.requests
403399
except ImportError:
404400
_LOGGER.warning("Import of Compute Engine auth library failed.")
405401
return None, None
406402

407403
if request is None:
408-
request = google.auth.transport._http_client.Request()
404+
request = google.auth.transport.requests.Request()
409405

410406
if _metadata.is_on_gce(request=request):
411407
# Get the project ID.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def __call__(
9494
if parts.scheme != "http":
9595
raise exceptions.TransportError(
9696
"http.client transport only supports the http scheme, {}"
97-
"was specified".format(parts.scheme)
97+
" was specified".format(parts.scheme)
9898
)
9999

100100
connection = http_client.HTTPConnection(parts.netloc, timeout=timeout)

packages/google-auth/tests/test__default.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,18 @@ def test__get_gce_credentials_explicit_request(ping):
890890
ping.assert_called_with(request=mock.sentinel.request)
891891

892892

893+
@mock.patch(
894+
"google.auth.compute_engine._metadata.is_on_gce", return_value=False, autospec=True
895+
)
896+
@mock.patch("google.auth.transport.requests.Request", autospec=True)
897+
def test__get_gce_credentials_default_request(mock_request_cls, ping):
898+
credentials, project_id = _default._get_gce_credentials()
899+
mock_request_cls.assert_called_once()
900+
ping.assert_called_with(request=mock_request_cls.return_value)
901+
assert credentials is None
902+
assert project_id is None
903+
904+
893905
@mock.patch(
894906
"google.auth._default._get_explicit_environ_credentials",
895907
return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
@@ -1006,6 +1018,35 @@ def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit):
10061018
assert excinfo.match(_default._CLOUD_SDK_MISSING_CREDENTIALS)
10071019

10081020

1021+
@mock.patch(
1022+
"google.auth._default._get_explicit_environ_credentials",
1023+
return_value=(None, None),
1024+
autospec=True,
1025+
)
1026+
@mock.patch(
1027+
"google.auth._default._get_gcloud_sdk_credentials",
1028+
return_value=(None, None),
1029+
autospec=True,
1030+
)
1031+
@mock.patch(
1032+
"google.auth._default._get_gae_credentials",
1033+
return_value=(None, None),
1034+
autospec=True,
1035+
)
1036+
@mock.patch(
1037+
"google.auth.compute_engine._metadata.is_on_gce", return_value=False, autospec=True
1038+
)
1039+
@mock.patch("google.auth.transport.requests.Request", autospec=True)
1040+
def test_default_gce_triggers_request_creation(
1041+
mock_request_cls, is_on_gce, unused_gae, unused_sdk, unused_explicit
1042+
):
1043+
with pytest.raises(exceptions.DefaultCredentialsError):
1044+
_default.default()
1045+
1046+
mock_request_cls.assert_called_once()
1047+
is_on_gce.assert_called_once_with(request=mock_request_cls.return_value)
1048+
1049+
10091050
@mock.patch(
10101051
"google.auth._default._get_explicit_environ_credentials",
10111052
return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),

0 commit comments

Comments
 (0)