コード例 #1
0
def stop_machine(machine):  # noqa: E501
    """Stop machine

    Stop target machine # noqa: E501

    :param machine:
    :type machine: str

    :rtype: None
    """
    from mist.api.methods import list_resources
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    from mist.api.logs.methods import log_event
    try:
        [machine], total = list_resources(auth_context, 'machine',
                                          search=machine, limit=1)
    except ValueError:
        return 'Machine does not exist', 404
    try:
        auth_context.check_perm('machine', 'stop', machine.id)
    except PolicyUnauthorizedError:
        return 'You are not authorized to perform this action', 403
    log_event(
        auth_context.owner.id, 'request', 'stop_machine',
        machine_id=machine.id, user_id=auth_context.user.id,
    )
    try:
        machine.ctl.stop()
    except ForbiddenError:
        return 'Action not supported on target machine', 422
    return 'Stopped machine `%s`' % machine.name, 200
コード例 #2
0
ファイル: helpers.py プロジェクト: lovelife100/mist.api
def _log_alert(owner,
               rule_id,
               value,
               triggered,
               timestamp,
               incident_id,
               cloud_id='',
               machine_id='',
               action='',
               **kwargs):
    info = _alert_pretty_details(owner, rule_id, value, triggered, timestamp,
                                 cloud_id, machine_id, action)
    event_kwargs = {
        'owner_id': owner.id,
        'event_type': 'incident',
        'action': 'rule_triggered' if triggered else 'rule_untriggered',
        'cloud_id': info['cloud_id'],
        'machine_id': info['machine_id'],
        'condition': info['condition'],
        'state': info['state'],
        'value': info['curr_value'],
        'machine_name': info['name'],
        'host': info['host'],
        'rule_action': info['action'],
        'incident_id': incident_id,
        'rule_id': info['rule_id'],
        'rule_title': info['rule_title'],
    }
    event_kwargs.update(kwargs)
    log_event(**event_kwargs)
コード例 #3
0
def delete_key(key):  # noqa: E501
    """Delete key

    Delete target key # noqa: E501

    :param key:
    :type key: str

    :rtype: None
    """
    from mist.api.keys.methods import delete_key as m_delete_key
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    result = get_resource(auth_context, 'key', search=key)
    result_data = result.get('data')
    if not result_data:
        return 'Cloud does not exist', 404
    key_id = result_data.get('id')
    try:
        auth_context.check_perm('key', 'remove', key_id)
    except PolicyUnauthorizedError:
        return 'You are not authorized to perform this action', 403
    try:
        m_delete_key(auth_context.owner, key_id)
    except KeyNotFoundError:
        return 'Key not found', 404
    log_event(
        auth_context.owner.id, 'request', 'delete_key',
        key_id=key_id, user_id=auth_context.user.id,
    )
    key_name = result_data.get('name')
    return f'Deleted key `{key_name}`', 200
コード例 #4
0
ファイル: methods.py プロジェクト: hb407033/mist.api
def register_user(email,
                  first_name,
                  last_name,
                  registration_method,
                  selected_plan=None,
                  promo_code=None,
                  token=None,
                  status='pending',
                  create_organization=True,
                  request=None):
    # User does not exist so we have to add him/her to the database
    # First make sure that email is not banned
    # Then create the User objects and the Organization
    if email.split('@')[1] in config.BANNED_EMAIL_PROVIDERS:
        raise MethodNotAllowedError("Email provider is banned.")

    user = User()
    user.email = email
    user.first_name = first_name
    user.last_name = last_name
    user.registration_method = registration_method
    user.registration_date = time()
    user.status = status
    user.activation_key = get_secure_rand_token()
    user.can_create_org = True
    user.save()

    # For some users registering through sso it might not be necessary to
    # create an organization, hence the flag
    org = create_org_for_user(user, '', promo_code, token, selected_plan) \
        if create_organization else None

    log_event_args = {
        'owner_id': org and org.id or '',
        'user_id': user.id,
        'first_name': user.first_name,
        'last_name': user.last_name,
        'company': user.feedback.company_name,
        'event_type': 'request',
        'action': 'register',
        'authentication_provider': registration_method
    }

    if request:
        log_event_args.update({
            'request_method': request.method,
            'request_path': request.path,
            'request_ip': ip_from_request(request),
            'user_agent': request.user_agent,
        })

    if org:
        log_event_args.update({'org_id': org.id, 'org_name': org.name})

    # Create log for the registration of a user and if an org has been created
    # add the id and name of the org
    from mist.api.logs.methods import log_event
    log_event(**log_event_args)

    return user, org
