Example #1
0
    def disassociate(self, machine):
        """Disassociates a key from a machine."""

        from mist.api.machines.models import KeyMachineAssociation

        log.info("Disassociating key of machine '%s' " % machine.machine_id)

        # removing key association
        KeyMachineAssociation.objects(key=self.key, machine=machine).delete()
        trigger_session_update(self.key.owner, ['keys'])
Example #2
0
    def add_machine_wrapper(self, name, fail_on_error=True,
                            fail_on_invalid_params=True, monitoring=False,
                            **kwargs):
        """Wrapper around add_machine for kwargs backwards compatibity

        FIXME: This wrapper should be deprecated

        """

        # Sanitize params.
        rename_kwargs(kwargs, 'machine_ip', 'host')
        rename_kwargs(kwargs, 'machine_user', 'ssh_user')
        rename_kwargs(kwargs, 'machine_key', 'ssh_key')
        rename_kwargs(kwargs, 'machine_port', 'ssh_port')
        rename_kwargs(kwargs, 'remote_desktop_port', 'rdp_port')
        if kwargs.get('operating_system') == 'windows':
            kwargs['os_type'] = 'windows'
        else:
            kwargs['os_type'] = 'unix'
        kwargs.pop('operating_system', None)
        errors = {}
        for key in list(kwargs.keys()):
            if key not in ('host', 'ssh_user', 'ssh_port', 'ssh_key',
                           'os_type', 'rdp_port'):
                error = "Invalid parameter %s=%r." % (key, kwargs[key])
                if fail_on_invalid_params:
                    errors[key] = error
                else:
                    log.warning(error)
                    kwargs.pop(key)
        if 'host' not in kwargs:
            errors['host'] = "Required parameter host missing"
            log.error(errors['host'])

        if not name:
            name = kwargs['host']

        if errors:
            log.error("Invalid parameters %s." % list(errors.keys()))
            raise BadRequestError({
                'msg': "Invalid parameters %s." % list(errors.keys()),
                'errors': errors,
            })

        # Add the machine.
        machine = self.add_machine(name, fail_on_error=fail_on_error, **kwargs)

        # Enable monitoring.
        if monitoring:
            from mist.api.monitoring.methods import enable_monitoring
            from mist.api.machines.models import KeyMachineAssociation
            enable_monitoring(
                self.cloud.owner, self.cloud.id, machine.machine_id,
                no_ssh=not (machine.os_type == 'unix' and
                            KeyMachineAssociation.objects(
                                machine=machine).count())
            )

        return machine
Example #3
0
 def find_ssh_settings(self, cloud_id, machine_id):
     cloud = Cloud.objects.get(owner=self.owner, id=cloud_id, deleted=None)
     machine = Machine.objects.get(cloud=cloud, machine_id=machine_id)
     key_associations = KeyMachineAssociation.objects(machine=machine)
     if not key_associations:
         raise Exception("Machine doesn't have SSH association")
     assoc = sorted(key_associations, key=lambda a: a.last_used)[-1]
     return assoc.key.name, assoc.ssh_user or 'root', assoc.port
Example #4
0
    def associate(self, machine, username='', port=22, no_connect=False):
        """Associates a key with a machine."""

        from mist.api.machines.models import KeyMachineAssociation

        log.info("Associating key %s to machine %s", self.key.id,
                 machine.machine_id)

        if isinstance(port, string_types):
            if port.isdigit():
                port = int(port)
            elif not port:
                port = 22
            else:
                raise BadRequestError("Port is required")
        elif isinstance(port, int):
            port = port
        else:
            raise BadRequestError("Invalid port type: %r" % port)

        # check if key already associated, if not already associated,
        # create the association.This is only needed if association doesn't
        # exist. Associations will otherwise be
        # created by shell.autoconfigure upon successful connection
        key_assoc = KeyMachineAssociation.objects(key=self.key,
                                                  machine=machine,
                                                  ssh_user=username,
                                                  port=port)
        if key_assoc:
            log.warning(
                "Key '%s' already associated with machine '%s' "
                "in cloud '%s'", self.key.id, machine.cloud.id,
                machine.machine_id)

            return key_assoc[0]

        key_assoc = KeyMachineAssociation(key=self.key,
                                          machine=machine,
                                          last_used=0,
                                          ssh_user=username,
                                          sudo=False,
                                          port=port)
        key_assoc.save()

        trigger_session_update(self.key.owner, ['keys'])
        return key_assoc
