def restore(**_): """ Restore an old backup of the system configuration Variables: None Arguments: None Data Block: <SERVICE BACKUP> Result example: {'success': true} """ data = request.data try: backup = yaml.safe_load(data) if "type" not in backup or "server" not in backup or "data" not in backup: return make_api_response( "", err="Invalid service configuration backup.", status_code=400) if backup["server"] != config.ui.fqdn: return make_api_response( "", err= "This backup was not created on this server, restore operation cancelled.", status_code=400) for service_name, service in backup['data'].items(): # Grab the old value for a service old_service = STORAGE.get_service_with_delta(service_name, as_obj=False) # Restore the service for v_id, v_data in service['versions'].items(): STORAGE.service.save(v_id, v_data) STORAGE.service_delta.save(service_name, service['config']) # Grab the new value for the service new_service = STORAGE.get_service_with_delta(service_name, as_obj=False) # Synchronize the sources if needed if old_service and old_service.get("update_config", {}).get( "sources", None) is not None: synchronize_sources( service_name, old_service.get("update_config", {}).get("sources", []), new_service.get("update_config", {}).get("sources", [])) return make_api_response({"success": True}) except ValueError as e: return make_api_response("", err=str(e), status_code=400)
def get_service(servicename, **_): """ Load the configuration for a given service Variables: servicename => Name of the service to get the info Arguments: version => Specific version of the service to get Data Block: None Result example: {'accepts': '(archive|executable|java|android)/.*', 'category': 'Extraction', 'classpath': 'al_services.alsvc_extract.Extract', 'config': {'DEFAULT_PW_LIST': ['password', 'infected']}, 'cpu_cores': 0.1, 'description': "Extracts some stuff" 'enabled': True, 'name': 'Extract', 'ram_mb': 256, 'rejects': 'empty|metadata/.*', 'stage': 'EXTRACT', 'submission_params': [{'default': u'', 'name': 'password', 'type': 'str', 'value': u''}, {'default': False, 'name': 'extract_pe_sections', 'type': 'bool', 'value': False}, {'default': False, 'name': 'continue_after_extract', 'type': 'bool', 'value': False}], 'timeout': 60} """ version = request.args.get('version', None) service = STORAGE.get_service_with_delta(servicename, version=version, as_obj=False) if service: return make_api_response(service) else: return make_api_response("", err=f"{servicename} service does not exist", status_code=404)
def update_signature_source(service, name, **_): """ Update a signature source by name for a given service Variables: service => Service to which we want to update the source name => Name of the source you want update Arguments: None Data Block: { "uri": "http://somesite/file_to_get", # URI to fetch for parsing the rules "name": "signature_file.yar", # Name of the file we will parse the rules as "username": null, # Username used to get to the URI "password": null, # Password used to get to the URI "header": { # Header sent during the request to the URI "X_TOKEN": "SOME RANDOM TOKEN" # Exemple of header }, "private_key": null, # Private key used to get to the URI "pattern": "^*.yar$" # Regex pattern use to get appropriate files from the URI } Result example: {"success": True/False} # if the operation succeeded of not """ data = request.json service_data = STORAGE.get_service_with_delta(service, as_obj=False) current_sources = service_data.get('update_config', {}).get('sources', []) # Ensure private_key (if any) ends with a \n if data.get('private_key', None) and not data['private_key'].endswith("\n"): data['private_key'] += "\n" if name != data['name']: return make_api_response( {"success": False}, err="You are not allowed to change the source name.", status_code=400) if not service_data.get('update_config', {}).get('generates_signatures', False): return make_api_response( {"success": False}, err= "This service does not generate alerts therefor you cannot update its sources.", status_code=400) if len(current_sources) == 0: return make_api_response( {"success": False}, err= "This service does not have any sources therefor you cannot update any source.", status_code=400) new_sources = [] found = False classification_changed = False for source in current_sources: if data['name'] == source['name']: new_sources.append(data) found = True classification_changed = data['default_classification'] != source[ 'default_classification'] else: new_sources.append(source) if not found: return make_api_response( {"success": False}, err=f"Could not found source '{data.name}' in service {service}.", status_code=404) service_delta = STORAGE.service_delta.get(service, as_obj=False) if service_delta.get('update_config') is None: service_delta['update_config'] = {"sources": new_sources} else: service_delta['update_config']['sources'] = new_sources # Has the classification changed? if classification_changed: class_norm = Classification.normalize_classification( data['default_classification']) STORAGE.signature.update_by_query(query=f'source:"{data["name"]}"', operations=[("SET", "classification", class_norm)]) _reset_service_updates(service) # Save the signature return make_api_response( {"success": STORAGE.service_delta.save(service, service_delta)})
def delete_signature_source(service, name, **_): """ Delete a signature source by name for a given service Variables: service => Service to which we want to delete the source from name => Name of the source you want to remove Arguments: None Data Block: None Result example: { "source": True, # if deleting the source succeeded or not "signatures": False # if deleting associated signatures deleted or not } """ service_data = STORAGE.get_service_with_delta(service, as_obj=False) current_sources = service_data.get('update_config', {}).get('sources', []) if not service_data.get('update_config', {}).get('generates_signatures', False): return make_api_response( {"success": False}, err="This service does not generate alerts therefor " "you cannot delete one of its sources.", status_code=400) new_sources = [] found = False for source in current_sources: if name == source['name']: found = True else: new_sources.append(source) if not found: return make_api_response( {"success": False}, err=f"Could not found source '{name}' in service {service}.", status_code=404) service_delta = STORAGE.service_delta.get(service, as_obj=False) if service_delta.get('update_config') is None: service_delta['update_config'] = {"sources": new_sources} else: service_delta['update_config']['sources'] = new_sources # Save the new sources success = STORAGE.service_delta.save(service, service_delta) if success: # Remove old source signatures STORAGE.signature.delete_matching( f'type:"{service.lower()}" AND source:"{name}"') _reset_service_updates(service) return make_api_response({"success": success})
def add_signature_source(service, **_): """ Add a signature source for a given service Variables: service => Service to which we want to add the source to Arguments: None Data Block: { "uri": "http://somesite/file_to_get", # URI to fetch for parsing the rules "name": "signature_file.yar", # Name of the file we will parse the rules as "username": null, # Username used to get to the URI "password": null, # Password used to get to the URI "header": { # Header sent during the request to the URI "X_TOKEN": "SOME RANDOM TOKEN" # Exemple of header }, "private_key": null, # Private key used to get to the URI "pattern": "^*.yar$" # Regex pattern use to get appropriate files from the URI } Result example: {"success": True/False} # if the operation succeeded of not """ try: data = request.json except (ValueError, KeyError): return make_api_response({"success": False}, err="Invalid source object data", status_code=400) # Ensure data source doesn't have spaces in name data['name'] = data['name'].replace(" ", "_") # Ensure private_key (if any) ends with a \n if data.get('private_key', None) and not data['private_key'].endswith("\n"): data['private_key'] += "\n" service_data = STORAGE.get_service_with_delta(service, as_obj=False) if not service_data.get('update_config', {}).get('generates_signatures', False): return make_api_response( {"success": False}, err="This service does not generate alerts therefor " "you cannot add a source to get the alerts from.", status_code=400) current_sources = service_data.get('update_config', {}).get('sources', []) for source in current_sources: if source['name'] == data['name']: return make_api_response( {"success": False}, err=f"Update source name already exist: {data['name']}", status_code=400) current_sources.append(data) service_delta = STORAGE.service_delta.get(service, as_obj=False) if service_delta.get('update_config') is None: service_delta['update_config'] = {"sources": current_sources} else: service_delta['update_config']['sources'] = current_sources _reset_service_updates(service) # Save the signature return make_api_response( {"success": STORAGE.service_delta.save(service, service_delta)})
def set_service(servicename, **_): """ Calculate the delta between the original service config and the posted service config then saves that delta as the current service delta. Variables: servicename => Name of the service to save Arguments: None Data Block: {'accepts': '(archive|executable|java|android)/.*', 'category': 'Extraction', 'classpath': 'al_services.alsvc_extract.Extract', 'config': {'DEFAULT_PW_LIST': ['password', 'infected']}, 'cpu_cores': 0.1, 'description': "Extract some stuff", 'enabled': True, 'name': 'Extract', 'ram_mb': 256, 'rejects': 'empty|metadata/.*', 'stage': 'EXTRACT', 'submission_params': [{'default': u'', 'name': 'password', 'type': 'str', 'value': u''}, {'default': False, 'name': 'extract_pe_sections', 'type': 'bool', 'value': False}, {'default': False, 'name': 'continue_after_extract', 'type': 'bool', 'value': False}], 'timeout': 60} Result example: {"success": true } #Saving the user info succeded """ data = request.json version = data.get('version', None) if not version: return make_api_response( {"success": False}, "The service you are trying to modify does not exist", 404) current_default = STORAGE.service.get(f"{servicename}_{version}", as_obj=False) current_service = STORAGE.get_service_with_delta(servicename, as_obj=False) if not current_default: return make_api_response( {"success": False}, "The service you are trying to modify does not exist", 404) if 'name' in data and servicename != data['name']: return make_api_response({"success": False}, "You cannot change the service name", 400) if current_service['version'] != version: # On version change, reset all container versions data['docker_config']['image'] = current_default['docker_config'][ 'image'] for k, v in data['dependencies'].items(): if k in current_default['dependencies']: v['container']['image'] = current_default['dependencies'][k][ 'container']['image'] delta = get_recursive_delta(current_default, data, stop_keys=['config']) delta['version'] = version removed_sources = {} # Check sources, especially to remove old sources if delta.get("update_config", {}).get("sources", None) is not None: delta["update_config"]["sources"] = preprocess_sources( delta["update_config"]["sources"]) c_srcs = current_service.get('update_config', {}).get('sources', []) removed_sources = synchronize_sources( servicename, c_srcs, delta["update_config"]["sources"]) # Notify components watching for service config changes success = STORAGE.service_delta.save(servicename, delta) if success: event_sender.send(servicename, { 'operation': Operation.Modified, 'name': servicename }) return make_api_response({ "success": success, "removed_sources": removed_sources })
def set_service(servicename, **_): """ Calculate the delta between the original service config and the posted service config then saves that delta as the current service delta. Variables: servicename => Name of the service to save Arguments: None Data Block: {'accepts': '(archive|executable|java|android)/.*', 'category': 'Extraction', 'classpath': 'al_services.alsvc_extract.Extract', 'config': {'DEFAULT_PW_LIST': ['password', 'infected']}, 'cpu_cores': 0.1, 'description': "Extract some stuff", 'enabled': True, 'name': 'Extract', 'ram_mb': 256, 'rejects': 'empty|metadata/.*', 'stage': 'EXTRACT', 'submission_params': [{'default': u'', 'name': 'password', 'type': 'str', 'value': u''}, {'default': False, 'name': 'extract_pe_sections', 'type': 'bool', 'value': False}, {'default': False, 'name': 'continue_after_extract', 'type': 'bool', 'value': False}], 'timeout': 60} Result example: {"success": true } #Saving the user info succeded """ data = request.json version = data.get('version', None) if not version: return make_api_response( {"success": False}, "The service you are trying to modify does not exist", 404) current_service = STORAGE.service.get(f"{servicename}_{version}", as_obj=False) if not current_service: return make_api_response( {"success": False}, "The service you are trying to modify does not exist", 404) if 'name' in data and servicename != data['name']: return make_api_response({"success": False}, "You cannot change the service name", 400) # Do not allow user to edit the docker_config.image since we will use the default image for each versions data['docker_config']['image'] = current_service['docker_config']['image'] delta = get_recursive_delta(current_service, data) delta['version'] = version removed_sources = {} # Check sources, especially to remove old sources if "update_config" in delta: if delta["update_config"].get("sources"): delta["update_config"]["sources"] = sanitize_source_names( delta["update_config"]["sources"]) current_sources = STORAGE.get_service_with_delta( servicename, as_obj=False).get('update_config', {}).get('sources', []) for source in current_sources: if source not in delta['update_config']['sources']: removed_sources[ source['name']] = STORAGE.signature.delete_matching( f"type:{servicename.lower()} AND source:{source['name']}" ) _reset_service_updates(servicename) return make_api_response({ "success": STORAGE.service_delta.save(servicename, delta), "removed_sources": removed_sources })