Beispiel #1
0
  def Run(self, args):
    """Clone GCP repo to current directory.

    Args:
      args: argparse.Namespace, the arguments this command is run with.

    Raises:
      ToolException: on project initialization errors.

    Returns:
      The path to the new git repo.
    """
    # Ensure that we're logged in.
    c_store.Load()

    project_id = properties.VALUES.core.project.Get(required=True)
    project_repo = git.Git(project_id, args.src)
    path = project_repo.Clone(destination_path=args.dst or args.src)
    if path:
      log.status.write('Project [{prj}] repository [{repo}] was cloned to '
                       '[{path}].\n'.format(prj=project_id, path=path,
                                            repo=project_repo.GetName()))
Beispiel #2
0
def generate_login_token_from_gcloud_auth(scopes):
    """Genearete a down-coped access token with given scopes for IAM DB authentication from gcloud credentials.

  Args:
    scopes: scopes to be included in the down-scoped token.

  Returns:
    Down-scoped access token.
  """
    cred = c_store.Load(allow_account_impersonation=True, use_google_auth=True)

    cred = _downscope_credential(cred, scopes)

    c_store.Refresh(cred)
    if c_creds.IsOauth2ClientCredentials(cred):
        token = cred.access_token
    else:
        token = cred.token
    if not token:
        raise auth_exceptions.InvalidCredentialsError(
            'No access token could be obtained from the current credentials.')
    return token
Beispiel #3
0
def UpdateDockerCredentials(server):
    """Updates the docker config to have fresh credentials.

  This reads the current contents of Docker's keyring, and extends it with
  a fresh entry for the provided 'server', based on the active gcloud
  credential.  If a credential exists for 'server' this replaces it.

  Args:
    server: The hostname of the registry for which we're freshening
       the credential.

  Raises:
    store.Error: There was an error loading the credentials.
  """
    # Loading credentials will ensure that we're logged in.
    # And prompt/abort to 'run gcloud auth login' otherwise.
    cred = store.Load()

    # Ensure our credential has a valid access token,
    # which has the full duration available.
    store.Refresh(cred)
    if not cred.access_token:
        raise exceptions.Error('No access token could be obtained '
                               'from the current credentials.')

    try:
        # Update the credentials stored by docker, passing the access token
        # as a password, and benign values as the email and username.
        _DockerLogin(server, _EMAIL, _USERNAME, cred.access_token)
    except exceptions.Error:
        # Fall back to the previous manual .dockercfg manipulation
        # in order to support gcloud app's docker-binaryless use case.
        # TODO(user) when app deploy is using Argo to take over builds,
        # remove this.
        _UpdateDockerConfig(server, _USERNAME, cred.access_token)
        log.warn(
            "'docker' was not discovered on the path. Credentials have been "
            "stored, but are not guaranteed to work with the 1.11 Docker client if"
            "an external credential store is configured.")
  def Run(self, args):
    """Clone a GCP repository to the current directory.

    Args:
      args: argparse.Namespace, the arguments this command is run with.

    Returns:
      The path to the new git repository.
    """
    # Ensure that we're logged in.
    c_store.Load()

    res = sourcerepo.ParseRepo(args.src)
    source_handler = sourcerepo.Source()

    repo = source_handler.GetRepo(res)
    if not repo:
      message = ('Repository "{src}" in project "{prj}" does not '
                 'exist.\nList current repos with\n'
                 '$ gcloud beta source repos list\n'
                 'or create with\n'
                 '$ gcloud beta source repos create {src}'.format(
                     src=args.src, prj=res.projectsId))
      raise c_exc.InvalidArgumentException('REPOSITORY_NAME', message)
    if hasattr(repo, 'mirrorConfig') and repo.mirrorConfig:
      mirror_url = repo.mirrorConfig.url
      self.ActionIfMirror(
          project=res.projectsId, repo=args.src, mirror_url=mirror_url)
    # do the actual clone
    git_helper = git.Git(res.projectsId, args.src, uri=repo.url)
    path = git_helper.Clone(
        destination_path=args.dst or args.src,
        dry_run=args.dry_run,
        full_path=self.UseFullGcloudPath(args))
    if path and not args.dry_run:
      log.status.write('Project [{prj}] repository [{repo}] was cloned '
                       'to [{path}].\n'.format(
                           prj=res.projectsId, path=path, repo=args.src))
Beispiel #5
0
  def SetUpCreds(self):
    """Set up service account credentials."""
    sa_creds = self.MakeServiceAccountCredentials()
    sa_creds.token_expiry = (
        datetime.datetime.utcnow() + datetime.timedelta(hours=2))

    store.Store(sa_creds, self.fake_account)
    store.Load(self.fake_account)
    creds_type = creds.CredentialType.FromCredentials(sa_creds)
    paths = config.Paths()
    with open(paths.LegacyCredentialsAdcPath(self.fake_account)) as f:
      adc_file = json.load(f)
    # Compare file contents to make sure credentials are set up correctly.
    self.assertEqual(
        json.loads("""\
        {
          "client_email": "*****@*****.**",
          "client_id": "bar.apps.googleusercontent.com",
          "private_key": "-----BEGIN PRIVATE KEY-----\\nasdf\\n-----END PRIVATE KEY-----\\n",
          "private_key_id": "key-id",
          "type": "service_account"
        }"""), adc_file)
    self.assertEqual(creds.CredentialType.SERVICE_ACCOUNT, creds_type)