コード例 #5
0
def _log_alert(resource,
               rule,
               value,
               triggered,
               timestamp,
               incident_id,
               action='',
               **kwargs):
    """Create a log entry for the triggered rule. Any special pre-processing
    of the log entry, such as renaming dict keys, should be taken care of at
    this point."""
    # Get dict with alert details.
    info = _get_alert_details(resource, rule, incident_id, value, triggered,
                              timestamp, action)

    # Get the resource's type. This will be set to None for arbitrary rules.
    resource_type = info.pop('resource_type', None)

    # Set of keys to remove.
    for key in (
            'uri',
            'name',
            'time',
            'since',
            'action',
            'incident_id',
            'resource_repr',
            'machine_link',
    ):
        if key in info:
            info.pop(key)

    # Rename resource-agnostic keys, if applicable.
    if resource_type is not None:
        for key in list(info.keys()):
            if key.startswith('resource_'):
                rename_kwargs(info, key, key.replace('resource',
                                                     resource_type))

    # Rename arbitrary keys.
    rename_kwargs(info, 'curr_value', 'value')
    rename_kwargs(info, 'action', 'rule_action')

    # FIXME For backwards compability.
    if isinstance(resource, Machine):
        info['cloud_id'] = resource.cloud.id
        info['machine_id'] = resource.id
        info['external_id'] = resource.machine_id

    # Update info with additional kwargs.
    info.update(kwargs)
    info.pop('owner_id', None)
    # Log the alert.
    log_event(owner_id=rule.owner_id,
              event_type='incident',
              incident_id=incident_id,
              action='rule_triggered' if triggered else 'rule_untriggered',
              **info)
コード例 #6
0
ファイル: test_stories.py プロジェクト: dzaporozhets/mist-api
def test_log_event(load_logs):
    """Log events to be tested."""
    for type in ('job', 'shell', 'session', 'incident'):
        for log in load_logs[type]:
            try:
                log_event(**log)
            except Exception as exc:
                traceback.print_exc()
                assert False
            else:
                # Wait to ensure log has actually been indexed.
                time.sleep(1)
コード例 #7
0
 def on_close(self, stale=False):
     if not self.closed:
         kwargs = {}
         if stale:
             kwargs['stale'] = True
         kwargs.update(self.log_kwargs)
         log_event(action='disconnect', **kwargs)
     if self.consumer is not None:
         try:
             self.consumer.stop()
         except Exception as exc:
             log.error("Error closing pika consumer: %r", exc)
     super(MainConnection, self).on_close(stale=stale)
コード例 #8
0
def get_key(key, private=False, sort=None, only=None, deref=None):  # noqa: E501
    """Get key

    Get details about target key # noqa: E501

    :param key:
    :type key: str
    :param private: Return the private key. Requires READ_PRIVATE permission on key.
    :type private: bool
    :param sort: Order results by
    :type sort: str
    :param only: Only return these fields
    :type only: str
    :param deref: Dereference foreign keys
    :type deref: str

    :rtype: GetKeyResponse
    """
    from mist.api.methods import list_resources
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    from mist.api.logs.methods import log_event
    try:
        [key], total = list_resources(auth_context, 'key',
                                      search=key, limit=1)
    except ValueError:
        return 'Key does not exist', 404

    meta = {
        'total': total,
        'returned': 1,
    }

    result = {
        'data': key.as_dict(),
        'meta': meta
    }

    if private:
        try:
            auth_context.check_perm('key', 'read_private', key.id)
        except PolicyUnauthorizedError:
            return 'You are not authorized to perform this action', 403
        log_event(
            auth_context.owner.id, 'request', 'read_private',
            key_id=key.id, user_id=auth_context.user.id,
        )
        result['data']['private'] = key.private
    return GetKeyResponse(data=result['data'], meta=result['meta'])
コード例 #9
0
 def on_open(self, conn_info):
     log.info("************** Open!")
     super(MainConnection, self).on_open(conn_info)
     self.running_machines = set()
     self.consumer = None
     self.log_kwargs = {
         'ip': self.ip,
         'user_agent': self.user_agent,
         'session_id': self.session_id,
         'user_id': self.auth_context.user.id,
         'owner_id': self.auth_context.owner.id,
         'event_type': 'session'
     }
     if self.auth_context.token.su:
         self.log_kwargs['su'] = self.auth_context.token.su
     log_event(action='connect', **self.log_kwargs)
