예제 #1
0
def GetCredentials(access_boundary_json):
    """Get an access token for the user's current credentials.

  Args:
    access_boundary_json: JSON string holding the definition of the access
      boundary to apply to the credentials.

  Raises:
    PersonalAuthError: If no access token could be fetched for the user.

  Returns:
    An access token for the user.
  """
    cred = c_store.Load(None,
                        allow_account_impersonation=True,
                        use_google_auth=True)
    c_store.Refresh(cred)
    if c_creds.IsOauth2ClientCredentials(cred):
        token = cred.access_token
    else:
        token = cred.token
    if not token:
        raise exceptions.PersonalAuthError(
            'No access token could be obtained from the current credentials.')
    return _DownscopeCredentials(token, access_boundary_json)
예제 #2
0
    def run_openssl_command(cls, openssl_executable, args, stdin=None):
        """Run the specified command, capturing and returning output as appropriate.

    Args:
      openssl_executable: The path to the openssl executable.
      args: The arguments to the openssl command to run.
      stdin: The input to the command.

    Returns:
      The output of the command.

    Raises:
      PersonalAuthError: If the call to openssl fails
    """
        command = [openssl_executable]
        command.extend(args)
        stderr = None
        try:
            if getattr(subprocess, 'run', None):
                proc = subprocess.run(command,
                                      input=stdin,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE,
                                      check=False)
                stderr = proc.stderr.decode('utf-8').strip()
                # N.B. It would be better if we could simply call `subprocess.run` with
                # the `check` keyword arg set to true rather than manually calling
                # `check_returncode`. However, we want to capture the stderr when the
                # command fails, and the CalledProcessError type did not have a field
                # for the stderr until Python version 3.5.
                #
                # As such, we need to manually call `check_returncode` as long as we
                # are supporting Python versions prior to 3.5.
                proc.check_returncode()
                return proc.stdout
            else:
                p = subprocess.Popen(command,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
                stdout, _ = p.communicate(input=stdin)
                return stdout
        except Exception as ex:
            if stderr:
                log.error(
                    'OpenSSL command "%s" failed with error message "%s"',
                    ' '.join(command), stderr)
            raise exceptions.PersonalAuthError(
                'Failure running openssl command: "' + ' '.join(command) +
                '": ' + six.text_type(ex))
예제 #3
0
 def inject_credentials(self, dataproc, project, region, cluster_name,
                        cluster_uuid, cluster_key, access_boundary_json,
                        openssl_executable, operation_poller):
     downscoped_token = util.GetCredentials(access_boundary_json)
     if not downscoped_token:
         raise exceptions.PersonalAuthError(
             'Failure getting credentials to inject into {}'.format(
                 cluster_name))
     credentials_ciphertext = self.encrypt_with_cluster_key(
         cluster_key, downscoped_token, openssl_executable)
     inject_operation = _inject_encrypted_credentials(
         dataproc, project, region, cluster_name, cluster_uuid,
         credentials_ciphertext)
     if inject_operation:
         waiter.WaitFor(operation_poller, inject_operation)
    def encrypt_with_cluster_key(self, cluster_public_key, secret,
                                 openssl_executable):
        if openssl_executable:
            return self.encode_token_using_openssl(cluster_public_key, secret,
                                                   openssl_executable)
        try:
            # pylint: disable=g-import-not-at-top
            import tink
            from tink import hybrid
            from tink import cleartext_keyset_handle
            # pylint: enable=g-import-not-at-top
        except ImportError:
            raise exceptions.PersonalAuthError(
                'Cannot load the Tink cryptography library. Either the '
                'library is not installed, or site packages are not '
                'enabled for the Google Cloud SDK. Please consult Cloud '
                'Dataproc Personal Auth documentation on adding Tink to '
                'Google Cloud SDK for further instructions.\n'
                'https://cloud.google.com/dataproc/docs/concepts/iam/personal-auth'
            )

        hybrid.register()
        context = b''

        # Extract value of key corresponding to primary key.
        public_key_value = json.loads(
            cluster_public_key)['key'][0]['keyData']['value']
        cluster_key_hash = hashlib.sha256(
            (public_key_value + '\n').encode('utf-8')).hexdigest()

        # Load public key and create keyset handle.
        reader = tink.JsonKeysetReader(cluster_public_key)
        kh_pub = cleartext_keyset_handle.read(reader)

        # Create encrypter instance.
        encrypter = kh_pub.primitive(hybrid.HybridEncrypt)
        ciphertext = encrypter.encrypt(secret.encode('utf-8'), context)

        encoded_token = base64.b64encode(ciphertext).decode('utf-8')
        return '{}:{}'.format(cluster_key_hash, encoded_token)
예제 #5
0
    def Run(self, args):
        message = (
            'A personal authentication session will propagate your personal '
            'credentials to the cluster, so make sure you trust the cluster '
            'and the user who created it.')
        console_io.PromptContinue(
            message=message,
            cancel_on_no=True,
            cancel_string='Enabling session aborted by user')
        dataproc = dp.Dataproc(self.ReleaseTrack())

        cluster_ref = args.CONCEPTS.cluster.Parse()
        project = cluster_ref.projectId
        region = cluster_ref.region
        cluster_name = cluster_ref.clusterName
        get_request = dataproc.messages.DataprocProjectsRegionsClustersGetRequest(
            projectId=project, region=region, clusterName=cluster_name)
        cluster = dataproc.client.projects_regions_clusters.Get(get_request)
        cluster_uuid = cluster.clusterUuid

        if args.access_boundary:
            with files.FileReader(args.access_boundary, mode='r') as abf:
                access_boundary_json = abf.read()
        else:
            access_boundary_json = flags.ProjectGcsObjectsAccessBoundary(
                project)

        openssl_executable = args.openssl_command
        if not openssl_executable:
            try:
                openssl_executable = files.FindExecutableOnPath('openssl')
            except ValueError:
                log.fatal(
                    'Could not find openssl on your system. The enable-session '
                    'command requires openssl to be installed.')

        operation_poller = waiter.CloudOperationPollerNoResources(
            dataproc.client.projects_regions_operations,
            lambda operation: operation.name)
        try:
            cluster_key = clusters.ClusterKey(cluster)
            if not cluster_key:
                raise exceptions.PersonalAuthError(
                    'The cluster {} does not support personal auth.'.format(
                        cluster_name))

            with progress_tracker.ProgressTracker(
                    'Injecting initial credentials into the cluster {}'.format(
                        cluster_name),
                    autotick=True):
                self.inject_credentials(dataproc, project, region,
                                        cluster_name, cluster_uuid,
                                        cluster_key, access_boundary_json,
                                        openssl_executable, operation_poller)

            if not args.refresh_credentials:
                return

            update_message = (
                'Periodically refreshing credentials for cluster {}. This'
                ' will continue running until the command is interrupted'
            ).format(cluster_name)

            with progress_tracker.ProgressTracker(update_message,
                                                  autotick=True):
                try:
                    # Cluster keys are periodically regenerated, so fetch the latest
                    # each time we inject credentials.
                    cluster = dataproc.client.projects_regions_clusters.Get(
                        get_request)
                    cluster_key = clusters.ClusterKey(cluster)
                    if not cluster_key:
                        raise exceptions.PersonalAuthError(
                            'The cluster {} does not support personal auth.'.
                            format(cluster_name))

                    failure_count = 0
                    while failure_count < 3:
                        try:
                            time.sleep(30)
                            self.inject_credentials(dataproc, project, region,
                                                    cluster_name, cluster_uuid,
                                                    cluster_key,
                                                    access_boundary_json,
                                                    openssl_executable,
                                                    operation_poller)
                            failure_count = 0
                        except ValueError as err:
                            log.error(err)
                            failure_count += 1
                    raise exceptions.PersonalAuthError(
                        'Credential injection failed three times in a row, giving up...'
                    )
                except (console_io.OperationCancelledError, KeyboardInterrupt):
                    return
        except exceptions.PersonalAuthError as err:
            log.error(err)
            return