Beispiel #6
0
    def Run(self, args):
        """Run the helper command."""

        if args.method == DockerHelper.LIST:
            return {
                # This tells Docker that the secret will be an access token, not a
                # username/password.
                # Docker normally expects a prefixed 'https://' for auth configs.
                ('https://' + url): '_dcgcloud_token'
                for url in credential_utils.DefaultAuthenticatedRegistries()
            }

        elif args.method == DockerHelper.GET:
            cred = c_store.Load(use_google_auth=True)
            c_store.RefreshIfExpireWithinWindow(cred,
                                                window=TOKEN_MIN_LIFETIME)
            url = sys.stdin.read().strip()
            if (url.replace('https://', '', 1)
                    not in credential_utils.SupportedRegistries()):
                raise exceptions.Error(
                    'Repository url [{url}] is not supported'.format(url=url))
            # Putting an actual username in the response doesn't work. Docker will
            # then prompt for a password instead of using the access token.
            token = (cred.token if c_creds.IsGoogleAuthCredentials(cred) else
                     cred.access_token)

            return {
                'Secret': token,
                'Username': '******',
            }

        # Don't print anything if we are not supporting the given action.
        # The credential helper protocol also support 'store' and 'erase' actions
        # that don't apply here. The full spec can be found here:
        # https://github.com/docker/docker-credential-helpers#development
        args.GetDisplayInfo().AddFormat('none')
        return None
    def testP12CryptoRequired(self):
        try:
            import OpenSSL  # pylint: disable=g-import-not-at-top,unused-variable
        except ImportError:
            raise self.SkipTest('Needs PyOpenSSL installed.')

        p12_key_file = self._GetTestDataPathFor('service_account_key.p12')
        self.Run('auth activate-service-account {0} --key-file={1}'.format(
            _SERVICE_ACCOUNT_EMAIL, p12_key_file))

        # Check that internally scopes and user agent is set.
        creds_dict = json.loads(store.Load().to_json())
        self.assertEqual('google-cloud-sdk', creds_dict['_user_agent'])
        self.assertEqual('google-cloud-sdk', creds_dict['user_agent'])
        scopes = [
            'openid', 'https://www.googleapis.com/auth/userinfo.email',
            'https://www.googleapis.com/auth/cloud-platform',
            'https://www.googleapis.com/auth/appengine.admin',
            'https://www.googleapis.com/auth/compute'
        ]
        self.assertEqual(' '.join(scopes), creds_dict['_scopes'])

        paths = config.Paths()
        expected_legacy_file = paths.LegacyCredentialsP12KeyPath(
            _SERVICE_ACCOUNT_EMAIL)

        self.AssertFileExists(expected_legacy_file)
        self.assertTrue(
            filecmp.cmp(p12_key_file, expected_legacy_file),
            'original and saved P12 keys are different in {0} and {1}'.format(
                p12_key_file, expected_legacy_file))

        self.AssertFileNotExists(
            paths.LegacyCredentialsAdcPath(_SERVICE_ACCOUNT_EMAIL))
        self.AssertFileNotExists(
            paths.LegacyCredentialsBqPath(_SERVICE_ACCOUNT_EMAIL))
Beispiel #8
0
def Http(auth=True, creds=None, timeout='unset'):
    """Get an httplib2.Http client for working with the Google API.

  Args:
    auth: bool, True if the http client returned should be authorized.
    creds: oauth2client.client.Credentials, If auth is True and creds is not
        None, use those credentials to authorize the httplib2.Http client.
    timeout: double, The timeout in seconds to pass to httplib2.  This is the
        socket level timeout.  If timeout is None, timeout is infinite.  If
        default argument 'unset' is given, a sensible default is selected.

  Returns:
    An authorized httplib2.Http client object, or a regular httplib2.Http object
    if no credentials are available.

  Raises:
    c_store.Error: If an error loading the credentials occurs.
  """
    http_client = http.Http(timeout=timeout)

    authority_selector = properties.VALUES.auth.authority_selector.Get()
    authorization_token_file = (
        properties.VALUES.auth.authorization_token_file.Get())
    if authority_selector or authorization_token_file:
        http_client = _WrapRequestForIAMAuth(http_client, authority_selector,
                                             authorization_token_file)

    if auth:
        if not creds:
            creds = store.Load()
        http_client = creds.authorize(http_client)
        # Wrap the request method to put in our own error handling.
        http_client = http.Modifiers.WrapRequest(
            http_client, [], _HandleAuthError, client.AccessTokenRefreshError)

    return http_client