コード例 #10
0
ファイル: test_stories.py プロジェクト: lovelife100/mist.api
def test_close_story(load_logs):
    """Test closing stories.

    Fetch an open story and close it manually.

    """
    owner_id = get_owner_id(load_logs)

    for story_type in ('incident', ):
        stories = get_stories(story_type,
                              owner_id=owner_id,
                              expand=True,
                              pending=True)
        assert len(stories) is 1

        story = stories[0]
        assert len(story['logs']) is 1
        assert not story['finished_at']
        assert story['type'] == story_type

        for log in story['logs']:
            assert log['type'] in TYPES[story_type]
            assert log['owner_id'] == story['owner_id']
            assert log['%s_id' % story_type] == story['story_id']

        # Close the story.
        log_event(owner_id=owner_id,
                  action='close_story',
                  event_type='request',
                  story_id=story['story_id'])

        # Wait for index refresh.
        time.sleep(1)

        # Ensure there are no more pending stories.
        stories = get_stories(story_type,
                              owner_id=owner_id,
                              expand=True,
                              pending=True)
        assert len(stories) is 0

        # Verify the story's `finished_at` timestamp.
        story = get_story(owner_id=owner_id, story_id=story['story_id'])
        assert story['finished_at']
        assert story['type'] == story_type
        assert len(story['logs']) is 2
コード例 #11
0
def clone_machine(machine, name, run_async=True):  # noqa: E501
    """Clone machine

    Clone target machine # noqa: E501

    :param machine:
    :type machine: str
    :param name:
    :type name: str
    :param run_async:
    :type run_async: bool

    :rtype: None
    """
    from mist.api.methods import list_resources
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    from mist.api.logs.methods import log_event
    try:
        [machine], total = list_resources(auth_context, 'machine',
                                          search=machine, limit=1)
    except ValueError:
        return 'Machine does not exist', 404
    try:
        auth_context.check_perm('machine', 'clone', machine.id)
    except PolicyUnauthorizedError:
        return 'You are not authorized to perform this action', 403
    log_event(
        auth_context.owner.id, 'request', 'clone_machine',
        machine_id=machine.id, user_id=auth_context.user.id,
    )
    job = 'clone_machine'
    job_id = uuid.uuid4().hex
    if run_async:  # noqa: W606
        args = (auth_context.serialize(), machine.id, name)
        kwargs = {'job': job, 'job_id': job_id}
        clone_machine_async.send_with_options(
            args=args, kwargs=kwargs, delay=1_000)
    else:
        try:
            machine.ctl.clone(name)
        except ForbiddenError as e:
            return str(e), 403
    return 'Machine clone issued successfully'
コード例 #12
0
def uninstall_telegraf(machine_id, job=None, job_id=None):
    """Undeploy Telegraf."""
    machine = Machine.objects.get(id=machine_id)
    error = None

    _log = {
        'owner_id': machine.owner.id,
        'cloud_id': machine.cloud.id,
        'machine_id': machine.id,
        'event_type': 'job',
        'job_id': job_id or uuid.uuid4().hex,
        'job': job,
    }
    log_event(action='telegraf_undeployment_started', **_log)

    try:
        shell = mist.api.shell.Shell(machine.ctl.get_host())
        key, user = shell.autoconfigure(machine.owner, machine.cloud.id,
                                        machine.id)
        exit_code, stdout = shell.command(fetch(unix_uninstall()))
        stdout = stdout.replace('\r\n', '\n').replace('\r', '\n')
    except Exception as err:
        log.error('Error during Telegraf undeployment: %r', err)
        error = err
    else:
        error = exit_code or None
        _log.update({
            'key_id': key,
            'ssh_user': user,
            'exit_code': exit_code,
            'stdout': stdout.encode('utf-8', 'ignore')
        })
    finally:
        # Close the SSH connection.
        shell.disconnect()

        # Update Machine's monitoring status.
        machine.monitoring.hasmonitoring = False
        machine.save()

        # Log undeployment's outcome.
        log_event(action='telegraf_undeployment_finished', error=error, **_log)

        # Trigger UI update.
        trigger_session_update(machine.owner, ['monitoring'])
コード例 #13
0
def edit_key(key, name=None, default=None):  # noqa: E501
    """Edit key

    Edit target key # noqa: E501

    :param key:
    :type key: str
    :param name: New key name
    :type name: str
    :param default: Set as default
    :type default: bool

    :rtype: None
    """
    from mist.api.methods import list_resources
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    from mist.api.logs.methods import log_event
    try:
        [key], total = list_resources(auth_context, 'key',
                                      search=key, limit=1)
    except ValueError:
        return 'Key does not exist', 404
    try:
        auth_context.check_perm('key', 'edit', key.id)
    except PolicyUnauthorizedError:
        return 'You are not authorized to perform this action', 403
    log_event(
        auth_context.owner.id, 'request', 'edit_key',
        key_id=key.id, user_id=auth_context.user.id,
        key_name=key.name, key_default=key.default,
        new_name=name, new_default=default
    )
    key_updated = False
    if name:
        key.name = name
        key_updated = True
    if default:
        key.ctl.set_default()
        key_updated = True
    if key_updated:
        key.save()
    return 'Updated key `%s`' % key.name, 200
