def testWriteHostKeyToKnownHosts(self): known_hosts = mock.Mock() known_hosts.file_path = '/tmp/foo' known_hosts.known_hosts = ['compute.1234 ssh-rsa asdfasdf'] known_hosts.Write.return_value = None known_hosts.AddMultiple.return_value = True host_keys = {'ssh-rsa': 'jkljkljkl', 'ecdsa': 'jkljkljkl'} ssh_helper = ssh_utils.BaseSSHHelper() ssh_helper.WriteHostKeysToKnownHosts(known_hosts, host_keys, 'compute.3456') known_hosts.AddMultiple.assert_called_with('compute.3456', [ 'ecdsa jkljkljkl', 'ssh-rsa jkljkljkl', ], overwrite=False)
def Run(self, args): """See ssh_utils.BaseSSHCommand.Run.""" holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) cua_holder = base_classes.ComputeUserAccountsApiHolder( 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() 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.warn( '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.warn( 'Unable to download Host Key for [{0}] from [{1}]. To ensure ' 'the security of the SSH connetion, 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) # 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'] 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 ssh_helper.EnsureSSHKeyExists(client, cua_holder.client, remote.user, instance, project) # 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.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) ssh_config_file = files.ExpandHomeDir(args.ssh_config_file or ssh.PER_USER_SSH_CONFIG_FILE) instances = None try: existing_content = files.ReadFileContents(ssh_config_file) except files.Error as e: existing_content = '' log.debug('SSH Config File [{0}] could not be opened: {1}'.format( ssh_config_file, e)) if args.remove: compute_section = '' try: new_content = _RemoveComputeSection(existing_content) except MultipleComputeSectionsError: raise MultipleComputeSectionsError(ssh_config_file) else: ssh_helper.EnsureSSHKeyIsInProject( client, ssh.GetDefaultSshUsername(warn_on_account_user=True), None) instances = list(self.GetRunningInstances(client)) if instances: compute_section = _BuildComputeSection( instances, ssh_helper.keys.key_file, ssh.KnownHosts.DEFAULT_PATH) else: compute_section = '' if existing_content and not args.remove: try: new_content = _MergeComputeSections(existing_content, compute_section) except MultipleComputeSectionsError: raise MultipleComputeSectionsError(ssh_config_file) elif not existing_content: new_content = compute_section if args.dry_run: log.out.write(new_content or '') return if new_content != existing_content: if (os.path.exists(ssh_config_file) and platforms.OperatingSystem.Current() is not platforms.OperatingSystem.WINDOWS): ssh_config_perms = os.stat(ssh_config_file).st_mode # From `man 5 ssh_config`: # this file must have strict permissions: read/write for the user, # and not accessible by others. # We check that here: if not (ssh_config_perms & stat.S_IRWXU == stat.S_IWUSR | stat.S_IRUSR and ssh_config_perms & stat.S_IWGRP == 0 and ssh_config_perms & stat.S_IWOTH == 0): log.warning( 'Invalid permissions on [{0}]. Please change to match ssh ' 'requirements (see man 5 ssh).') # TODO(b/36050483): This write will not work very well if there is # a lot of write contention for the SSH config file. We should # add a function to do a better job at "atomic file writes". files.WriteFileContents(ssh_config_file, new_content, private=True) if compute_section: log.out.write( textwrap.dedent("""\ You should now be able to use ssh/scp with your instances. For example, try running: $ ssh {alias} """.format(alias=_CreateAlias(instances[0])))) elif not instances and not args.remove: log.warning( 'No host aliases were added to your SSH configs because you do not ' 'have any running instances. Try running this command again after ' 'running some instances.')