Beispiel #9
0
def MakeSecureChannel(target):
    """Creates grpc secure channel.

  Args:
    target: str, The server address, for example:
      bigtableadmin.googleapis.com:443.

  Returns:
    grpc.secure channel.
  """

    credentials = cred_store.Load()
    # ssl_channel_credentials() loads root certificates from
    # `grpc/_adapter/credentials/roots.pem`.
    transport_creds = grpc.ssl_channel_credentials()
    custom_metadata_plugin = _MetadataPlugin(credentials)
    auth_creds = grpc.metadata_call_credentials(custom_metadata_plugin,
                                                name='google_creds')
    channel_creds = grpc.composite_channel_credentials(transport_creds,
                                                       auth_creds)
    channel_args = (('grpc.primary_user_agent',
                     http.MakeUserAgentString(
                         properties.VALUES.metrics.command_name.Get())), )
    return grpc.secure_channel(target, channel_creds, options=channel_args)
  def Run(self, args):
    """Revoke credentials and update active account."""
    accounts = args.accounts or []
    if isinstance(accounts, str):
      accounts = [accounts]
    available_accounts = c_store.AvailableAccounts()
    unknown_accounts = set(accounts) - set(available_accounts)
    if unknown_accounts:
      raise c_exc.UnknownArgumentException(
          'accounts', ' '.join(unknown_accounts))
    if args.all:
      accounts = available_accounts

    active_account = properties.VALUES.core.account.Get()

    if not accounts and active_account:
      accounts = [active_account]

    if not accounts:
      raise c_exc.InvalidArgumentException(
          'accounts', 'No credentials available to revoke.')

    for account in accounts:
      if active_account == account:
        properties.PersistProperty(properties.VALUES.core.account, None)
      # External account and external account user credentials cannot be
      # revoked.
      # Detect these type of credentials to show a more user friendly message
      # on revocation calls.
      # Note that impersonated external account credentials will appear like
      # service accounts. These will end with gserviceaccount.com and will be
      # handled the same way service account credentials are handled.
      try:
        creds = c_store.Load(
            account, prevent_refresh=True, use_google_auth=True)
      except creds_exceptions.Error:
        # Ignore all errors. These will be properly handled in the subsequent
        # Revoke call.
        creds = None
      if not c_store.Revoke(account):
        if account.endswith('.gserviceaccount.com'):
          log.warning(
              '[{}] appears to be a service account. Service account tokens '
              'cannot be revoked, but they will expire automatically. To '
              'prevent use of the service account token earlier than the '
              'expiration, delete or disable the parent service account.'
              .format(account))
        elif c_creds.IsExternalAccountCredentials(creds):
          log.warning(
              '[{}] appears to be an external account. External account '
              'tokens cannot be revoked, but they will expire automatically.'
              .format(account))
        elif c_creds.IsExternalAccountUserCredentials(creds):
          log.warning(
              '[{}] appears to be an external account user. External account '
              'user tokens cannot be revoked, but they will expire '
              'automatically.'.format(account))
        else:
          log.warning(
              '[{}] already inactive (previously revoked?)'.format(account))
    return accounts
Beispiel #11
0
    def Run(self, args):
        """Run the helper command."""

        if args.method != 'get':
            if args.ignore_unknown:
                return
            raise c_exc.ToolException(
                'Unexpected method [{meth}]. "get" Expected.'.format(
                    meth=args.method))

        info = {}

        lines = sys.stdin.readlines()
        for line in lines:
            if _BLANK_LINE_RE.match(line):
                continue
            match = _KEYVAL_RE.match(line)
            if not match:
                raise c_exc.ToolException(
                    'Invalid input line format: [{format}].'.format(
                        format=line.rstrip('\n')))
            key, val = match.groups()
            info[key] = val.strip()

        if 'protocol' not in info:
            raise c_exc.ToolException('Required key "protocol" missing.')

        if 'host' not in info:
            raise c_exc.ToolException('Required key "host" missing.')

        if info.get('protocol') != 'https':
            raise c_exc.ToolException(
                'Invalid protocol [{p}].  "https" expected.'.format(
                    p=info.get('protocol')))

        credentialed_domains = [
            'code.google.com', 'source.developers.google.com'
        ]
        extra = properties.VALUES.core.credentialed_hosted_repo_domains.Get()
        if extra:
            credentialed_domains.extend(extra.split(','))
        if info.get('host') not in credentialed_domains:
            if args.ignore_unknown:
                return
            raise c_exc.ToolException(
                'Unknown host [{host}].'.format(host=info.get('host')))

        account = properties.VALUES.core.account.Get()

        try:
            cred = c_store.Load(account)
            c_store.Refresh(cred)
        except c_store.Error as e:
            sys.stderr.write(
                textwrap.dedent("""\
          ERROR: {error}
          Run 'gcloud auth login' to log in.
          """.format(error=str(e))))
            return

        self._CheckNetrc()

        sys.stdout.write(
            textwrap.dedent("""\
        username={username}
        password={password}
        """).format(username=account, password=cred.access_token))
