def Run(self, args): """See ssh_utils.BaseSSHCLICommand.Run.""" holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) client = holder.client ssh_helper = ssh_utils.BaseSSHCLIHelper() ssh_helper.Run(args) user, instance_name = ssh_utils.GetUserAndInstance(args.user_host) instance_ref = instance_flags.SSH_INSTANCE_RESOLVER.ResolveResources( [instance_name], compute_scope.ScopeEnum.ZONE, args.zone, holder.resources, scope_lister=instance_flags.GetInstanceZoneScopeLister(client))[0] instance = ssh_helper.GetInstance(client, instance_ref) project = ssh_helper.GetProject(client, instance_ref.project) if self.get_host_keys: host_keys = ssh_helper.GetHostKeysFromGuestAttributes( client, instance_ref) if not host_keys: log.warning('Unable to retrieve host keys from instance metadata. ' 'Continuing.') else: host_keys = {} expiration, expiration_micros = ssh_utils.GetSSHKeyExpirationFromArgs(args) if args.plain: use_oslogin = False else: public_key = ssh_helper.keys.GetPublicKey().ToEntry(include_comment=True) user, use_oslogin = ssh.CheckForOsloginAndGetUser( instance, project, user, public_key, expiration_micros, self.ReleaseTrack()) iap_tunnel_args = iap_tunnel.SshTunnelArgs.FromArgs( args, self.ReleaseTrack(), instance_ref, ssh_utils.GetExternalInterface(instance, no_raise=True)) internal_address = ssh_utils.GetInternalIPAddress(instance) if iap_tunnel_args: # IAP Tunnel only uses instance_address for the purpose of --ssh-flag # substitution. In this case, dest_addr doesn't do much, it just matches # against entries in the user's ssh_config file. It's best to use # something unique to avoid false positive matches, thus we use # HostKeyAlias. instance_address = internal_address dest_addr = ssh_utils.HostKeyAlias(instance) elif args.internal_ip: instance_address = internal_address dest_addr = instance_address else: instance_address = ssh_utils.GetExternalIPAddress(instance) dest_addr = instance_address remote = ssh.Remote(dest_addr, user) identity_file = None options = None if not args.plain: identity_file = ssh_helper.keys.key_file options = ssh_helper.GetConfig(ssh_utils.HostKeyAlias(instance), args.strict_host_key_checking, host_keys_to_add=host_keys) extra_flags = ssh.ParseAndSubstituteSSHFlags(args, remote, instance_address, internal_address) remainder = [] if args.ssh_args: remainder.extend(args.ssh_args) # Transform args.command into arg list or None if no command command_list = args.command.split(' ') if args.command else None tty = containers.GetTty(args.container, command_list) remote_command = containers.GetRemoteCommand(args.container, command_list) # Do not include default port since that will prevent users from # specifying a custom port (b/121998342). ssh_cmd_args = {'remote': remote, 'identity_file': identity_file, 'options': options, 'extra_flags': extra_flags, 'remote_command': remote_command, 'tty': tty, 'iap_tunnel_args': iap_tunnel_args, 'remainder': remainder} cmd = ssh.SSHCommand(**ssh_cmd_args) if args.dry_run: log.out.Print(' '.join(cmd.Build(ssh_helper.env))) return if args.plain or use_oslogin: keys_newly_added = False else: keys_newly_added = ssh_helper.EnsureSSHKeyExists( client, remote.user, instance, project, expiration=expiration) if keys_newly_added: poller = ssh_utils.CreateSSHPoller(remote, identity_file, options, iap_tunnel_args, extra_flags=extra_flags) log.status.Print('Waiting for SSH key to propagate.') # TODO(b/35355795): Don't force_connect try: poller.Poll(ssh_helper.env, force_connect=True) except retry.WaitException: raise ssh_utils.NetworkError() if args.internal_ip: ssh_helper.PreliminarilyVerifyInstance(instance.id, remote, identity_file, options) # Errors from SSH itself result in an ssh.CommandError being raised return_code = cmd.Run(ssh_helper.env, force_connect=True) if return_code: # This is the return code of the remote command. Problems with SSH itself # will result in ssh.CommandError being raised above. sys.exit(return_code)
def PopulatePublicKey(api_client, service_id, version_id, instance_id, public_key, release_track): """Enable debug mode on and send SSH keys to a flex instance. Common method for SSH-like commands, does the following: - Makes sure that the service/version/instance specified exists and is of the right type (Flexible). - If not already done, prompts and enables debug on the instance. - Populates the public key onto the instance. Args: api_client: An appengine_api_client.AppEngineApiClient. service_id: str, The service ID. version_id: str, The version ID. instance_id: str, The instance ID. public_key: ssh.Keys.PublicKey, Public key to send. release_track: calliope.base.ReleaseTrack, The current release track. Raises: InvalidInstanceTypeError: The instance is not supported for SSH. MissingVersionError: The version specified does not exist. MissingInstanceError: The instance specified does not exist. UnattendedPromptError: Not running in a tty. OperationCancelledError: User cancelled the operation. Returns: ConnectionDetails, the details to use for SSH/SCP for the SSH connection. """ try: version = api_client.GetVersionResource(service=service_id, version=version_id) except apitools_exceptions.HttpNotFoundError: raise command_exceptions.MissingVersionError('{}/{}'.format( service_id, version_id)) version = version_util.Version.FromVersionResource(version, None) if version.environment is not env.FLEX: if version.environment is env.MANAGED_VMS: environment = 'Managed VMs' msg = 'Use `gcloud compute ssh` for Managed VMs instances.' else: environment = 'Standard' msg = None raise command_exceptions.InvalidInstanceTypeError(environment, msg) res = resources.REGISTRY.Parse( instance_id, params={ 'appsId': properties.VALUES.core.project.GetOrFail, 'versionsId': version_id, 'instancesId': instance_id, 'servicesId': service_id, }, collection='appengine.apps.services.versions.instances') rel_name = res.RelativeName() try: instance = api_client.GetInstanceResource(res) except apitools_exceptions.HttpNotFoundError: raise command_exceptions.MissingInstanceError(rel_name) if not instance.vmDebugEnabled: log.warning(_ENABLE_DEBUG_WARNING) console_io.PromptContinue(cancel_on_no=True, throw_if_unattended=True) user = ssh.GetDefaultSshUsername() project = _GetComputeProject(release_track) user, use_oslogin = ssh.CheckForOsloginAndGetUser(None, project, user, public_key.ToEntry(), release_track) remote = ssh.Remote(instance.vmIp, user=user) if not use_oslogin: ssh_key = '{user}:{key} {user}'.format(user=user, key=public_key.ToEntry()) log.status.Print( 'Sending public key to instance [{}].'.format(rel_name)) api_client.DebugInstance(res, ssh_key) options = { 'IdentitiesOnly': 'yes', # No ssh-agent as of yet 'UserKnownHostsFile': ssh.KnownHosts.DEFAULT_PATH, 'CheckHostIP': 'no', 'HostKeyAlias': _HOST_KEY_ALIAS.format(project=api_client.project, instance_id=instance_id) } return ConnectionDetails(remote, options)
def Run(self, args): """See ssh_utils.BaseSSHCommand.Run.""" holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) client = holder.client ssh_helper = ssh_utils.BaseSSHHelper() ssh_helper.Run(args) ssh_helper.keys.EnsureKeysExist(args.force_key_file_overwrite, allow_passphrase=True) remote = ssh.Remote.FromArg(args.user_host) if not remote: raise ssh_utils.ArgumentError( 'Expected argument of the form [USER@]INSTANCE. Received [{0}].' .format(args.user_host)) if not remote.user: remote.user = ssh.GetDefaultSshUsername() public_key = ssh_helper.keys.GetPublicKey().ToEntry(include_comment=True) hostname = '[{0}]:{1}'.format(args.serial_port_gateway, CONNECTION_PORT) # Update google_compute_known_hosts file with published host key if args.serial_port_gateway == SERIAL_PORT_GATEWAY: http_client = http.Http() http_response = http_client.request(HOST_KEY_URL) known_hosts = ssh.KnownHosts.FromDefaultFile() if http_response[0]['status'] == '200': host_key = http_response[1].strip() known_hosts.Add(hostname, host_key, overwrite=True) known_hosts.Write() elif known_hosts.ContainsAlias(hostname): log.warning( 'Unable to download and update Host Key for [{0}] from [{1}]. ' 'Attempting to connect using existing Host Key in [{2}]. If ' 'the connection fails, please try again to update the Host ' 'Key.'.format(SERIAL_PORT_GATEWAY, HOST_KEY_URL, known_hosts.file_path)) else: known_hosts.Add(hostname, DEFAULT_HOST_KEY) known_hosts.Write() log.warning( 'Unable to download Host Key for [{0}] from [{1}]. To ensure ' 'the security of the SSH connection, gcloud will attempt to ' 'connect using a hard-coded Host Key value. If the connection ' 'fails, please try again. If the problem persists, try ' 'updating gcloud and connecting again.' .format(SERIAL_PORT_GATEWAY, HOST_KEY_URL)) instance_ref = instance_flags.SSH_INSTANCE_RESOLVER.ResolveResources( [remote.host], compute_scope.ScopeEnum.ZONE, args.zone, holder.resources, scope_lister=instance_flags.GetInstanceZoneScopeLister(client))[0] instance = ssh_helper.GetInstance(client, instance_ref) project = ssh_helper.GetProject(client, instance_ref.project) expiration, expiration_micros = ssh_utils.GetSSHKeyExpirationFromArgs(args) remote.user, use_os_login = ssh.CheckForOsloginAndGetUser( instance, project, remote.user, public_key, expiration_micros, self.ReleaseTrack()) # Determine the serial user, host tuple (remote) port = 'port={0}'.format(args.port) constructed_username_list = [instance_ref.project, instance_ref.zone, instance_ref.Name(), remote.user, port] if args.extra_args: for k, v in args.extra_args.items(): constructed_username_list.append('{0}={1}'.format(k, v)) serial_user = '******'.join(constructed_username_list) serial_remote = ssh.Remote(args.serial_port_gateway, user=serial_user) identity_file = ssh_helper.keys.key_file options = ssh_helper.GetConfig(hostname, strict_host_key_checking='yes') del options['HostKeyAlias'] options['ControlPath'] = 'none' cmd = ssh.SSHCommand(serial_remote, identity_file=identity_file, port=CONNECTION_PORT, options=options) if args.dry_run: log.out.Print(' '.join(cmd.Build(ssh_helper.env))) return if not use_os_login: ssh_helper.EnsureSSHKeyExists( client, remote.user, instance, project, expiration) # Don't wait for the instance to become SSHable. We are not connecting to # the instance itself through SSH, so the instance doesn't need to have # fully booted to connect to the serial port. Also, ignore exit code 255, # since the normal way to terminate the serial port connection is ~. and # that causes ssh to exit with 255. try: return_code = cmd.Run(ssh_helper.env, force_connect=True) except ssh.CommandError: return_code = 255 if return_code: sys.exit(return_code)
def Run(self, args): """See ssh_utils.BaseSSHCLICommand.Run.""" holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) client = holder.client ssh_helper = ssh_utils.BaseSSHCLIHelper() ssh_helper.Run(args) user, instance_name = ssh_utils.GetUserAndInstance(args.user_host) instance_ref = instance_flags.SSH_INSTANCE_RESOLVER.ResolveResources( [instance_name], compute_scope.ScopeEnum.ZONE, args.zone, holder.resources, scope_lister=instance_flags.GetInstanceZoneScopeLister(client))[0] instance = ssh_helper.GetInstance(client, instance_ref) project = ssh_helper.GetProject(client, instance_ref.project) if args.plain: use_oslogin = False else: public_key = ssh_helper.keys.GetPublicKey().ToEntry( include_comment=True) user, use_oslogin = ssh.CheckForOsloginAndGetUser( instance, project, user, public_key, self.ReleaseTrack()) if args.internal_ip: ip_address = ssh_utils.GetInternalIPAddress(instance) else: ip_address = ssh_utils.GetExternalIPAddress(instance) remote = ssh.Remote(ip_address, user) identity_file = None options = None if not args.plain: identity_file = ssh_helper.keys.key_file options = ssh_helper.GetConfig(ssh_utils.HostKeyAlias(instance), args.strict_host_key_checking) extra_flags = ssh.ParseAndSubstituteSSHFlags(args, remote, ip_address) remainder = [] if args.ssh_args: remainder.extend(args.ssh_args) # Transform args.command into arg list or None if no command command_list = args.command.split(' ') if args.command else None tty = containers.GetTty(args.container, command_list) remote_command = containers.GetRemoteCommand(args.container, command_list) target_remote = remote port = ssh_utils.DEFAULT_SSH_PORT ip_type = (ip.IpTypeEnum.INTERNAL if args.internal_ip else ip.IpTypeEnum.EXTERNAL) tunnel_helper = None interface = None if hasattr(args, 'tunnel_through_iap') and args.tunnel_through_iap: tunnel_helper, interface = ssh_utils.CreateIapTunnelHelper( args, instance_ref, instance, ip_type) tunnel_helper.StartListener() target_remote = ssh.Remote('localhost', user) port = tunnel_helper.GetLocalPort() cmd = ssh.SSHCommand(target_remote, port=str(port), identity_file=identity_file, options=options, extra_flags=extra_flags, remote_command=remote_command, tty=tty, remainder=remainder) if args.dry_run: log.out.Print(' '.join(cmd.Build(ssh_helper.env))) if tunnel_helper: tunnel_helper.StopListener() return if args.plain or use_oslogin: keys_newly_added = False else: keys_newly_added = ssh_helper.EnsureSSHKeyExists( client, remote.user, instance, project) if keys_newly_added: poller_tunnel_helper = None if tunnel_helper: poller_tunnel_helper, _ = ssh_utils.CreateIapTunnelHelper( args, instance_ref, instance, ip_type, interface=interface) poller_tunnel_helper.StartListener( accept_multiple_connections=True) poller = ssh_utils.CreateSSHPoller(remote, identity_file, options, poller_tunnel_helper, extra_flags=extra_flags) log.status.Print('Waiting for SSH key to propagate.') # TODO(b/35355795): Don't force_connect try: poller.Poll(ssh_helper.env, force_connect=True) except retry.WaitException: if tunnel_helper: tunnel_helper.StopListener() raise ssh_utils.NetworkError() finally: if poller_tunnel_helper: poller_tunnel_helper.StopListener() if args.internal_ip and not tunnel_helper: # The IAP Tunnel connection uses instance name and network interface name, # so do not need to additionally verify the instance. Also, the # SSHCommand used within the function does not support IAP Tunnels. ssh_helper.PreliminarilyVerifyInstance(instance.id, remote, identity_file, options) try: # Errors from SSH itself result in an ssh.CommandError being raised return_code = cmd.Run(ssh_helper.env, force_connect=True) finally: if tunnel_helper: tunnel_helper.StopListener() if return_code: # This is the return code of the remote command. Problems with SSH itself # will result in ssh.CommandError being raised above. sys.exit(return_code)
def Run(self, args): """See ssh_utils.BaseSSHCLICommand.Run.""" holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) client = holder.client ssh_helper = ssh_utils.BaseSSHCLIHelper() ssh_helper.Run(args) user, instance_name = ssh_utils.GetUserAndInstance(args.user_host) instance_ref = instance_flags.SSH_INSTANCE_RESOLVER.ResolveResources( [instance_name], compute_scope.ScopeEnum.ZONE, args.zone, holder.resources, scope_lister=instance_flags.GetInstanceZoneScopeLister(client))[0] instance = ssh_helper.GetInstance(client, instance_ref) project = ssh_helper.GetProject(client, instance_ref.project) if args.plain: use_oslogin = False else: public_key = ssh_helper.keys.GetPublicKey().ToEntry( include_comment=True) user, use_oslogin = ssh.CheckForOsloginAndGetUser( instance, project, user, public_key, self.ReleaseTrack()) if args.internal_ip: ip_address = ssh_utils.GetInternalIPAddress(instance) else: ip_address = ssh_utils.GetExternalIPAddress(instance) remote = ssh.Remote(ip_address, user) identity_file = None options = None if not args.plain: identity_file = ssh_helper.keys.key_file options = ssh_helper.GetConfig(ssh_utils.HostKeyAlias(instance), args.strict_host_key_checking) extra_flags = ssh.ParseAndSubstituteSSHFlags(args, remote, ip_address) remainder = [] if args.ssh_args: remainder.extend(args.ssh_args) # Transform args.command into arg list or None if no command command_list = args.command.split(' ') if args.command else None tty = containers.GetTty(args.container, command_list) remote_command = containers.GetRemoteCommand(args.container, command_list) cmd = ssh.SSHCommand(remote, identity_file=identity_file, options=options, extra_flags=extra_flags, remote_command=remote_command, tty=tty, remainder=remainder) if args.dry_run: log.out.Print(' '.join(cmd.Build(ssh_helper.env))) return if args.plain or use_oslogin: keys_newly_added = False else: keys_newly_added = ssh_helper.EnsureSSHKeyExists( client, remote.user, instance, project) if keys_newly_added: poller = ssh.SSHPoller( remote, identity_file=identity_file, options=options, extra_flags=extra_flags, max_wait_ms=ssh_utils.SSH_KEY_PROPAGATION_TIMEOUT_SEC) log.status.Print('Waiting for SSH key to propagate.') # TODO(b/35355795): Don't force_connect try: poller.Poll(ssh_helper.env, force_connect=True) except retry.WaitException: raise ssh_utils.NetworkError() if args.internal_ip: ssh_helper.PreliminarilyVerifyInstance(instance.id, remote, identity_file, options) return_code = cmd.Run(ssh_helper.env, force_connect=True) if return_code: # Can't raise an exception because we don't want any "ERROR" message # printed; the output from `ssh` will be enough. sys.exit(return_code)
def RunScp(self, compute_holder, args, port=None, recursive=False, compress=False, extra_flags=None, release_track=None, ip_type=ip.IpTypeEnum.EXTERNAL): """SCP files between local and remote GCE instance. Run this method from subclasses' Run methods. Args: compute_holder: The ComputeApiHolder. args: argparse.Namespace, the args the command was invoked with. port: str, int or None, Port number to use for SSH connection. recursive: bool, Whether to use recursive copying using -R flag. compress: bool, Whether to use compression. extra_flags: [str] or None, extra flags to add to command invocation. release_track: obj, The current release track. ip_type: IpTypeEnum, Specify using internal ip or external ip address. Raises: ssh_utils.NetworkError: Network issue which likely is due to failure of SSH key propagation. ssh.CommandError: The SSH command exited with SSH exit code, which usually implies that a connection problem occurred. """ if release_track is None: release_track = base.ReleaseTrack.GA super(BaseScpHelper, self).Run(args) dst = ssh.FileReference.FromPath(args.destination) srcs = [ssh.FileReference.FromPath(src) for src in args.sources] # Make sure we have a unique remote ssh.SCPCommand.Verify(srcs, dst, single_remote=True) remote = dst.remote or srcs[0].remote if not dst.remote: # Make sure all remotes point to the same ref for src in srcs: src.remote = remote instance_ref = instance_flags.SSH_INSTANCE_RESOLVER.ResolveResources( [remote.host], compute_scope.ScopeEnum.ZONE, args.zone, compute_holder.resources, scope_lister=instance_flags.GetInstanceZoneScopeLister( compute_holder.client))[0] instance = self.GetInstance(compute_holder.client, instance_ref) project = self.GetProject(compute_holder.client, instance_ref.project) # Now replace the instance name with the actual IP/hostname if ip_type is ip.IpTypeEnum.INTERNAL: remote.host = ssh_utils.GetInternalIPAddress(instance) else: remote.host = ssh_utils.GetExternalIPAddress(instance) if not remote.user: remote.user = ssh.GetDefaultSshUsername(warn_on_account_user=True) if args.plain: use_oslogin = False else: public_key = self.keys.GetPublicKey().ToEntry(include_comment=True) remote.user, use_oslogin = ssh.CheckForOsloginAndGetUser( instance, project, remote.user, public_key, release_track) identity_file = None options = None if not args.plain: identity_file = self.keys.key_file options = self.GetConfig(ssh_utils.HostKeyAlias(instance), args.strict_host_key_checking) cmd = ssh.SCPCommand( srcs, dst, identity_file=identity_file, options=options, recursive=recursive, compress=compress, port=port, extra_flags=extra_flags) if args.dry_run: log.out.Print(' '.join(cmd.Build(self.env))) return if args.plain or use_oslogin: keys_newly_added = False else: keys_newly_added = self.EnsureSSHKeyExists( compute_holder.client, remote.user, instance, project) if keys_newly_added: poller = ssh.SSHPoller( remote, identity_file=identity_file, options=options, max_wait_ms=ssh_utils.SSH_KEY_PROPAGATION_TIMEOUT_SEC) log.status.Print('Waiting for SSH key to propagate.') # TODO(b/35355795): Don't force_connect try: poller.Poll(self.env, force_connect=True) except retry.WaitException: raise ssh_utils.NetworkError() if ip_type is ip.IpTypeEnum.INTERNAL: self.PreliminarilyVerifyInstance(instance.id, remote, identity_file, options) return_code = cmd.Run(self.env, force_connect=True) if return_code: # Can't raise an exception because we don't want any "ERROR" message # printed; the output from `ssh` will be enough. sys.exit(return_code)
def RunScp(self, compute_holder, args, port=None, recursive=False, compress=False, extra_flags=None, release_track=None, ip_type=ip.IpTypeEnum.EXTERNAL): """SCP files between local and remote GCE instance. Run this method from subclasses' Run methods. Args: compute_holder: The ComputeApiHolder. args: argparse.Namespace, the args the command was invoked with. port: str or None, Port number to use for SSH connection. recursive: bool, Whether to use recursive copying using -R flag. compress: bool, Whether to use compression. extra_flags: [str] or None, extra flags to add to command invocation. release_track: obj, The current release track. ip_type: IpTypeEnum, Specify using internal ip or external ip address. Raises: ssh_utils.NetworkError: Network issue which likely is due to failure of SSH key propagation. ssh.CommandError: The SSH command exited with SSH exit code, which usually implies that a connection problem occurred. """ if release_track is None: release_track = base.ReleaseTrack.GA super(BaseScpHelper, self).Run(args) dst = ssh.FileReference.FromPath(args.destination) srcs = [ssh.FileReference.FromPath(src) for src in args.sources] # Make sure we have a unique remote ssh.SCPCommand.Verify(srcs, dst, single_remote=True) remote = dst.remote or srcs[0].remote if not dst.remote: # Make sure all remotes point to the same ref for src in srcs: src.remote = remote instance_ref = instance_flags.SSH_INSTANCE_RESOLVER.ResolveResources( [remote.host], compute_scope.ScopeEnum.ZONE, args.zone, compute_holder.resources, scope_lister=instance_flags.GetInstanceZoneScopeLister( compute_holder.client))[0] instance = self.GetInstance(compute_holder.client, instance_ref) project = self.GetProject(compute_holder.client, instance_ref.project) expiration, expiration_micros = ssh_utils.GetSSHKeyExpirationFromArgs( args) if not remote.user: remote.user = ssh.GetDefaultSshUsername(warn_on_account_user=True) if args.plain: use_oslogin = False else: public_key = self.keys.GetPublicKey().ToEntry(include_comment=True) remote.user, use_oslogin = ssh.CheckForOsloginAndGetUser( instance, project, remote.user, public_key, expiration_micros, release_track) identity_file = None options = None if not args.plain: identity_file = self.keys.key_file options = self.GetConfig(ssh_utils.HostKeyAlias(instance), args.strict_host_key_checking) iap_tunnel_args = iap_tunnel.SshTunnelArgs.FromArgs( args, release_track, instance_ref, ssh_utils.GetExternalInterface(instance, no_raise=True)) if iap_tunnel_args: remote.host = ssh_utils.HostKeyAlias(instance) elif ip_type is ip.IpTypeEnum.INTERNAL: remote.host = ssh_utils.GetInternalIPAddress(instance) else: remote.host = ssh_utils.GetExternalIPAddress(instance) cmd = ssh.SCPCommand(srcs, dst, identity_file=identity_file, options=options, recursive=recursive, compress=compress, port=port, extra_flags=extra_flags, iap_tunnel_args=iap_tunnel_args) if args.dry_run: log.out.Print(' '.join(cmd.Build(self.env))) return if args.plain or use_oslogin: keys_newly_added = False else: keys_newly_added = self.EnsureSSHKeyExists(compute_holder.client, remote.user, instance, project, expiration=expiration) if keys_newly_added: poller = ssh_utils.CreateSSHPoller(remote, identity_file, options, iap_tunnel_args, port=port) log.status.Print('Waiting for SSH key to propagate.') # TODO(b/35355795): Don't force_connect try: poller.Poll(self.env, force_connect=True) except retry.WaitException: raise ssh_utils.NetworkError() if ip_type is ip.IpTypeEnum.INTERNAL: # This will never happen when IAP Tunnel is enabled, because ip_type is # always EXTERNAL when IAP Tunnel is enabled, even if the instance has no # external IP. IAP Tunnel doesn't need verification because it uses # unambiguous identifiers for the instance. self.PreliminarilyVerifyInstance(instance.id, remote, identity_file, options) # Errors from the SCP command result in an ssh.CommandError being raised cmd.Run(self.env, force_connect=True)
def SSHToInstance(self, args, instance): """Helper to manage authentication followed by SSH to the instance.""" args = self._DefaultArgsForSSH(args) external_nat = ssh_utils.GetExternalIPAddress(instance) log.status.Print( 'Trying to SSH to VM with NAT IP:{}'.format(external_nat)) args.ssh_key_file = ssh.Keys.DEFAULT_KEY_FILE ssh_helper = ssh_utils.BaseSSHCLIHelper() ssh_helper.Run(args) identity_file = ssh_helper.keys.key_file user, _ = ssh_utils.GetUserAndInstance(args.name) host_keys = self._GetHostKeyFromInstance(args.zone, ssh_helper, instance) options = self._GetSSHOptions(args.name, ssh_helper, instance, host_keys) public_key = ssh_helper.keys.GetPublicKey().ToEntry(include_comment=True) user, use_oslogin = ssh.CheckForOsloginAndGetUser( instance, ssh_helper.GetProject( self.client, properties.VALUES.core.project.Get(required=True)), user, public_key, None, self.release_track, username_requested=False) remote = ssh.Remote(external_nat, user) if not use_oslogin: self._WaitForSSHKeysToPropagate(ssh_helper, remote, identity_file, user, instance, options) extra_flags = [] # Ctpu seems to be forwarding some other ports on what # seems like the TPU node. Need to understand better before enabling. if args.forward_ports: extra_flags.extend( ['-A', '-L', '6006:localhost:6006', '-L', '8888:localhost:8888']) ssh_cmd_args = { 'remote': remote, 'identity_file': identity_file, 'options': options, 'extra_flags': extra_flags } cmd = ssh.SSHCommand(**ssh_cmd_args) max_attempts = 10 sleep_interval = 30 # Since the instance was just created, it can take a while for the instance # to be ready to accept ssh connections, therefore retry up to 5m. Doesn't # need to be backed off, regular interval retry is sufficient since we # aren't looking to throttle. for i in range(max_attempts): try: log.status.Print('SSH Attempt #{}...'.format(i)) # Errors from SSH itself result in an ssh.CommandError being raised return_code = cmd.Run( ssh_helper.env, force_connect=properties.VALUES.ssh.putty_force_connect.GetBool()) if return_code: # This is the return code of the remote command. # Problems with SSH itself will result in ssh.CommandError # being raised above. sys.exit(return_code) except ssh.CommandError as e: if i == max_attempts - 1: raise e log.status.Print( 'Retrying: SSH command error: {}'.format(six.text_type(e))) time.sleep(sleep_interval) continue break
def RunScp(self, compute_holder, args, port=None, recursive=False, compress=False, extra_flags=None, release_track=None, ip_type=ip.IpTypeEnum.EXTERNAL): """SCP files between local and remote GCE instance. Run this method from subclasses' Run methods. Args: compute_holder: The ComputeApiHolder. args: argparse.Namespace, the args the command was invoked with. port: str or None, Port number to use for SSH connection. recursive: bool, Whether to use recursive copying using -R flag. compress: bool, Whether to use compression. extra_flags: [str] or None, extra flags to add to command invocation. release_track: obj, The current release track. ip_type: IpTypeEnum, Specify using internal ip or external ip address. Raises: ssh_utils.NetworkError: Network issue which likely is due to failure of SSH key propagation. ssh.CommandError: The SSH command exited with SSH exit code, which usually implies that a connection problem occurred. """ if release_track is None: release_track = base.ReleaseTrack.GA super(BaseScpHelper, self).Run(args) dst = ssh.FileReference.FromPath(args.destination) srcs = [ssh.FileReference.FromPath(src) for src in args.sources] # Make sure we have a unique remote ssh.SCPCommand.Verify(srcs, dst, single_remote=True) remote = dst.remote or srcs[0].remote if not dst.remote: # Make sure all remotes point to the same ref for src in srcs: src.remote = remote instance_ref = instance_flags.SSH_INSTANCE_RESOLVER.ResolveResources( [remote.host], compute_scope.ScopeEnum.ZONE, args.zone, compute_holder.resources, scope_lister=instance_flags.GetInstanceZoneScopeLister( compute_holder.client))[0] instance = self.GetInstance(compute_holder.client, instance_ref) project = self.GetProject(compute_holder.client, instance_ref.project) if not remote.user: remote.user = ssh.GetDefaultSshUsername(warn_on_account_user=True) if args.plain: use_oslogin = False else: public_key = self.keys.GetPublicKey().ToEntry(include_comment=True) remote.user, use_oslogin = ssh.CheckForOsloginAndGetUser( instance, project, remote.user, public_key, release_track) identity_file = None options = None if not args.plain: identity_file = self.keys.key_file options = self.GetConfig(ssh_utils.HostKeyAlias(instance), args.strict_host_key_checking) tunnel_helper = None cmd_port = port if hasattr(args, 'tunnel_through_iap') and args.tunnel_through_iap: tunnel_helper = ssh_utils.CreateIapTunnelHelper(args, instance_ref, instance, port=port) tunnel_helper.StartListener() cmd_port = str(tunnel_helper.GetLocalPort()) if dst.remote: dst.remote.host = 'localhost' else: for src in srcs: src.remote.host = 'localhost' else: # Now replace the instance name with the actual IP/hostname if ip_type is ip.IpTypeEnum.INTERNAL: remote.host = ssh_utils.GetInternalIPAddress(instance) else: remote.host = ssh_utils.GetExternalIPAddress(instance) cmd = ssh.SCPCommand(srcs, dst, identity_file=identity_file, options=options, recursive=recursive, compress=compress, port=cmd_port, extra_flags=extra_flags) if args.dry_run: log.out.Print(' '.join(cmd.Build(self.env))) if tunnel_helper: tunnel_helper.StopListener() return if args.plain or use_oslogin: keys_newly_added = False else: keys_newly_added = self.EnsureSSHKeyExists(compute_holder.client, remote.user, instance, project) if keys_newly_added: poller_tunnel_helper = None if tunnel_helper: poller_tunnel_helper = ssh_utils.CreateIapTunnelHelper( args, instance_ref, instance, port=port) poller_tunnel_helper.StartListener( accept_multiple_connections=True) poller = ssh_utils.CreateSSHPoller(remote, identity_file, options, poller_tunnel_helper, port=port) log.status.Print('Waiting for SSH key to propagate.') # TODO(b/35355795): Don't force_connect try: poller.Poll(self.env, force_connect=True) except retry.WaitException: if tunnel_helper: tunnel_helper.StopListener() raise ssh_utils.NetworkError() finally: if poller_tunnel_helper: poller_tunnel_helper.StopListener() if ip_type is ip.IpTypeEnum.INTERNAL and not tunnel_helper: # The IAP Tunnel connection uses instance name and network interface name, # so do not need to additionally verify the instance. Also, the # SSHCommand used within the function does not support IAP Tunnels. self.PreliminarilyVerifyInstance(instance.id, remote, identity_file, options) try: # Errors from the SCP command result in an ssh.CommandError being raised cmd.Run(self.env, force_connect=True) finally: if tunnel_helper: tunnel_helper.StopListener()