Example #5
0
def list_keys(owner):
    """List owner's keys
    :param owner:
    :return:
    """
    keys = Key.objects(owner=owner, deleted=None)
    key_objects = []
    # FIXME: This must be taken care of in Keys.as_dict
    for key in keys:
        key_object = {}
        key_object["id"] = key.id
        key_object['name'] = key.name
        key_object['owned_by'] = key.owned_by.id if key.owned_by else ''
        key_object['created_by'] = key.created_by.id if key.created_by else ''
        key_object["isDefault"] = key.default
        key_associations = KeyMachineAssociation.objects(key=key)
        key_object["machines"] = transform_key_machine_associations(
            key_associations)
        key_object['tags'] = get_tags_for_resource(owner, key)
        key_objects.append(key_object)
    return key_objects
Example #6
0
    def autoconfigure(self,
                      owner,
                      cloud_id,
                      machine_id,
                      key_id=None,
                      username=None,
                      password=None,
                      port=22):
        """Autoconfigure SSH client.

        This will do its best effort to find a suitable key and username
        and will try to connect. If it fails it raises
        MachineUnauthorizedError, otherwise it initializes self and returns a
        (key_id, ssh_user) tuple. If connection succeeds, it updates the
        association information in the key with the current timestamp and the
        username used to connect.

        """
        log.info("autoconfiguring Shell for machine %s:%s", cloud_id,
                 machine_id)

        cloud = Cloud.objects.get(owner=owner, id=cloud_id, deleted=None)
        machine = Machine.objects.get(cloud=cloud, id=machine_id)
        key_associations = KeyMachineAssociation.objects(machine=machine)
        log.info('Got cloud & machine: %d key associations' %
                 len(key_associations))
        if key_id:
            keys = [Key.objects.get(owner=owner, id=key_id, deleted=None)]
            log.info('Got key')
        else:
            keys = [
                association.key for association in key_associations
                if isinstance(association.key, Key)
            ]
            log.info('Got keys %d' % len(keys))
        if username:
            users = [username]
        else:
            users = list(
                set([
                    association.ssh_user for association in key_associations
                    if association.ssh_user
                ]))
        log.info('Got users:{}'.format(users))
        if not users:
            for name in [
                    'root', 'ubuntu', 'ec2-user', 'user', 'azureuser', 'core',
                    'centos', 'cloud-user', 'fedora'
            ]:
                if name not in users:
                    users.append(name)
        if port != 22:
            ports = [port]
        else:
            ports = list(
                set([key_assoc.port for key_assoc in key_associations]))
        if 22 not in ports:
            ports.append(22)
        log.info('Got ports:{}'.format(ports))
        # store the original destination IP to prevent rewriting it when NATing
        ssh_host = self.host
        for key in keys:
            for ssh_user in users:
                for port in ports:
                    try:
                        # store the original ssh port in case of NAT
                        # by the OpenVPN server
                        ssh_port = port
                        self.host, port = dnat(owner, ssh_host, port)
                        log.info("ssh -i %s %s@%s:%s", key.name, ssh_user,
                                 self.host, port)
                        cert_file = ''
                        if isinstance(key, SignedSSHKey):
                            cert_file = key.certificate

                        self.connect(username=ssh_user,
                                     key=key,
                                     password=password,
                                     cert_file=cert_file,
                                     port=port)
                    except MachineUnauthorizedError:
                        continue

                    retval, resp = self.command('uptime')
                    new_ssh_user = None
                    if 'Please login as the user ' in resp:
                        new_ssh_user = resp.split()[5].strip('"')
                    elif 'Please login as the' in resp:
                        # for EC2 Amazon Linux machines, usually with ec2-user
                        new_ssh_user = resp.split()[4].strip('"')
                    if new_ssh_user:
                        log.info("retrying as %s", new_ssh_user)
                        try:
                            self.disconnect()
                            cert_file = ''
                            if isinstance(key, SignedSSHKey):
                                cert_file = key.certificate
                            self.connect(username=new_ssh_user,
                                         key=key,
                                         password=password,
                                         cert_file=cert_file,
                                         port=port)
                            ssh_user = new_ssh_user
                        except MachineUnauthorizedError:
                            continue
                    # we managed to connect successfully, return
                    # but first update key
                    trigger_session_update_flag = False
                    for key_assoc in KeyMachineAssociation.objects(
                            machine=machine):
                        if key_assoc.key == key:
                            if key_assoc.ssh_user != ssh_user:
                                key_assoc.ssh_user = ssh_user
                                trigger_session_update_flag = True
                            break
                    else:
                        trigger_session_update_flag = True
                        # in case of a private host do NOT update the key
                        # associations with the port allocated by the OpenVPN
                        # server, instead use the original ssh_port
                        key_assoc = KeyMachineAssociation(
                            key=key,
                            machine=machine,
                            ssh_user=ssh_user,
                            port=ssh_port,
                            sudo=self.check_sudo())
                        key_assoc.save()
                    machine.save()
                    if trigger_session_update_flag:
                        trigger_session_update(owner.id, ['keys'])
                    return key.name, ssh_user

        raise MachineUnauthorizedError("%s:%s" % (cloud_id, machine_id))