Beispiel #12
0
    def Run(self, args):
        """Run the helper command."""

        if args.method not in GitHelper.METHODS:
            if args.ignore_unknown:
                return
            raise c_exc.ToolException(
                'Unexpected method [{meth}]. One of [{methods}] expected.'.
                format(meth=args.method, methods=', '.join(GitHelper.METHODS)))

        info = self._ParseInput()
        credentialed_domains = ['source.developers.google.com']
        extra = properties.VALUES.core.credentialed_hosted_repo_domains.Get()
        if extra:
            credentialed_domains.extend(extra.split(','))
        if info.get('host') not in credentialed_domains:
            if args.ignore_unknown:
                return
            raise c_exc.ToolException(
                'Unknown host [{host}].'.format(host=info.get('host')))

        if args.method == GitHelper.GET:
            account = properties.VALUES.core.account.Get()
            try:
                cred = c_store.Load(account)
                c_store.Refresh(cred)
            except c_store.Error as e:
                sys.stderr.write(
                    textwrap.dedent("""\
            ERROR: {error}
            Run 'gcloud auth login' to log in.
            """.format(error=str(e))))
                return

            self._CheckNetrc()

            sys.stdout.write(
                textwrap.dedent("""\
          username={username}
          password={password}
          """).format(username=account, password=cred.access_token))
        elif args.method == GitHelper.STORE:
            # On OSX, there is an additional credential helper that gets called before
            # ours does.  When we return a token, it gets cached there.  Git continues
            # to get it from there first until it expires.  That command then fails,
            # and the token is deleted, but it does not retry the operation.  The next
            # command gets a new token from us and it starts working again, for an
            # hour.  This erases our credential from the other cache whenever 'store'
            # is called on us.  Because they are called first, the token will already
            # be stored there, and so we can successfully erase it to prevent caching.
            if (platforms.OperatingSystem.Current() ==
                    platforms.OperatingSystem.MACOSX):
                log.debug('Clearing OSX credential cache.')
                try:
                    input_string = 'protocol={protocol}\nhost={host}\n\n'.format(
                        protocol=info.get('protocol'), host=info.get('host'))
                    log.debug('Calling erase with input:\n%s', input_string)
                    p = subprocess.Popen(
                        ['git-credential-osxkeychain', 'erase'],
                        stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)
                    (out, err) = p.communicate(input_string)
                    if p.returncode:
                        log.debug(
                            'Failed to clear OSX keychain:\nstdout: {%s}\nstderr: {%s}',
                            out, err)
                # pylint:disable=broad-except, This can fail and should only be done as
                # best effort.
                except Exception as e:
                    log.debug('Failed to clear OSX keychain', exc_info=True)
Beispiel #13
0
    def Run(self, args):
        """Create the .gcloud folder, if possible.

    Args:
      args: argparse.Namespace, the arguments this command is run with.

    Raises:
      ToolException: on project initialization errors.

    Returns:
      The path to the new gcloud workspace.
    """
        # Ensure that we're logged in.
        creds = c_store.Load()

        is_new_directory = False

        try:
            workspace = workspaces.FromCWD()
            # Cannot re-init when in a workspace.
            current_project = workspace.GetProperty(
                properties.VALUES.core.project)
            if current_project != args.project:
                message = (
                    'Directory [{root_directory}] is already initialized to project'
                    ' [{project}].').format(
                        root_directory=workspace.root_directory,
                        project=current_project)
            else:
                message = (
                    'Directory [{root_directory}] is already initialized.'
                ).format(root_directory=workspace.root_directory)
            raise c_exc.ToolException(message)
        except workspaces.NoContainingWorkspaceException:
            workspace_dir = os.path.join(os.getcwd(), args.project)
            message = ('Directory [{root_directory}] is not empty.').format(
                root_directory=workspace_dir)
            if os.path.exists(workspace_dir) and os.listdir(workspace_dir):
                raise c_exc.ToolException(message)
            else:
                files.MakeDir(workspace_dir)
                is_new_directory = True
                workspace = workspaces.Create(workspace_dir)

        workspace.SetProperty(properties.VALUES.core.project, args.project)
        if args.devshell_image:
            workspace.SetProperty(properties.VALUES.devshell.image,
                                  args.devshell_image)

        # Everything that can fail should happen within this next try: block.
        # If something fails, and the result is an empty directory that we just
        # created, we clean it up.
        try:
            source_client = source_v0.SourceV0(credentials=creds)
            try:
                response = source_client.repos.List(
                    source_v0.SourceReposListRequest(projectId=args.project))
            except apitools_base.HttpError:
                # Source API is down! Let's guess the repo.
                log.status.write(
                    textwrap.dedent("""\
            Unable to fetch repository URL. Guessing the URL, but if your
            project uses repo-sync then the cloned repository may be read-only.
            """))
                try:
                    workspace.CloneProjectRepository(
                        args.project, workspaces.DEFAULT_REPOSITORY_ALIAS)
                except workspaces.CannotFetchRepositoryException as e:
                    log.error(e)
            else:
                for repo in response.repos:
                    try:
                        workspace.CloneProjectRepository(
                            args.project, repo.repoName, repo.cloneUrl)
                    except workspaces.CannotFetchRepositoryException as e:
                        log.error(e)
        finally:
            cleared_files = False
            if is_new_directory:
                dir_files = os.listdir(workspace_dir)
                if not dir_files or dir_files == [
                        config.Paths().CLOUDSDK_WORKSPACE_CONFIG_DIR_NAME
                ]:
                    log.error((
                        'Unable to initialize project [{project}], cleaning up'
                        ' [{path}].').format(project=args.project,
                                             path=workspace_dir))
                    files.RmTree(workspace_dir)
                    cleared_files = True
        if cleared_files:
            raise c_exc.ToolException(
                'Unable to initialize project [{project}].'.format(
                    project=args.project))
        log.status.write(
            'Project [{prj}] was initialized in [{path}].\n'.format(
                path=workspace.root_directory, prj=args.project))

        return workspace