コード例 #14
0
 def __call__(self, environ, start_response):
     request = Request(environ)
     session = environ['session']
     # when someone is POSTing to /auth (check_auth) then he is trying
     # to authenticate and does not have a csrf token in the SessionToken
     # which has been produced by default
     if request.path not in self.exempt and \
        isinstance(session, SessionToken) and \
        not getattr(session, 'internal', False) and \
        request.method in ('POST', 'PUT', 'PATCH', 'DELETE'):
         csrf_token = request.headers.get('Csrf-Token', '').lower()
         if not csrf_token:
             csrf_token = request.params.get('Csrf-Token', '').lower()
         if csrf_token != session.csrf_token:
             log.error("Bad CSRF token '%s'", csrf_token)
             user = session.get_user()
             if user is not None:
                 owner_id = session.org.id
                 user_id = user.id
                 email = user.email
             else:
                 owner_id = user_id = None
                 params = params_from_request(request)
                 email = params.get('email', '')
             log_event(
                 owner_id=owner_id,
                 user_id=user_id,
                 email=email,
                 request_method=request.method,
                 request_path=request.path,
                 request_ip=ip_from_request(request),
                 user_agent=request.user_agent,
                 csrf_token=csrf_token,
                 session_csrf=session.csrf_token,
                 event_type='request',
                 action='csrf_validation',
                 error=True,
             )
             start_response('403 Forbidden',
                            [('Content-Type', 'text/plain')])
             return ["Invalid csrf token\n"]
     return self.app(environ, start_response)
コード例 #15
0
def edit_machine(machine, edit_machine_request=None):  # noqa: E501
    """Edit machine

    Edit target machine # noqa: E501

    :param machine:
    :type machine: str
    :param edit_machine_request:
    :type edit_machine_request: dict | bytes

    :rtype: None
    """
    from mist.api.methods import list_resources
    if connexion.request.is_json:
        edit_machine_request = EditMachineRequest.from_dict(connexion.request.get_json())  # noqa: E501
    params = delete_none(edit_machine_request.to_dict())
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    from mist.api.logs.methods import log_event
    try:
        [machine], total = list_resources(auth_context, 'machine',
                                          search=machine, limit=1)
    except ValueError:
        return 'Machine does not exist', 404
    if machine.cloud.owner != auth_context.owner:
        return 'Machine does not exist', 404
    # VMs in libvirt can be started no matter if they are terminated
    if machine.state == 'terminated' and not isinstance(machine.cloud,
                                                        LibvirtCloud):
        return 'Machine does not exist', 404
    log_event(
        auth_context.owner.id, 'request', 'edit_machine',
        machine_id=machine.id, user_id=auth_context.user.id,
    )
    try:
        auth_context.check_perm('machine', 'edit', machine.id)
    except PolicyUnauthorizedError:
        return 'You are not authorized to perform this action', 403
    machine.ctl.update(auth_context, params)
    return 'Machine successfully updated'
コード例 #16
0
ファイル: test_stories.py プロジェクト: lovelife100/mist.api
def test_incidents(load_logs):
    """Test incidents."""
    owner_id = get_owner_id(load_logs)

    for story_type in ('incident', ):
        stories = get_stories(story_type,
                              owner_id=owner_id,
                              expand=True,
                              pending=True)
        assert len(stories) is 2

        story = stories[0]
        assert not story['finished_at']
        assert story['type'] == story_type

        for log in story['logs']:
            assert log['type'] in TYPES[story_type]
            assert log['owner_id'] == story['owner_id']
            assert log['%s_id' % story_type] == story['story_id']

        logs = []
        for log in load_logs[story_type]:
            if log['%s_id' % story_type] == story['story_id']:
                logs.append(log)
        assert len(story['logs']) == len(logs)

    # Publish new event, which is meant to close the open incident.
    for log in load_logs['request']:
        log_event(**log)

    # Wait to ensure log has actually been indexed.
    time.sleep(1)

    # Verify that the incident has closed.
    for story_type in ('incident', ):
        stories = get_stories(story_type,
                              owner_id=owner_id,
                              expand=True,
                              pending=True)
        assert len(stories) is 1
コード例 #17
0
 def run(self, machine, value, triggered, timestamp, incident_id, **kwargs):
     if timestamp + 60 * 60 * 24 < time.time():
         # FIXME Imported here due to circular dependency issues.
         from mist.api.monitoring.methods import disable_monitoring
         # If NoData alerts are being triggered for over 24h, disable
         # monitoring and log the action to close any open incidents.
         disable_monitoring(machine.owner,
                            machine.cloud.id,
                            machine.machine_id,
                            no_ssh=True)
         log_event(machine.owner.id,
                   'incident',
                   'disable_monitoring',
                   cloud_id=machine.cloud.id,
                   machine_id=machine.id,
                   external_id=machine.machine_id,
                   incident_id=incident_id)
         action = 'Disable Monitoring'
     else:
         action = 'Alert'
     super(NoDataAction, self).run(machine, value, triggered, timestamp,
                                   incident_id, action)