Example #7
0
def add_machine(request):
    """
    Tags: machines
    ---
    Add a machine to an OtherServer Cloud. This works for bare_metal clouds.
    ---
    cloud:
      in: path
      required: true
      type: string
    machine_ip:
      type: string
      required: true
    operating_system:
      type: string
    machine_name:
      type: string
    machine_key:
      type: string
    machine_user:
      type: string
    machine_port:
      type: string
    remote_desktop_port:
      type: string
    monitoring:
      type: boolean
    """
    cloud_id = request.matchdict.get('cloud')
    params = params_from_request(request)
    machine_ip = params.get('machine_ip')
    if not machine_ip:
        raise RequiredParameterMissingError("machine_ip")

    operating_system = params.get('operating_system', '')
    machine_name = params.get('machine_name', '')
    machine_key = params.get('machine_key', '')
    machine_user = params.get('machine_user', '')
    machine_port = params.get('machine_port', '')
    remote_desktop_port = params.get('remote_desktop_port', '')
    monitoring = params.get('monitoring', '')

    job_id = params.get('job_id')
    if not job_id:
        job = 'add_machine'
        job_id = uuid.uuid4().hex
    else:
        job = None

    auth_context = auth_context_from_request(request)
    auth_context.check_perm("cloud", "read", cloud_id)

    if machine_key:
        auth_context.check_perm("key", "read", machine_key)

    try:
        Cloud.objects.get(owner=auth_context.owner, id=cloud_id, deleted=None)
    except Cloud.DoesNotExist:
        raise NotFoundError('Cloud does not exist')

    log.info('Adding bare metal machine %s on cloud %s' %
             (machine_name, cloud_id))
    cloud = Cloud.objects.get(owner=auth_context.owner,
                              id=cloud_id,
                              deleted=None)

    try:
        machine = cloud.ctl.add_machine(machine_name,
                                        host=machine_ip,
                                        ssh_user=machine_user,
                                        ssh_port=machine_port,
                                        ssh_key=machine_key,
                                        os_type=operating_system,
                                        rdp_port=remote_desktop_port,
                                        fail_on_error=True)
    except Exception as e:
        raise MachineCreationError("OtherServer, got exception %r" % e, exc=e)

    # Enable monitoring
    if monitoring:
        monitor = enable_monitoring(
            auth_context.owner,
            cloud.id,
            machine.machine_id,
            no_ssh=not (machine.os_type == 'unix'
                        and KeyMachineAssociation.objects(machine=machine)))

    ret = {
        'id': machine.id,
        'name': machine.name,
        'extra': {},
        'public_ips': machine.public_ips,
        'private_ips': machine.private_ips,
        'job_id': job_id,
        'job': job
    }

    if monitoring:
        ret.update({'monitoring': monitor})

    return ret
