Beispiel #1
0
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)
Beispiel #3
0
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)})
Beispiel #4
0
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})
Beispiel #5
0
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
    })
Beispiel #7
0
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
    })