def test_result_for_service(datastore, login_session): _, session, host = login_session rand_key = random.choice(file_res_list) rand_hash = rand_key[:64] service_name = rand_key.split('.')[1] resp = get_api_data( session, f"{host}/api/v4/file/result/{rand_hash}/{service_name}/") result_dict = resp['results'][0] for s in result_dict['result']['sections']: s['tags'] = unflatten(tag_list_to_dict(s['tags'])) res_data = Result(result_dict) assert res_data.build_key() in file_res_list
def try_run(self): while self.running: task = self.dispatch_client.request_work('worker', self.service_name, '0', timeout=1) if not task: continue print(self.service_name, 'has received a job', task.sid) file = self.filestore.get(task.fileinfo.sha256) instructions = json.loads(file) instructions = instructions.get(self.service_name, {}) print(self.service_name, 'following instruction:', instructions) hits = self.hits[task.fileinfo.sha256] = self.hits.get( task.fileinfo.sha256, 0) + 1 if instructions.get('semaphore', False): _global_semaphore.acquire(blocking=True, timeout=instructions['semaphore']) continue if 'drop' in instructions: if instructions['drop'] >= hits: self.drops[task.fileinfo.sha256] = self.drops.get( task.fileinfo.sha256, 0) + 1 continue if instructions.get('failure', False): error = Error(instructions['error']) error.sha256 = task.fileinfo.sha256 self.dispatch_client.service_failed(task.sid, error=error, error_key=get_random_id()) continue result_data = { 'archive_ts': time.time() + 300, 'classification': 'U', 'response': { 'service_version': '0', 'service_tool_version': '0', 'service_name': self.service_name, }, 'result': {}, 'sha256': task.fileinfo.sha256, 'expiry_ts': time.time() + 600 } result_data.update(instructions.get('result', {})) result_data['response'].update(instructions.get('response', {})) result = Result(result_data) result_key = instructions.get('result_key', get_random_id()) self.dispatch_client.service_finished(task.sid, result_key, result)
def create_empty_result_from_key(key, cl_engine=forge.get_classification(), as_obj=True): sha256, svc_name, svc_version, _ = key.split(".", 3) svc_version = svc_version[1:] data = Result({ "archive_ts": now_as_iso(days_until_archive * 24 * 60 * 60), "expiry_ts": now_as_iso(days_until_archive * 24 * 60 * 60), "classification": cl_engine.UNRESTRICTED, "response": { "service_name": svc_name, "service_version": svc_version, }, "sha256": sha256 }) if as_obj: return data else: return data.as_primitives()
def _handle_task_result(self, exec_time: int, task: ServiceTask, result: Dict[str, Any], client_id, service_name, freshen: bool, metric_factory): def freshen_file(file_info_list, item): file_info = file_info_list.get(item['sha256'], None) if file_info is None or not self.filestore.exists(item['sha256']): return True else: file_info['archive_ts'] = archive_ts file_info['expiry_ts'] = expiry_ts file_info['classification'] = item['classification'] self.datastore.save_or_freshen_file( item['sha256'], file_info, file_info['expiry_ts'], file_info['classification'], is_section_image=item.get('is_section_image', False)) return False archive_ts = now_as_iso(self.config.datastore.ilm.days_until_archive * 24 * 60 * 60) if task.ttl: expiry_ts = now_as_iso(task.ttl * 24 * 60 * 60) else: expiry_ts = None # Check if all files are in the filestore if freshen: missing_files = [] hashes = list( set([ f['sha256'] for f in result['response']['extracted'] + result['response']['supplementary'] ])) file_infos = self.datastore.file.multiget(hashes, as_obj=False, error_on_missing=False) with elasticapm.capture_span( name="handle_task_result.freshen_files", span_type="tasking_client"): with concurrent.futures.ThreadPoolExecutor( max_workers=5) as executor: res = { f['sha256']: executor.submit(freshen_file, file_infos, f) for f in result['response']['extracted'] + result['response']['supplementary'] } for k, v in res.items(): if v.result(): missing_files.append(k) if missing_files: return missing_files # Add scores to the heuristics, if any section set a heuristic with elasticapm.capture_span( name="handle_task_result.process_heuristics", span_type="tasking_client"): total_score = 0 for section in result['result']['sections']: zeroize_on_sig_safe = section.pop('zeroize_on_sig_safe', True) section['tags'] = flatten(section['tags']) if section.get('heuristic'): heur_id = f"{service_name.upper()}.{str(section['heuristic']['heur_id'])}" section['heuristic']['heur_id'] = heur_id try: section[ 'heuristic'], new_tags = self.heuristic_handler.service_heuristic_to_result_heuristic( section['heuristic'], self.heuristics, zeroize_on_sig_safe) for tag in new_tags: section['tags'].setdefault(tag[0], []) if tag[1] not in section['tags'][tag[0]]: section['tags'][tag[0]].append(tag[1]) total_score += section['heuristic']['score'] except InvalidHeuristicException: section['heuristic'] = None # Update the total score of the result result['result']['score'] = total_score # Add timestamps for creation, archive and expiry result['created'] = now_as_iso() result['archive_ts'] = archive_ts result['expiry_ts'] = expiry_ts # Pop the temporary submission data temp_submission_data = result.pop('temp_submission_data', None) if temp_submission_data: old_submission_data = { row.name: row.value for row in task.temporary_submission_data } temp_submission_data = { k: v for k, v in temp_submission_data.items() if k not in old_submission_data or v != old_submission_data[k] } big_temp_data = { k: len(str(v)) for k, v in temp_submission_data.items() if len(str(v)) > self.config.submission.max_temp_data_length } if big_temp_data: big_data_sizes = [f"{k}={v}" for k, v in big_temp_data.items()] self.log.warning( f"[{task.sid}] The following temporary submission keys where ignored because they are " "bigger then the maximum data size allowed " f"[{self.config.submission.max_temp_data_length}]: {' | '.join(big_data_sizes)}" ) temp_submission_data = { k: v for k, v in temp_submission_data.items() if k not in big_temp_data } # Process the tag values with elasticapm.capture_span(name="handle_task_result.process_tags", span_type="tasking_client"): for section in result['result']['sections']: # Perform tag safelisting tags, safelisted_tags = self.tag_safelister.get_validated_tag_map( section['tags']) section['tags'] = unflatten(tags) section['safelisted_tags'] = safelisted_tags section['tags'], dropped = construct_safe( Tagging, section.get('tags', {})) # Set section score to zero and lower total score if service is set to zeroize score # and all tags were safelisted if section.pop('zeroize_on_tag_safe', False) and \ section.get('heuristic') and \ len(tags) == 0 and \ len(safelisted_tags) != 0: result['result']['score'] -= section['heuristic']['score'] section['heuristic']['score'] = 0 if dropped: self.log.warning( f"[{task.sid}] Invalid tag data from {service_name}: {dropped}" ) result = Result(result) result_key = result.build_key( service_tool_version=result.response.service_tool_version, task=task) self.dispatch_client.service_finished(task.sid, result_key, result, temp_submission_data) # Metrics if result.result.score > 0: metric_factory.increment('scored') else: metric_factory.increment('not_scored') self.log.info( f"[{task.sid}] {client_id} - {service_name} " f"successfully completed task {f' in {exec_time}ms' if exec_time else ''}" ) self.status_table.set( client_id, (service_name, ServiceStatus.Idle, time.time() + 5))
def get_task(self, client_id, service_name, service_version, service_tool_version, metric_factory, status_expiry=None, timeout=30): if status_expiry is None: status_expiry = time.time() + timeout cache_found = False try: service_data = self.dispatch_client.service_data[service_name] except KeyError: raise ServiceMissingException( "The service you're asking task for does not exist, try later", 404) # Set the service status to Idle since we will be waiting for a task self.status_table.set( client_id, (service_name, ServiceStatus.Idle, status_expiry)) # Getting a new task task = self.dispatch_client.request_work(client_id, service_name, service_version, timeout=timeout) if not task: # We've reached the timeout and no task found in service queue return None, False # We've got a task to process, consider us busy self.status_table.set(client_id, (service_name, ServiceStatus.Running, time.time() + service_data.timeout)) metric_factory.increment('execute') result_key = Result.help_build_key( sha256=task.fileinfo.sha256, service_name=service_name, service_version=service_version, service_tool_version=service_tool_version, is_empty=False, task=task) # If we are allowed, try to see if the result has been cached if not task.ignore_cache and not service_data.disable_cache: # Checking for previous results for this key result = self.datastore.result.get_if_exists(result_key) if result: metric_factory.increment('cache_hit') if result.result.score: metric_factory.increment('scored') else: metric_factory.increment('not_scored') result.archive_ts = now_as_iso( self.config.datastore.ilm.days_until_archive * 24 * 60 * 60) if task.ttl: result.expiry_ts = now_as_iso(task.ttl * 24 * 60 * 60) self.dispatch_client.service_finished(task.sid, result_key, result) cache_found = True if not cache_found: # Checking for previous empty results for this key result = self.datastore.emptyresult.get_if_exists( f"{result_key}.e") if result: metric_factory.increment('cache_hit') metric_factory.increment('not_scored') result = self.datastore.create_empty_result_from_key( result_key) self.dispatch_client.service_finished( task.sid, f"{result_key}.e", result) cache_found = True if not cache_found: metric_factory.increment('cache_miss') else: metric_factory.increment('cache_skipped') if not cache_found: # No luck with the cache, lets dispatch the task to a client return task.as_primitives(), False return None, True
def try_run(self): try: self.service_class = load_module_by_path(SERVICE_PATH) except ValueError: raise except Exception: LOG.error("Could not find service in path. Check your environment variables.") raise self.load_service_manifest() if not os.path.isfile(FILE_PATH): LOG.info(f"File not found: {FILE_PATH}") return self.file_dir = os.path.dirname(FILE_PATH) # Get filename and working dir file_name = os.path.basename(FILE_PATH) working_dir = os.path.join(self.file_dir, f'{os.path.basename(FILE_PATH)}_{SERVICE_NAME.lower()}') # Start service self.service.start_service() # Identify the file file_info = identify.fileinfo(FILE_PATH) if file_info['type'] == "archive/cart": # This is a CART file, uncart it and recreate the file info object original_temp = os.path.join(tempfile.gettempdir(), file_info['sha256']) with open(FILE_PATH, 'rb') as ifile, open(original_temp, 'wb') as ofile: unpack_stream(ifile, ofile) file_info = identify.fileinfo(original_temp) target_file = os.path.join(tempfile.gettempdir(), file_info['sha256']) shutil.move(original_temp, target_file) LOG.info(f"File was a CaRT archive, it was un-CaRTed to {target_file} for processing") else: # It not a cart, move the file to the right place to be processed target_file = os.path.join(tempfile.gettempdir(), file_info['sha256']) shutil.copyfile(FILE_PATH, target_file) # Create service processing task service_task = ServiceTask(dict( sid=get_random_id(), metadata={}, service_name=SERVICE_NAME, service_config=self.submission_params, fileinfo=dict( magic=file_info['magic'], md5=file_info['md5'], mime=file_info['mime'], sha1=file_info['sha1'], sha256=file_info['sha256'], size=file_info['size'], type=file_info['type'], ), filename=file_name, min_classification=forge.get_classification().UNRESTRICTED, max_files=501, # TODO: get the actual value ttl=3600, )) LOG.info(f"Starting task with SID: {service_task.sid}") # Set the working directory to a directory with same parent as input file if os.path.isdir(working_dir): shutil.rmtree(working_dir) if not os.path.isdir(working_dir): os.makedirs(os.path.join(working_dir, 'working_directory')) self.service.handle_task(service_task) # Move the result.json and extracted/supplementary files to the working directory source = os.path.join(tempfile.gettempdir(), 'working_directory') if not os.path.exists(source): os.makedirs(source) files = os.listdir(source) for f in files: shutil.move(os.path.join(source, f), os.path.join(working_dir, 'working_directory')) # Cleanup files from the original directory created by the service base shutil.rmtree(source) result_json = os.path.join(tempfile.gettempdir(), f'{service_task.sid}_{service_task.fileinfo.sha256}_result.json') if not os.path.exists(result_json): raise Exception("A service error occured and no result json was found.") # Validate the generated result with open(result_json, 'r') as fh: try: result = json.load(fh) result.pop('temp_submission_data', None) for file in result['response']['extracted'] + result['response']['supplementary']: file.pop('path', None) # Load heuristics heuristics = get_heuristics() # Transform heuristics and calculate score total_score = 0 for section in result['result']['sections']: if section['heuristic']: heur_id = section['heuristic']['heur_id'] try: section['heuristic'] = service_heuristic_to_result_heuristic(section['heuristic'], heuristics) total_score += section['heuristic']['score'] except InvalidHeuristicException: section['heuristic'] = None section['heuristic']['name'] = heuristics[heur_id]['name'] result['result']['score'] = total_score # Add timestamps for creation, archive and expiry result['created'] = now_as_iso() result['archive_ts'] = now_as_iso(1 * 24 * 60 * 60) result['expiry_ts'] = now_as_iso(service_task.ttl * 24 * 60 * 60) result = Result(result) # Print the result on console if in debug mode if args.debug: f"{SERVICE_NAME.upper()}-RESULT".center(60, '-') for line in pprint.pformat(result.result.as_primitives()).split('\n'): LOG.debug(line) except Exception as e: LOG.error(f"Invalid result created: {str(e)}") LOG.info(f"Cleaning up file used for temporary processing: {target_file}") os.unlink(target_file) LOG.info(f"Moving {result_json} to the working directory: {working_dir}/result.json") shutil.move(result_json, os.path.join(working_dir, 'result.json')) LOG.info(f"Successfully completed task. Output directory: {working_dir}")
def service_finished(self, sid: str, result_key: str, result: Result, temporary_data: Optional[Dict[str, Any]] = None): """Notifies the dispatcher of service completion, and possible new files to dispatch.""" # Make sure the dispatcher knows we were working on this task task_key = ServiceTask.make_key( sid=sid, service_name=result.response.service_name, sha=result.sha256) task = self.running_tasks.pop(task_key) if not task: self.log.warning( f"[{sid}/{result.sha256}] {result.response.service_name} could not find the specified " f"task in its set of running tasks while processing successful results." ) return task = ServiceTask(task) # Check if the service is a candidate for dynamic recursion prevention if not task.ignore_dynamic_recursion_prevention: service_info = self.service_data.get(result.response.service_name, None) if service_info and service_info.category == "Dynamic Analysis": # TODO: This should be done in lua because it can introduce race condition in the future # but in the meantime it will remain this way while we can confirm it work as expected submission = self.active_submissions.get(sid) submission['submission']['params']['services'][ 'runtime_excluded'].append(result.response.service_name) self.active_submissions.set(sid, submission) # Save or freshen the result, the CONTENT of the result shouldn't change, but we need to keep the # most distant expiry time to prevent pulling it out from under another submission too early if result.is_empty(): # Empty Result will not be archived therefore result.archive_ts drives their deletion self.ds.emptyresult.save(result_key, {"expiry_ts": result.archive_ts}) else: with Lock(f"lock-{result_key}", 5, self.redis): old = self.ds.result.get(result_key) if old: if old.expiry_ts and result.expiry_ts: result.expiry_ts = max(result.expiry_ts, old.expiry_ts) else: result.expiry_ts = None self.ds.result.save(result_key, result) # Let the logs know we have received a result for this task if result.drop_file: self.log.debug( f"[{sid}/{result.sha256}] {task.service_name} succeeded. " f"Result will be stored in {result_key} but processing will stop after this service." ) else: self.log.debug( f"[{sid}/{result.sha256}] {task.service_name} succeeded. " f"Result will be stored in {result_key}") # Store the result object and mark the service finished in the global table process_table = DispatchHash(task.sid, self.redis) remaining, duplicate = process_table.finish( task.fileinfo.sha256, task.service_name, result_key, result.result.score, result.classification, result.drop_file) self.timeout_watcher.clear(f'{task.sid}-{task.key()}') if duplicate: self.log.warning( f"[{sid}/{result.sha256}] {result.response.service_name}'s current task was already " f"completed in the global processing table.") return # Push the result tags into redis new_tags = [] for section in result.result.sections: new_tags.extend(tag_dict_to_list(section.tags.as_primitives())) if new_tags: tag_set = ExpiringSet(get_tag_set_name( sid=task.sid, file_hash=task.fileinfo.sha256), host=self.redis) tag_set.add(*new_tags) # Update the temporary data table for this file temp_data_hash = ExpiringHash(get_temporary_submission_data_name( sid=task.sid, file_hash=task.fileinfo.sha256), host=self.redis) for key, value in (temporary_data or {}).items(): temp_data_hash.set(key, value) # Send the extracted files to the dispatcher depth_limit = self.config.submission.max_extraction_depth new_depth = task.depth + 1 if new_depth < depth_limit: # Prepare the temporary data from the parent to build the temporary data table for # these newly extract files parent_data = dict(temp_data_hash.items()) for extracted_data in result.response.extracted: if not process_table.add_file( extracted_data.sha256, task.max_files, parent_hash=task.fileinfo.sha256): if parent_data: child_hash_name = get_temporary_submission_data_name( task.sid, extracted_data.sha256) ExpiringHash(child_hash_name, host=self.redis).multi_set(parent_data) self._dispatching_error( task, process_table, Error({ 'archive_ts': result.archive_ts, 'expiry_ts': result.expiry_ts, 'response': { 'message': f"Too many files extracted for submission {task.sid} " f"{extracted_data.sha256} extracted by " f"{task.service_name} will be dropped", 'service_name': task.service_name, 'service_tool_version': result.response.service_tool_version, 'service_version': result.response.service_version, 'status': 'FAIL_NONRECOVERABLE' }, 'sha256': extracted_data.sha256, 'type': 'MAX FILES REACHED' })) continue file_data = self.files.get(extracted_data.sha256) self.file_queue.push( FileTask( dict(sid=task.sid, min_classification=task.min_classification.max( extracted_data.classification).value, file_info=dict( magic=file_data.magic, md5=file_data.md5, mime=file_data.mime, sha1=file_data.sha1, sha256=file_data.sha256, size=file_data.size, type=file_data.type, ), depth=new_depth, parent_hash=task.fileinfo.sha256, max_files=task.max_files)).as_primitives()) else: for extracted_data in result.response.extracted: self._dispatching_error( task, process_table, Error({ 'archive_ts': result.archive_ts, 'expiry_ts': result.expiry_ts, 'response': { 'message': f"{task.service_name} has extracted a file " f"{extracted_data.sha256} beyond the depth limits", 'service_name': result.response.service_name, 'service_tool_version': result.response.service_tool_version, 'service_version': result.response.service_version, 'status': 'FAIL_NONRECOVERABLE' }, 'sha256': extracted_data.sha256, 'type': 'MAX DEPTH REACHED' })) # If the global table said that this was the last outstanding service, # send a message to the dispatchers. if remaining <= 0: self.file_queue.push( FileTask( dict(sid=task.sid, min_classification=task.min_classification.value, file_info=task.fileinfo, depth=task.depth, max_files=task.max_files)).as_primitives()) # Send the result key to any watching systems msg = {'status': 'OK', 'cache_key': result_key} for w in self._get_watcher_list(task.sid).members(): NamedQueue(w).push(msg)
def try_run(self): while self.running: task = self.dispatch_client.request_work('worker', self.service_name, '0', timeout=3) if not task: continue self.log.info(f"{self.service_name} has received a job {task.sid}") file = self.filestore.get(task.fileinfo.sha256) instructions = json.loads(file) instructions = instructions.get(self.service_name, {}) self.log.info( f"{self.service_name} following instruction: {instructions}") hits = self.hits[task.fileinfo.sha256] = self.hits.get( task.fileinfo.sha256, 0) + 1 if instructions.get('hold', False): queue = get_service_queue(self.service_name, self.dispatch_client.redis) queue.push(0, task.as_primitives()) self.log.info( f"{self.service_name} Requeued task to {queue.name} holding for {instructions['hold']}" ) _global_semaphore.acquire(blocking=True, timeout=instructions['hold']) continue if instructions.get('lock', False): _global_semaphore.acquire(blocking=True, timeout=instructions['lock']) if 'drop' in instructions: if instructions['drop'] >= hits: self.drops[task.fileinfo.sha256] = self.drops.get( task.fileinfo.sha256, 0) + 1 continue if instructions.get('failure', False): error = Error(instructions['error']) error.sha256 = task.fileinfo.sha256 self.dispatch_client.service_failed(task.sid, error=error, error_key=get_random_id()) continue result_data = { 'archive_ts': time.time() + 300, 'classification': 'U', 'response': { 'service_version': '0', 'service_tool_version': '0', 'service_name': self.service_name, }, 'result': {}, 'sha256': task.fileinfo.sha256, 'expiry_ts': time.time() + 600 } result_data.update(instructions.get('result', {})) result_data['response'].update(instructions.get('response', {})) result = Result(result_data) result_key = instructions.get('result_key', get_random_id()) self.dispatch_client.service_finished(task.sid, result_key, result)
def run(self): self.log.info("Random service result generator ready!") self.log.info("Monitoring queues:") for q in self.queues: self.log.info(f"\t{q.name}") self.log.info("Waiting for messages...") while self.running: # Reset Idle flags for s in self.service_info: if s['enabled']: self.service_state_hash.set( f"{self.client_id}_{s['name']}", (s['name'], ServiceStatus.Idle, time.time() + 30 + 5)) message = select(*self.queues, timeout=1) if not message: continue archive_ts = now_as_iso( self.config.datastore.ilm.days_until_archive * 24 * 60 * 60) if self.config.submission.dtl: expiry_ts = now_as_iso(self.config.submission.dtl * 24 * 60 * 60) else: expiry_ts = None queue, msg = message task = ServiceTask(msg) if not self.dispatch_client.running_tasks.add( task.key(), task.as_primitives()): continue # Set service busy flag self.service_state_hash.set( f"{self.client_id}_{task.service_name}", (task.service_name, ServiceStatus.Running, time.time() + 30 + 5)) # METRICS self.counters[task.service_name].increment('execute') # METRICS (not caching here so always miss) self.counters[task.service_name].increment('cache_miss') self.log.info( f"\tQueue {queue} received a new task for sid {task.sid}.") action = random.randint(1, 10) if action >= 2: if action > 8: result = random_minimal_obj(Result) else: result = random_model_obj(Result) result.sha256 = task.fileinfo.sha256 result.response.service_name = task.service_name result.archive_ts = archive_ts result.expiry_ts = expiry_ts result.response.extracted = result.response.extracted[task. depth + 2:] result.response.supplementary = result.response.supplementary[ task.depth + 2:] result_key = Result.help_build_key( sha256=task.fileinfo.sha256, service_name=task.service_name, service_version='0', is_empty=result.is_empty()) self.log.info( f"\t\tA result was generated for this task: {result_key}") new_files = result.response.extracted + result.response.supplementary for f in new_files: if not self.datastore.file.get(f.sha256): random_file = random_model_obj(File) random_file.archive_ts = archive_ts random_file.expiry_ts = expiry_ts random_file.sha256 = f.sha256 self.datastore.file.save(f.sha256, random_file) if not self.filestore.exists(f.sha256): self.filestore.put(f.sha256, f.sha256) time.sleep(random.randint(0, 2)) self.dispatch_client.service_finished(task.sid, result_key, result) # METRICS if result.result.score > 0: self.counters[task.service_name].increment('scored') else: self.counters[task.service_name].increment('not_scored') else: error = random_model_obj(Error) error.archive_ts = archive_ts error.expiry_ts = expiry_ts error.sha256 = task.fileinfo.sha256 error.response.service_name = task.service_name error.type = random.choice( ["EXCEPTION", "SERVICE DOWN", "SERVICE BUSY"]) error_key = error.build_key('0') self.log.info( f"\t\tA {error.response.status}:{error.type} " f"error was generated for this task: {error_key}") self.dispatch_client.service_failed(task.sid, error_key, error) # METRICS if error.response.status == "FAIL_RECOVERABLE": self.counters[task.service_name].increment( 'fail_recoverable') else: self.counters[task.service_name].increment( 'fail_nonrecoverable')
def service_finished(self, sid: str, result_key: str, result: Result, temporary_data: Optional[Dict[str, Any]] = None): """Notifies the dispatcher of service completion, and possible new files to dispatch.""" # Make sure the dispatcher knows we were working on this task task_key = ServiceTask.make_key(sid=sid, service_name=result.response.service_name, sha=result.sha256) task = self.running_tasks.pop(task_key) if not task: self.log.warning(f"[{sid}/{result.sha256}] {result.response.service_name} could not find the specified " f"task in its set of running tasks while processing successful results.") return task = ServiceTask(task) # Save or freshen the result, the CONTENT of the result shouldn't change, but we need to keep the # most distant expiry time to prevent pulling it out from under another submission too early if result.is_empty(): # Empty Result will not be archived therefore result.archive_ts drives their deletion self.ds.emptyresult.save(result_key, {"expiry_ts": result.archive_ts}) else: while True: old, version = self.ds.result.get_if_exists( result_key, archive_access=self.config.datastore.ilm.update_archive, version=True) if old: if old.expiry_ts and result.expiry_ts: result.expiry_ts = max(result.expiry_ts, old.expiry_ts) else: result.expiry_ts = None try: self.ds.result.save(result_key, result, version=version) break except VersionConflictException as vce: self.log.info(f"Retrying to save results due to version conflict: {str(vce)}") # Send the result key to any watching systems msg = {'status': 'OK', 'cache_key': result_key} for w in self._get_watcher_list(task.sid).members(): NamedQueue(w, host=self.redis).push(msg) # Save the tags tags = [] for section in result.result.sections: tags.extend(tag_dict_to_list(flatten(section.tags.as_primitives()))) # Pull out file names if we have them file_names = {} for extracted_data in result.response.extracted: if extracted_data.name: file_names[extracted_data.sha256] = extracted_data.name # dispatcher = task.metadata['dispatcher__'] result_queue = self._get_queue_from_cache(DISPATCH_RESULT_QUEUE + dispatcher) ex_ts = result.expiry_ts.strftime(DATEFORMAT) if result.expiry_ts else result.archive_ts.strftime(DATEFORMAT) result_queue.push({ # 'service_task': task.as_primitives(), # 'result': result.as_primitives(), 'sid': task.sid, 'sha256': result.sha256, 'service_name': task.service_name, 'service_version': result.response.service_version, 'service_tool_version': result.response.service_tool_version, 'archive_ts': result.archive_ts.strftime(DATEFORMAT), 'expiry_ts': ex_ts, 'result_summary': { 'key': result_key, 'drop': result.drop_file, 'score': result.result.score, 'children': [r.sha256 for r in result.response.extracted], }, 'tags': tags, 'extracted_names': file_names, 'temporary_data': temporary_data })