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)
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))
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)
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