Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
downscoped.py513 linesDownload Raw Back to auth
1# Copyright 2021 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"""Downscoping with Credential Access Boundaries16 17This module provides the ability to downscope credentials using18`Downscoping with Credential Access Boundaries`_. This is useful to restrict the19Identity and Access Management (IAM) permissions that a short-lived credential20can use.21 22To downscope permissions of a source credential, a Credential Access Boundary23that specifies which resources the new credential can access, as well as24an upper bound on the permissions that are available on each resource, has to25be defined. A downscoped credential can then be instantiated using the source26credential and the Credential Access Boundary.27 28The common pattern of usage is to have a token broker with elevated access29generate these downscoped credentials from higher access source credentials and30pass the downscoped short-lived access tokens to a token consumer via some31secure authenticated channel for limited access to Google Cloud Storage32resources.33 34For example, a token broker can be set up on a server in a private network.35Various workloads (token consumers) in the same network will send authenticated36requests to that broker for downscoped tokens to access or modify specific google37cloud storage buckets.38 39The broker will instantiate downscoped credentials instances that can be used to40generate short lived downscoped access tokens that can be passed to the token41consumer. These downscoped access tokens can be injected by the consumer into42google.oauth2.Credentials and used to initialize a storage client instance to43access Google Cloud Storage resources with restricted access.44 45Note: Only Cloud Storage supports Credential Access Boundaries. Other Google46Cloud services do not support this feature.47 48.. _Downscoping with Credential Access Boundaries: https://cloud.google.com/iam/docs/downscoping-short-lived-credentials49"""50 51import datetime52 53from google.auth import _helpers54from google.auth import credentials55from google.auth import exceptions56from google.oauth2 import sts57 58# The maximum number of access boundary rules a Credential Access Boundary can59# contain.60_MAX_ACCESS_BOUNDARY_RULES_COUNT = 1061# The token exchange grant_type used for exchanging credentials.62_STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"63# The token exchange requested_token_type. This is always an access_token.64_STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"65# The STS token URL used to exchanged a short lived access token for a downscoped one.66_STS_TOKEN_URL_PATTERN = "https://sts.{}/v1/token"67# The subject token type to use when exchanging a short lived access token for a68# downscoped token.69_STS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"70 71 72class CredentialAccessBoundary(object):73    """Defines a Credential Access Boundary which contains a list of access boundary74    rules. Each rule contains information on the resource that the rule applies to,75    the upper bound of the permissions that are available on that resource and an76    optional condition to further restrict permissions.77    """78 79    def __init__(self, rules=[]):80        """Instantiates a Credential Access Boundary. A Credential Access Boundary81        can contain up to 10 access boundary rules.82 83        Args:84            rules (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of85                access boundary rules limiting the access that a downscoped credential86                will have.87        Raises:88            InvalidType: If any of the rules are not a valid type.89            InvalidValue: If the provided rules exceed the maximum allowed.90        """91        self.rules = rules92 93    @property94    def rules(self):95        """Returns the list of access boundary rules defined on the Credential96        Access Boundary.97 98        Returns:99            Tuple[google.auth.downscoped.AccessBoundaryRule, ...]: The list of access100                boundary rules defined on the Credential Access Boundary. These are returned101                as an immutable tuple to prevent modification.102        """103        return tuple(self._rules)104 105    @rules.setter106    def rules(self, value):107        """Updates the current rules on the Credential Access Boundary. This will overwrite108        the existing set of rules.109 110        Args:111            value (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of112                access boundary rules limiting the access that a downscoped credential113                will have.114        Raises:115            InvalidType: If any of the rules are not a valid type.116            InvalidValue: If the provided rules exceed the maximum allowed.117        """118        if len(value) > _MAX_ACCESS_BOUNDARY_RULES_COUNT:119            raise exceptions.InvalidValue(120                "Credential access boundary rules can have a maximum of {} rules.".format(121                    _MAX_ACCESS_BOUNDARY_RULES_COUNT122                )123            )124        for access_boundary_rule in value:125            if not isinstance(access_boundary_rule, AccessBoundaryRule):126                raise exceptions.InvalidType(127                    "List of rules provided do not contain a valid 'google.auth.downscoped.AccessBoundaryRule'."128                )129        # Make a copy of the original list.130        self._rules = list(value)131 132    def add_rule(self, rule):133        """Adds a single access boundary rule to the existing rules.134 135        Args:136            rule (google.auth.downscoped.AccessBoundaryRule): The access boundary rule,137                limiting the access that a downscoped credential will have, to be added to138                the existing rules.139        Raises:140            InvalidType: If any of the rules are not a valid type.141            InvalidValue: If the provided rules exceed the maximum allowed.142        """143        if len(self.rules) == _MAX_ACCESS_BOUNDARY_RULES_COUNT:144            raise exceptions.InvalidValue(145                "Credential access boundary rules can have a maximum of {} rules.".format(146                    _MAX_ACCESS_BOUNDARY_RULES_COUNT147                )148            )149        if not isinstance(rule, AccessBoundaryRule):150            raise exceptions.InvalidType(151                "The provided rule does not contain a valid 'google.auth.downscoped.AccessBoundaryRule'."152            )153        self._rules.append(rule)154 155    def to_json(self):156        """Generates the dictionary representation of the Credential Access Boundary.157        This uses the format expected by the Security Token Service API as documented in158        `Defining a Credential Access Boundary`_.159 160        .. _Defining a Credential Access Boundary:161            https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary162 163        Returns:164            Mapping: Credential Access Boundary Rule represented in a dictionary object.165        """166        rules = []167        for access_boundary_rule in self.rules:168            rules.append(access_boundary_rule.to_json())169 170        return {"accessBoundary": {"accessBoundaryRules": rules}}171 172 173class AccessBoundaryRule(object):174    """Defines an access boundary rule which contains information on the resource that175    the rule applies to, the upper bound of the permissions that are available on that176    resource and an optional condition to further restrict permissions.177    """178 179    def __init__(180        self, available_resource, available_permissions, availability_condition=None181    ):182        """Instantiates a single access boundary rule.183 184        Args:185            available_resource (str): The full resource name of the Cloud Storage bucket186                that the rule applies to. Use the format187                "//storage.googleapis.com/projects/_/buckets/bucket-name".188            available_permissions (Sequence[str]): A list defining the upper bound that189                the downscoped token will have on the available permissions for the190                resource. Each value is the identifier for an IAM predefined role or191                custom role, with the prefix "inRole:". For example:192                "inRole:roles/storage.objectViewer".193                Only the permissions in these roles will be available.194            availability_condition (Optional[google.auth.downscoped.AvailabilityCondition]):195                Optional condition that restricts the availability of permissions to196                specific Cloud Storage objects.197 198        Raises:199            InvalidType: If any of the parameters are not of the expected types.200            InvalidValue: If any of the parameters are not of the expected values.201        """202        self.available_resource = available_resource203        self.available_permissions = available_permissions204        self.availability_condition = availability_condition205 206    @property207    def available_resource(self):208        """Returns the current available resource.209 210        Returns:211           str: The current available resource.212        """213        return self._available_resource214 215    @available_resource.setter216    def available_resource(self, value):217        """Updates the current available resource.218 219        Args:220            value (str): The updated value of the available resource.221 222        Raises:223            google.auth.exceptions.InvalidType: If the value is not a string.224        """225        if not isinstance(value, str):226            raise exceptions.InvalidType(227                "The provided available_resource is not a string."228            )229        self._available_resource = value230 231    @property232    def available_permissions(self):233        """Returns the current available permissions.234 235        Returns:236           Tuple[str, ...]: The current available permissions. These are returned237               as an immutable tuple to prevent modification.238        """239        return tuple(self._available_permissions)240 241    @available_permissions.setter242    def available_permissions(self, value):243        """Updates the current available permissions.244 245        Args:246            value (Sequence[str]): The updated value of the available permissions.247 248        Raises:249            InvalidType: If the value is not a list of strings.250            InvalidValue: If the value is not valid.251        """252        for available_permission in value:253            if not isinstance(available_permission, str):254                raise exceptions.InvalidType(255                    "Provided available_permissions are not a list of strings."256                )257            if available_permission.find("inRole:") != 0:258                raise exceptions.InvalidValue(259                    "available_permissions must be prefixed with 'inRole:'."260                )261        # Make a copy of the original list.262        self._available_permissions = list(value)263 264    @property265    def availability_condition(self):266        """Returns the current availability condition.267 268        Returns:269           Optional[google.auth.downscoped.AvailabilityCondition]: The current270               availability condition.271        """272        return self._availability_condition273 274    @availability_condition.setter275    def availability_condition(self, value):276        """Updates the current availability condition.277 278        Args:279            value (Optional[google.auth.downscoped.AvailabilityCondition]): The updated280                value of the availability condition.281 282        Raises:283            google.auth.exceptions.InvalidType: If the value is not of type google.auth.downscoped.AvailabilityCondition284                or None.285        """286        if not isinstance(value, AvailabilityCondition) and value is not None:287            raise exceptions.InvalidType(288                "The provided availability_condition is not a 'google.auth.downscoped.AvailabilityCondition' or None."289            )290        self._availability_condition = value291 292    def to_json(self):293        """Generates the dictionary representation of the access boundary rule.294        This uses the format expected by the Security Token Service API as documented in295        `Defining a Credential Access Boundary`_.296 297        .. _Defining a Credential Access Boundary:298            https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary299 300        Returns:301            Mapping: The access boundary rule represented in a dictionary object.302        """303        json = {304            "availablePermissions": list(self.available_permissions),305            "availableResource": self.available_resource,306        }307        if self.availability_condition:308            json["availabilityCondition"] = self.availability_condition.to_json()309        return json310 311 312class AvailabilityCondition(object):313    """An optional condition that can be used as part of a Credential Access Boundary314    to further restrict permissions."""315 316    def __init__(self, expression, title=None, description=None):317        """Instantiates an availability condition using the provided expression and318        optional title or description.319 320        Args:321            expression (str): A condition expression that specifies the Cloud Storage322                objects where permissions are available. For example, this expression323                makes permissions available for objects whose name starts with "customer-a":324                "resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-a')"325            title (Optional[str]): An optional short string that identifies the purpose of326                the condition.327            description (Optional[str]): Optional details about the purpose of the condition.328 329        Raises:330            InvalidType: If any of the parameters are not of the expected types.331            InvalidValue: If any of the parameters are not of the expected values.332        """333        self.expression = expression334        self.title = title335        self.description = description336 337    @property338    def expression(self):339        """Returns the current condition expression.340 341        Returns:342           str: The current conditon expression.343        """344        return self._expression345 346    @expression.setter347    def expression(self, value):348        """Updates the current condition expression.349 350        Args:351            value (str): The updated value of the condition expression.352 353        Raises:354            google.auth.exceptions.InvalidType: If the value is not of type string.355        """356        if not isinstance(value, str):357            raise exceptions.InvalidType("The provided expression is not a string.")358        self._expression = value359 360    @property361    def title(self):362        """Returns the current title.363 364        Returns:365           Optional[str]: The current title.366        """367        return self._title368 369    @title.setter370    def title(self, value):371        """Updates the current title.372 373        Args:374            value (Optional[str]): The updated value of the title.375 376        Raises:377            google.auth.exceptions.InvalidType: If the value is not of type string or None.378        """379        if not isinstance(value, str) and value is not None:380            raise exceptions.InvalidType("The provided title is not a string or None.")381        self._title = value382 383    @property384    def description(self):385        """Returns the current description.386 387        Returns:388           Optional[str]: The current description.389        """390        return self._description391 392    @description.setter393    def description(self, value):394        """Updates the current description.395 396        Args:397            value (Optional[str]): The updated value of the description.398 399        Raises:400            google.auth.exceptions.InvalidType: If the value is not of type string or None.401        """402        if not isinstance(value, str) and value is not None:403            raise exceptions.InvalidType(404                "The provided description is not a string or None."405            )406        self._description = value407 408    def to_json(self):409        """Generates the dictionary representation of the availability condition.410        This uses the format expected by the Security Token Service API as documented in411        `Defining a Credential Access Boundary`_.412 413        .. _Defining a Credential Access Boundary:414            https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary415 416        Returns:417            Mapping[str, str]: The availability condition represented in a dictionary418                object.419        """420        json = {"expression": self.expression}421        if self.title:422            json["title"] = self.title423        if self.description:424            json["description"] = self.description425        return json426 427 428class Credentials(credentials.CredentialsWithQuotaProject):429    """Defines a set of Google credentials that are downscoped from an existing set430    of Google OAuth2 credentials. This is useful to restrict the Identity and Access431    Management (IAM) permissions that a short-lived credential can use.432    The common pattern of usage is to have a token broker with elevated access433    generate these downscoped credentials from higher access source credentials and434    pass the downscoped short-lived access tokens to a token consumer via some435    secure authenticated channel for limited access to Google Cloud Storage436    resources.437    """438 439    def __init__(440        self,441        source_credentials,442        credential_access_boundary,443        quota_project_id=None,444        universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,445    ):446        """Instantiates a downscoped credentials object using the provided source447        credentials and credential access boundary rules.448        To downscope permissions of a source credential, a Credential Access Boundary449        that specifies which resources the new credential can access, as well as an450        upper bound on the permissions that are available on each resource, has to be451        defined. A downscoped credential can then be instantiated using the source452        credential and the Credential Access Boundary.453 454        Args:455            source_credentials (google.auth.credentials.Credentials): The source credentials456                to be downscoped based on the provided Credential Access Boundary rules.457            credential_access_boundary (google.auth.downscoped.CredentialAccessBoundary):458                The Credential Access Boundary which contains a list of access boundary459                rules. Each rule contains information on the resource that the rule applies to,460                the upper bound of the permissions that are available on that resource and an461                optional condition to further restrict permissions.462            quota_project_id (Optional[str]): The optional quota project ID.463            universe_domain (Optional[str]): The universe domain value, default is googleapis.com464        Raises:465            google.auth.exceptions.RefreshError: If the source credentials466                return an error on token refresh.467            google.auth.exceptions.OAuthError: If the STS token exchange468                endpoint returned an error during downscoped token generation.469        """470 471        super(Credentials, self).__init__()472        self._source_credentials = source_credentials473        self._credential_access_boundary = credential_access_boundary474        self._quota_project_id = quota_project_id475        self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN476        self._sts_client = sts.Client(477            _STS_TOKEN_URL_PATTERN.format(self.universe_domain)478        )479 480    @_helpers.copy_docstring(credentials.Credentials)481    def refresh(self, request):482        # Generate an access token from the source credentials.483        self._source_credentials.refresh(request)484        now = _helpers.utcnow()485        # Exchange the access token for a downscoped access token.486        response_data = self._sts_client.exchange_token(487            request=request,488            grant_type=_STS_GRANT_TYPE,489            subject_token=self._source_credentials.token,490            subject_token_type=_STS_SUBJECT_TOKEN_TYPE,491            requested_token_type=_STS_REQUESTED_TOKEN_TYPE,492            additional_options=self._credential_access_boundary.to_json(),493        )494        self.token = response_data.get("access_token")495        # For downscoping CAB flow, the STS endpoint may not return the expiration496        # field for some flows. The generated downscoped token should always have497        # the same expiration time as the source credentials. When no expires_in498        # field is returned in the response, we can just get the expiration time499        # from the source credentials.500        if response_data.get("expires_in"):501            lifetime = datetime.timedelta(seconds=response_data.get("expires_in"))502            self.expiry = now + lifetime503        else:504            self.expiry = self._source_credentials.expiry505 506    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)507    def with_quota_project(self, quota_project_id):508        return self.__class__(509            self._source_credentials,510            self._credential_access_boundary,511            quota_project_id=quota_project_id,512        )513 
codekingpro/portable-devtools · Team Ai