-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(auth): add bound token support for access and JWT id tokens for Cloud Run #17698
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1171223
865fc5b
939d005
93cd395
82a07c4
7a295e1
f7fdd75
dbef436
76147a3
901c5f3
a6be0d2
d053dde
de557d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ | |
|
|
||
| import requests | ||
|
|
||
| from google.auth import _agent_identity_utils | ||
| from google.auth import _helpers | ||
| from google.auth import environment_vars | ||
| from google.auth import exceptions | ||
|
|
@@ -255,6 +256,8 @@ def get( | |
| headers=None, | ||
| return_none_for_not_found_error=False, | ||
| timeout=_METADATA_DEFAULT_TIMEOUT, | ||
| method="GET", | ||
| body=None, | ||
| ): | ||
| """Fetch a resource from the metadata server. | ||
|
|
||
|
|
@@ -276,6 +279,8 @@ def get( | |
| return_none_for_not_found_error (Optional[bool]): If True, returns None | ||
| for 404 error instead of throwing an exception. | ||
| timeout (int): How long to wait, in seconds for the metadata server to respond. | ||
| method (str): The HTTP method to use for the request. Defaults to "GET". | ||
|
nbayati marked this conversation as resolved.
|
||
| body (Optional[bytes]): The HTTP request body payload to send. Defaults to None. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to raise a ValueError (or similar) here to "exit early" if a body is specified byt the method is GET. While I think technically valid to include a body in GET requests (most often I think the body just gets ignored), it may lead a caller to think it is getting a bound token when in reality it isn't?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! that's a great suggestion! Done! |
||
|
|
||
| Returns: | ||
| Union[Mapping, str]: If the metadata server returns JSON, a mapping of | ||
|
|
@@ -288,8 +293,12 @@ def get( | |
| google.auth.exceptions.MutualTLSChannelError: if using mtls and the environment | ||
| configuration is invalid for mTLS (for example, the metadata host | ||
| has been overridden in strict mTLS mode). | ||
| ValueError: if a request body is specified with the GET method. | ||
|
|
||
| """ | ||
| if body is not None and method.upper() == "GET": | ||
| raise ValueError("Request body cannot be specified with GET method.") | ||
|
|
||
| use_mtls = _mtls.should_use_mds_mtls() | ||
| # Prepare the request object for mTLS if needed. | ||
| # This will create a new request object with the mTLS session. | ||
|
|
@@ -319,9 +328,15 @@ def get( | |
| last_exception = None | ||
| for attempt in backoff: | ||
| try: | ||
| response = request( | ||
| url=url, method="GET", headers=headers_to_use, timeout=timeout | ||
| ) | ||
| kwargs = { | ||
| "url": url, | ||
| "method": method, | ||
| "headers": headers_to_use, | ||
| "timeout": timeout, | ||
| } | ||
| if body is not None: | ||
| kwargs["body"] = body | ||
| response = request(**kwargs) | ||
| if response.status in transport.DEFAULT_RETRYABLE_STATUS_CODES: | ||
| _LOGGER.warning( | ||
| "Compute Engine Metadata server unavailable on " | ||
|
|
@@ -465,6 +480,32 @@ def get_service_account_info(request, service_account="default"): | |
| return get(request, path, params={"recursive": "true"}) | ||
|
|
||
|
|
||
| def _get_token_request_params(metrics_header_value): | ||
| """Returns (method, body, headers) for a metadata server token request. | ||
|
|
||
| Defaults to a standard GET request with the x-goog-api-client metrics header. | ||
| Upgrades to a POST request with a JSON certificate_chain body and | ||
| Content-Type header if an Agent Identity certificate is present and bound | ||
| tokens are enabled. | ||
|
|
||
| Args: | ||
| metrics_header_value (str): Value for the x-goog-api-client header. | ||
|
|
||
| Returns: | ||
| Tuple[str, Optional[bytes], Mapping[str, str]]: A tuple of | ||
| (HTTP method, request body bytes, request headers). | ||
| """ | ||
| headers = {metrics.API_CLIENT_HEADER: metrics_header_value} | ||
| cert, cert_bytes = _agent_identity_utils.get_agent_identity_certificate_and_bytes() | ||
| if cert and _agent_identity_utils.should_request_bound_token(cert): | ||
| headers["Content-Type"] = "application/json" | ||
| body = json.dumps({"certificate_chain": cert_bytes.decode("utf-8")}).encode( | ||
| "utf-8" | ||
| ) | ||
| return "POST", body, headers | ||
| return "GET", None, headers | ||
|
|
||
|
|
||
| def get_service_account_token(request, service_account="default", scopes=None): | ||
| """Get the OAuth 2.0 access token for a service account. | ||
|
|
||
|
|
@@ -483,26 +524,20 @@ def get_service_account_token(request, service_account="default", scopes=None): | |
| google.auth.exceptions.TransportError: if an error occurred while | ||
| retrieving metadata. | ||
| """ | ||
| from google.auth import _agent_identity_utils | ||
|
|
||
| params = {} | ||
| if scopes: | ||
| if not isinstance(scopes, str): | ||
| scopes = ",".join(scopes) | ||
| params["scopes"] = scopes | ||
|
|
||
| cert = _agent_identity_utils.get_and_parse_agent_identity_certificate() | ||
| if cert: | ||
| if _agent_identity_utils.should_request_bound_token(cert): | ||
| fingerprint = _agent_identity_utils.calculate_certificate_fingerprint(cert) | ||
| params["bindCertificateFingerprint"] = fingerprint | ||
|
|
||
| metrics_header = { | ||
| metrics.API_CLIENT_HEADER: metrics.token_request_access_token_mds() | ||
| } | ||
| method, body, headers = _get_token_request_params( | ||
| metrics.token_request_access_token_mds() | ||
| ) | ||
|
|
||
| path = "instance/service-accounts/{0}/token".format(service_account) | ||
| token_json = get(request, path, params=params, headers=metrics_header) | ||
| token_json = get( | ||
| request, path, params=params, headers=headers, method=method, body=body | ||
| ) | ||
| token_expiry = _helpers.utcnow() + datetime.timedelta( | ||
| seconds=token_json["expires_in"] | ||
| ) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.