Beispiel #14
0
  def MakeRequest(url, command_path):
    """Gets the request object for the given URL.

    If the URL is for cloud storage and we get a 403, this will try to load the
    active credentials and use them to authenticate the download.

    Args:
      url: str, The URL to download.
      command_path: the command path to include in the User-Agent header if the
        URL is HTTP

    Raises:
      AuthenticationError: If this download requires authentication and there
        are no credentials or the credentials do not have access.

    Returns:
      urllib2.Request, The request.
    """
    headers = {
        b'Cache-Control': b'no-cache',
        b'User-Agent': http_encoding.Encode(
            http.MakeUserAgentString(command_path))
    }
    timeout = TIMEOUT_IN_SEC
    if command_path == UPDATE_MANAGER_COMMAND_PATH:
      timeout = UPDATE_MANAGER_TIMEOUT_IN_SEC
    try:
      if url.startswith(ComponentInstaller.GCS_BROWSER_DL_URL):
        url = url.replace(ComponentInstaller.GCS_BROWSER_DL_URL,
                          ComponentInstaller.GCS_API_DL_URL,
                          1)
      req = urllib.request.Request(url, headers=headers)
      return ComponentInstaller._RawRequest(req, timeout=timeout)
    except urllib.error.HTTPError as e:
      if e.code != 403 or not url.startswith(ComponentInstaller.GCS_API_DL_URL):
        raise e
      try:
        creds = store.Load()
        store.Refresh(creds)
        creds.apply(headers)
      except store.Error as e:
        # If we fail here, it is because there are no active credentials or the
        # credentials are bad.
        raise AuthenticationError(
            'This component requires valid credentials to install.', e)
      try:
        # Retry the download using the credentials.
        req = urllib.request.Request(url, headers=headers)
        return ComponentInstaller._RawRequest(req, timeout=timeout)
      except urllib.error.HTTPError as e:
        if e.code != 403:
          raise e
        # If we fail again with a 403, that means we used the credentials, but
        # they didn't have access to the resource.
        raise AuthenticationError("""\
Account [{account}] does not have permission to install this component.  Please
ensure that this account should have access or run:

  $ gcloud config set account `ACCOUNT`

to choose another account.""".format(
    account=properties.VALUES.core.account.Get()), e)
