2424import os
2525from urllib .parse import urljoin
2626
27+ import requests
28+
2729from google .auth import _helpers
2830from google .auth import environment_vars
2931from google .auth import exceptions
3032from google .auth import metrics
3133from google .auth import transport
3234from google .auth ._exponential_backoff import ExponentialBackoff
35+ from google .auth .compute_engine import _mtls
36+
3337
3438_LOGGER = logging .getLogger (__name__ )
3539
40+ _GCE_DEFAULT_MDS_IP = "169.254.169.254"
41+ _GCE_DEFAULT_HOST = "metadata.google.internal"
42+ _GCE_DEFAULT_MDS_HOSTS = [_GCE_DEFAULT_HOST , _GCE_DEFAULT_MDS_IP ]
43+
3644# Environment variable GCE_METADATA_HOST is originally named
3745# GCE_METADATA_ROOT. For compatibility reasons, here it checks
3846# the new variable first; if not set, the system falls back
3947# to the old variable.
4048_GCE_METADATA_HOST = os .getenv (environment_vars .GCE_METADATA_HOST , None )
4149if not _GCE_METADATA_HOST :
4250 _GCE_METADATA_HOST = os .getenv (
43- environment_vars .GCE_METADATA_ROOT , "metadata.google.internal"
51+ environment_vars .GCE_METADATA_ROOT , _GCE_DEFAULT_HOST
52+ )
53+
54+
55+ def _validate_gce_mds_configured_environment ():
56+ """Validates the GCE metadata server environment configuration for mTLS.
57+
58+ mTLS is only supported when connecting to the default metadata server hosts.
59+ If we are in strict mode (which requires mTLS), ensure that the metadata host
60+ has not been overridden to a custom value (which means mTLS will fail).
61+
62+ Raises:
63+ google.auth.exceptions.MutualTLSChannelError: if the environment
64+ configuration is invalid for mTLS.
65+ """
66+ mode = _mtls ._parse_mds_mode ()
67+ if mode == _mtls .MdsMtlsMode .STRICT :
68+ # mTLS is only supported when connecting to the default metadata host.
69+ # Raise an exception if we are in strict mode (which requires mTLS)
70+ # but the metadata host has been overridden to a custom MDS. (which means mTLS will fail)
71+ if _GCE_METADATA_HOST not in _GCE_DEFAULT_MDS_HOSTS :
72+ raise exceptions .MutualTLSChannelError (
73+ "Mutual TLS is required, but the metadata host has been overridden. "
74+ "mTLS is only supported when connecting to the default metadata host."
75+ )
76+
77+
78+ def _get_metadata_root (use_mtls : bool ):
79+ """Returns the metadata server root URL."""
80+
81+ scheme = "https" if use_mtls else "http"
82+ return "{}://{}/computeMetadata/v1/" .format (scheme , _GCE_METADATA_HOST )
83+
84+
85+ def _get_metadata_ip_root (use_mtls : bool ):
86+ """Returns the metadata server IP root URL."""
87+ scheme = "https" if use_mtls else "http"
88+ return "{}://{}" .format (
89+ scheme , os .getenv (environment_vars .GCE_METADATA_IP , _GCE_DEFAULT_MDS_IP )
4490 )
45- _METADATA_ROOT = "http://{}/computeMetadata/v1/" .format (_GCE_METADATA_HOST )
4691
47- # This is used to ping the metadata server, it avoids the cost of a DNS
48- # lookup.
49- _METADATA_IP_ROOT = "http://{}" .format (
50- os .getenv (environment_vars .GCE_METADATA_IP , "169.254.169.254" )
51- )
92+
5293_METADATA_FLAVOR_HEADER = "metadata-flavor"
5394_METADATA_FLAVOR_VALUE = "Google"
5495_METADATA_HEADERS = {_METADATA_FLAVOR_HEADER : _METADATA_FLAVOR_VALUE }
@@ -102,6 +143,33 @@ def detect_gce_residency_linux():
102143 return content .startswith (_GOOGLE )
103144
104145
146+ def _prepare_request_for_mds (request , use_mtls = False ) -> None :
147+ """Prepares a request for the metadata server.
148+
149+ This will check if mTLS should be used and mount the mTLS adapter if needed.
150+
151+ Args:
152+ request (google.auth.transport.Request): A callable used to make
153+ HTTP requests.
154+ use_mtls (bool): Whether to use mTLS for the request.
155+
156+ Returns:
157+ google.auth.transport.Request: A request object to use.
158+ If mTLS is enabled, the request will have the mTLS adapter mounted.
159+ Otherwise, the original request will be returned unchanged.
160+ """
161+ # Only modify the request if mTLS is enabled.
162+ if use_mtls :
163+ # Ensure the request has a session to mount the adapter to.
164+ if not request .session :
165+ request .session = requests .Session ()
166+
167+ adapter = _mtls .MdsMtlsAdapter ()
168+ # Mount the adapter for all default GCE metadata hosts.
169+ for host in _GCE_DEFAULT_MDS_HOSTS :
170+ request .session .mount (f"https://{ host } /" , adapter )
171+
172+
105173def ping (request , timeout = _METADATA_DEFAULT_TIMEOUT , retry_count = 3 ):
106174 """Checks to see if the metadata server is available.
107175
@@ -115,6 +183,8 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3):
115183 Returns:
116184 bool: True if the metadata server is reachable, False otherwise.
117185 """
186+ use_mtls = _mtls .should_use_mds_mtls ()
187+ _prepare_request_for_mds (request , use_mtls = use_mtls )
118188 # NOTE: The explicit ``timeout`` is a workaround. The underlying
119189 # issue is that resolving an unknown host on some networks will take
120190 # 20-30 seconds; making this timeout short fixes the issue, but
@@ -129,7 +199,10 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3):
129199 for attempt in backoff :
130200 try :
131201 response = request (
132- url = _METADATA_IP_ROOT , method = "GET" , headers = headers , timeout = timeout
202+ url = _get_metadata_ip_root (use_mtls ),
203+ method = "GET" ,
204+ headers = headers ,
205+ timeout = timeout ,
133206 )
134207
135208 metadata_flavor = response .headers .get (_METADATA_FLAVOR_HEADER )
@@ -153,7 +226,7 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3):
153226def get (
154227 request ,
155228 path ,
156- root = _METADATA_ROOT ,
229+ root = None ,
157230 params = None ,
158231 recursive = False ,
159232 retry_count = 5 ,
@@ -168,7 +241,8 @@ def get(
168241 HTTP requests.
169242 path (str): The resource to retrieve. For example,
170243 ``'instance/service-accounts/default'``.
171- root (str): The full path to the metadata server root.
244+ root (Optional[str]): The full path to the metadata server root. If not
245+ provided, the default root will be used.
172246 params (Optional[Mapping[str, str]]): A mapping of query parameter
173247 keys to values.
174248 recursive (bool): Whether to do a recursive query of metadata. See
@@ -189,7 +263,24 @@ def get(
189263 Raises:
190264 google.auth.exceptions.TransportError: if an error occurred while
191265 retrieving metadata.
266+ google.auth.exceptions.MutualTLSChannelError: if using mtls and the environment
267+ configuration is invalid for mTLS (for example, the metadata host
268+ has been overridden in strict mTLS mode).
269+
192270 """
271+ use_mtls = _mtls .should_use_mds_mtls ()
272+ # Prepare the request object for mTLS if needed.
273+ # This will create a new request object with the mTLS session.
274+ _prepare_request_for_mds (request , use_mtls = use_mtls )
275+
276+ if root is None :
277+ root = _get_metadata_root (use_mtls )
278+
279+ # mTLS is only supported when connecting to the default metadata host.
280+ # If we are in strict mode (which requires mTLS), ensure that the metadata host
281+ # has not been overridden to a non-default host value (which means mTLS will fail).
282+ _validate_gce_mds_configured_environment ()
283+
193284 base_url = urljoin (root , path )
194285 query_params = {} if params is None else params
195286
0 commit comments