Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
impersonated_credentials.py466 linesDownload Raw Back to auth
1# Copyright 2018 Google Inc.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 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"""Google Cloud Impersonated credentials.16 17This module provides authentication for applications where local credentials18impersonates a remote service account using `IAM Credentials API`_.19 20This class can be used to impersonate a service account as long as the original21Credential object has the "Service Account Token Creator" role on the target22service account.23 24    .. _IAM Credentials API:25        https://cloud.google.com/iam/credentials/reference/rest/26"""27 28import base6429import copy30from datetime import datetime31import http.client as http_client32import json33 34from google.auth import _helpers35from google.auth import credentials36from google.auth import exceptions37from google.auth import jwt38from google.auth import metrics39 40_IAM_SCOPE = ["https://www.googleapis.com/auth/iam"]41 42_IAM_ENDPOINT = (43    "https://iamcredentials.googleapis.com/v1/projects/-"44    + "/serviceAccounts/{}:generateAccessToken"45)46 47_IAM_SIGN_ENDPOINT = (48    "https://iamcredentials.googleapis.com/v1/projects/-"49    + "/serviceAccounts/{}:signBlob"50)51 52_IAM_IDTOKEN_ENDPOINT = (53    "https://iamcredentials.googleapis.com/v1/"54    + "projects/-/serviceAccounts/{}:generateIdToken"55)56 57_REFRESH_ERROR = "Unable to acquire impersonated credentials"58 59_DEFAULT_TOKEN_LIFETIME_SECS = 3600  # 1 hour in seconds60 61_DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token"62 63 64def _make_iam_token_request(65    request, principal, headers, body, iam_endpoint_override=None66):67    """Makes a request to the Google Cloud IAM service for an access token.68    Args:69        request (Request): The Request object to use.70        principal (str): The principal to request an access token for.71        headers (Mapping[str, str]): Map of headers to transmit.72        body (Mapping[str, str]): JSON Payload body for the iamcredentials73            API call.74        iam_endpoint_override (Optiona[str]): The full IAM endpoint override75            with the target_principal embedded. This is useful when supporting76            impersonation with regional endpoints.77 78    Raises:79        google.auth.exceptions.TransportError: Raised if there is an underlying80            HTTP connection error81        google.auth.exceptions.RefreshError: Raised if the impersonated82            credentials are not available.  Common reasons are83            `iamcredentials.googleapis.com` is not enabled or the84            `Service Account Token Creator` is not assigned85    """86    iam_endpoint = iam_endpoint_override or _IAM_ENDPOINT.format(principal)87 88    body = json.dumps(body).encode("utf-8")89 90    response = request(url=iam_endpoint, method="POST", headers=headers, body=body)91 92    # support both string and bytes type response.data93    response_body = (94        response.data.decode("utf-8")95        if hasattr(response.data, "decode")96        else response.data97    )98 99    if response.status != http_client.OK:100        raise exceptions.RefreshError(_REFRESH_ERROR, response_body)101 102    try:103        token_response = json.loads(response_body)104        token = token_response["accessToken"]105        expiry = datetime.strptime(token_response["expireTime"], "%Y-%m-%dT%H:%M:%SZ")106 107        return token, expiry108 109    except (KeyError, ValueError) as caught_exc:110        new_exc = exceptions.RefreshError(111            "{}: No access token or invalid expiration in response.".format(112                _REFRESH_ERROR113            ),114            response_body,115        )116        raise new_exc from caught_exc117 118 119class Credentials(120    credentials.Scoped, credentials.CredentialsWithQuotaProject, credentials.Signing121):122    """This module defines impersonated credentials which are essentially123    impersonated identities.124 125    Impersonated Credentials allows credentials issued to a user or126    service account to impersonate another. The target service account must127    grant the originating credential principal the128    `Service Account Token Creator`_ IAM role:129 130    For more information about Token Creator IAM role and131    IAMCredentials API, see132    `Creating Short-Lived Service Account Credentials`_.133 134    .. _Service Account Token Creator:135        https://cloud.google.com/iam/docs/service-accounts#the_service_account_token_creator_role136 137    .. _Creating Short-Lived Service Account Credentials:138        https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials139 140    Usage:141 142    First grant source_credentials the `Service Account Token Creator`143    role on the target account to impersonate.   In this example, the144    service account represented by svc_account.json has the145    token creator role on146    `impersonated-account@_project_.iam.gserviceaccount.com`.147 148    Enable the IAMCredentials API on the source project:149    `gcloud services enable iamcredentials.googleapis.com`.150 151    Initialize a source credential which does not have access to152    list bucket::153 154        from google.oauth2 import service_account155 156        target_scopes = [157            'https://www.googleapis.com/auth/devstorage.read_only']158 159        source_credentials = (160            service_account.Credentials.from_service_account_file(161                '/path/to/svc_account.json',162                scopes=target_scopes))163 164    Now use the source credentials to acquire credentials to impersonate165    another service account::166 167        from google.auth import impersonated_credentials168 169        target_credentials = impersonated_credentials.Credentials(170          source_credentials=source_credentials,171          target_principal='impersonated-account@_project_.iam.gserviceaccount.com',172          target_scopes = target_scopes,173          lifetime=500)174 175    Resource access is granted::176 177        client = storage.Client(credentials=target_credentials)178        buckets = client.list_buckets(project='your_project')179        for bucket in buckets:180          print(bucket.name)181    """182 183    def __init__(184        self,185        source_credentials,186        target_principal,187        target_scopes,188        delegates=None,189        lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,190        quota_project_id=None,191        iam_endpoint_override=None,192    ):193        """194        Args:195            source_credentials (google.auth.Credentials): The source credential196                used as to acquire the impersonated credentials.197            target_principal (str): The service account to impersonate.198            target_scopes (Sequence[str]): Scopes to request during the199                authorization grant.200            delegates (Sequence[str]): The chained list of delegates required201                to grant the final access_token.  If set, the sequence of202                identities must have "Service Account Token Creator" capability203                granted to the prceeding identity.  For example, if set to204                [serviceAccountB, serviceAccountC], the source_credential205                must have the Token Creator role on serviceAccountB.206                serviceAccountB must have the Token Creator on207                serviceAccountC.208                Finally, C must have Token Creator on target_principal.209                If left unset, source_credential must have that role on210                target_principal.211            lifetime (int): Number of seconds the delegated credential should212                be valid for (upto 3600).213            quota_project_id (Optional[str]): The project ID used for quota and billing.214                This project may be different from the project used to215                create the credentials.216            iam_endpoint_override (Optiona[str]): The full IAM endpoint override217                with the target_principal embedded. This is useful when supporting218                impersonation with regional endpoints.219        """220 221        super(Credentials, self).__init__()222 223        self._source_credentials = copy.copy(source_credentials)224        # Service account source credentials must have the _IAM_SCOPE225        # added to refresh correctly. User credentials cannot have226        # their original scopes modified.227        if isinstance(self._source_credentials, credentials.Scoped):228            self._source_credentials = self._source_credentials.with_scopes(_IAM_SCOPE)229            # If the source credential is service account and self signed jwt230            # is needed, we need to create a jwt credential inside it231            if (232                hasattr(self._source_credentials, "_create_self_signed_jwt")233                and self._source_credentials._always_use_jwt_access234            ):235                self._source_credentials._create_self_signed_jwt(None)236        self._target_principal = target_principal237        self._target_scopes = target_scopes238        self._delegates = delegates239        self._lifetime = lifetime or _DEFAULT_TOKEN_LIFETIME_SECS240        self.token = None241        self.expiry = _helpers.utcnow()242        self._quota_project_id = quota_project_id243        self._iam_endpoint_override = iam_endpoint_override244 245    def _metric_header_for_usage(self):246        return metrics.CRED_TYPE_SA_IMPERSONATE247 248    @_helpers.copy_docstring(credentials.Credentials)249    def refresh(self, request):250        self._update_token(request)251 252    def _update_token(self, request):253        """Updates credentials with a new access_token representing254        the impersonated account.255 256        Args:257            request (google.auth.transport.requests.Request): Request object258                to use for refreshing credentials.259        """260 261        # Refresh our source credentials if it is not valid.262        if (263            self._source_credentials.token_state == credentials.TokenState.STALE264            or self._source_credentials.token_state == credentials.TokenState.INVALID265        ):266            self._source_credentials.refresh(request)267 268        body = {269            "delegates": self._delegates,270            "scope": self._target_scopes,271            "lifetime": str(self._lifetime) + "s",272        }273 274        headers = {275            "Content-Type": "application/json",276            metrics.API_CLIENT_HEADER: metrics.token_request_access_token_impersonate(),277        }278 279        # Apply the source credentials authentication info.280        self._source_credentials.apply(headers)281 282        self.token, self.expiry = _make_iam_token_request(283            request=request,284            principal=self._target_principal,285            headers=headers,286            body=body,287            iam_endpoint_override=self._iam_endpoint_override,288        )289 290    def sign_bytes(self, message):291        from google.auth.transport.requests import AuthorizedSession292 293        iam_sign_endpoint = _IAM_SIGN_ENDPOINT.format(self._target_principal)294 295        body = {296            "payload": base64.b64encode(message).decode("utf-8"),297            "delegates": self._delegates,298        }299 300        headers = {"Content-Type": "application/json"}301 302        authed_session = AuthorizedSession(self._source_credentials)303 304        try:305            response = authed_session.post(306                url=iam_sign_endpoint, headers=headers, json=body307            )308        finally:309            authed_session.close()310 311        if response.status_code != http_client.OK:312            raise exceptions.TransportError(313                "Error calling sign_bytes: {}".format(response.json())314            )315 316        return base64.b64decode(response.json()["signedBlob"])317 318    @property319    def signer_email(self):320        return self._target_principal321 322    @property323    def service_account_email(self):324        return self._target_principal325 326    @property327    def signer(self):328        return self329 330    @property331    def requires_scopes(self):332        return not self._target_scopes333 334    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)335    def with_quota_project(self, quota_project_id):336        return self.__class__(337            self._source_credentials,338            target_principal=self._target_principal,339            target_scopes=self._target_scopes,340            delegates=self._delegates,341            lifetime=self._lifetime,342            quota_project_id=quota_project_id,343            iam_endpoint_override=self._iam_endpoint_override,344        )345 346    @_helpers.copy_docstring(credentials.Scoped)347    def with_scopes(self, scopes, default_scopes=None):348        return self.__class__(349            self._source_credentials,350            target_principal=self._target_principal,351            target_scopes=scopes or default_scopes,352            delegates=self._delegates,353            lifetime=self._lifetime,354            quota_project_id=self._quota_project_id,355            iam_endpoint_override=self._iam_endpoint_override,356        )357 358 359class IDTokenCredentials(credentials.CredentialsWithQuotaProject):360    """Open ID Connect ID Token-based service account credentials.361 362    """363 364    def __init__(365        self,366        target_credentials,367        target_audience=None,368        include_email=False,369        quota_project_id=None,370    ):371        """372        Args:373            target_credentials (google.auth.Credentials): The target374                credential used as to acquire the id tokens for.375            target_audience (string): Audience to issue the token for.376            include_email (bool): Include email in IdToken377            quota_project_id (Optional[str]):  The project ID used for378                quota and billing.379        """380        super(IDTokenCredentials, self).__init__()381 382        if not isinstance(target_credentials, Credentials):383            raise exceptions.GoogleAuthError(384                "Provided Credential must be " "impersonated_credentials"385            )386        self._target_credentials = target_credentials387        self._target_audience = target_audience388        self._include_email = include_email389        self._quota_project_id = quota_project_id390 391    def from_credentials(self, target_credentials, target_audience=None):392        return self.__class__(393            target_credentials=target_credentials,394            target_audience=target_audience,395            include_email=self._include_email,396            quota_project_id=self._quota_project_id,397        )398 399    def with_target_audience(self, target_audience):400        return self.__class__(401            target_credentials=self._target_credentials,402            target_audience=target_audience,403            include_email=self._include_email,404            quota_project_id=self._quota_project_id,405        )406 407    def with_include_email(self, include_email):408        return self.__class__(409            target_credentials=self._target_credentials,410            target_audience=self._target_audience,411            include_email=include_email,412            quota_project_id=self._quota_project_id,413        )414 415    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)416    def with_quota_project(self, quota_project_id):417        return self.__class__(418            target_credentials=self._target_credentials,419            target_audience=self._target_audience,420            include_email=self._include_email,421            quota_project_id=quota_project_id,422        )423 424    @_helpers.copy_docstring(credentials.Credentials)425    def refresh(self, request):426        from google.auth.transport.requests import AuthorizedSession427 428        iam_sign_endpoint = _IAM_IDTOKEN_ENDPOINT.format(429            self._target_credentials.signer_email430        )431 432        body = {433            "audience": self._target_audience,434            "delegates": self._target_credentials._delegates,435            "includeEmail": self._include_email,436        }437 438        headers = {439            "Content-Type": "application/json",440            metrics.API_CLIENT_HEADER: metrics.token_request_id_token_impersonate(),441        }442 443        authed_session = AuthorizedSession(444            self._target_credentials._source_credentials, auth_request=request445        )446 447        try:448            response = authed_session.post(449                url=iam_sign_endpoint,450                headers=headers,451                data=json.dumps(body).encode("utf-8"),452            )453        finally:454            authed_session.close()455 456        if response.status_code != http_client.OK:457            raise exceptions.RefreshError(458                "Error getting ID token: {}".format(response.json())459            )460 461        id_token = response.json()["token"]462        self.token = id_token463        self.expiry = datetime.utcfromtimestamp(464            jwt.decode(id_token, verify=False)["exp"]465        )466 
codekingpro/portable-devtools · Team Ai