Example #8
0
def machine_console(request):
    """
    Tags: machines
    ---
    Open VNC console.
    Generate and return an URI to open a VNC console to target machine
    READ permission required on cloud.
    READ permission required on machine.
    ---
    cloud:
      in: path
      required: true
      type: string
    machine:
      in: path
      required: true
      type: string
    rdp_port:
      default: 3389
      in: query
      required: true
      type: integer
    host:
      in: query
      required: true
      type: string
    """
    cloud_id = request.matchdict.get('cloud')

    auth_context = auth_context_from_request(request)

    if cloud_id:
        machine_id = request.matchdict['machine']
        auth_context.check_perm("cloud", "read", cloud_id)
        try:
            machine = Machine.objects.get(cloud=cloud_id,
                                          machine_id=machine_id,
                                          state__ne='terminated')
            # used by logging_view_decorator
            request.environ['machine_uuid'] = machine.id
        except Machine.DoesNotExist:
            raise NotFoundError("Machine %s doesn't exist" % machine_id)
    else:
        machine_uuid = request.matchdict['machine_uuid']
        try:
            machine = Machine.objects.get(id=machine_uuid,
                                          state__ne='terminated')
            # used by logging_view_decorator
            request.environ['machine_id'] = machine.machine_id
            request.environ['cloud_id'] = machine.cloud.id
        except Machine.DoesNotExist:
            raise NotFoundError("Machine %s doesn't exist" % machine_uuid)

        cloud_id = machine.cloud.id
        auth_context.check_perm("cloud", "read", cloud_id)

    auth_context.check_perm("machine", "read", machine.id)

    if machine.cloud.ctl.provider not in ['vsphere', 'openstack', 'libvirt']:
        raise MistNotImplementedError(
            "VNC console only supported for vSphere, OpenStack or KVM")

    if machine.cloud.ctl.provider == 'libvirt':
        import xml.etree.ElementTree as ET
        from html import unescape
        from datetime import datetime
        import hmac
        import hashlib
        xml_desc = unescape(machine.extra.get('xml_description', ''))
        root = ET.fromstring(xml_desc)
        vnc_element = root.find('devices').find('graphics[@type="vnc"]')
        if not vnc_element:
            raise MethodNotAllowedError(
                "VNC console not supported by this KVM domain")
        vnc_port = vnc_element.attrib.get('port')
        vnc_host = vnc_element.attrib.get('listen')
        from mongoengine import Q
        # Get key associations, prefer root or sudoer ones
        key_associations = KeyMachineAssociation.objects(
            Q(machine=machine.parent) & (Q(ssh_user='******') | Q(sudo=True))) \
            or KeyMachineAssociation.objects(machine=machine.parent)
        if not key_associations:
            raise ForbiddenError()
        key_id = key_associations[0].key.id
        host = '%s@%s:%d' % (key_associations[0].ssh_user,
                             machine.parent.hostname, key_associations[0].port)
        expiry = int(datetime.now().timestamp()) + 100
        msg = '%s,%s,%s,%s,%s' % (host, key_id, vnc_host, vnc_port, expiry)
        mac = hmac.new(config.SECRET.encode(),
                       msg=msg.encode(),
                       digestmod=hashlib.sha256).hexdigest()
        base_ws_uri = config.CORE_URI.replace('http', 'ws')
        proxy_uri = '%s/proxy/%s/%s/%s/%s/%s/%s' % (
            base_ws_uri, host, key_id, vnc_host, vnc_port, expiry, mac)
        return render_to_response('../templates/novnc.pt', {'url': proxy_uri})
    if machine.cloud.ctl.provider == 'vsphere':
        console_uri = machine.cloud.ctl.compute.connection.ex_open_console(
            machine.machine_id)
        protocol, host = config.CORE_URI.split('://')
        protocol = protocol.replace('http', 'ws')
        params = urllib.parse.urlencode({'url': console_uri})
        proxy_uri = f"{protocol}://{host}/wsproxy/?{params}"
        return render_to_response('../templates/novnc.pt', {'url': proxy_uri})
    else:
        console_url = machine.cloud.ctl.compute.connection.ex_open_console(
            machine.machine_id)
    raise RedirectError(console_url)