Beispiel #15
0
    def Run(self, args):
        """Create the .gcloud folder, if possible.

    Args:
      args: argparse.Namespace, the arguments this command is run with.

    Raises:
      ToolException: on project initialization errors.

    Returns:
      The path to the new gcloud workspace.
    """
        log.warn(
            '`gcloud init` will be changing soon. '
            'To clone git repo consider using `gcloud alpha source repo clone`'
            ' command.')
        # Ensure that we're logged in.
        c_store.Load()

        is_new_directory = False

        try:
            workspace = workspaces.FromCWD()
            # Cannot re-init when in a workspace.
            current_project = workspace.GetProperty(
                properties.VALUES.core.project)
            if current_project != args.project:
                message = (
                    'Directory [{root_directory}] is already initialized to project'
                    ' [{project}].').format(
                        root_directory=workspace.root_directory,
                        project=current_project)
            else:
                message = (
                    'Directory [{root_directory}] is already initialized.'
                ).format(root_directory=workspace.root_directory)
            raise c_exc.ToolException(message)
        except workspaces.NoContainingWorkspaceException:
            workspace_dir = os.path.join(os.getcwd(), args.project)
            message = ('Directory [{root_directory}] is not empty.').format(
                root_directory=workspace_dir)
            if os.path.exists(workspace_dir) and os.listdir(workspace_dir):
                raise c_exc.ToolException(message)
            else:
                files.MakeDir(workspace_dir)
                is_new_directory = True
                workspace = workspaces.Create(workspace_dir)

        workspace.SetProperty(properties.VALUES.core.project, args.project)
        if args.devshell_image:
            workspace.SetProperty(properties.VALUES.devshell.image,
                                  args.devshell_image)

        # Everything that can fail should happen within this next try: block.
        # If something fails, and the result is an empty directory that we just
        # created, we clean it up.
        try:
            workspace.CloneProjectRepository(
                args.project, workspaces.DEFAULT_REPOSITORY_ALIAS)
        except workspaces.CannotFetchRepositoryException as e:
            log.error(e)
        finally:
            cleared_files = False
            if is_new_directory:
                dir_files = os.listdir(workspace_dir)
                if not dir_files or dir_files == [
                        config.Paths().CLOUDSDK_WORKSPACE_CONFIG_DIR_NAME
                ]:
                    log.error((
                        'Unable to initialize project [{project}], cleaning up'
                        ' [{path}].').format(project=args.project,
                                             path=workspace_dir))
                    files.RmTree(workspace_dir)
                    cleared_files = True
        if cleared_files:
            raise c_exc.ToolException(
                'Unable to initialize project [{project}].'.format(
                    project=args.project))
        log.status.write(
            'Project [{prj}] was initialized in [{path}].\n'.format(
                path=workspace.root_directory, prj=args.project))

        return workspace
  def Run(self, args):
    client = apis.GetClientInstance('storagetransfer', 'v1')
    messages = apis.GetMessagesModule('storagetransfer', 'v1')

    if args.creds_file:
      expanded_file_path = os.path.abspath(os.path.expanduser(args.creds_file))
      with files.FileReader(expanded_file_path) as file_reader:
        try:
          parsed_creds_file = json.load(file_reader)
          account_email = parsed_creds_file['client_email']
          is_service_account = parsed_creds_file['type'] == 'service_account'
        except (ValueError, KeyError) as e:
          log.error(e)
          raise ValueError('Invalid creds file format.'
                           ' Run command with "--help" flag for more details.')
        prefixed_account_email = _get_iam_prefixed_email(
            account_email, is_service_account)
    else:
      account_email = properties.VALUES.core.account.Get()
      is_service_account = creds.IsServiceAccountCredentials(creds_store.Load())
      prefixed_account_email = _get_iam_prefixed_email(account_email,
                                                       is_service_account)

    project_id = properties.VALUES.core.project.Get()
    parsed_project_id = projects_util.ParseProject(project_id)
    project_iam_policy = projects_api.GetIamPolicy(parsed_project_id)

    existing_user_roles = _get_existing_transfer_roles_for_account(
        project_iam_policy, prefixed_account_email, EXPECTED_USER_ROLES)
    log.status.Print('User {} has roles:\n{}'.format(account_email,
                                                     list(existing_user_roles)))
    missing_user_roles = EXPECTED_USER_ROLES - existing_user_roles
    log.status.Print('Missing roles:\n{}'.format(list(missing_user_roles)))

    all_missing_role_tuples = [
        (prefixed_account_email, role) for role in missing_user_roles
    ]

    log.status.Print('***')

    transfer_p4sa_email = client.googleServiceAccounts.Get(
        messages.StoragetransferGoogleServiceAccountsGetRequest(
            projectId=project_id)).accountEmail
    prefixed_transfer_p4sa_email = _get_iam_prefixed_email(
        transfer_p4sa_email, is_service_account=True)

    existing_p4sa_roles = _get_existing_transfer_roles_for_account(
        project_iam_policy, prefixed_transfer_p4sa_email, EXPECTED_P4SA_ROLES)
    log.status.Print('Google-managed transfer account {} has roles:\n{}'.format(
        transfer_p4sa_email, list(existing_p4sa_roles)))
    missing_p4sa_roles = EXPECTED_P4SA_ROLES - existing_p4sa_roles
    log.status.Print('Missing roles:\n{}'.format(list(missing_p4sa_roles)))

    all_missing_role_tuples += [
        (prefixed_transfer_p4sa_email, role) for role in missing_p4sa_roles
    ]

    if args.add_missing or all_missing_role_tuples:
      log.status.Print('***')
      if args.add_missing:
        if all_missing_role_tuples:
          log.status.Print('Adding roles:\n{}'.format(all_missing_role_tuples))
          projects_api.AddIamPolicyBindings(parsed_project_id,
                                            all_missing_role_tuples)
          log.status.Print('***')
          # Source:
          # https://cloud.google.com/iam/docs/granting-changing-revoking-access
          log.status.Print(
              'Done. Permissions typically take seconds to propagate, but,'
              ' in some cases, it can take up to seven minutes.')
        else:
          log.status.Print('No missing roles to add.')
      else:
        log.status.Print('Rerun with --add-missing to add missing roles.')
Beispiel #17
0
 def password(self):
   cred = c_store.Load()
   return cred.access_token
 def testSimpleGoogleAuthIntegration(self):
     creds = c_store.Load(use_google_auth=True)
     self.assertIsInstance(creds, devshell.DevShellCredentialsGoogleAuth)
     self.Run('sql flags list')
