Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
aws.py862 linesDownload Raw Back to auth
1# Copyright 2020 Google LLC2#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 at6#7#      http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# 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 and13# 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 Amazon18Web Services (AWS) workloads. These credentials are recommended over the19use of service account credentials in AWS as they do not involve the management20of long-live service account private keys.21 22AWS Credentials are initialized using external_account arguments which are23typically loaded from the external credentials JSON file.24 25This module also provides a definition for an abstract AWS security credentials supplier.26This supplier can be implemented to return valid AWS security credentials and an AWS region27and used to create AWS credentials. The credentials will then call the28supplier instead of using pre-defined methods such as calling the EC2 metadata endpoints.29 30This module also provides a basic implementation of the31`AWS Signature Version 4`_ request signing algorithm.32 33AWS Credentials use serialized signed requests to the34`AWS STS GetCallerIdentity`_ API that can be exchanged for Google access tokens35via the GCP STS endpoint.36 37.. _AWS Signature Version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html38.. _AWS STS GetCallerIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html39"""40 41import abc42from dataclasses import dataclass43import hashlib44import hmac45import http.client as http_client46import json47import os48import posixpath49import re50from typing import Optional51import urllib52from urllib.parse import urljoin53 54from google.auth import _helpers55from google.auth import environment_vars56from google.auth import exceptions57from google.auth import external_account58 59# AWS Signature Version 4 signing algorithm identifier.60_AWS_ALGORITHM = "AWS4-HMAC-SHA256"61# The termination string for the AWS credential scope value as defined in62# https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html63_AWS_REQUEST_TYPE = "aws4_request"64# The AWS authorization header name for the security session token if available.65_AWS_SECURITY_TOKEN_HEADER = "x-amz-security-token"66# The AWS authorization header name for the auto-generated date.67_AWS_DATE_HEADER = "x-amz-date"68# The default AWS regional credential verification URL.69_DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL = (70    "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"71)72# IMDSV2 session token lifetime. This is set to a low value because the session token is used immediately.73_IMDSV2_SESSION_TOKEN_TTL_SECONDS = "300"74 75 76class RequestSigner(object):77    """Implements an AWS request signer based on the AWS Signature Version 4 signing78    process.79    https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html80    """81 82    def __init__(self, region_name):83        """Instantiates an AWS request signer used to compute authenticated signed84        requests to AWS APIs based on the AWS Signature Version 4 signing process.85 86        Args:87            region_name (str): The AWS region to use.88        """89 90        self._region_name = region_name91 92    def get_request_options(93        self,94        aws_security_credentials,95        url,96        method,97        request_payload="",98        additional_headers={},99    ):100        """Generates the signed request for the provided HTTP request for calling101        an AWS API. This follows the steps described at:102        https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html103 104        Args:105            aws_security_credentials (AWSSecurityCredentials): The AWS security credentials.106            url (str): The AWS service URL containing the canonical URI and107                query string.108            method (str): The HTTP method used to call this API.109            request_payload (Optional[str]): The optional request payload if110                available.111            additional_headers (Optional[Mapping[str, str]]): The optional112                additional headers needed for the requested AWS API.113 114        Returns:115            Mapping[str, str]: The AWS signed request dictionary object.116        """117 118        additional_headers = additional_headers or {}119 120        uri = urllib.parse.urlparse(url)121        # Normalize the URL path. This is needed for the canonical_uri.122        # os.path.normpath can't be used since it normalizes "/" paths123        # to "\\" in Windows OS.124        normalized_uri = urllib.parse.urlparse(125            urljoin(url, posixpath.normpath(uri.path))126        )127        # Validate provided URL.128        if not uri.hostname or uri.scheme != "https":129            raise exceptions.InvalidResource("Invalid AWS service URL")130 131        header_map = _generate_authentication_header_map(132            host=uri.hostname,133            canonical_uri=normalized_uri.path or "/",134            canonical_querystring=_get_canonical_querystring(uri.query),135            method=method,136            region=self._region_name,137            aws_security_credentials=aws_security_credentials,138            request_payload=request_payload,139            additional_headers=additional_headers,140        )141        headers = {142            "Authorization": header_map.get("authorization_header"),143            "host": uri.hostname,144        }145        # Add x-amz-date if available.146        if "amz_date" in header_map:147            headers[_AWS_DATE_HEADER] = header_map.get("amz_date")148        # Append additional optional headers, eg. X-Amz-Target, Content-Type, etc.149        for key in additional_headers:150            headers[key] = additional_headers[key]151 152        # Add session token if available.153        if aws_security_credentials.session_token is not None:154            headers[_AWS_SECURITY_TOKEN_HEADER] = aws_security_credentials.session_token155 156        signed_request = {"url": url, "method": method, "headers": headers}157        if request_payload:158            signed_request["data"] = request_payload159        return signed_request160 161 162def _get_canonical_querystring(query):163    """Generates the canonical query string given a raw query string.164    Logic is based on165    https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html166 167    Args:168        query (str): The raw query string.169 170    Returns:171        str: The canonical query string.172    """173    # Parse raw query string.174    querystring = urllib.parse.parse_qs(query)175    querystring_encoded_map = {}176    for key in querystring:177        quote_key = urllib.parse.quote(key, safe="-_.~")178        # URI encode key.179        querystring_encoded_map[quote_key] = []180        for item in querystring[key]:181            # For each key, URI encode all values for that key.182            querystring_encoded_map[quote_key].append(183                urllib.parse.quote(item, safe="-_.~")184            )185        # Sort values for each key.186        querystring_encoded_map[quote_key].sort()187    # Sort keys.188    sorted_keys = list(querystring_encoded_map.keys())189    sorted_keys.sort()190    # Reconstruct the query string. Preserve keys with multiple values.191    querystring_encoded_pairs = []192    for key in sorted_keys:193        for item in querystring_encoded_map[key]:194            querystring_encoded_pairs.append("{}={}".format(key, item))195    return "&".join(querystring_encoded_pairs)196 197 198def _sign(key, msg):199    """Creates the HMAC-SHA256 hash of the provided message using the provided200    key.201 202    Args:203        key (str): The HMAC-SHA256 key to use.204        msg (str): The message to hash.205 206    Returns:207        str: The computed hash bytes.208    """209    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()210 211 212def _get_signing_key(key, date_stamp, region_name, service_name):213    """Calculates the signing key used to calculate the signature for214    AWS Signature Version 4 based on:215    https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html216 217    Args:218        key (str): The AWS secret access key.219        date_stamp (str): The '%Y%m%d' date format.220        region_name (str): The AWS region.221        service_name (str): The AWS service name, eg. sts.222 223    Returns:224        str: The signing key bytes.225    """226    k_date = _sign(("AWS4" + key).encode("utf-8"), date_stamp)227    k_region = _sign(k_date, region_name)228    k_service = _sign(k_region, service_name)229    k_signing = _sign(k_service, "aws4_request")230    return k_signing231 232 233def _generate_authentication_header_map(234    host,235    canonical_uri,236    canonical_querystring,237    method,238    region,239    aws_security_credentials,240    request_payload="",241    additional_headers={},242):243    """Generates the authentication header map needed for generating the AWS244    Signature Version 4 signed request.245 246    Args:247        host (str): The AWS service URL hostname.248        canonical_uri (str): The AWS service URL path name.249        canonical_querystring (str): The AWS service URL query string.250        method (str): The HTTP method used to call this API.251        region (str): The AWS region.252        aws_security_credentials (AWSSecurityCredentials): The AWS security credentials.253        request_payload (Optional[str]): The optional request payload if254            available.255        additional_headers (Optional[Mapping[str, str]]): The optional256            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 aws_security_credentials.session_token is not None:276        full_headers[277            _AWS_SECURITY_TOKEN_HEADER278        ] = aws_security_credentials.session_token279 280    # Required headers281    full_headers["host"] = host282    # Do not use generated x-amz-date if the date header is provided.283    # Previously the date was not fixed with x-amz- and could be provided284    # manually.285    # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req286    if "date" not in full_headers:287        full_headers[_AWS_DATE_HEADER] = amz_date288 289    # Header keys need to be sorted alphabetically.290    canonical_headers = ""291    header_keys = list(full_headers.keys())292    header_keys.sort()293    for key in header_keys:294        canonical_headers = "{}{}:{}\n".format(295            canonical_headers, key, full_headers[key]296        )297    signed_headers = ";".join(header_keys)298 299    payload_hash = hashlib.sha256((request_payload or "").encode("utf-8")).hexdigest()300 301    # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html302    canonical_request = "{}\n{}\n{}\n{}\n{}\n{}".format(303        method,304        canonical_uri,305        canonical_querystring,306        canonical_headers,307        signed_headers,308        payload_hash,309    )310 311    credential_scope = "{}/{}/{}/{}".format(312        date_stamp, region, service_name, _AWS_REQUEST_TYPE313    )314 315    # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html316    string_to_sign = "{}\n{}\n{}\n{}".format(317        _AWS_ALGORITHM,318        amz_date,319        credential_scope,320        hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(),321    )322 323    # https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html324    signing_key = _get_signing_key(325        aws_security_credentials.secret_access_key, date_stamp, region, service_name326    )327    signature = hmac.new(328        signing_key, string_to_sign.encode("utf-8"), hashlib.sha256329    ).hexdigest()330 331    # https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html332    authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format(333        _AWS_ALGORITHM,334        aws_security_credentials.access_key_id,335        credential_scope,336        signed_headers,337        signature,338    )339 340    authentication_header = {"authorization_header": authorization_header}341    # Do not use generated x-amz-date if the date header is provided.342    if "date" not in full_headers:343        authentication_header["amz_date"] = amz_date344    return authentication_header345 346 347@dataclass348class AwsSecurityCredentials:349    """A class that models AWS security credentials with an optional session token.350 351        Attributes:352            access_key_id (str): The AWS security credentials access key id.353            secret_access_key (str): The AWS security credentials secret access key.354            session_token (Optional[str]): The optional AWS security credentials session token. This should be set when using temporary credentials.355    """356 357    access_key_id: str358    secret_access_key: str359    session_token: Optional[str] = None360 361 362class AwsSecurityCredentialsSupplier(metaclass=abc.ABCMeta):363    """Base class for AWS security credential suppliers. This can be implemented with custom logic to retrieve364    AWS security credentials to exchange for a Google Cloud access token. The AWS external account credential does365    not cache the AWS security credentials, so caching logic should be added in the implementation.366    """367 368    @abc.abstractmethod369    def get_aws_security_credentials(self, context, request):370        """Returns the AWS security credentials for the requested context.371 372        .. warning: This is not cached by the calling Google credential, so caching logic should be implemented in the supplier.373 374        Args:375            context (google.auth.externalaccount.SupplierContext): The context object376                containing information about the requested audience and subject token type.377            request (google.auth.transport.Request): The object used to make378                HTTP requests.379 380        Raises:381            google.auth.exceptions.RefreshError: If an error is encountered during382                security credential retrieval logic.383 384        Returns:385            AwsSecurityCredentials: The requested AWS security credentials.386        """387        raise NotImplementedError("")388 389    @abc.abstractmethod390    def get_aws_region(self, context, request):391        """Returns the AWS region for the requested context.392 393        Args:394            context (google.auth.externalaccount.SupplierContext): The context object395                containing information about the requested audience and subject token type.396            request (google.auth.transport.Request): The object used to make397                HTTP requests.398 399        Raises:400            google.auth.exceptions.RefreshError: If an error is encountered during401                region retrieval logic.402 403        Returns:404            str: The AWS region.405        """406        raise NotImplementedError("")407 408 409class _DefaultAwsSecurityCredentialsSupplier(AwsSecurityCredentialsSupplier):410    """Default implementation of AWS security credentials supplier. Supports retrieving411    credentials and region via EC2 metadata endpoints and environment variables.412    """413 414    def __init__(self, credential_source):415        self._region_url = credential_source.get("region_url")416        self._security_credentials_url = credential_source.get("url")417        self._imdsv2_session_token_url = credential_source.get(418            "imdsv2_session_token_url"419        )420 421    @_helpers.copy_docstring(AwsSecurityCredentialsSupplier)422    def get_aws_security_credentials(self, context, request):423 424        # Check environment variables for permanent credentials first.425        # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html426        env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID)427        env_aws_secret_access_key = os.environ.get(428            environment_vars.AWS_SECRET_ACCESS_KEY429        )430        # This is normally not available for permanent credentials.431        env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN)432        if env_aws_access_key_id and env_aws_secret_access_key:433            return AwsSecurityCredentials(434                env_aws_access_key_id, env_aws_secret_access_key, env_aws_session_token435            )436 437        imdsv2_session_token = self._get_imdsv2_session_token(request)438        role_name = self._get_metadata_role_name(request, imdsv2_session_token)439 440        # Get security credentials.441        credentials = self._get_metadata_security_credentials(442            request, role_name, imdsv2_session_token443        )444 445        return AwsSecurityCredentials(446            credentials.get("AccessKeyId"),447            credentials.get("SecretAccessKey"),448            credentials.get("Token"),449        )450 451    @_helpers.copy_docstring(AwsSecurityCredentialsSupplier)452    def get_aws_region(self, context, request):453        # The AWS metadata server is not available in some AWS environments454        # such as AWS lambda. Instead, it is available via environment455        # variable.456        env_aws_region = os.environ.get(environment_vars.AWS_REGION)457        if env_aws_region is not None:458            return env_aws_region459 460        env_aws_region = os.environ.get(environment_vars.AWS_DEFAULT_REGION)461        if env_aws_region is not None:462            return env_aws_region463 464        if not self._region_url:465            raise exceptions.RefreshError("Unable to determine AWS region")466 467        headers = None468        imdsv2_session_token = self._get_imdsv2_session_token(request)469        if imdsv2_session_token is not None:470            headers = {"X-aws-ec2-metadata-token": imdsv2_session_token}471 472        response = request(url=self._region_url, method="GET", headers=headers)473 474        # Support both string and bytes type response.data.475        response_body = (476            response.data.decode("utf-8")477            if hasattr(response.data, "decode")478            else response.data479        )480 481        if response.status != http_client.OK:482            raise exceptions.RefreshError(483                "Unable to retrieve AWS region: {}".format(response_body)484            )485 486        # This endpoint will return the region in format: us-east-2b.487        # Only the us-east-2 part should be used.488        return response_body[:-1]489 490    def _get_imdsv2_session_token(self, request):491        if request is not None and self._imdsv2_session_token_url is not None:492            headers = {493                "X-aws-ec2-metadata-token-ttl-seconds": _IMDSV2_SESSION_TOKEN_TTL_SECONDS494            }495 496            imdsv2_session_token_response = request(497                url=self._imdsv2_session_token_url, method="PUT", headers=headers498            )499 500            if imdsv2_session_token_response.status != http_client.OK:501                raise exceptions.RefreshError(502                    "Unable to retrieve AWS Session Token: {}".format(503                        imdsv2_session_token_response.data504                    )505                )506 507            return imdsv2_session_token_response.data508        else:509            return None510 511    def _get_metadata_security_credentials(512        self, request, role_name, imdsv2_session_token513    ):514        """Retrieves the AWS security credentials required for signing AWS515        requests from the AWS metadata server.516 517        Args:518            request (google.auth.transport.Request): A callable used to make519                HTTP requests.520            role_name (str): The AWS role name required by the AWS metadata521                server security_credentials endpoint in order to return the522                credentials.523            imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a524                header in the requests to AWS metadata endpoint.525 526        Returns:527            Mapping[str, str]: The AWS metadata server security credentials528                response.529 530        Raises:531            google.auth.exceptions.RefreshError: If an error occurs while532                retrieving the AWS security credentials.533        """534        headers = {"Content-Type": "application/json"}535        if imdsv2_session_token is not None:536            headers["X-aws-ec2-metadata-token"] = imdsv2_session_token537 538        response = request(539            url="{}/{}".format(self._security_credentials_url, role_name),540            method="GET",541            headers=headers,542        )543 544        # support both string and bytes type response.data545        response_body = (546            response.data.decode("utf-8")547            if hasattr(response.data, "decode")548            else response.data549        )550 551        if response.status != http_client.OK:552            raise exceptions.RefreshError(553                "Unable to retrieve AWS security credentials: {}".format(response_body)554            )555 556        credentials_response = json.loads(response_body)557 558        return credentials_response559 560    def _get_metadata_role_name(self, request, imdsv2_session_token):561        """Retrieves the AWS role currently attached to the current AWS562        workload by querying the AWS metadata server. This is needed for the563        AWS metadata server security credentials endpoint in order to retrieve564        the AWS security credentials needed to sign requests to AWS APIs.565 566        Args:567            request (google.auth.transport.Request): A callable used to make568                HTTP requests.569            imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a570                header in the requests to AWS metadata endpoint.571 572        Returns:573            str: The AWS role name.574 575        Raises:576            google.auth.exceptions.RefreshError: If an error occurs while577                retrieving the AWS role name.578        """579        if self._security_credentials_url is None:580            raise exceptions.RefreshError(581                "Unable to determine the AWS metadata server security credentials endpoint"582            )583 584        headers = None585        if imdsv2_session_token is not None:586            headers = {"X-aws-ec2-metadata-token": imdsv2_session_token}587 588        response = request(589            url=self._security_credentials_url, method="GET", headers=headers590        )591 592        # support both string and bytes type response.data593        response_body = (594            response.data.decode("utf-8")595            if hasattr(response.data, "decode")596            else response.data597        )598 599        if response.status != http_client.OK:600            raise exceptions.RefreshError(601                "Unable to retrieve AWS role name {}".format(response_body)602            )603 604        return response_body605 606 607class Credentials(external_account.Credentials):608    """AWS external account credentials.609    This is used to exchange serialized AWS signature v4 signed requests to610    AWS STS GetCallerIdentity service for Google access tokens.611    """612 613    def __init__(614        self,615        audience,616        subject_token_type,617        token_url=external_account._DEFAULT_TOKEN_URL,618        credential_source=None,619        aws_security_credentials_supplier=None,620        *args,621        **kwargs622    ):623        """Instantiates an AWS workload external account credentials object.624 625        Args:626            audience (str): The STS audience field.627            subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec.628                Expected values include::629 630                    “urn:ietf:params:aws:token-type:aws4_request”631 632            token_url (Optional [str]): The STS endpoint URL. If not provided, will default to "https://sts.googleapis.com/v1/token".633            credential_source (Optional [Mapping]): The credential source dictionary used634                to provide instructions on how to retrieve external credential to be exchanged for Google access tokens.635                Either a credential source or an AWS security credentials supplier must be provided.636 637                Example credential_source for AWS credential::638 639                    {640                        "environment_id": "aws1",641                        "regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",642                        "region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",643                        "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",644                        imdsv2_session_token_url": "http://169.254.169.254/latest/api/token"645                    }646 647            aws_security_credentials_supplier (Optional [AwsSecurityCredentialsSupplier]): Optional AWS security credentials supplier.648                This will be called to supply valid AWS security credentails which will then649                be exchanged for Google access tokens. Either an AWS security credentials supplier650                or a credential source must be provided.651            args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.652            kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.653 654        Raises:655            google.auth.exceptions.RefreshError: If an error is encountered during656                access token retrieval logic.657            ValueError: For invalid parameters.658 659        .. note:: Typically one of the helper constructors660            :meth:`from_file` or661            :meth:`from_info` are used instead of calling the constructor directly.662        """663        super(Credentials, self).__init__(664            audience=audience,665            subject_token_type=subject_token_type,666            token_url=token_url,667            credential_source=credential_source,668            *args,669            **kwargs670        )671        if credential_source is None and aws_security_credentials_supplier is None:672            raise exceptions.InvalidValue(673                "A valid credential source or AWS security credentials supplier must be provided."674            )675        if (676            credential_source is not None677            and aws_security_credentials_supplier is not None678        ):679            raise exceptions.InvalidValue(680                "AWS credential cannot have both a credential source and an AWS security credentials supplier."681            )682 683        if aws_security_credentials_supplier:684            self._aws_security_credentials_supplier = aws_security_credentials_supplier685            # The regional cred verification URL would normally be provided through the credential source. So set it to the default one here.686            self._cred_verification_url = (687                _DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL688            )689        else:690            environment_id = credential_source.get("environment_id") or ""691            self._aws_security_credentials_supplier = _DefaultAwsSecurityCredentialsSupplier(692                credential_source693            )694            self._cred_verification_url = credential_source.get(695                "regional_cred_verification_url"696            )697 698            # Get the environment ID, i.e. "aws1". Currently, only one version supported (1).699            matches = re.match(r"^(aws)([\d]+)$", environment_id)700            if matches:701                env_id, env_version = matches.groups()702            else:703                env_id, env_version = (None, None)704 705            if env_id != "aws" or self._cred_verification_url is None:706                raise exceptions.InvalidResource(707                    "No valid AWS 'credential_source' provided"708                )709            elif env_version is None or int(env_version) != 1:710                raise exceptions.InvalidValue(711                    "aws version '{}' is not supported in the current build.".format(712                        env_version713                    )714                )715 716        self._target_resource = audience717        self._request_signer = None718 719    def retrieve_subject_token(self, request):720        """Retrieves the subject token using the credential_source object.721        The subject token is a serialized `AWS GetCallerIdentity signed request`_.722 723        The logic is summarized as:724 725        Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION726        environment variable or from the AWS metadata server availability-zone727        if not found in the environment variable.728 729        Check AWS credentials in environment variables. If not found, retrieve730        from the AWS metadata server security-credentials endpoint.731 732        When retrieving AWS credentials from the metadata server733        security-credentials endpoint, the AWS role needs to be determined by734        calling the security-credentials endpoint without any argument. Then the735        credentials can be retrieved via: security-credentials/role_name736 737        Generate the signed request to AWS STS GetCallerIdentity action.738 739        Inject x-goog-cloud-target-resource into header and serialize the740        signed request. This will be the subject-token to pass to GCP STS.741 742        .. _AWS GetCallerIdentity signed request:743            https://cloud.google.com/iam/docs/access-resources-aws#exchange-token744 745        Args:746            request (google.auth.transport.Request): A callable used to make747                HTTP requests.748        Returns:749            str: The retrieved subject token.750        """751 752        # Initialize the request signer if not yet initialized after determining753        # the current AWS region.754        if self._request_signer is None:755            self._region = self._aws_security_credentials_supplier.get_aws_region(756                self._supplier_context, request757            )758            self._request_signer = RequestSigner(self._region)759 760        # Retrieve the AWS security credentials needed to generate the signed761        # request.762        aws_security_credentials = self._aws_security_credentials_supplier.get_aws_security_credentials(763            self._supplier_context, request764        )765        # Generate the signed request to AWS STS GetCallerIdentity API.766        # Use the required regional endpoint. Otherwise, the request will fail.767        request_options = self._request_signer.get_request_options(768            aws_security_credentials,769            self._cred_verification_url.replace("{region}", self._region),770            "POST",771        )772        # The GCP STS endpoint expects the headers to be formatted as:773        # [774        #   {key: 'x-amz-date', value: '...'},775        #   {key: 'Authorization', value: '...'},776        #   ...777        # ]778        # And then serialized as:779        # quote(json.dumps({780        #   url: '...',781        #   method: 'POST',782        #   headers: [{key: 'x-amz-date', value: '...'}, ...]783        # }))784        request_headers = request_options.get("headers")785        # The full, canonical resource name of the workload identity pool786        # provider, with or without the HTTPS prefix.787        # Including this header as part of the signature is recommended to788        # ensure data integrity.789        request_headers["x-goog-cloud-target-resource"] = self._target_resource790 791        # Serialize AWS signed request.792        aws_signed_req = {}793        aws_signed_req["url"] = request_options.get("url")794        aws_signed_req["method"] = request_options.get("method")795        aws_signed_req["headers"] = []796        # Reformat header to GCP STS expected format.797        for key in request_headers.keys():798            aws_signed_req["headers"].append(799                {"key": key, "value": request_headers[key]}800            )801 802        return urllib.parse.quote(803            json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True)804        )805 806    def _create_default_metrics_options(self):807        metrics_options = super(Credentials, self)._create_default_metrics_options()808        metrics_options["source"] = "aws"809        if self._has_custom_supplier():810            metrics_options["source"] = "programmatic"811        return metrics_options812 813    def _has_custom_supplier(self):814        return self._credential_source is None815 816    def _constructor_args(self):817        args = super(Credentials, self)._constructor_args()818        # If a custom supplier was used, append it to the args dict.819        if self._has_custom_supplier():820            args.update(821                {822                    "aws_security_credentials_supplier": self._aws_security_credentials_supplier823                }824            )825        return args826 827    @classmethod828    def from_info(cls, info, **kwargs):829        """Creates an AWS Credentials instance from parsed external account info.830 831        Args:832            info (Mapping[str, str]): The AWS external account info in Google833                format.834            kwargs: Additional arguments to pass to the constructor.835 836        Returns:837            google.auth.aws.Credentials: The constructed credentials.838 839        Raises:840            ValueError: For invalid parameters.841        """842        aws_security_credentials_supplier = info.get(843            "aws_security_credentials_supplier"844        )845        kwargs.update(846            {"aws_security_credentials_supplier": aws_security_credentials_supplier}847        )848        return super(Credentials, cls).from_info(info, **kwargs)849 850    @classmethod851    def from_file(cls, filename, **kwargs):852        """Creates an AWS Credentials instance from an external account json file.853 854        Args:855            filename (str): The path to the AWS external account json file.856            kwargs: Additional arguments to pass to the constructor.857 858        Returns:859            google.auth.aws.Credentials: The constructed credentials.860        """861        return super(Credentials, cls).from_file(filename, **kwargs)862 
codekingpro/portable-devtools · Team Ai