Example #9
0
def add_machine(request):
    """
    Tags: machines
    ---
    Add a machine to an OtherServer/Libvirt Cloud.
    READ permission required on cloud.
    EDIT permission required on cloud.
    READ permission required on key.
    ---
    cloud:
      in: path
      required: true
      type: string
    machine_hostname:
      type: string
      required: true
    operating_system:
      type: string
    machine_name:
      type: string
    machine_key:
      type: string
    machine_user:
      type: string
    machine_port:
      type: string
    remote_desktop_port:
      type: string
    monitoring:
      type: boolean
    images_location:
      type: string
    """
    cloud_id = request.matchdict.get('cloud')

    auth_context = auth_context_from_request(request)

    try:
        cloud = Cloud.objects.get(owner=auth_context.owner,
                                  id=cloud_id,
                                  deleted=None)
    except Cloud.DoesNotExist:
        raise NotFoundError('Cloud does not exist')

    if cloud.ctl.provider not in ['libvirt', 'bare_metal']:
        raise MistNotImplementedError()

    params = params_from_request(request)
    machine_hostname = params.get('machine_hostname')
    if not machine_hostname:
        raise RequiredParameterMissingError("machine_hostname")

    operating_system = params.get('operating_system', '')
    machine_name = params.get('machine_name', '')
    machine_key = params.get('machine_key', '')
    machine_user = params.get('machine_user', '')
    machine_port = params.get('machine_port', 22)
    remote_desktop_port = params.get('remote_desktop_port', '')
    images_location = params.get('images_location', '')
    monitoring = params.get('monitoring', False)

    job_id = params.get('job_id')
    if not job_id:
        job = 'add_machine'
        job_id = uuid.uuid4().hex
    else:
        job = None

    auth_context.check_perm("cloud", "read", cloud_id)
    auth_context.check_perm("cloud", "edit", cloud_id)

    if machine_key:
        auth_context.check_perm("key", "read", machine_key)

    log.info('Adding host machine %s on cloud %s' % (machine_name, cloud_id))

    try:
        machine = cloud.ctl.add_machine(host=machine_hostname,
                                        ssh_user=machine_user,
                                        ssh_port=machine_port,
                                        ssh_key=machine_key,
                                        name=machine_name,
                                        os_type=operating_system,
                                        rdp_port=remote_desktop_port,
                                        images_location=images_location)
    except Exception as e:
        raise MachineCreationError("Adding host got exception %r" % e, exc=e)

    # Enable monitoring
    if monitoring:
        monitor = enable_monitoring(
            auth_context.owner,
            cloud.id,
            machine.machine_id,
            no_ssh=not (machine.os_type == 'unix'
                        and KeyMachineAssociation.objects(machine=machine)))

    ret = {
        'id': machine.id,
        'name': machine.name,
        'extra': {},
        'public_ips': machine.public_ips,
        'private_ips': machine.private_ips,
        'job_id': job_id,
        'job': job
    }

    if monitoring:
        ret.update({'monitoring': monitor})

    return ret