コード例 #18
0
def rename_machine(machine, name):  # noqa: E501
    """Rename machine

    Rename target machine # noqa: E501

    :param machine:
    :type machine: str
    :param name: New machine name
    :type name: str

    :rtype: None
    """
    from mist.api.methods import list_resources
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    from mist.api.logs.methods import log_event
    try:
        [machine], total = list_resources(auth_context, 'machine',
                                          search=machine, limit=1)
    except ValueError:
        return 'Machine does not exist', 404
    if not methods.run_pre_action_hooks(machine, 'rename', auth_context.user):
        return 'OK', 200  # webhook requires stopping action propagation
    log_event(
        auth_context.owner.id, 'request', 'rename_machine',
        machine_id=machine.id, user_id=auth_context.user.id,
    )
    try:
        auth_context.check_perm('machine', 'rename', machine.id)
    except PolicyUnauthorizedError:
        return 'You are not authorized to perform this action', 403
    try:
        result = machine.ctl.rename(name)
    except ForbiddenError:
        return 'Action not supported on target machine', 422
    methods.run_post_action_hooks(machine, 'rename', auth_context.user, result)
    return 'Machine renamed successfully'
コード例 #19
0
ファイル: views.py プロジェクト: ghoul008/mist.api
def get_private_key(request):
    """
    Tags: keys
    ---
    Gets private key from key name.
    It is used in single key view when the user clicks the display private key
    button.
    READ_PRIVATE permission required on key.
    ---
    key:
      description: The key id
      in: path
      required: true
      type: string
    """

    key_id = request.matchdict['key']
    if not key_id:
        raise RequiredParameterMissingError("key_id")

    auth_context = auth_context_from_request(request)
    try:
        key = SSHKey.objects.get(owner=auth_context.owner,
                                 id=key_id,
                                 deleted=None)
    except me.DoesNotExist:
        raise NotFoundError('Key id does not exist')

    auth_context.check_perm('key', 'read_private', key.id)
    log_event(
        auth_context.owner.id,
        'request',
        'read_private',
        key_id=key.id,
        user_id=auth_context.user.id,
    )
    return key.private
コード例 #20
0
def delete_script(script):  # noqa: E501
    """Delete script

    Delete target script # noqa: E501

    :param script:
    :type script: str

    :rtype: None
    """
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    result = get_resource(auth_context, 'script', search=script)
    result_data = result.get('data')
    if not result_data:
        return 'Script does not exist', 404
    from mist.api.scripts.models import Script
    script_id = result_data.get('id')
    try:
        auth_context.check_perm('script', 'remove', script_id)
    except PolicyUnauthorizedError:
        return 'You are not authorized to perform this action', 403
    script = Script.objects.get(owner=auth_context.owner,
                                id=script_id,
                                deleted=None)
    script.ctl.delete()
    log_event(
        auth_context.owner.id,
        'request',
        'delete_script',
        script_id=script_id,
        user_id=auth_context.user.id,
    )
    return 'Deleted script `%s`' % script.name, 200
コード例 #21
0
def end_job(request):
    """
    Tags: logs+stories
    ---
    Ends a running job.
    Closes/ends an open job. This is very similar to the close_story API
    endpoint. However, this endpoint may be used to perform additional
    actions upon closing an open story.
    ---
    job_id:
      in: path
      type: string
      required: true
    """
    auth_context = auth_context_from_request(request)
    params = params_from_request(request)

    if config.HAS_ORCHESTRATION:
        from mist.orchestration.models import Stack
        from mist.orchestration.methods import finish_workflow
    else:
        raise NotImplementedError()

    job_id = request.matchdict['job_id']
    job = get_story(auth_context.owner.id, job_id)  # Raises NotFoundError.

    stack_id = job['logs'][0].get('stack_id')
    workflow = job['logs'][0].get('workflow', 'install')
    try:
        stack = Stack.objects.get(owner=auth_context.owner,
                                  id=stack_id,
                                  deleted=None)
    except Stack.DoesNotExist:
        raise NotFoundError('Stack does not exist')

    if params.get('action', '') == 'cloud_init_finished':
        if not params.get('machine_name'):
            raise RequiredParameterMissingError('machine_name')
        try:
            error = bool(int(params.get('error')))
        except (TypeError, ValueError):
            raise RequiredParameterMissingError('error')
        # Log cloud-init's outcome. This is expected by the kubernetes
        # blueprint in order for execution to continue. The cloud-init
        # script POSTs back once it's done, since there is no other way
        # to know when it's finished running, especially in case there
        # is no SSH connectivity to the machine.
        log_event(
            job_id=job_id,
            workflow=workflow,
            stack_id=stack.id,
            owner_id=stack.owner.id,
            template_id=stack.template.id,
            machine_name=params['machine_name'],
            action=params['action'],
            event_type='job',
            error=error,
        )
    else:
        # Finish the workflow. Update the Stack and its status.
        finish_workflow(stack, job_id, workflow, params.get('exit_code'),
                        params.get('cmdout'), params.get('error'),
                        params.get('node_instances'), params.get('outputs'))

        # Delete the Stack, if it's been deleted, rather than just uninstalled.
        action = job['logs'][1].get('action', '')
        if workflow == 'uninstall' and action == 'delete_stack':
            stack.update(set__deleted=datetime.datetime.utcnow())
            trigger_session_update(auth_context.owner, ['stacks'])

    # FIXME:The MistClient expects a JSON-decodable response.
    # return Response('OK', 200)
    return {}
