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