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)
Exemple #2
0
    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)
Exemple #3
0
    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.')