Beispiel #19
0
  def Run(self, args):
    """Run the helper command."""

    if args.method not in GitHelper.METHODS:
      if args.ignore_unknown:
        return
      raise auth_exceptions.GitCredentialHelperError(
          'Unexpected method [{meth}]. One of [{methods}] expected.'
          .format(meth=args.method, methods=', '.join(GitHelper.METHODS)))

    info = self._ParseInput()
    credentialed_domains = [
        'source.developers.google.com',
        GitHelper.GOOGLESOURCE,  # Requires a different username value.
    ]
    credentialed_domains_suffix = [
        '.'+GitHelper.GOOGLESOURCE,
    ]
    extra = properties.VALUES.core.credentialed_hosted_repo_domains.Get()
    if extra:
      credentialed_domains.extend(extra.split(','))
    host = info.get('host')

    def _ValidateHost(host):
      if host in credentialed_domains:
        return True
      for suffix in credentialed_domains_suffix:
        if host.endswith(suffix):
          return True
      return False

    if not _ValidateHost(host):
      if not args.ignore_unknown:
        raise auth_exceptions.GitCredentialHelperError(
            'Unknown host [{host}].'.format(host=host))
      return

    if args.method == GitHelper.GET:
      account = properties.VALUES.core.account.Get()
      try:
        cred = c_store.Load(account, use_google_auth=True)
        c_store.Refresh(cred)
      except c_store.Error as e:
        sys.stderr.write(textwrap.dedent("""\
            ERROR: {error}
            Run 'gcloud auth login' to log in.
            """.format(error=six.text_type(e))))
        return

      self._CheckNetrc()

      # For googlesource.com, any username beginning with "git-" is accepted
      # and the identity of the user is extracted from the token server-side.
      if (host == GitHelper.GOOGLESOURCE
          or host.endswith('.'+GitHelper.GOOGLESOURCE)):
        sent_account = 'git-account'
      else:
        sent_account = account

      if c_creds.IsOauth2ClientCredentials(cred):
        access_token = cred.access_token
      else:
        access_token = cred.token

      sys.stdout.write(
          textwrap.dedent("""\
          username={username}
          password={password}
          """).format(username=sent_account, password=access_token))
    elif args.method == GitHelper.STORE:
      # On OSX, there is an additional credential helper that gets called before
      # ours does.  When we return a token, it gets cached there.  Git continues
      # to get it from there first until it expires.  That command then fails,
      # and the token is deleted, but it does not retry the operation.  The next
      # command gets a new token from us and it starts working again, for an
      # hour.  This erases our credential from the other cache whenever 'store'
      # is called on us.  Because they are called first, the token will already
      # be stored there, and so we can successfully erase it to prevent caching.
      if (platforms.OperatingSystem.Current() ==
          platforms.OperatingSystem.MACOSX):
        log.debug('Clearing OSX credential cache.')
        try:
          input_string = 'protocol={protocol}\nhost={host}\n\n'.format(
              protocol=info.get('protocol'), host=info.get('host'))
          log.debug('Calling erase with input:\n%s', input_string)
          p = subprocess.Popen(['git-credential-osxkeychain', 'erase'],
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)
          (out, err) = p.communicate(input_string)
          if p.returncode:
            log.debug(
                'Failed to clear OSX keychain:\nstdout: {%s}\nstderr: {%s}',
                out, err)
        # pylint:disable=broad-except, This can fail and should only be done as
        # best effort.
        except Exception as e:
          log.debug('Failed to clear OSX keychain', exc_info=True)
Beispiel #20
0
def Http(cmd_path=None, trace_token=None, trace_email=None, trace_log=False,
         auth=True, creds=None, timeout='unset', log_http=False,
         authority_selector=None, authorization_token_file=None):
  """Get an httplib2.Http object for working with the Google API.

  Args:
    cmd_path: str, Path of command that will use the httplib2.Http object.
    trace_token: str, Token to be used to route service request traces.
    trace_email: str, username to which service request traces should be sent.
    trace_log: bool, Enable/disable server side logging of service requests.
    auth: bool, True if the http object returned should be authorized.
    creds: oauth2client.client.Credentials, If auth is True and creds is not
        None, use those credentials to authorize the httplib2.Http object.
    timeout: double, The timeout in seconds to pass to httplib2.  This is the
        socket level timeout.  If timeout is None, timeout is infinite.  If
        default argument 'unset' is given, a sensible default is selected.
    log_http: bool, Enable/disable client side logging of service requests.
    authority_selector: str, The IAM authority selector to pass as a header,
        or None to not pass anything.

  Returns:
    An authorized httplib2.Http object, or a regular httplib2.Http object if no
    credentials are available.

  Raises:
    c_store.Error: If an error loading the credentials occurs.
  """

  # Compared with setting the default timeout in the function signature (i.e.
  # timeout=300), this lets you test with short default timeouts by mocking
  # GetDefaultTimeout.
  effective_timeout = timeout if timeout != 'unset' else GetDefaultTimeout()

  # TODO(user): Have retry-once-if-denied logic, to allow client tools to not
  # worry about refreshing credentials.

  http = c_store._Http(  # pylint:disable=protected-access
      timeout=effective_timeout, proxy_info=_GetHttpProxyInfo())

  # Wrap first to dump any data added by other wrappers.
  if log_http:
    http = _WrapRequestForLogging(http)

  # Wrap the request method to put in our own user-agent, and trace reporting.
  gcloud_ua = MakeUserAgentString(cmd_path)

  http = _WrapRequestForUserAgentAndTracing(http, trace_token,
                                            trace_email,
                                            trace_log,
                                            gcloud_ua)

  if authority_selector or authorization_token_file:
    http = _WrapRequestForIAMAuth(http, authority_selector,
                                  authorization_token_file)

  if auth:
    if not creds:
      creds = c_store.Load()
    http = creds.authorize(http)
    # Wrap the request method to put in our own error handling.
    http = _WrapRequestForAuthErrHandling(http)

  return http
 def testSimpleGoogleAuthIntegration(self):
     creds = c_store.Load(use_google_auth=True)
     self.assertIsInstance(creds, devshell.DevShellCredentialsGoogleAuth)
     # dns surface is on google-auth
     self.Run('dns managed-zones list')
 def testSimpleIntegration(self):
     creds = c_store.Load()
     self.assertEqual(type(creds), devshell.DevshellCredentials)
     self.Run('sql flags list')
 def testNoSerialize(self):
     creds = c_store.Load()
     # This should just do nothing. If the creds are actually serialized,
     # things blow up.
     c_store.Store(creds)