コード例 #22
0
def add_key(add_key_request=None):  # noqa: E501
    """Add key

    Adds a new key and returns the key's id. ADD permission required on key. # noqa: E501

    :param add_key_request:
    :type add_key_request: dict | bytes

    :rtype: AddKeyResponse
    """
    if connexion.request.is_json:
        add_key_request = AddKeyRequest.from_dict(connexion.request.get_json())  # noqa: E501

    from mist.api.exceptions import BadRequestError, KeyExistsError
    from mist.api.keys.models import SignedSSHKey, SSHKey
    from mist.api.tag.methods import add_tags_to_resource
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    try:
        key_tags, _ = auth_context.check_perm("key", "add", None)
    except PolicyUnauthorizedError:
        return 'You are not authorized to perform this action', 403

    if add_key_request.generate:
        key = SSHKey()
        key.ctl.generate()
        if add_key_request.dry:  # If dry generate requested then we're done
            log_event(
                auth_context.owner.id, 'request', 'generate_key',
                key_id=key.id, user_id=auth_context.user.id,
            )
            return AddKeyResponse(private=key.private, public=key.public)

        add_key_request.private = key.private

    try:
        if add_key_request.certificate:
            key = SignedSSHKey.add(
                auth_context.owner,
                add_key_request.name,
                private=add_key_request.private,
                certificate=add_key_request.certificate
            )
        else:
            key = SSHKey.add(
                auth_context.owner,
                add_key_request.name,
                private=add_key_request.private
            )
    except BadRequestError as exc:
        return exc.args[0], 400
    except KeyExistsError as exc:
        return exc.args[0], 409
    # Set ownership.
    key.assign_to(auth_context.user)

    # Add tags returned by RBAC check
    if key_tags:
        add_tags_to_resource(auth_context.owner, key, list(key_tags.items()))

    log_event(
        auth_context.owner.id, 'request', 'add_key',
        key_id=key.id, user_id=auth_context.user.id,
    )
    return AddKeyResponse(key.id)
