def WriteHeader(self): """Setup the Elasticsearch index and the Timesketch database object. Creates the Elasticsearch index with Timesketch specific settings and the Timesketch SearchIndex database object. """ # This cannot be static because we use the value of self._doc_type from # arguments. _document_mapping = { self._doc_type: { u'properties': { u'timesketch_label': { u'type': u'nested' } } } } # Get Elasticsearch host and port from Timesketch configuration. with self._timesketch.app_context(): _host = current_app.config[u'ELASTIC_HOST'] _port = current_app.config[u'ELASTIC_PORT'] self._elastic = ElasticSearchHelper( self._output_mediator, _host, _port, self._flush_interval, self._index_name, _document_mapping, self._doc_type) user = None if self._username: user = User.query.filter_by(username=self._username).first() if not user: raise RuntimeError( u'Unknown Timesketch user: {0:s}'.format(self._username)) else: logging.warning(u'Timeline will be visible to all Timesketch users') with self._timesketch.app_context(): search_index = SearchIndex.get_or_create( name=self._timeline_name, description=self._timeline_name, user=user, index_name=self._index_name) # Grant the user read permission on the mapping object and set status. # If user is None the timeline becomes visible to all users. search_index.grant_permission(user=user, permission=u'read') # In case we have a user grant additional permissions. if user: search_index.grant_permission(user=user, permission=u'write') search_index.grant_permission(user=user, permission=u'delete') # Let the Timesketch UI know that the timeline is processing. search_index.set_status(u'processing') # Save the mapping object to the Timesketch database. db_session.add(search_index) db_session.commit() logging.info(u'Adding events to Timesketch.')
def post(self): """Handles POST request to the resource. Returns: A search index in JSON (instance of flask.wrappers.Response) """ form = forms.SearchIndexForm.build(request) searchindex_name = form.searchindex_name.data es_index_name = form.es_index_name.data public = form.public.data if not form.validate_on_submit(): abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to validate form data') searchindex = SearchIndex.query.filter_by( index_name=es_index_name).first() metadata = {'created': True} if searchindex: metadata['created'] = False metadata['deleted'] = searchindex.get_status.status == 'deleted' status_code = HTTP_STATUS_CODE_OK else: searchindex = SearchIndex.get_or_create( name=searchindex_name, description=searchindex_name, user=current_user, index_name=es_index_name) searchindex.grant_permission(permission='read', user=current_user) searchindex.grant_permission(permission='write', user=current_user) searchindex.grant_permission(permission='delete', user=current_user) if public: searchindex.grant_permission(permission='read', user=None) datastore = self.datastore if not datastore.client.indices.exists(index=es_index_name): # Create the index in Elasticsearch self.datastore.create_index(index_name=es_index_name, doc_type='generic_event') db_session.add(searchindex) db_session.commit() status_code = HTTP_STATUS_CODE_CREATED return self.to_json(searchindex, meta=metadata, status_code=status_code)
def WriteHeader(self): """Setup the Elasticsearch index and the Timesketch database object. Creates the Elasticsearch index with Timesketch specific settings and the Timesketch SearchIndex database object. """ # This cannot be static because we use the value of self._doc_type from # arguments. _document_mapping = { self._doc_type: { u'_timestamp': { u'enabled': True, u'path': u'datetime', u'format': u'date_time_no_millis' }, u'properties': { u'timesketch_label': { u'type': u'nested' } } } } if not self._elastic_db.client.indices.exists(self._index_name): try: self._elastic_db.client.indices.create( index=self._index_name, body={u'mappings': _document_mapping}) except elastic_exceptions.ConnectionError as exception: logging.error(( u'Unable to proceed, cannot connect to Timesketch backend ' u'with error: {0:s}.\nPlease verify connection.' ).format(exception)) raise RuntimeError(u'Unable to connect to Timesketch backend.') with self._timesketch.app_context(): search_index = SearchIndex.get_or_create( name=self._timeline_name, description=self._timeline_name, user=None, index_name=self._index_name) # Grant all users read permission on the mapping object and set status. search_index.grant_permission(None, u'read') search_index.set_status(u'processing') # Save the mapping object to the Timesketch database. db_session.add(search_index) db_session.commit() logging.info(u'Adding events to Timesketch..')
def post(self): """Handles POST request to the resource. Returns: A view in JSON (instance of flask.wrappers.Response) Raises: ApiHTTPError """ UPLOAD_ENABLED = current_app.config[u'UPLOAD_ENABLED'] UPLOAD_FOLDER = current_app.config[u'UPLOAD_FOLDER'] form = UploadFileForm() if form.validate_on_submit() and UPLOAD_ENABLED: from timesketch.lib.tasks import run_plaso file_storage = form.file.data timeline_name = form.name.data # We do not need a human readable filename or # datastore index name, so we use UUIDs here. filename = unicode(uuid.uuid4().hex) index_name = unicode(uuid.uuid4().hex) file_path = os.path.join(UPLOAD_FOLDER, filename) file_storage.save(file_path) search_index = SearchIndex.get_or_create(name=timeline_name, description=timeline_name, user=current_user, index_name=index_name) search_index.grant_permission(permission=u'read', user=current_user) search_index.grant_permission(permission=u'write', user=current_user) search_index.grant_permission(permission=u'delete', user=current_user) search_index.set_status(u'processing') db_session.add(search_index) db_session.commit() run_plaso.apply_async((file_path, timeline_name, index_name), task_id=index_name) return self.to_json(search_index, status_code=HTTP_STATUS_CODE_CREATED) else: raise ApiHTTPError(message=form.errors[u'file'][0], status_code=HTTP_STATUS_CODE_BAD_REQUEST)
def post(self): """Handles POST request to the resource. Returns: A view in JSON (instance of flask.wrappers.Response) Raises: ApiHTTPError """ UPLOAD_ENABLED = current_app.config[u'UPLOAD_ENABLED'] UPLOAD_FOLDER = current_app.config[u'UPLOAD_FOLDER'] form = UploadFileForm() if form.validate_on_submit() and UPLOAD_ENABLED: from timesketch.lib.tasks import run_plaso file_storage = form.file.data timeline_name = form.name.data # We do not need a human readable filename or # datastore index name, so we use UUIDs here. filename = unicode(uuid.uuid4().hex) index_name = unicode(uuid.uuid4().hex) file_path = os.path.join(UPLOAD_FOLDER, filename) file_storage.save(file_path) search_index = SearchIndex.get_or_create( name=timeline_name, description=timeline_name, user=current_user, index_name=index_name) search_index.grant_permission(permission=u'read', user=current_user) search_index.grant_permission( permission=u'write', user=current_user) search_index.grant_permission( permission=u'delete', user=current_user) search_index.set_status(u'processing') db_session.add(search_index) db_session.commit() run_plaso.apply_async( (file_path, timeline_name, index_name), task_id=index_name) return self.to_json( search_index, status_code=HTTP_STATUS_CODE_CREATED) else: raise ApiHTTPError( message=form.errors[u'file'][0], status_code=HTTP_STATUS_CODE_BAD_REQUEST)
def _create_searchindex(self, name, user, acl=False): """Create a searchindex in the database. Args: name: Name of the searchindex (string) user: A user (instance of timesketch.models.user.User) acl: Boolean value to decide if ACL permissions should be set Returns: A searchindex (instance of timesketch.models.sketch.SearchIndex) """ searchindex = SearchIndex.get_or_create( name=name, description=name, index_name=name, user=user) if acl: for permission in ['read', 'write', 'delete']: searchindex.grant_permission(permission=permission, user=user) searchindex.set_status(status='ready') self._commit_to_database(searchindex) return searchindex
def _create_searchindex(self, name, user, acl=False): """Create a searchindex in the database. Args: name: Name of the searchindex (string) user: A user (instance of timesketch.models.user.User) acl: Boolean value to decide if ACL permissions should be set Returns: A searchindex (instance of timesketch.models.sketch.SearchIndex) """ searchindex = SearchIndex.get_or_create( name=name, description=name, index_name=name, user=user) if acl: for permission in ['read', 'write', 'delete']: searchindex.grant_permission(permission=permission, user=user) self._commit_to_database(searchindex) return searchindex
def WriteHeader(self): """Setup the Elasticsearch index and the Timesketch database object. Creates the Elasticsearch index with Timesketch specific settings and the Timesketch SearchIndex database object. """ # This cannot be static because we use the value of self._doc_type from # arguments. _document_mapping = { self._doc_type: { u'_timestamp': { u'enabled': True, u'path': u'datetime', u'format': u'date_time_no_millis' }, u'properties': {u'timesketch_label': {u'type': u'nested'}} } } if not self._elastic_db.client.indices.exists(self._index_name): try: self._elastic_db.client.indices.create( index=self._index_name, body={u'mappings': _document_mapping}) except elastic_exceptions.ConnectionError as exception: logging.error(( u'Unable to proceed, cannot connect to Timesketch backend ' u'with error: {0:s}.\nPlease verify connection.').format(exception)) raise RuntimeError(u'Unable to connect to Timesketch backend.') with self._timesketch.app_context(): search_index = SearchIndex.get_or_create( name=self._timeline_name, description=self._timeline_name, user=None, index_name=self._index_name) # Grant all users read permission on the mapping object and set status. search_index.grant_permission(None, u'read') search_index.set_status(u'processing') # Save the mapping object to the Timesketch database. db_session.add(search_index) db_session.commit() logging.info(u'Adding events to Timesketch..')
def post(self): """Handles POST request to the resource. Returns: A search index in JSON (instance of flask.wrappers.Response) """ form = SearchIndexForm.build(request) timeline_name = form.timeline_name.data index_name = form.index_name.data public = form.public.data if form.validate_on_submit(): searchindex = SearchIndex.query.filter_by( index_name=index_name).first() if not searchindex: searchindex = SearchIndex.get_or_create( name=timeline_name, description=timeline_name, user=current_user, index_name=index_name) searchindex.grant_permission(permission=u'read', user=current_user) if public: searchindex.grant_permission(permission=u'read', user=None) # Create the index in Elasticsearch self.datastore.create_index(index_name=index_name, doc_type=u'generic_event') db_session.add(searchindex) db_session.commit() return self.to_json(searchindex, status_code=HTTP_STATUS_CODE_CREATED) return abort(HTTP_STATUS_CODE_BAD_REQUEST)
def post(self, sketch_id): """Handles POST request to the resource. Handler for /api/v1/sketches/:sketch_id/event/create/ Args: sketch_id: Integer primary key for a sketch database model Returns: An annotation in JSON (instance of flask.wrappers.Response) """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have write access controls on sketch.') form = request.json if not form: form = request.data timeline_name = 'sketch specific timeline' index_name_seed = 'timesketch_{0:d}'.format(sketch_id) event_type = 'user_created_event' date_string = form.get('date_string') if not date_string: date = datetime.datetime.utcnow().isoformat() else: # derive datetime from timestamp: try: date = dateutil.parser.parse(date_string) except (dateutil.parser.ParserError, OverflowError) as e: logger.error('Unable to convert date string', exc_info=True) abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to add event, not able to convert the date ' 'string. Was it properly formatted? Error: ' '{0!s}'.format(e)) timestamp = int(time.mktime(date.utctimetuple())) * 1000000 timestamp += date.microsecond event = { 'datetime': date_string, 'timestamp': timestamp, 'timestamp_desc': form.get('timestamp_desc', 'Event Happened'), 'message': form.get('message', 'No message string'), } attributes = form.get('attributes', {}) if not isinstance(attributes, dict): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to add an event where the attributes are not a ' 'dict object.') event.update(attributes) tag = form.get('tag', []) if not isinstance(tag, list): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to add an event where the tags are not a ' 'list of strings.') if tag and any([not isinstance(x, str) for x in tag]): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to add an event where the tags are not a ' 'list of strings.') event['tag'] = tag # We do not need a human readable filename or # datastore index name, so we use UUIDs here. index_name = hashlib.md5(index_name_seed.encode()).hexdigest() if six.PY2: index_name = codecs.decode(index_name, 'utf-8') # Try to create index try: # Create the index in Elasticsearch (unless it already exists) self.datastore.create_index(index_name=index_name, doc_type=event_type) # Create the search index in the Timesketch database searchindex = SearchIndex.get_or_create( name=timeline_name, description='internal timeline for user-created events', user=current_user, index_name=index_name) searchindex.grant_permission(permission='read', user=current_user) searchindex.grant_permission(permission='write', user=current_user) searchindex.grant_permission(permission='delete', user=current_user) searchindex.set_status('ready') db_session.add(searchindex) db_session.commit() timeline = None if sketch and sketch.has_permission(current_user, 'write'): self.datastore.import_event(index_name, event_type, event, flush_interval=1) timeline = Timeline.get_or_create( name=searchindex.name, description=searchindex.description, sketch=sketch, user=current_user, searchindex=searchindex) if timeline not in sketch.timelines: sketch.timelines.append(timeline) timeline.set_status('ready') db_session.add(timeline) db_session.commit() # Return Timeline if it was created. # pylint: disable=no-else-return if timeline: return self.to_json(timeline, status_code=HTTP_STATUS_CODE_CREATED) else: return self.to_json(searchindex, status_code=HTTP_STATUS_CODE_CREATED) # TODO: Can this be narrowed down, both in terms of the scope it # applies to, as well as not to catch a generic exception. except Exception as e: # pylint: disable=broad-except abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Failed to add event ({0!s})'.format(e))
def post(self): """Handles POST request to the resource. Returns: A view in JSON (instance of flask.wrappers.Response) """ upload_enabled = current_app.config['UPLOAD_ENABLED'] if not upload_enabled: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Failed to create timeline, upload not enabled') form = forms.CreateTimelineForm() if not form.validate_on_submit(): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Failed to create timeline, form data not validated') sketch_id = form.sketch_id.data timeline_name = form.name.data sketch = None if sketch_id: sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') # We do not need a human readable filename or # datastore index name, so we use UUIDs here. index_name = uuid.uuid4().hex if not isinstance(index_name, six.text_type): index_name = codecs.decode(index_name, 'utf-8') # Create the search index in the Timesketch database searchindex = SearchIndex.get_or_create( name=timeline_name, description=timeline_name, user=current_user, index_name=index_name) searchindex.grant_permission(permission='read', user=current_user) searchindex.grant_permission(permission='write', user=current_user) searchindex.grant_permission( permission='delete', user=current_user) searchindex.set_status('processing') db_session.add(searchindex) db_session.commit() timeline = None if sketch and sketch.has_permission(current_user, 'write'): timeline = Timeline( name=searchindex.name, description=searchindex.description, sketch=sketch, user=current_user, searchindex=searchindex) sketch.timelines.append(timeline) db_session.add(timeline) db_session.commit() # Return Timeline if it was created. # pylint: disable=no-else-return if timeline: return self.to_json( timeline, status_code=HTTP_STATUS_CODE_CREATED) # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json( searchindex, status_code=HTTP_STATUS_CODE_CREATED)
def run(self, file_path, sketch_id, username, timeline_name): """This is the run method.""" file_path = os.path.realpath(file_path) file_path_no_extension, extension = os.path.splitext(file_path) extension = extension.lstrip('.') filename = os.path.basename(file_path_no_extension) supported_extensions = ('plaso', 'csv', 'jsonl') if not os.path.isfile(file_path): sys.exit('No such file: {0:s}'.format(file_path)) if extension not in supported_extensions: sys.exit('Extension {0:s} is not supported. ' '(supported extensions are: {1:s})'.format( extension, ', '.join(supported_extensions))) user = None if not username: username = pwd.getpwuid(os.stat(file_path).st_uid).pw_name if not username == 'root': if not isinstance(username, six.text_type): username = codecs.decode(username, 'utf-8') user = User.query.filter_by(username=username).first() if not user: sys.exit('Cannot determine user for file: {0:s}'.format(file_path)) sketch = None # If filename starts with <number> then use that as sketch_id. # E.g: 42_file_name.plaso means sketch_id is 42. sketch_id_from_filename = filename.split('_')[0] if not sketch_id and sketch_id_from_filename.isdigit(): sketch_id = sketch_id_from_filename if sketch_id: try: sketch = Sketch.query.get_with_acl(sketch_id, user=user) except Forbidden: pass if not timeline_name: if timeline_name is None: timeline_name = '{0:s}_timeline'.format(filename) if not isinstance(timeline_name, six.text_type): timeline_name = codecs.decode(timeline_name, 'utf-8') timeline_name = timeline_name.replace('_', ' ') # Remove sketch ID if present in the filename. timeline_parts = timeline_name.split() if timeline_parts[0].isdigit(): timeline_name = ' '.join(timeline_name.split()[1:]) if not sketch: # Create a new sketch. sketch_name = 'Sketch for: {0:s}'.format(timeline_name) sketch = Sketch(name=sketch_name, description=sketch_name, user=user) # Need to commit here to be able to set permissions later. db_session.add(sketch) db_session.commit() sketch.grant_permission(permission='read', user=user) sketch.grant_permission(permission='write', user=user) sketch.grant_permission(permission='delete', user=user) sketch.status.append(sketch.Status(user=None, status='new')) db_session.add(sketch) db_session.commit() index_name = uuid.uuid4().hex if not isinstance(index_name, six.text_type): index_name = codecs.decode(index_name, 'utf-8') searchindex = SearchIndex.get_or_create(name=timeline_name, description=timeline_name, user=user, index_name=index_name) searchindex.grant_permission(permission='read', user=user) searchindex.grant_permission(permission='write', user=user) searchindex.grant_permission(permission='delete', user=user) searchindex.set_status('processing') db_session.add(searchindex) db_session.commit() if sketch and sketch.has_permission(user, 'write'): timeline = Timeline(name=searchindex.name, description=searchindex.description, sketch=sketch, user=user, searchindex=searchindex) timeline.set_status('processing') sketch.timelines.append(timeline) db_session.add(timeline) db_session.commit() # Start Celery pipeline for indexing and analysis. # Import here to avoid circular imports. from timesketch.lib import tasks # pylint: disable=import-outside-toplevel pipeline = tasks.build_index_pipeline(file_path=file_path, events='', timeline_name=timeline_name, index_name=index_name, file_extension=extension, sketch_id=sketch.id) pipeline.apply_async(task_id=index_name) print('Imported {0:s} to sketch: {1:d} ({2:s})'.format( file_path, sketch.id, sketch.name))
def post(self, sketch_id=None): """Handles POST request to the resource. Returns: A view in JSON (instance of flask.wrappers.Response) Raises: ApiHTTPError """ UPLOAD_ENABLED = current_app.config[u'UPLOAD_ENABLED'] UPLOAD_FOLDER = current_app.config[u'UPLOAD_FOLDER'] sketch = None if sketch_id: sketch = Sketch.query.get_with_acl(sketch_id) form = UploadFileForm() if form.validate_on_submit() and UPLOAD_ENABLED: from timesketch.lib.tasks import run_plaso from timesketch.lib.tasks import run_csv # Map the right task based on the file type task_directory = {u'plaso': run_plaso, u'csv': run_csv} file_storage = form.file.data timeline_name = form.name.data _, _extension = os.path.splitext(file_storage.filename) file_extension = _extension.lstrip(u'.') # Current user username = current_user.username # We do not need a human readable filename or # datastore index name, so we use UUIDs here. filename = unicode(uuid.uuid4().hex) index_name = unicode(uuid.uuid4().hex) file_path = os.path.join(UPLOAD_FOLDER, filename) file_storage.save(file_path) # Create the search index in the Timesketch database searchindex = SearchIndex.get_or_create(name=timeline_name, description=timeline_name, user=current_user, index_name=index_name) searchindex.grant_permission(permission=u'read', user=current_user) searchindex.grant_permission(permission=u'write', user=current_user) searchindex.grant_permission(permission=u'delete', user=current_user) searchindex.set_status(u'processing') db_session.add(searchindex) db_session.commit() if sketch and sketch.has_permission(current_user, u'write'): timeline = Timeline(name=searchindex.name, description=searchindex.description, sketch=sketch, user=current_user, searchindex=searchindex) db_session.add(timeline) sketch.timelines.append(timeline) db_session.commit() # Run the task in the background task = task_directory.get(file_extension) task.apply_async((file_path, timeline_name, index_name, username), task_id=index_name) return self.to_json(searchindex, status_code=HTTP_STATUS_CODE_CREATED) else: raise ApiHTTPError(message=form.errors[u'file'][0], status_code=HTTP_STATUS_CODE_BAD_REQUEST)
def _get_index( self, name, description, sketch, index_name='', data_label='', extension=''): """Returns a SearchIndex object to be used for uploads. Args: name: the name of the searchindex. description: the description of the searchindex. sketch: sketch object (instance of Sketch). index_name: optional index name, if supplied and if it exists then the index associated with that will be returned. data_label: optional label of the data, if supplied will be used to determine whether an already existing index can be used or a new one created. extension: optional file extension if a file is being uploaded, if supplied and no data label used, then the extension will be used as a data label. Returns: A SearchIndex object. """ if index_name: if not isinstance(index_name, str): index_name = codecs.decode(index_name, 'utf-8') searchindex = SearchIndex.query.filter_by( name=name, index_name=index_name).first() if searchindex and searchindex.has_permission( permission='write', user=current_user): return searchindex if extension and not data_label: data_label = extension if not data_label: data_label = 'generic' # Since CSV and JSON are basically the same label, we combine it here. if data_label in ('csv', 'json', 'jsonl'): data_label = 'csv_jsonl' indices = [t.searchindex for t in sketch.active_timelines] for index in indices: if index.has_label(data_label) and sketch.has_permission( permission='write', user=current_user): return index index_name = index_name or uuid.uuid4().hex searchindex = SearchIndex.get_or_create( name=name, index_name=index_name, description=description, user=current_user) searchindex.grant_permission(permission='read', user=current_user) searchindex.grant_permission(permission='write', user=current_user) searchindex.grant_permission(permission='delete', user=current_user) searchindex.set_status('processing') db_session.add(searchindex) db_session.commit() searchindex.add_label(data_label, user=current_user) return searchindex
def setup_sketch(timeline_name, index_name, username, sketch_id=None): """Use existing sketch or create a new sketch. Args: timeline_name: (str) Name of the Timeline index_name: (str) Name of the index username: (str) Who should own the timeline sketch_id: (str) Optional sketch_id to add timeline to Returns: (tuple) sketch ID and timeline ID as integers """ with app.app_context(): user = User.get_or_create(username=username) sketch = None if sketch_id: try: sketch = Sketch.query.get_with_acl(sketch_id, user=user) logger.info('Using existing sketch: {} ({})'.format( sketch.name, sketch.id)) except Forbidden: pass if not (sketch or sketch_id): # Create a new sketch. sketch_name = 'Turbinia: {}'.format(timeline_name) sketch = Sketch(name=sketch_name, description=sketch_name, user=user) # Need to commit here to be able to set permissions later. db_session.add(sketch) db_session.commit() sketch.grant_permission(permission='read', user=user) sketch.grant_permission(permission='write', user=user) sketch.grant_permission(permission='delete', user=user) sketch.status.append(sketch.Status(user=None, status='new')) db_session.add(sketch) db_session.commit() logger.info('Created new sketch: {} ({})'.format( sketch.name, sketch.id)) searchindex = SearchIndex.get_or_create( name=timeline_name, description='Created by Turbinia.', user=user, index_name=index_name) searchindex.grant_permission(permission='read', user=user) searchindex.grant_permission(permission='write', user=user) searchindex.grant_permission(permission='delete', user=user) searchindex.set_status('processing') db_session.add(searchindex) db_session.commit() timeline = Timeline(name=searchindex.name, description=searchindex.description, sketch=sketch, user=user, searchindex=searchindex) # If the user doesn't have write access to the sketch then create the # timeline but don't attach it to the sketch. if not sketch.has_permission(user, 'write'): timeline.sketch = None else: sketch.timelines.append(timeline) db_session.add(timeline) db_session.commit() timeline.set_status('processing') return sketch.id, timeline.id
def WriteHeader(self): """Setup the Elasticsearch index and the Timesketch database object. Creates the Elasticsearch index with Timesketch specific settings and the Timesketch SearchIndex database object. """ # This cannot be static because we use the value of self._doc_type from # arguments. _document_mapping = { self._doc_type: { u'properties': { u'timesketch_label': { u'type': u'nested' } } } } # Get Elasticsearch host and port from Timesketch configuration. with self._timesketch.app_context(): _host = current_app.config[u'ELASTIC_HOST'] _port = current_app.config[u'ELASTIC_PORT'] self._elastic = ElasticSearchHelper(self._output_mediator, _host, _port, self._flush_interval, self._index_name, _document_mapping, self._doc_type) user = None if self._username: user = User.query.filter_by(username=self._username).first() if not user: raise RuntimeError(u'Unknown Timesketch user: {0:s}'.format( self._username)) else: logging.warning( u'Timeline will be visible to all Timesketch users') with self._timesketch.app_context(): search_index = SearchIndex.get_or_create( name=self._timeline_name, description=self._timeline_name, user=user, index_name=self._index_name) # Grant the user read permission on the mapping object and set status. # If user is None the timeline becomes visible to all users. search_index.grant_permission(user=user, permission=u'read') # In case we have a user grant additional permissions. if user: search_index.grant_permission(user=user, permission=u'write') search_index.grant_permission(user=user, permission=u'delete') # Let the Timesketch UI know that the timeline is processing. search_index.set_status(u'processing') # Save the mapping object to the Timesketch database. db_session.add(search_index) db_session.commit() logging.info(u'Adding events to Timesketch.')
def _upload_and_index(self, file_extension, timeline_name, index_name, sketch, enable_stream, file_path='', events='', meta=None): """Creates a full pipeline for an uploaded file and returns the results. Args: file_extension: the extension of the uploaded file. timeline_name: name the timeline will be stored under in the datastore. index_name: the Elastic index name for the timeline. sketch: Instance of timesketch.models.sketch.Sketch enable_stream: boolean indicating whether this is file is part of a stream or not. file_path: the path to the file to be uploaded (optional). events: a string with events to upload (optional). meta: optional dict with additional meta fields that will be included in the return. Returns: A timeline if created otherwise a search index in JSON (instance of flask.wrappers.Response) """ # Check if search index already exists. searchindex = SearchIndex.query.filter_by( name=timeline_name, description=timeline_name, user=current_user, index_name=index_name).first() timeline = None if searchindex: searchindex.set_status('processing') timeline = Timeline.query.filter_by( name=searchindex.name, description=searchindex.description, sketch=sketch, user=current_user, searchindex=searchindex).first() else: # Create the search index in the Timesketch database searchindex = SearchIndex.get_or_create(name=timeline_name, description='', user=current_user, index_name=index_name) searchindex.grant_permission(permission='read', user=current_user) searchindex.grant_permission(permission='write', user=current_user) searchindex.grant_permission(permission='delete', user=current_user) searchindex.set_status('processing') db_session.add(searchindex) db_session.commit() if sketch and sketch.has_permission(current_user, 'write'): labels_to_prevent_deletion = current_app.config.get( 'LABELS_TO_PREVENT_DELETION', []) timeline = Timeline(name=searchindex.name, description=searchindex.description, sketch=sketch, user=current_user, searchindex=searchindex) timeline.set_status('processing') sketch.timelines.append(timeline) for label in sketch.get_labels: if label not in labels_to_prevent_deletion: continue timeline.add_label(label) searchindex.add_label(label) db_session.add(timeline) db_session.commit() # Start Celery pipeline for indexing and analysis. # Import here to avoid circular imports. # pylint: disable=import-outside-toplevel from timesketch.lib import tasks pipeline = tasks.build_index_pipeline(file_path=file_path, events=events, timeline_name=timeline_name, index_name=index_name, file_extension=file_extension, sketch_id=sketch.id, only_index=enable_stream) pipeline.apply_async() # Return Timeline if it was created. # pylint: disable=no-else-return if timeline: return self.to_json(timeline, status_code=HTTP_STATUS_CODE_CREATED, meta=meta) return self.to_json(searchindex, status_code=HTTP_STATUS_CODE_CREATED, meta=meta)
def run(self, file_path, sketch_id, username, timeline_name): """This is the run method.""" file_path = os.path.realpath(file_path) file_path_no_extension, extension = os.path.splitext(file_path) extension = extension.lstrip('.') filename = os.path.basename(file_path_no_extension) supported_extensions = ('plaso', 'csv', 'jsonl') if not os.path.isfile(file_path): sys.exit('No such file: {0:s}'.format(file_path)) if extension not in supported_extensions: sys.exit( 'Extension {0:s} is not supported. ' '(supported extensions are: {1:s})'.format( extension, ', '.join(supported_extensions))) user = None if not username: username = pwd.getpwuid(os.stat(file_path).st_uid).pw_name if not username == 'root': if not isinstance(username, six.text_type): username = codecs.decode(username, 'utf-8') user = User.query.filter_by(username=username).first() if not user: sys.exit('Cannot determine user for file: {0:s}'.format(file_path)) sketch = None # If filename starts with <number> then use that as sketch_id. # E.g: 42_file_name.plaso means sketch_id is 42. sketch_id_from_filename = filename.split('_')[0] if not sketch_id and sketch_id_from_filename.isdigit(): sketch_id = sketch_id_from_filename if sketch_id: try: sketch = Sketch.query.get_with_acl(sketch_id, user=user) except Forbidden: pass if not timeline_name: if not isinstance(timeline_name, six.text_type): timeline_name = codecs.decode(timeline_name, 'utf-8') timeline_name = timeline_name.replace('_', ' ') # Remove sketch ID if present in the filename. timeline_parts = timeline_name.split() if timeline_parts[0].isdigit(): timeline_name = ' '.join(timeline_name.split()[1:]) if not sketch: # Create a new sketch. sketch_name = 'Sketch for: {0:s}'.format(timeline_name) sketch = Sketch( name=sketch_name, description=sketch_name, user=user) # Need to commit here to be able to set permissions later. db_session.add(sketch) db_session.commit() sketch.grant_permission(permission='read', user=user) sketch.grant_permission(permission='write', user=user) sketch.grant_permission(permission='delete', user=user) sketch.status.append(sketch.Status(user=None, status='new')) db_session.add(sketch) db_session.commit() index_name = uuid.uuid4().hex if not isinstance(index_name, six.text_type): index_name = codecs.decode(index_name, 'utf-8') searchindex = SearchIndex.get_or_create( name=timeline_name, description=timeline_name, user=user, index_name=index_name) searchindex.grant_permission(permission='read', user=user) searchindex.grant_permission(permission='write', user=user) searchindex.grant_permission(permission='delete', user=user) searchindex.set_status('processing') db_session.add(searchindex) db_session.commit() if sketch and sketch.has_permission(user, 'write'): timeline = Timeline( name=searchindex.name, description=searchindex.description, sketch=sketch, user=user, searchindex=searchindex) timeline.set_status('processing') sketch.timelines.append(timeline) db_session.add(timeline) db_session.commit() # Start Celery pipeline for indexing and analysis. # Import here to avoid circular imports. from timesketch.lib import tasks pipeline = tasks.build_index_pipeline( file_path, timeline_name, index_name, extension, sketch.id) pipeline.apply_async(task_id=index_name) print('Imported {0:s} to sketch: {1:d} ({2:s})'.format( file_path, sketch.id, sketch.name))