# -*- coding: utf-8 -*- # # Copyright 2018 Google LLC. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Command line processing utilities for access policies.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from googlecloudsdk.api_lib.accesscontextmanager import policies as policies_api from googlecloudsdk.api_lib.cloudresourcemanager import organizations from googlecloudsdk.calliope.concepts import concepts from googlecloudsdk.calliope.concepts import deps from googlecloudsdk.command_lib.meta import cache_util as meta_cache_util from googlecloudsdk.command_lib.util import cache_util from googlecloudsdk.command_lib.util.concepts import concept_parsers from googlecloudsdk.core import exceptions from googlecloudsdk.core import log from googlecloudsdk.core import properties from googlecloudsdk.core import resources class DefaultPolicyResolutionError(exceptions.Error): pass def ValidateAccessPolicyArg(ref, args, req=None): """Add the particular service filter message based on specified args.""" del ref # unused if args.IsSpecified('policy'): properties.AccessPolicyValidator(args.policy) return req def GetAttributeConfig(): property_ = properties.VALUES.access_context_manager.policy return concepts.ResourceParameterAttributeConfig( name='policy', help_text='The ID of the access policy.', fallthroughs=[deps.PropertyFallthrough(property_)]) def GetResourceSpec(): return concepts.ResourceSpec( 'accesscontextmanager.accessPolicies', resource_name='policy', accessPoliciesId=GetAttributeConfig()) def AddResourceArg(parser, verb): """Add a resource argument for an access policy. NOTE: Must be used only if it's the only resource arg in the command. Args: parser: the parser for the command. verb: str, the verb to describe the resource, such as 'to update'. """ concept_parsers.ConceptParser.ForResource( 'policy', GetResourceSpec(), 'The access policy {}.'.format(verb), required=True).AddToParser(parser) @cache_util.CacheResource('organizations-by-domain', 10) def _GetOrganization(domain): """Get the organization for the given domain. The current user must have permission to list the organization. Args: domain: str, the domain (e.g. 'example.com') to look up the organization of, or None to just list the organizations for the current account. Returns: resources.Resource, a reference to a cloudresourcemanager.organizations resource Raises: DefaultPolicyResolutionError: if the number of organizations matching the given domain is not exactly 1, or searching organizations fails. """ filter_ = 'domain:' + domain try: orgs = list(organizations.Client().List(filter_=filter_, limit=2)) except Exception as err: raise DefaultPolicyResolutionError( 'Unable to resolve organization for domain [{}]: {}'.format( domain, err)) if not orgs: raise DefaultPolicyResolutionError( 'No matching organizations found for domain [{}].'.format(domain)) elif len(orgs) > 1: raise DefaultPolicyResolutionError( 'Found more than one organization for domain [{}].\n{}'.format( domain, orgs)) return resources.REGISTRY.Parse( orgs[0].name, collection='cloudresourcemanager.organizations') @cache_util.CacheResource('policies-by-organization', 10) def _GetPolicy(organization_ref): """Get the access policy for the given organization. The current user must have permission to list the policies for the organization. Args: organization_ref: resources.Resource, a reference to a cloudresourcemanager.organizations resource to look up the policy for. Returns: resources.Resource, a reference to an accesscontextmanager.accessPolicies resource Raises: DefaultPolicyResolutionError: if the number of policies matching the given organization is not exactly 1, or listing policies fails. """ try: policies = list(policies_api.Client().List(organization_ref, limit=2)) except Exception as err: raise DefaultPolicyResolutionError( 'Unable to resolve policy for organization [{}]: {}'.format( organization_ref.Name(), err)) if not policies: raise DefaultPolicyResolutionError( 'No matching policies found for organization [{}]'.format( organization_ref.Name())) elif len(policies) > 1: raise DefaultPolicyResolutionError( 'Found more than one access policy for organization [{}]:\n{}'.format( organization_ref.Name(), policies)) policy_ref = resources.REGISTRY.Parse( policies[0].name, collection='accesscontextmanager.accessPolicies') return policy_ref _IAM_SUFFIX = '.iam.gserviceaccount.com' _DEVELOPER_DOMAIN = 'developer.gserviceaccount.com' def _GetDomain(account): _, _, host = account.partition('@') if host.endswith(_IAM_SUFFIX) or host == _DEVELOPER_DOMAIN: return None return host def GetDefaultPolicy(): """Gets the ID of the default policy for the current account.""" account = properties.VALUES.core.account.Get() if not account: log.info('Unable to automatically resolve policy since account property ' 'is not set.') return None domain = _GetDomain(account) if not domain: log.info('Unable to resolve domain for account [%s]', account) return None with meta_cache_util.GetCache('resource://', create=True) as cache: try: # pylint: disable=too-many-function-args organization_ref = _GetOrganization(cache, domain) policy_ref = _GetPolicy(cache, organization_ref.RelativeName(), (organization_ref,)) except DefaultPolicyResolutionError as err: log.info('Unable to automatically resolve policy: %s', err) return None return policy_ref.Name()