def CreateRequests(self, args): name = args.name if not name: name = gaia_utils.GetDefaultAccountName(self.http) user_ref = self.clouduseraccounts_resources.Parse( name, collection='clouduseraccounts.users') if args.fingerprints: fingerprints = args.fingerprints else: fetcher = users_client.UserResourceFetcher( self.clouduseraccounts, self.project, self.http, self.batch_url) fingerprints = [k.fingerprint for k in fetcher.LookupUser(user_ref.Name()).publicKeys] # Generate warning before deleting. prompt_list = ['[{0}]'.format(fingerprint) for fingerprint in fingerprints] prompt_title = ('The following public keys will be removed from the user ' + user_ref.Name()) utils.PromptForDeletionHelper(None, prompt_list, prompt_title=prompt_title) requests = [] for fingerprint in fingerprints: request = self.messages.ClouduseraccountsUsersRemovePublicKeyRequest( project=self.project, fingerprint=fingerprint, user=user_ref.Name()) requests.append(request) return requests
def Run(self, args): holder = base_classes.ComputeUserAccountsApiHolder(self.ReleaseTrack()) client = holder.client name = args.name if not name: name = gaia.GetDefaultAccountName(client.http) user_ref = holder.resources.Parse( name, params={'project': properties.VALUES.core.project.GetOrFail}, collection='clouduseraccounts.users') if args.fingerprints: fingerprints = args.fingerprints else: fetcher = users_client.UserResourceFetcher( client, user_ref.project, client.http, 'https://www.googleapis.com/batch/') fingerprints = [ k.fingerprint for k in fetcher.LookupUser(user_ref.Name()).publicKeys ] # Generate warning before deleting. prompt_list = [ '[{0}]'.format(fingerprint) for fingerprint in fingerprints ] prompt_title = ( 'The following public keys will be removed from the user ' + user_ref.Name()) utils.PromptForDeletionHelper(None, prompt_list, prompt_title=prompt_title) requests = [] for fingerprint in fingerprints: request = (client.MESSAGES_MODULE. ClouduseraccountsUsersRemovePublicKeyRequest( project=user_ref.project, fingerprint=fingerprint, user=user_ref.Name())) requests.append((client.users, 'RemovePublicKey', request)) errors = [] responses = list( request_helper.MakeRequests( requests=requests, http=client.http, batch_url='https://www.googleapis.com/batch/', errors=errors)) if errors: utils.RaiseToolException(errors, error_message='Could not fetch resource:') return responses
def Run(self, args): compute_holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) holder = base_classes.ComputeUserAccountsApiHolder(self.ReleaseTrack()) client = holder.client name = args.name if not name: name = gaia.GetDefaultAccountName(client.http) user_ref = holder.resources.Parse( name, params={'project': properties.VALUES.core.project.GetOrFail}, collection='clouduseraccounts.users') if args.fingerprints: fingerprints = args.fingerprints else: fetcher = users_client.UserResourceFetcher( client, user_ref.project, client.http, compute_holder.client.batch_url) fingerprints = [ k.fingerprint for k in fetcher.LookupUser(user_ref.Name()).publicKeys ] # Generate warning before deleting. prompt_list = [ '[{0}]'.format(fingerprint) for fingerprint in fingerprints ] prompt_title = ( 'The following public keys will be removed from the user ' + user_ref.Name()) utils.PromptForDeletionHelper(None, prompt_list, prompt_title=prompt_title) requests = [] for fingerprint in fingerprints: request = (client.MESSAGES_MODULE. ClouduseraccountsUsersRemovePublicKeyRequest( project=user_ref.project, fingerprint=fingerprint, user=user_ref.Name())) requests.append((client.users, 'RemovePublicKey', request)) return compute_holder.client.MakeRequests(requests)
def EnsureSSHKeyExists(self, user, instance, project, use_account_service=False): """Controller for EnsureSSHKey* variants. Sends the key to the project metadata, instance metadata or account service, and signals whether the key was newly added. Args: user: str, The user name. instance: Instance, the instance to connect to. project: Project, the project instance is in use_account_service: bool, when false upload ssh keys to project metadata. Returns: bool, True if the key was newly added. """ if use_account_service: log.status.Print('using accounts service') fetcher = user_client.UserResourceFetcher(self.clouduseraccounts, self.project, self.http, self.batch_url) try: keys_newly_added = self._EnsureSSHKeyExistsForUser( fetcher, user) # TODO(b/37739425): find out what desired fallback mechanism is and # implement it. except user_client.UserException as e: log.info( 'Error when attempting to prepare keys using clouduaseraccounts ' 'API, falling back to metadata keys: %s', e) use_account_service = False if not use_account_service: # There are two kinds of metadata: project-wide metadata and per-instance # metadata. There are four SSH-key related metadata keys: # # * project['sshKeys']: shared project-wide # * instance['sshKeys']: legacy. Acts as an override to project['sshKeys'] # * instance['block-project-ssh-keys']: If true, instance['ssh-keys'] # overrides project['sshKeys']. Otherwise, keys from both metadata # pairs are valid. # * instance['ssh-keys']: Acts either in conjunction with or as an # override to project['sshKeys'], depending on # instance['block-project-ssh-keys'] # # SSH-like commands work by copying a relevant SSH key to # the appropriate metadata value. The VM grabs keys from the metadata as # follows (pseudo-Python): # # def GetAllSshKeys(project, instance): # if 'sshKeys' in instance.metadata: # return (instance.metadata['sshKeys'] + # instance.metadata['ssh-keys']) # elif instance.metadata['block-project-ssh-keys'] == 'true': # return instance.metadata['ssh-keys'] # else: # return (instance.metadata['ssh-keys'] + # project.metadata['sshKeys']) # if _GetSSHKeysFromMetadata(instance.metadata): # If we add a key to project-wide metadata but the per-instance # 'sshKeys' metadata exists, we won't be able to ssh in because the VM # won't check the project-wide metadata. To avoid this, if the instance # has per-instance SSH key metadata, we add the key there instead. keys_newly_added = self.EnsureSSHKeyIsInInstance( user, instance) elif _MetadataHasBlockProjectSshKeys(instance.metadata): # If the instance 'ssh-keys' metadata overrides the project-wide # 'sshKeys' metadata, we should put our key there. keys_newly_added = self.EnsureSSHKeyIsInInstance(user, instance, iam_keys=True) else: # Otherwise, try to add to the project-wide metadata. If we don't have # permissions to do that, add to the instance 'ssh-keys' metadata. try: keys_newly_added = self.EnsureSSHKeyIsInProject( user, project) except SetProjectMetadataError: log.info('Could not set project metadata:', exc_info=True) # If we can't write to the project metadata, it may be because of a # permissions problem (we could inspect this exception object further # to make sure, but because we only get a string back this would be # fragile). If that's the case, we want to try the writing to the # iam_keys metadata (we may have permissions to write to instance # metadata). We prefer this to the per-instance override of the # project metadata. log.info('Attempting to set instance metadata.') keys_newly_added = self.EnsureSSHKeyIsInInstance( user, instance, iam_keys=True) return keys_newly_added
def ActuallyRun(self, args, cmd_args, user, instance, project, strict_error_checking=True, use_account_service=False, wait_for_sshable=True, ignore_ssh_errors=False): """Runs the scp/ssh command specified in cmd_args. If the scp/ssh command exits non-zero, this command will exit with the same exit code. Args: args: argparse.Namespace, The calling command invocation args. cmd_args: [str], The argv for the command to execute. user: str, The user name. instance: Instance, the instance to connect to project: str, the project instance is in strict_error_checking: bool, whether to fail on a non-zero, non-255 exit code (alternative behavior is to return the exit code use_account_service: bool, when false upload ssh keys to project metadata. wait_for_sshable: bool, when false skip the sshability check. ignore_ssh_errors: bool, when true ignore all errors, including the 255 exit code. Raises: CommandError: If the scp/ssh command fails. Returns: int, the exit code of the command that was run """ cmd_args = ssh.LocalizeCommand(cmd_args, self.env) if args.dry_run: log.out.Print(' '.join(cmd_args)) return if args.plain: keys_newly_added = [] elif use_account_service: fetcher = user_client.UserResourceFetcher(self.clouduseraccounts, self.project, self.http, self.batch_url) keys_newly_added = self._EnsureSSHKeyExistsForUser(fetcher, user) else: # There are two kinds of metadata: project-wide metadata and per-instance # metadata. There are four SSH-key related metadata keys: # # * project['sshKeys']: shared project-wide # * instance['sshKeys']: legacy. Acts as an override to project['sshKeys'] # * instance['block-project-ssh-keys']: If true, instance['ssh-keys'] # overrides project['sshKeys']. Otherwise, keys from both metadata # pairs are valid. # * instance['ssh-keys']: Acts either in conjunction with or as an # override to project['sshKeys'], depending on # instance['block-project-ssh-keys'] # # SSH-like commands work by copying a relevant SSH key to # the appropriate metadata value. The VM grabs keys from the metadata as # follows (pseudo-Python): # # def GetAllSshKeys(project, instance): # if 'sshKeys' in instance.metadata: # return (instance.metadata['sshKeys'] + # instance.metadata['ssh-keys']) # elif instance.metadata['block-project-ssh-keys'] == 'true': # return instance.metadata['ssh-keys'] # else: # return (instance.metadata['ssh-keys'] + # project.metadata['sshKeys']) # if _GetSSHKeysFromMetadata(instance.metadata): # If we add a key to project-wide metadata but the per-instance # 'sshKeys' metadata exists, we won't be able to ssh in because the VM # won't check the project-wide metadata. To avoid this, if the instance # has per-instance SSH key metadata, we add the key there instead. keys_newly_added = self.EnsureSSHKeyIsInInstance( user, instance) elif _MetadataHasBlockProjectSshKeys(instance.metadata): # If the instance 'ssh-keys' metadata overrides the project-wide # 'sshKeys' metadata, we should put our key there. keys_newly_added = self.EnsureSSHKeyIsInInstance(user, instance, iam_keys=True) else: # Otherwise, try to add to the project-wide metadata. If we don't have # permissions to do that, add to the instance 'ssh-keys' metadata. try: keys_newly_added = self.EnsureSSHKeyIsInProject( user, project) except SetProjectMetadataError: log.info('Could not set project metadata:', exc_info=True) # If we can't write to the project metadata, it may be because of a # permissions problem (we could inspect this exception object further # to make sure, but because we only get a string back this would be # fragile). If that's the case, we want to try the writing to the # iam_keys metadata (we may have permissions to write to instance # metadata). We prefer this to the per-instance override of the # project metadata. log.info('Attempting to set instance metadata.') keys_newly_added = self.EnsureSSHKeyIsInInstance( user, instance, iam_keys=True) if keys_newly_added and wait_for_sshable: external_ip_address = GetExternalIPAddress(instance) host_key_alias = self.HostKeyAlias(instance) ssh.WaitUntilSSHable(user, external_ip_address, self.env, self.keys.key_file, host_key_alias, args.plain, args.strict_host_key_checking, _SSH_KEY_PROPAGATION_TIMEOUT_SEC) logging.debug('%s command: %s', cmd_args[0], ' '.join(cmd_args)) try: return ssh.RunExecutable( cmd_args, strict_error_checking=strict_error_checking, ignore_ssh_errors=ignore_ssh_errors) except ssh.CommandError as e: raise CommandError(e)
def EnsureSSHKeyExists(self, compute_client, cua_client, user, instance, project, use_account_service=False): """Controller for EnsureSSHKey* variants. Sends the key to the project metadata, instance metadata or account service, and signals whether the key was newly added. Args: compute_client: The compute client. cua_client: The clouduseraccounts client. user: str, The user name. instance: Instance, the instance to connect to. project: Project, the project instance is in use_account_service: bool, when false upload ssh keys to project metadata. Returns: bool, True if the key was newly added. """ if use_account_service: fetcher = user_client.UserResourceFetcher( cua_client, properties.VALUES.core.project.GetOrFail(), compute_client.apitools_client.http, compute_client.batch_url) try: keys_newly_added = self._EnsureSSHKeyExistsForUser(fetcher, user) # TODO(b/37739425): find out what desired fallback mechanism is and # implement it. except user_client.UserException as e: log.info( 'Error when attempting to prepare keys using clouduaseraccounts ' 'API, falling back to metadata keys: %s', e) use_account_service = False if not use_account_service: # There are two kinds of metadata: project-wide metadata and per-instance # metadata. There are five SSH-key related metadata keys: # # * project['ssh-keys']: shared project-wide list of keys. # * project['sshKeys']: legacy, shared project-wide list of keys. # * instance['block-project-ssh-keys']: bool, when true indicates that # instance keys should replace project keys rather than being added # to them. # * instance['ssh-keys']: instance specific list of keys. # * instance['sshKeys']: legacy, instance specific list of keys. When # present, instance keys override project keys as if # instance['block-project-ssh-keys'] was true. # # SSH-like commands work by copying a relevant SSH key to # the appropriate metadata value. The VM grabs keys from the metadata as # follows (pseudo-Python): # # def GetAllSshKeys(project, instance): # if 'sshKeys' in instance.metadata: # return (instance.metadata['sshKeys'] + # instance.metadata['ssh-keys']) # elif instance.metadata['block-project-ssh-keys'] == 'true': # return instance.metadata['ssh-keys'] # else: # return (instance.metadata['ssh-keys'] + # project.metadata['ssh-keys'] + # project.metadata['sshKeys']) # Legacy Project Keys # _, ssh_legacy_keys = _GetSSHKeysFromMetadata(instance.metadata) if ssh_legacy_keys: # If we add a key to project-wide metadata but the per-instance # 'sshKeys' metadata exists, we won't be able to ssh in because the VM # won't check the project-wide metadata. To avoid this, if the instance # has per-instance SSH key metadata, we add the key there instead. keys_newly_added = self.EnsureSSHKeyIsInInstance( compute_client, user, instance, legacy=True) elif _MetadataHasBlockProjectSshKeys(instance.metadata): # If the instance 'ssh-keys' metadata overrides the project-wide # 'ssh-keys' metadata, we should put our key there. keys_newly_added = self.EnsureSSHKeyIsInInstance( compute_client, user, instance) else: # Otherwise, try to add to the project-wide metadata. If we don't have # permissions to do that, add to the instance 'ssh-keys' metadata. try: keys_newly_added = self.EnsureSSHKeyIsInProject( compute_client, user, project) except SetProjectMetadataError: log.info('Could not set project metadata:', exc_info=True) # If we can't write to the project metadata, it may be because of a # permissions problem (we could inspect this exception object further # to make sure, but because we only get a string back this would be # fragile). If that's the case, we want to try the writing to instance # metadata. We prefer this to the per-instance override of the # project metadata. log.info('Attempting to set instance metadata.') keys_newly_added = self.EnsureSSHKeyIsInInstance( compute_client, user, instance) return keys_newly_added