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
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)
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
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
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)
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)
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)
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'])
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)
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
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'
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'])
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
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)
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'
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
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)
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'
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
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
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 {}
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)
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
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'])
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'
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)