blob: c8deee7079f430cc1dfea363d02015dc921fac0d [file] [log] [blame]
bojeil-googled4d7f382021-02-16 12:33:20 -08001# Copyright 2020 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""AWS Credentials and AWS Signature V4 Request Signer.
16
17This module provides credentials to access Google Cloud resources from Amazon
18Web Services (AWS) workloads. These credentials are recommended over the
19use of service account credentials in AWS as they do not involve the management
20of long-live service account private keys.
21
22AWS Credentials are initialized using external_account arguments which are
23typically loaded from the external credentials JSON file.
24Unlike other Credentials that can be initialized with a list of explicit
25arguments, secrets or credentials, external account clients use the
26environment and hints/guidelines provided by the external_account JSON
27file to retrieve credentials and exchange them for Google access tokens.
28
29This module also provides a basic implementation of the
30`AWS Signature Version 4`_ request signing algorithm.
31
32AWS Credentials use serialized signed requests to the
33`AWS STS GetCallerIdentity`_ API that can be exchanged for Google access tokens
34via the GCP STS endpoint.
35
36.. _AWS Signature Version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
37.. _AWS STS GetCallerIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html
38"""
39
40import hashlib
41import hmac
Tres Seaver560cf1e2021-08-03 16:35:54 -040042import http.client
bojeil-googled4d7f382021-02-16 12:33:20 -080043import io
44import json
45import os
46import re
Tres Seaver560cf1e2021-08-03 16:35:54 -040047import urllib
bojeil-google4e0fb1c2021-08-17 15:56:39 -070048from urllib.parse import urljoin
bojeil-googled4d7f382021-02-16 12:33:20 -080049
50from google.auth import _helpers
51from google.auth import environment_vars
52from google.auth import exceptions
53from google.auth import external_account
54
55# AWS Signature Version 4 signing algorithm identifier.
56_AWS_ALGORITHM = "AWS4-HMAC-SHA256"
57# The termination string for the AWS credential scope value as defined in
58# https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
59_AWS_REQUEST_TYPE = "aws4_request"
60# The AWS authorization header name for the security session token if available.
61_AWS_SECURITY_TOKEN_HEADER = "x-amz-security-token"
62# The AWS authorization header name for the auto-generated date.
63_AWS_DATE_HEADER = "x-amz-date"
64
65
66class RequestSigner(object):
67 """Implements an AWS request signer based on the AWS Signature Version 4 signing
68 process.
69 https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
70 """
71
72 def __init__(self, region_name):
73 """Instantiates an AWS request signer used to compute authenticated signed
74 requests to AWS APIs based on the AWS Signature Version 4 signing process.
75
76 Args:
77 region_name (str): The AWS region to use.
78 """
79
80 self._region_name = region_name
81
82 def get_request_options(
83 self,
84 aws_security_credentials,
85 url,
86 method,
87 request_payload="",
88 additional_headers={},
89 ):
90 """Generates the signed request for the provided HTTP request for calling
91 an AWS API. This follows the steps described at:
92 https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
93
94 Args:
95 aws_security_credentials (Mapping[str, str]): A dictionary containing
96 the AWS security credentials.
97 url (str): The AWS service URL containing the canonical URI and
98 query string.
99 method (str): The HTTP method used to call this API.
100 request_payload (Optional[str]): The optional request payload if
101 available.
102 additional_headers (Optional[Mapping[str, str]]): The optional
103 additional headers needed for the requested AWS API.
104
105 Returns:
106 Mapping[str, str]: The AWS signed request dictionary object.
107 """
108 # Get AWS credentials.
109 access_key = aws_security_credentials.get("access_key_id")
110 secret_key = aws_security_credentials.get("secret_access_key")
111 security_token = aws_security_credentials.get("security_token")
112
113 additional_headers = additional_headers or {}
114
115 uri = urllib.parse.urlparse(url)
bojeil-google4e0fb1c2021-08-17 15:56:39 -0700116 # Normalize the URL path. This is needed for the canonical_uri.
117 # os.path.normpath can't be used since it normalizes "/" paths
118 # to "\\" in Windows OS.
119 normalized_uri = urllib.parse.urlparse(urljoin(url, uri.path))
bojeil-googled4d7f382021-02-16 12:33:20 -0800120 # Validate provided URL.
121 if not uri.hostname or uri.scheme != "https":
122 raise ValueError("Invalid AWS service URL")
123
124 header_map = _generate_authentication_header_map(
125 host=uri.hostname,
bojeil-google4e0fb1c2021-08-17 15:56:39 -0700126 canonical_uri=normalized_uri.path or "/",
bojeil-googled4d7f382021-02-16 12:33:20 -0800127 canonical_querystring=_get_canonical_querystring(uri.query),
128 method=method,
129 region=self._region_name,
130 access_key=access_key,
131 secret_key=secret_key,
132 security_token=security_token,
133 request_payload=request_payload,
134 additional_headers=additional_headers,
135 )
136 headers = {
137 "Authorization": header_map.get("authorization_header"),
138 "host": uri.hostname,
139 }
140 # Add x-amz-date if available.
141 if "amz_date" in header_map:
142 headers[_AWS_DATE_HEADER] = header_map.get("amz_date")
143 # Append additional optional headers, eg. X-Amz-Target, Content-Type, etc.
144 for key in additional_headers:
145 headers[key] = additional_headers[key]
146
147 # Add session token if available.
148 if security_token is not None:
149 headers[_AWS_SECURITY_TOKEN_HEADER] = security_token
150
151 signed_request = {"url": url, "method": method, "headers": headers}
152 if request_payload:
153 signed_request["data"] = request_payload
154 return signed_request
155
156
157def _get_canonical_querystring(query):
158 """Generates the canonical query string given a raw query string.
159 Logic is based on
160 https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
161
162 Args:
163 query (str): The raw query string.
164
165 Returns:
166 str: The canonical query string.
167 """
168 # Parse raw query string.
169 querystring = urllib.parse.parse_qs(query)
170 querystring_encoded_map = {}
171 for key in querystring:
172 quote_key = urllib.parse.quote(key, safe="-_.~")
173 # URI encode key.
174 querystring_encoded_map[quote_key] = []
175 for item in querystring[key]:
176 # For each key, URI encode all values for that key.
177 querystring_encoded_map[quote_key].append(
178 urllib.parse.quote(item, safe="-_.~")
179 )
180 # Sort values for each key.
181 querystring_encoded_map[quote_key].sort()
182 # Sort keys.
183 sorted_keys = list(querystring_encoded_map.keys())
184 sorted_keys.sort()
185 # Reconstruct the query string. Preserve keys with multiple values.
186 querystring_encoded_pairs = []
187 for key in sorted_keys:
188 for item in querystring_encoded_map[key]:
189 querystring_encoded_pairs.append("{}={}".format(key, item))
190 return "&".join(querystring_encoded_pairs)
191
192
193def _sign(key, msg):
194 """Creates the HMAC-SHA256 hash of the provided message using the provided
195 key.
196
197 Args:
198 key (str): The HMAC-SHA256 key to use.
199 msg (str): The message to hash.
200
201 Returns:
202 str: The computed hash bytes.
203 """
204 return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
205
206
207def _get_signing_key(key, date_stamp, region_name, service_name):
208 """Calculates the signing key used to calculate the signature for
209 AWS Signature Version 4 based on:
210 https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
211
212 Args:
213 key (str): The AWS secret access key.
214 date_stamp (str): The '%Y%m%d' date format.
215 region_name (str): The AWS region.
216 service_name (str): The AWS service name, eg. sts.
217
218 Returns:
219 str: The signing key bytes.
220 """
221 k_date = _sign(("AWS4" + key).encode("utf-8"), date_stamp)
222 k_region = _sign(k_date, region_name)
223 k_service = _sign(k_region, service_name)
224 k_signing = _sign(k_service, "aws4_request")
225 return k_signing
226
227
228def _generate_authentication_header_map(
229 host,
230 canonical_uri,
231 canonical_querystring,
232 method,
233 region,
234 access_key,
235 secret_key,
236 security_token,
237 request_payload="",
238 additional_headers={},
239):
240 """Generates the authentication header map needed for generating the AWS
241 Signature Version 4 signed request.
242
243 Args:
244 host (str): The AWS service URL hostname.
245 canonical_uri (str): The AWS service URL path name.
246 canonical_querystring (str): The AWS service URL query string.
247 method (str): The HTTP method used to call this API.
248 region (str): The AWS region.
249 access_key (str): The AWS access key ID.
250 secret_key (str): The AWS secret access key.
251 security_token (Optional[str]): The AWS security session token. This is
252 available for temporary sessions.
253 request_payload (Optional[str]): The optional request payload if
254 available.
255 additional_headers (Optional[Mapping[str, str]]): The optional
256 additional headers needed for the requested AWS API.
257
258 Returns:
259 Mapping[str, str]: The AWS authentication header dictionary object.
260 This contains the x-amz-date and authorization header information.
261 """
262 # iam.amazonaws.com host => iam service.
263 # sts.us-east-2.amazonaws.com host => sts service.
264 service_name = host.split(".")[0]
265
266 current_time = _helpers.utcnow()
267 amz_date = current_time.strftime("%Y%m%dT%H%M%SZ")
268 date_stamp = current_time.strftime("%Y%m%d")
269
270 # Change all additional headers to be lower case.
271 full_headers = {}
272 for key in additional_headers:
273 full_headers[key.lower()] = additional_headers[key]
274 # Add AWS session token if available.
275 if security_token is not None:
276 full_headers[_AWS_SECURITY_TOKEN_HEADER] = security_token
277
278 # Required headers
279 full_headers["host"] = host
280 # Do not use generated x-amz-date if the date header is provided.
281 # Previously the date was not fixed with x-amz- and could be provided
282 # manually.
283 # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req
284 if "date" not in full_headers:
285 full_headers[_AWS_DATE_HEADER] = amz_date
286
287 # Header keys need to be sorted alphabetically.
288 canonical_headers = ""
289 header_keys = list(full_headers.keys())
290 header_keys.sort()
291 for key in header_keys:
292 canonical_headers = "{}{}:{}\n".format(
293 canonical_headers, key, full_headers[key]
294 )
295 signed_headers = ";".join(header_keys)
296
297 payload_hash = hashlib.sha256((request_payload or "").encode("utf-8")).hexdigest()
298
299 # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
300 canonical_request = "{}\n{}\n{}\n{}\n{}\n{}".format(
301 method,
302 canonical_uri,
303 canonical_querystring,
304 canonical_headers,
305 signed_headers,
306 payload_hash,
307 )
308
309 credential_scope = "{}/{}/{}/{}".format(
310 date_stamp, region, service_name, _AWS_REQUEST_TYPE
311 )
312
313 # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
314 string_to_sign = "{}\n{}\n{}\n{}".format(
315 _AWS_ALGORITHM,
316 amz_date,
317 credential_scope,
318 hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(),
319 )
320
321 # https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
322 signing_key = _get_signing_key(secret_key, date_stamp, region, service_name)
323 signature = hmac.new(
324 signing_key, string_to_sign.encode("utf-8"), hashlib.sha256
325 ).hexdigest()
326
327 # https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
328 authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format(
329 _AWS_ALGORITHM, access_key, credential_scope, signed_headers, signature
330 )
331
332 authentication_header = {"authorization_header": authorization_header}
333 # Do not use generated x-amz-date if the date header is provided.
334 if "date" not in full_headers:
335 authentication_header["amz_date"] = amz_date
336 return authentication_header
337
338
339class Credentials(external_account.Credentials):
340 """AWS external account credentials.
341 This is used to exchange serialized AWS signature v4 signed requests to
342 AWS STS GetCallerIdentity service for Google access tokens.
343 """
344
345 def __init__(
346 self,
347 audience,
348 subject_token_type,
349 token_url,
350 credential_source=None,
351 service_account_impersonation_url=None,
352 client_id=None,
353 client_secret=None,
354 quota_project_id=None,
355 scopes=None,
356 default_scopes=None,
357 ):
358 """Instantiates an AWS workload external account credentials object.
359
360 Args:
361 audience (str): The STS audience field.
362 subject_token_type (str): The subject token type.
363 token_url (str): The STS endpoint URL.
364 credential_source (Mapping): The credential source dictionary used
365 to provide instructions on how to retrieve external credential
366 to be exchanged for Google access tokens.
367 service_account_impersonation_url (Optional[str]): The optional
368 service account impersonation getAccessToken URL.
369 client_id (Optional[str]): The optional client ID.
370 client_secret (Optional[str]): The optional client secret.
371 quota_project_id (Optional[str]): The optional quota project ID.
372 scopes (Optional[Sequence[str]]): Optional scopes to request during
373 the authorization grant.
374 default_scopes (Optional[Sequence[str]]): Default scopes passed by a
375 Google client library. Use 'scopes' for user-defined scopes.
376
377 Raises:
378 google.auth.exceptions.RefreshError: If an error is encountered during
379 access token retrieval logic.
380 ValueError: For invalid parameters.
381
382 .. note:: Typically one of the helper constructors
383 :meth:`from_file` or
384 :meth:`from_info` are used instead of calling the constructor directly.
385 """
386 super(Credentials, self).__init__(
387 audience=audience,
388 subject_token_type=subject_token_type,
389 token_url=token_url,
390 credential_source=credential_source,
391 service_account_impersonation_url=service_account_impersonation_url,
392 client_id=client_id,
393 client_secret=client_secret,
394 quota_project_id=quota_project_id,
395 scopes=scopes,
396 default_scopes=default_scopes,
397 )
398 credential_source = credential_source or {}
399 self._environment_id = credential_source.get("environment_id") or ""
400 self._region_url = credential_source.get("region_url")
401 self._security_credentials_url = credential_source.get("url")
402 self._cred_verification_url = credential_source.get(
403 "regional_cred_verification_url"
404 )
405 self._region = None
406 self._request_signer = None
407 self._target_resource = audience
408
409 # Get the environment ID. Currently, only one version supported (v1).
410 matches = re.match(r"^(aws)([\d]+)$", self._environment_id)
411 if matches:
412 env_id, env_version = matches.groups()
413 else:
414 env_id, env_version = (None, None)
415
416 if env_id != "aws" or self._cred_verification_url is None:
417 raise ValueError("No valid AWS 'credential_source' provided")
418 elif int(env_version or "") != 1:
419 raise ValueError(
420 "aws version '{}' is not supported in the current build.".format(
421 env_version
422 )
423 )
424
425 def retrieve_subject_token(self, request):
426 """Retrieves the subject token using the credential_source object.
427 The subject token is a serialized `AWS GetCallerIdentity signed request`_.
428
429 The logic is summarized as:
430
Ryan Kohler199da472021-03-12 16:04:02 -0800431 Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION
432 environment variable or from the AWS metadata server availability-zone
433 if not found in the environment variable.
bojeil-googled4d7f382021-02-16 12:33:20 -0800434
435 Check AWS credentials in environment variables. If not found, retrieve
436 from the AWS metadata server security-credentials endpoint.
437
438 When retrieving AWS credentials from the metadata server
439 security-credentials endpoint, the AWS role needs to be determined by
440 calling the security-credentials endpoint without any argument. Then the
441 credentials can be retrieved via: security-credentials/role_name
442
443 Generate the signed request to AWS STS GetCallerIdentity action.
444
445 Inject x-goog-cloud-target-resource into header and serialize the
446 signed request. This will be the subject-token to pass to GCP STS.
447
448 .. _AWS GetCallerIdentity signed request:
449 https://cloud.google.com/iam/docs/access-resources-aws#exchange-token
450
451 Args:
452 request (google.auth.transport.Request): A callable used to make
453 HTTP requests.
454 Returns:
455 str: The retrieved subject token.
456 """
457 # Initialize the request signer if not yet initialized after determining
458 # the current AWS region.
459 if self._request_signer is None:
460 self._region = self._get_region(request, self._region_url)
461 self._request_signer = RequestSigner(self._region)
462
463 # Retrieve the AWS security credentials needed to generate the signed
464 # request.
465 aws_security_credentials = self._get_security_credentials(request)
466 # Generate the signed request to AWS STS GetCallerIdentity API.
467 # Use the required regional endpoint. Otherwise, the request will fail.
468 request_options = self._request_signer.get_request_options(
469 aws_security_credentials,
470 self._cred_verification_url.replace("{region}", self._region),
471 "POST",
472 )
473 # The GCP STS endpoint expects the headers to be formatted as:
474 # [
475 # {key: 'x-amz-date', value: '...'},
476 # {key: 'Authorization', value: '...'},
477 # ...
478 # ]
479 # And then serialized as:
480 # quote(json.dumps({
481 # url: '...',
482 # method: 'POST',
483 # headers: [{key: 'x-amz-date', value: '...'}, ...]
484 # }))
485 request_headers = request_options.get("headers")
486 # The full, canonical resource name of the workload identity pool
487 # provider, with or without the HTTPS prefix.
488 # Including this header as part of the signature is recommended to
489 # ensure data integrity.
490 request_headers["x-goog-cloud-target-resource"] = self._target_resource
491
492 # Serialize AWS signed request.
493 # Keeping inner keys in sorted order makes testing easier for Python
494 # versions <=3.5 as the stringified JSON string would have a predictable
495 # key order.
496 aws_signed_req = {}
497 aws_signed_req["url"] = request_options.get("url")
498 aws_signed_req["method"] = request_options.get("method")
499 aws_signed_req["headers"] = []
500 # Reformat header to GCP STS expected format.
501 for key in sorted(request_headers.keys()):
502 aws_signed_req["headers"].append(
503 {"key": key, "value": request_headers[key]}
504 )
505
506 return urllib.parse.quote(
507 json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True)
508 )
509
510 def _get_region(self, request, url):
Ryan Kohler199da472021-03-12 16:04:02 -0800511 """Retrieves the current AWS region from either the AWS_REGION or
512 AWS_DEFAULT_REGION environment variable or from the AWS metadata server.
bojeil-googled4d7f382021-02-16 12:33:20 -0800513
514 Args:
515 request (google.auth.transport.Request): A callable used to make
516 HTTP requests.
517 url (str): The AWS metadata server region URL.
518
519 Returns:
520 str: The current AWS region.
521
522 Raises:
523 google.auth.exceptions.RefreshError: If an error occurs while
524 retrieving the AWS region.
525 """
526 # The AWS metadata server is not available in some AWS environments
527 # such as AWS lambda. Instead, it is available via environment
528 # variable.
529 env_aws_region = os.environ.get(environment_vars.AWS_REGION)
530 if env_aws_region is not None:
531 return env_aws_region
532
Ryan Kohler199da472021-03-12 16:04:02 -0800533 env_aws_region = os.environ.get(environment_vars.AWS_DEFAULT_REGION)
534 if env_aws_region is not None:
535 return env_aws_region
536
bojeil-googled4d7f382021-02-16 12:33:20 -0800537 if not self._region_url:
538 raise exceptions.RefreshError("Unable to determine AWS region")
539 response = request(url=self._region_url, method="GET")
540
541 # Support both string and bytes type response.data.
542 response_body = (
543 response.data.decode("utf-8")
544 if hasattr(response.data, "decode")
545 else response.data
546 )
547
548 if response.status != 200:
549 raise exceptions.RefreshError(
550 "Unable to retrieve AWS region", response_body
551 )
552
553 # This endpoint will return the region in format: us-east-2b.
554 # Only the us-east-2 part should be used.
555 return response_body[:-1]
556
557 def _get_security_credentials(self, request):
558 """Retrieves the AWS security credentials required for signing AWS
559 requests from either the AWS security credentials environment variables
560 or from the AWS metadata server.
561
562 Args:
563 request (google.auth.transport.Request): A callable used to make
564 HTTP requests.
565
566 Returns:
567 Mapping[str, str]: The AWS security credentials dictionary object.
568
569 Raises:
570 google.auth.exceptions.RefreshError: If an error occurs while
571 retrieving the AWS security credentials.
572 """
573
574 # Check environment variables for permanent credentials first.
575 # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
576 env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID)
577 env_aws_secret_access_key = os.environ.get(
578 environment_vars.AWS_SECRET_ACCESS_KEY
579 )
580 # This is normally not available for permanent credentials.
581 env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN)
582 if env_aws_access_key_id and env_aws_secret_access_key:
583 return {
584 "access_key_id": env_aws_access_key_id,
585 "secret_access_key": env_aws_secret_access_key,
586 "security_token": env_aws_session_token,
587 }
588
589 # Get role name.
590 role_name = self._get_metadata_role_name(request)
591
592 # Get security credentials.
593 credentials = self._get_metadata_security_credentials(request, role_name)
594
595 return {
596 "access_key_id": credentials.get("AccessKeyId"),
597 "secret_access_key": credentials.get("SecretAccessKey"),
598 "security_token": credentials.get("Token"),
599 }
600
601 def _get_metadata_security_credentials(self, request, role_name):
602 """Retrieves the AWS security credentials required for signing AWS
603 requests from the AWS metadata server.
604
605 Args:
606 request (google.auth.transport.Request): A callable used to make
607 HTTP requests.
608 role_name (str): The AWS role name required by the AWS metadata
609 server security_credentials endpoint in order to return the
610 credentials.
611
612 Returns:
613 Mapping[str, str]: The AWS metadata server security credentials
614 response.
615
616 Raises:
617 google.auth.exceptions.RefreshError: If an error occurs while
618 retrieving the AWS security credentials.
619 """
620 headers = {"Content-Type": "application/json"}
621 response = request(
622 url="{}/{}".format(self._security_credentials_url, role_name),
623 method="GET",
624 headers=headers,
625 )
626
627 # support both string and bytes type response.data
628 response_body = (
629 response.data.decode("utf-8")
630 if hasattr(response.data, "decode")
631 else response.data
632 )
633
Tres Seaver560cf1e2021-08-03 16:35:54 -0400634 if response.status != http.client.OK:
bojeil-googled4d7f382021-02-16 12:33:20 -0800635 raise exceptions.RefreshError(
636 "Unable to retrieve AWS security credentials", response_body
637 )
638
639 credentials_response = json.loads(response_body)
640
641 return credentials_response
642
643 def _get_metadata_role_name(self, request):
644 """Retrieves the AWS role currently attached to the current AWS
645 workload by querying the AWS metadata server. This is needed for the
646 AWS metadata server security credentials endpoint in order to retrieve
647 the AWS security credentials needed to sign requests to AWS APIs.
648
649 Args:
650 request (google.auth.transport.Request): A callable used to make
651 HTTP requests.
652
653 Returns:
654 str: The AWS role name.
655
656 Raises:
657 google.auth.exceptions.RefreshError: If an error occurs while
658 retrieving the AWS role name.
659 """
660 if self._security_credentials_url is None:
661 raise exceptions.RefreshError(
662 "Unable to determine the AWS metadata server security credentials endpoint"
663 )
664 response = request(url=self._security_credentials_url, method="GET")
665
666 # support both string and bytes type response.data
667 response_body = (
668 response.data.decode("utf-8")
669 if hasattr(response.data, "decode")
670 else response.data
671 )
672
Tres Seaver560cf1e2021-08-03 16:35:54 -0400673 if response.status != http.client.OK:
bojeil-googled4d7f382021-02-16 12:33:20 -0800674 raise exceptions.RefreshError(
675 "Unable to retrieve AWS role name", response_body
676 )
677
678 return response_body
679
680 @classmethod
681 def from_info(cls, info, **kwargs):
682 """Creates an AWS Credentials instance from parsed external account info.
683
684 Args:
685 info (Mapping[str, str]): The AWS external account info in Google
686 format.
687 kwargs: Additional arguments to pass to the constructor.
688
689 Returns:
690 google.auth.aws.Credentials: The constructed credentials.
691
692 Raises:
693 ValueError: For invalid parameters.
694 """
695 return cls(
696 audience=info.get("audience"),
697 subject_token_type=info.get("subject_token_type"),
698 token_url=info.get("token_url"),
699 service_account_impersonation_url=info.get(
700 "service_account_impersonation_url"
701 ),
702 client_id=info.get("client_id"),
703 client_secret=info.get("client_secret"),
704 credential_source=info.get("credential_source"),
705 quota_project_id=info.get("quota_project_id"),
706 **kwargs
707 )
708
709 @classmethod
710 def from_file(cls, filename, **kwargs):
711 """Creates an AWS Credentials instance from an external account json file.
712
713 Args:
714 filename (str): The path to the AWS external account json file.
715 kwargs: Additional arguments to pass to the constructor.
716
717 Returns:
718 google.auth.aws.Credentials: The constructed credentials.
719 """
720 with io.open(filename, "r", encoding="utf-8") as json_file:
721 data = json.load(json_file)
722 return cls.from_info(data, **kwargs)