Beispiel #24
0
  def Run(self, args):
    """Run the authentication command."""

    scopes = config.CLOUDSDK_SCOPES
    # Add REAUTH scope in case the user has 2fact activated.
    # This scope is only used here and when refreshing the access token.
    scopes += (config.REAUTH_SCOPE,)

    if args.enable_gdrive_access:
      scopes += (auth_util.GOOGLE_DRIVE_SCOPE,)

    if c_devshell.IsDevshellEnvironment():
      message = """
          You are already authenticated with gcloud when running
          inside the Cloud Shell and so do not need to run this
          command.

          Do you wish to proceed anyway?
        """
      answer = console_io.PromptContinue(message=message)
      if not answer:
        return None
    elif c_gce.Metadata().connected:
      message = textwrap.dedent("""
          You are running on a Google Compute Engine virtual machine.
          It is recommended that you use service accounts for authentication.

          You can run:

            $ gcloud config set account `ACCOUNT`

          to switch accounts if necessary.

          Your credentials may be visible to others with access to this
          virtual machine. Are you sure you want to authenticate with
          your personal account?
          """)
      answer = console_io.PromptContinue(message=message)
      if not answer:
        return None

    account = args.account

    if account and not args.force:
      try:
        creds = c_store.Load(account=account, scopes=scopes)
      except c_store.Error:
        creds = None
      if creds:
        # Account already has valid creds, just switch to it.
        return self.LoginAs(account, creds, args.project, args.activate,
                            args.brief)

    # No valid creds, do the web flow.
    launch_browser = check_browser.ShouldLaunchBrowser(args.launch_browser)
    creds = auth_util.DoInstalledAppBrowserFlow(launch_browser, scopes)
    web_flow_account = creds.id_token['email']
    if account and account.lower() != web_flow_account.lower():
      raise auth_exceptions.WrongAccountError(
          'You attempted to log in as account [{account}] but the received '
          'credentials were for account [{web_flow_account}].\n\n'
          'Please check that your browser is logged in as account [{account}] '
          'and that you are using the correct browser profile.'.format(
              account=account, web_flow_account=web_flow_account))

    account = web_flow_account
    # We got new creds, and they are for the correct user.
    c_store.Store(creds, account, scopes)
    return self.LoginAs(account, creds, args.project, args.activate,
                        args.brief)
def LoginWithCredFileConfig(cred_config, scopes, project, activate, brief,
                            update_adc, add_quota_project_to_adc,
                            args_account):
    """Login with the provided configuration loaded from --cred-file.

  Args:
    cred_config (Mapping): The configuration dictionary representing the
      credentials. This is loaded from the --cred-file argument.
    scopes (Tuple[str]): The default OAuth scopes to use.
    project (Optional[str]): The optional project ID to activate / persist.
    activate (bool): Whether to set the new account associated with the
      credentials to active.
    brief (bool): Whether to use minimal user output.
    update_adc (bool): Whether to write the obtained credentials to the
      well-known location for Application Default Credentials (ADC).
    add_quota_project_to_adc (bool): Whether to add the quota project to the
      application default credentials file.
    args_account (Optional[str]): The optional ACCOUNT argument. When provided,
      this should match the account ID on the authenticated credentials.

  Returns:
    google.auth.credentials.Credentials: The authenticated stored credentials.

  Raises:
    calliope_exceptions.ConflictingArgumentsException: If conflicting arguments
      are provided.
    calliope_exceptions.InvalidArgumentException: If invalid arguments are
      provided.
  """
    # Remove reauth scope (only applicable to 1P user accounts).
    scopes = tuple(x for x in scopes if x != config.REAUTH_SCOPE)
    # Reject unsupported arguments.
    if add_quota_project_to_adc:
        raise calliope_exceptions.ConflictingArgumentsException(
            '[--add-quota-project-to-adc] cannot be specified with --cred-file'
        )
    if auth_external_account.IsExternalAccountConfig(cred_config):
        creds = auth_external_account.CredentialsFromAdcDictGoogleAuth(
            cred_config)
        account = auth_external_account.GetExternalAccountId(creds)
    elif auth_service_account.IsServiceAccountConfig(cred_config):
        creds = auth_service_account.CredentialsFromAdcDictGoogleAuth(
            cred_config)
        account = creds.service_account_email
    else:
        raise calliope_exceptions.InvalidArgumentException(
            '--cred-file',
            'Only external account or service account JSON credential file types '
            'are supported.')

    if args_account and args_account != account:
        raise calliope_exceptions.InvalidArgumentException(
            'ACCOUNT',
            'The given account name does not match the account name in the '
            'credential file. This argument can be omitted when using '
            'credential files.')
    # Check if account already exists in storage.
    try:
        exist_creds = c_store.Load(account=account, scopes=scopes)
    except creds_exceptions.Error:
        exist_creds = None
    if exist_creds:
        message = textwrap.dedent("""
      You are already authenticated with '%s'.
      Do you wish to proceed and overwrite existing credentials?
      """)
        answer = console_io.PromptContinue(message=message % account,
                                           default=True)
        if not answer:
            return None
    # Store credentials and activate if --activate is true.
    c_store.Store(creds, account, scopes=scopes)
    return LoginAs(account, creds, project, activate, brief, update_adc, False)