コード例 #23
0
    def __call__(self, environ, start_response):
        request = Request(environ)
        session = session_from_request(request)

        def session_start_response(status, headers, exc_info=None):
            session = environ['session']  # reload in case it was reissued
            if isinstance(session, SessionToken) and \
                    not getattr(session, 'internal', False) and \
                    not session.last_accessed_at:
                cookie = 'session.id=%s; Path=/;' % session.token
                headers.append(('Set-Cookie', cookie))

            # ApiTokens with 'dummy' in name are handed out by session from
            # request function when the api token is not correct, to prevent
            # csrf checks by the CsrfMiddleware but allow calls to function
            # that don't require authentication. When the response is sent out
            # they are to be thrown away, not saved.
            if not (isinstance(session, ApiToken) and 'dummy' in session.name
                    or getattr(session, 'internal', False)):
                session.touch()
                session.save()
            # CORS
            if (environ.get('HTTP_ORIGIN')
                    and environ.get('PATH_INFO') in CORS_ENABLED_PATHS):
                if ('OPTIONS' in environ['REQUEST_METHOD']
                        or isinstance(session, ApiToken)):
                    for header in [
                        ('Access-Control-Allow-Origin',
                         environ['HTTP_ORIGIN']),
                        ('Access-Control-Allow-Methods', 'GET,OPTIONS'),
                        ('Access-Control-Allow-Headers',
                         'Origin, Content-Type, Accept, Authorization'),
                        ('Access-Control-Allow-Credentials', 'true'),
                        ('Access-Control-Max-Age', '1728000'),
                    ]:
                        headers.append(header)
                    if 'OPTIONS' in environ['REQUEST_METHOD']:
                        return start_response('204 No Content', headers,
                                              exc_info)

            return start_response(status, headers, exc_info)

        user = session.get_user()
        # Check whether the request IP is in the user whitelisted ones.
        if session and user is not None and request.path != '/logout' and \
                not getattr(session, 'internal', False):
            current_user_ip = netaddr.IPAddress(ip_from_request(request))
            saved_wips = [netaddr.IPNetwork(ip.cidr) for ip in user.ips]
            config_wips = [
                netaddr.IPNetwork(cidr) for cidr in config.WHITELIST_CIDR
            ]
            wips = saved_wips + config_wips
            if len(saved_wips) > 0:
                for ipnet in wips:
                    if current_user_ip in ipnet:
                        break
                else:
                    log_event(
                        owner_id=session.org.id,
                        user_id=user.id,
                        email=user.email,
                        request_method=request.method,
                        request_path=request.path,
                        request_ip=ip_from_request(request),
                        user_agent=request.user_agent,
                        event_type='ip_whitelist_mismatch',
                        action=request.path,
                        error=True,
                    )
                    # Only logout user if token is SessionToken
                    # Do not logout if it's ApiToken
                    if isinstance(session, SessionToken):
                        reissue_cookie_session(request)
                    start_response('403 Forbidden',
                                   [('Content-type', 'text/plain')])
                    return [
                        "Request sent from non-whitelisted IP.\n"
                        "You have been logged out from this account.\n"
                        "Please sign in to request whitelisting your "
                        "current IP via email."
                    ]

        # Enforce read-only access.
        if config.HAS_RBAC:
            if isinstance(session, ReadOnlyApiToken):
                if request.method not in (
                        'GET',
                        'HEAD',
                        'OPTIONS',
                ):
                    start_response('405 Method Not Allowed',
                                   [('Content-type', 'text/plain')])
                    return ['State-changing HTTP method not allowed\n']

        response = self.app(environ, session_start_response)
        return response
コード例 #24
0
def install_telegraf(machine_id, job=None, job_id=None, plugins=None):
    """Deploy Telegraf over SSH."""
    machine = Machine.objects.get(id=machine_id)
    machine.monitoring.installation_status.state = 'installing'
    machine.save()

    trigger_session_update(machine.owner, ['monitoring'])

    _log = {
        'owner_id': machine.owner.id,
        'cloud_id': machine.cloud.id,
        'machine_id': machine.id,
        'event_type': 'job',
        'job_id': job_id or uuid.uuid4().hex,
        'job': job,
    }
    log_event(action='telegraf_deployment_started', **_log)

    error = None
    try:
        shell = mist.api.shell.Shell(machine.ctl.get_host())
        key, user = shell.autoconfigure(machine.owner, machine.cloud.id,
                                        machine.id)
    except Exception as err:
        log.error('Error during Telegraf installation: %r', err)
        stdout = ''
        error = err
    else:
        exit_code, stdout = shell.command(fetch(unix_install(machine)))
        shell.disconnect()  # Close the SSH connection.

        error = exit_code or ''
        stdout = stdout.replace('\r\n', '\n').replace('\r', '\n')
        _log.update({
            'key_id': key,
            'ssh_user': user,
            'exit_code': exit_code,
            'stdout': stdout.encode('utf-8', 'ignore')
        })

    # Update Machine's InstallationStatus.
    if error:
        machine.monitoring.installation_status.state = 'failed'
    else:
        machine.monitoring.installation_status.state = 'succeeded'
    machine.monitoring.installation_status.finished_at = time.time()
    machine.monitoring.installation_status.stdout = stdout
    machine.monitoring.installation_status.error_msg = str(error)
    machine.save()

    # Deploy custom scripts for metrics' collection.
    if not error and plugins:
        failed = []
        # FIXME Imported here due to circular dependency issues.
        from mist.api.scripts.models import Script
        for script_id in plugins:
            try:
                s = Script.objects.get(owner=machine.owner,
                                       id=script_id,
                                       deleted=None)
                ret = s.ctl.deploy_and_assoc_python_plugin_from_script(machine)
            except Exception as exc:
                failed.append(script_id)
                log_event(action='deploy_telegraf_script',
                          script_id=script_id,
                          error=str(exc),
                          **_log)
            else:
                log_event(action='deploy_telegraf_script',
                          script_id=script_id,
                          metrics=ret['metrics'],
                          stdout=ret['stdout'],
                          **_log)
        if not error and failed:
            error = 'Deployment of scripts with IDs %s failed' % ','.join(
                failed)

    # Log deployment's outcome.
    log_event(action='telegraf_deployment_finished', error=str(error), **_log)

    # Trigger UI update.
    trigger_session_update(machine.owner, ['monitoring'])
コード例 #25
0
def resize_machine(machine, size):  # noqa: E501
    """Resize machine

    Resize target machine # noqa: E501

    :param machine:
    :type machine: str
    :param size:
    :type size: str

    :rtype: None
    """
    from mist.api.methods import list_resources
    try:
        auth_context = connexion.context['token_info']['auth_context']
    except KeyError:
        return 'Authentication failed', 401
    from mist.api.logs.methods import log_event
    try:
        [machine], total = list_resources(auth_context, 'machine',
                                          search=machine, limit=1)
    except ValueError:
        return 'Machine does not exist', 404
    if not methods.run_pre_action_hooks(machine, 'resize', auth_context.user):
        return 'OK', 200  # webhook requires stopping action propagation
    try:
        [size], total = list_resources(auth_context, 'size',
                                       cloud=machine.cloud.id,
                                       search=size, limit=1)
    except ValueError:
        return 'Size does not exist', 404
    try:
        _, constraints = auth_context.check_perm(
            'machine', 'resize', machine.id)
    except PolicyUnauthorizedError:
        return 'You are not authorized to perform this action', 403
    # check cost constraint
    cost_constraint = constraints.get('cost', {})
    if cost_constraint:
        try:
            from mist.rbac.methods import check_cost
            check_cost(auth_context.org, cost_constraint)
        except ImportError:
            pass
    # check size constraint
    size_constraint = constraints.get('size', {})
    if size_constraint:
        try:
            from mist.rbac.methods import check_size
            check_size(machine.cloud.id, size_constraint, size)
        except ImportError:
            pass
    log_event(
        auth_context.owner.id, 'request', 'resize_machine',
        machine_id=machine.id, user_id=auth_context.user.id,
    )
    try:
        result = machine.ctl.resize(size.id, {})
    except ForbiddenError:
        return 'Action not supported on target machine', 422
    except BadRequestError as e:
        return str(e), 400
    methods.run_post_action_hooks(machine, 'resize', auth_context.user, result)
    return 'Machine resize issued successfully'
コード例 #26
0
def log_request_to_elastic(exception):
    try:
        log_dict = g.log_dict
    except AttributeError:
        return

    # log original exception
    if isinstance(exception, MistError):
        if exception.orig_exc:
            log_dict['_exc'] = repr(exception.orig_exc)
            log_dict['_exc_type'] = type(exception.orig_exc)
            if exception.orig_traceback:
                log_dict['_traceback'] = exception.orig_traceback
    elif isinstance(exception, Exception):
        log_dict['_exc'] = repr(exception)
        log_dict['_exc_type'] = type(exception)
        log_dict['_traceback'] = traceback.format_exc()

    if log_dict:
        log_methods.log_event(**log_dict)

    # if a bad exception didn't occur then return, else log it to file
    if g.exc_flag is False or exception is None:
        return

    # Publish traceback in rabbitmq, for heka to parse and forward to
    # elastic
    log.info('Bad exception occured, logging to rabbitmq')
    es_dict = log_dict.copy()
    es_dict.pop('_exc_type', '')
    es_dict['time'] = time.time()
    es_dict['traceback'] = es_dict.pop('_traceback', '')
    es_dict['exception'] = es_dict.pop('_exc', '')
    es_dict['type'] = 'exception'
    routing_key = '%s.%s' % (es_dict['owner_id'], es_dict['action'])
    pickler = jsonpickle.pickler.Pickler()
    helpers.amqp_publish('exceptions',
                         routing_key,
                         pickler.flatten(es_dict),
                         ex_type='topic',
                         ex_declare=True,
                         auto_delete=False)

    # log bad exception to file
    log.info('Bad exception occured, logging to file')
    lines = []
    lines.append('Exception: %s' % log_dict.pop('_exc', ''))
    lines.append('Exception type: %s' % log_dict.pop('_exc_type', ''))
    lines.append('Time: %s' % time.strftime('%Y-%m-%d %H:%M %Z'))
    lines += ([
        '%s: %s' % (key, value) for key, value in list(log_dict.items())
        if value and key != '_traceback'
    ])
    for key in ('owner', 'user', 'sudoer'):
        id_ = log_dict.get('%s_id' % key)
        if id_:
            try:
                value = Owner.objects.get(id=id_)
                lines.append('%s: %s' % (key, value))
            except Owner.DoesNotExist:
                pass
            except Exception as exc:
                log.error('Error finding user in logged exc: %r', exc)
    lines.append('-' * 10)
    lines.append(log_dict.get('_traceback', ''))
    lines.append('=' * 10)
    msg = '\n'.join(lines) + '\n'
    directory = 'var/log/exceptions'
    if not os.path.exists(directory):
        os.makedirs(directory)
    filename = '%s/%s' % (directory, int(time.time()))
    with open(filename, 'w+') as f:
        f.write(msg)