Example #1
0
def test_edit_service(datastore, login_session):
    _, session, host = login_session
    ds = datastore

    delta_data = ds.service_delta.search("id:*", rows=100, as_obj=False)
    svc_data = ds.service.search("id:*", rows=100, as_obj=False)

    service = random.choice(list(TEMP_SERVICES.keys()))
    service_data = Service({
        "name": service,
        "enabled": True,
        "category": TEMP_SERVICES[service][0],
        "stage": TEMP_SERVICES[service][1],
        "version": "3.3.0",
        "docker_config": {
            "image": f"cccs/alsvc_{service.lower()}:latest",
        },
    }).as_primitives()
    resp = get_api_data(session, f"{host}/api/v4/service/{service}/", method="POST", data=json.dumps(service_data))
    assert resp['success']

    ds.service_delta.commit()
    ds.service.commit()

    new_delta_data = ds.service_delta.search("id:*", rows=100, as_obj=False)
    new_svc_data = ds.service.search("id:*", rows=100, as_obj=False)

    assert new_delta_data != delta_data
    assert new_svc_data == svc_data
    for svc in new_delta_data['items']:
        if svc['id'] == service:
            assert svc['version'] == '3.3.0'
        else:
            assert svc['version'] == '4.0.0'
    def load_service_manifest(self, return_heuristics=False) -> Union[None, Dict]:
        service_manifest_yml = os.path.join(os.getcwd(), 'service_manifest.yml')

        if os.path.exists(service_manifest_yml):
            with open(service_manifest_yml) as yml_fh:
                service_manifest_data = yaml.safe_load(yml_fh.read())

            heuristics = service_manifest_data.get('heuristics', None)

            # Pop the 'extra' data from the service manifest
            for x in ['file_required', 'tool_version', 'heuristics']:
                service_manifest_data.pop(x, None)

            # Validate the service manifest
            try:
                self.service = Service(service_manifest_data)
            except Exception as e:
                LOG.error(f"Invalid service manifest: {str(e)}")

            service_config = {}
            if service_manifest_data:
                service_config = service_manifest_data.get('config', {})

            self.submission_params = {x['name']: x['default'] for x in service_manifest_data.get('submission_params', [])}

            self.service = self.service_class(config=service_config)
            if return_heuristics:
                return heuristics
        else:
            raise Exception("Service manifest YAML file not found in root folder of service.")
Example #3
0
def suricata_change_config(service_conf, login_session):
    _, session, host = login_session
    service_data = Service(service_conf).as_primitives()
    resp = get_api_data(session,
                        f"{host}/api/v4/service/Suricata/",
                        method="POST",
                        data=json.dumps(service_data))
    return resp['success']
Example #4
0
def test_service_delta_to_service_model():
    try:
        data = random_model_obj(ServiceDelta).as_primitives()
        Service(data).as_primitives()
    except (ValueError, TypeError, KeyError):
        pytest.fail(
            "Could not use a 'ServiceDelta' object to create a 'Service' object."
        )
Example #5
0
def suricata_init_config(datastore, login_session):
    _, session, host = login_session
    service_conf = {
        "name": "Suricata",
        "enabled": True,
        "category": "Networking",
        "stage": "CORE",
        "version": "4.0.0",
        "docker_config": {
            "image": f"cccs/assemblyline-service-suricata:4.0.0.dev69",
        },
        "update_config": {
            "generates_signatures":
            True,
            "method":
            "run",
            "run_options": {
                "allow_internet_access":
                True,
                "command": ["python", "-m", "suricata_.suricata_updater"],
                "image":
                "${REGISTRY}cccs/assemblyline-service-suricata:4.0.0.dev69"
            },
            "sources": [{
                "name":
                "old",
                "pattern":
                ".*\\.rules",
                "uri":
                "https://rules.emergingthreats.net/open/suricata/emerging.rules.tar.gz"
            }, {
                "name":
                "old with space",
                "pattern":
                ".*\\.rules",
                "uri":
                "https://rules.emergingthreats.net/open/suricata/emerging.rules.tar.gz"
            }],
            "update_interval_seconds":
            60  # Quarter-day (every 6 hours)
        }
    }

    service_data = Service(service_conf).as_primitives()
    resp = get_api_data(session,
                        f"{host}/api/v4/service/Suricata/",
                        method="POST",
                        data=json.dumps(service_data))
    if resp['success']:
        datastore.service_delta.commit()
        datastore.service.commit()

        delta_sources = datastore.get_service_with_delta(
            "Suricata", as_obj=False)['update_config']['sources']
        passed = delta_sources[0]['name'] == "old" and delta_sources[1][
            'name'] == "old_with_space"
        return passed, service_conf
    return False
Example #6
0
def create_services(ds: AssemblylineDatastore, log=None, limit=None):
    if not limit:
        limit = len(SERVICES)

    for svc_name, svc in list(SERVICES.items())[:limit]:
        service_data = {
            "name": svc_name,
            "enabled": True,
            "category": svc[0],
            "stage": svc[1],
            "version": "3.3.0",
            "docker_config": {
                "image": f"cccs/alsvc_{svc_name.lower()}:latest",
            },
        }

        if random.choice([True, False]):
            service_data['update_config'] = {
                "method": "run",
                "sources": [random_model_obj(UpdateSource)],
                "update_interval_seconds": 600,
                "generates_signatures": True
            }

        service_data = Service(service_data)
        # Save a v3 service
        ds.service.save(f"{service_data.name}_{service_data.version}",
                        service_data)

        # Save the same service as v4
        service_data.version = "4.0.0"
        ds.service.save(f"{service_data.name}_{service_data.version}",
                        service_data)

        # Save the default delta entry
        ds.service_delta.save(service_data.name,
                              {"version": service_data.version})
        if log:
            log.info(f'\t{svc_name}')

    ds.service_delta.commit()
    ds.service.commit()
Example #7
0
def get_service_attributes() -> Service:
    service_manifest_data = get_service_manifest()

    # Pop the 'extra' data from the service manifest
    for x in ['file_required', 'tool_version', 'heuristics']:
        service_manifest_data.pop(x, None)

    try:
        service_attributes = Service(service_manifest_data)
    except ValueError as e:
        raise ValueError(f"Service manifest yaml contains invalid parameter(s): {str(e)}")

    return service_attributes
def dummy_service(name, stage, category='static', accepts='', rejects=None):
    return Service({
        'name': name,
        'stage': stage,
        'category': category,
        'accepts': accepts,
        'rejects': rejects,
        'version': '0',
        'enabled': True,
        'timeout': 2,
        'docker_config': {
            'image': 'somefakedockerimage:latest'
        }
    })
    def get_service_with_delta(self, service_name, version=None, as_obj=True):
        svc = self.ds.service_delta.get(service_name)
        if svc is None:
            return svc

        if version is not None:
            svc.version = version

        svc_version_data = self.ds.service.get(f"{service_name}_{svc.version}")
        if svc_version_data is None:
            return svc_version_data

        svc_version_data = recursive_update(
            svc_version_data.as_primitives(strip_null=True),
            svc.as_primitives(strip_null=True))
        if as_obj:
            return Service(svc_version_data)
        else:
            return svc_version_data
Example #10
0
    def list_all_services(self,
                          as_obj=True,
                          full=False) -> Union[List[dict], List[Service]]:
        """
        :param as_obj: Return ODM objects rather than dicts
        :param full: If true retrieve all the fields of the service object, otherwise only
                     fields returned by search are given.
        """
        items = list(self.ds.service_delta.stream_search("id:*", as_obj=False))

        if full:
            service_data = self.ds.service.multiget(
                [f"{item['id']}_{item['version']}" for item in items],
                as_dictionary=False)
            service_delta = self.ds.service_delta.multiget(
                [item['id'] for item in items], as_dictionary=False)
            services = [
                recursive_update(data.as_primitives(strip_null=True),
                                 delta.as_primitives(strip_null=True))
                for data, delta in zip(service_data, service_delta)
            ]

        else:
            services_versions = {
                item['id']: item
                for item in self.ds.service.stream_search("id:*", as_obj=False)
            }
            services = [
                recursive_update(
                    services_versions[f"{item['id']}_{item['version']}"], item)
                for item in items
                if f"{item['id']}_{item['version']}" in services_versions
            ]

        if as_obj:
            mask = None
            if not full and services:
                mask = services[0].keys()
            return [Service(s, mask=mask) for s in services]
        else:
            return services
def dummy_service(name,
                  stage,
                  category='static',
                  accepts='',
                  rejects=None,
                  docid=None,
                  extra_data=False):
    return Service(
        {
            'name': name,
            'stage': stage,
            'category': category,
            'accepts': accepts,
            'uses_temp_submission_data': extra_data,
            'uses_tags': extra_data,
            'rejects': rejects,
            'version': '0',
            'enabled': True,
            'timeout': 2,
            'docker_config': {
                'image': 'somefakedockerimage:latest'
            }
        },
        docid=docid)
Example #12
0
    def register_service(self, service_data, log_prefix=""):
        keep_alive = True

        try:
            # Get heuristics list
            heuristics = service_data.pop('heuristics', None)

            # Patch update_channel, registry_type before Service registration object creation
            service_data['update_channel'] = service_data.get(
                'update_channel',
                self.config.services.preferred_update_channel)
            service_data['docker_config']['registry_type'] = service_data['docker_config'] \
                .get('registry_type', self.config.services.preferred_registry_type)
            service_data['privileged'] = service_data.get(
                'privileged', self.config.services.prefer_service_privileged)
            for dep in service_data.get('dependencies', {}).values():
                dep['container']['registry_type'] = dep.get(
                    'registry_type',
                    self.config.services.preferred_registry_type)

            # Pop unused registration service_data
            for x in ['file_required', 'tool_version']:
                service_data.pop(x, None)

            # Create Service registration object
            service = Service(service_data)

            # Fix service version, we don't need to see the stable label
            service.version = service.version.replace('stable', '')

            # Save service if it doesn't already exist
            if not self.datastore.service.exists(
                    f'{service.name}_{service.version}'):
                self.datastore.service.save(
                    f'{service.name}_{service.version}', service)
                self.datastore.service.commit()
                self.log.info(f"{log_prefix}{service.name} registered")
                keep_alive = False

            # Save service delta if it doesn't already exist
            if not self.datastore.service_delta.exists(service.name):
                self.datastore.service_delta.save(service.name,
                                                  {'version': service.version})
                self.datastore.service_delta.commit()
                self.log.info(f"{log_prefix}{service.name} "
                              f"version ({service.version}) registered")

            new_heuristics = []
            if heuristics:
                plan = self.datastore.heuristic.get_bulk_plan()
                for index, heuristic in enumerate(heuristics):
                    heuristic_id = f'#{index}'  # Set heuristic id to it's position in the list for logging purposes
                    try:
                        # Append service name to heuristic ID
                        heuristic[
                            'heur_id'] = f"{service.name.upper()}.{str(heuristic['heur_id'])}"

                        # Attack_id field is now a list, make it a list if we receive otherwise
                        attack_id = heuristic.get('attack_id', None)
                        if isinstance(attack_id, str):
                            heuristic['attack_id'] = [attack_id]

                        heuristic = Heuristic(heuristic)
                        heuristic_id = heuristic.heur_id
                        plan.add_upsert_operation(heuristic_id, heuristic)
                    except Exception as e:
                        msg = f"{service.name} has an invalid heuristic ({heuristic_id}): {str(e)}"
                        self.log.exception(f"{log_prefix}{msg}")
                        raise ValueError(msg)

                for item in self.datastore.heuristic.bulk(plan)['items']:
                    if item['update']['result'] != "noop":
                        new_heuristics.append(item['update']['_id'])
                        self.log.info(
                            f"{log_prefix}{service.name} "
                            f"heuristic {item['update']['_id']}: {item['update']['result'].upper()}"
                        )

                self.datastore.heuristic.commit()

            service_config = self.datastore.get_service_with_delta(
                service.name, as_obj=False)

            # Notify components watching for service config changes
            self.event_sender.send(service.name, {
                'operation': Operation.Added,
                'name': service.name
            })

        except ValueError as e:  # Catch errors when building Service or Heuristic model(s)
            raise e

        return dict(keep_alive=keep_alive,
                    new_heuristics=new_heuristics,
                    service_config=service_config or dict())
def add_service(**_):
    """
    Add a service using its yaml manifest

    Variables:
    None

    Arguments:
    None

    Data Block:
    <service_manifest.yml content>

    Result example:
    { "success": true }  # Return true is the service was added
    """
    data = request.data
    enable_allowed = True
    try:
        if b"$SERVICE_TAG" in data:
            tmp_service = yaml.safe_load(data)
            tmp_service.pop('tool_version', None)
            tmp_service.pop('file_required', None)
            tmp_service.pop('heuristics', [])

            # Apply global preferences, if missing, to get the appropriate container image tags
            tmp_service['update_channel'] = tmp_service.get(
                'update_channel', config.services.preferred_update_channel)
            tmp_service['docker_config']['registry_type'] = tmp_service['docker_config'] \
                .get('registry_type', config.services.preferred_registry_type)
            _, tag_name, _ = get_latest_tag_for_service(
                Service(tmp_service), config, LOGGER)
            enable_allowed = bool(tag_name)
            tag_name = tag_name.encode() if tag_name else b'latest'
            data = data.replace(b"$SERVICE_TAG", tag_name)

        service = yaml.safe_load(data)
        # Pop the data not part of service model
        service.pop('tool_version', None)
        service.pop('file_required', None)
        heuristics = service.pop('heuristics', [])

        # Validate submission params
        for sp in service.get('submission_params', []):
            if sp['type'] == 'list' and 'list' not in sp:
                return make_api_response(
                    "",
                    err=
                    f"Missing list field for submission param: {sp['name']}",
                    status_code=400)

            if sp['default'] != sp['value']:
                return make_api_response(
                    "",
                    err=
                    f"Default and value mismatch for submission param: {sp['name']}",
                    status_code=400)

        # Apply default global configurations (if absent in service configuration)
        if not service.get('update_channel'):
            service[
                'update_channel'] = config.services.preferred_update_channel
        if not service.get('docker_config', {}).get('registry_type'):
            service['docker_config'][
                'registry_type'] = config.services.preferred_registry_type

        # Privilege can be set explicitly but also granted to services that don't require the file for analysis
        service['privileged'] = service.get(
            'privileged', config.services.prefer_service_privileged)

        for dep in service.get('dependencies', {}).values():
            dep['container']['registry_type'] = dep.get(
                'registry_type', config.services.preferred_registry_type)
        service['enabled'] = service['enabled'] and enable_allowed

        # Load service info
        service = Service(service)

        # Fix service version, we don't want to see stable if it's a stable container
        service.version = service.version.replace("stable", "")

        # Save service if it doesn't already exist
        if not STORAGE.service.get_if_exists(
                f'{service.name}_{service.version}'):
            STORAGE.service.save(f'{service.name}_{service.version}', service)
            STORAGE.service.commit()

        # Save service delta if it doesn't already exist
        if not STORAGE.service_delta.get_if_exists(service.name):
            STORAGE.service_delta.save(service.name,
                                       {'version': service.version})
            STORAGE.service_delta.commit()

        # Notify components watching for service config changes
        event_sender.send(service.name, {
            'operation': Operation.Added,
            'name': service.name
        })

        new_heuristics = []
        if heuristics:
            plan = STORAGE.heuristic.get_bulk_plan()
            for _, heuristic in enumerate(heuristics):
                try:
                    # Append service name to heuristic ID
                    heuristic[
                        'heur_id'] = f"{service.name.upper()}.{str(heuristic['heur_id'])}"

                    # Attack_id field is now a list, make it a list if we receive otherwise
                    attack_id = heuristic.get('attack_id', None)
                    if isinstance(attack_id, str):
                        heuristic['attack_id'] = [attack_id]

                    heuristic = Heuristic(heuristic)
                    heuristic_id = heuristic.heur_id
                    plan.add_upsert_operation(heuristic_id, heuristic)
                except Exception:
                    raise ValueError("Error parsing heuristics")

            for item in STORAGE.heuristic.bulk(plan)['items']:
                if item['update']['result'] != "noop":
                    new_heuristics.append(item['update']['_id'])

            STORAGE.heuristic.commit()

        return make_api_response(
            dict(service_name=service.name, new_heuristics=new_heuristics))
    except ValueError as e:  # Catch errors when building Service or Heuristic model(s)
        return make_api_response("", err=str(e), status_code=400)
Example #14
0
def add_service(**_):
    """
    Add a service using its yaml manifest

    Variables:
    None

    Arguments:
    None

    Data Block:
    <service_manifest.yml content>

    Result example:
    { "success": true }  # Return true is the service was added
    """
    data = request.data

    try:
        if b"$SERVICE_TAG" in data:
            tmp_service = yaml.safe_load(data)
            tmp_service.pop('tool_version', None)
            tmp_service.pop('file_required', None)
            tmp_service.pop('heuristics', [])
            tmp_service[
                'update_channel'] = config.services.preferred_update_channel
            image_name, tag_name, _ = get_latest_tag_for_service(
                Service(tmp_service), config, LOGGER)
            if tag_name:
                tag_name = tag_name.encode()
            else:
                tag_name = b'latest'

            data = data.replace(b"$SERVICE_TAG", tag_name)

        service = yaml.safe_load(data)
        # Pop the data not part of service model
        service.pop('tool_version', None)
        service.pop('file_required', None)
        heuristics = service.pop('heuristics', [])

        # Validate submission params
        for sp in service.get('submission_params', []):
            if sp['type'] == 'list' and 'list' not in sp:
                return make_api_response(
                    "",
                    err=
                    f"Missing list field for submission param: {sp['name']}",
                    status_code=400)

            if sp['default'] != sp['value']:
                return make_api_response(
                    "",
                    err=
                    f"Default and value mismatch for submission param: {sp['name']}",
                    status_code=400)

        # Fix update_channel with the system default
        service['update_channel'] = config.services.preferred_update_channel

        # Load service info
        service = Service(service)

        # Fix service version, we don't want to see stable if it's a stable container
        service.version = service.version.replace("stable", "")

        # Save service if it doesn't already exist
        if not STORAGE.service.get_if_exists(
                f'{service.name}_{service.version}'):
            STORAGE.service.save(f'{service.name}_{service.version}', service)
            STORAGE.service.commit()

        # Save service delta if it doesn't already exist
        if not STORAGE.service_delta.get_if_exists(service.name):
            STORAGE.service_delta.save(service.name,
                                       {'version': service.version})
            STORAGE.service_delta.commit()

        new_heuristics = []
        if heuristics:
            plan = STORAGE.heuristic.get_bulk_plan()
            for index, heuristic in enumerate(heuristics):
                try:
                    # Append service name to heuristic ID
                    heuristic[
                        'heur_id'] = f"{service.name.upper()}.{str(heuristic['heur_id'])}"

                    # Attack_id field is now a list, make it a list if we receive otherwise
                    attack_id = heuristic.get('attack_id', None)
                    if isinstance(attack_id, str):
                        heuristic['attack_id'] = [attack_id]

                    heuristic = Heuristic(heuristic)
                    heuristic_id = heuristic.heur_id
                    plan.add_upsert_operation(heuristic_id, heuristic)
                except Exception:
                    raise ValueError("Error parsing heuristics")

            for item in STORAGE.heuristic.bulk(plan)['items']:
                if item['update']['result'] != "noop":
                    new_heuristics.append(item['update']['_id'])

            STORAGE.heuristic.commit()

        return make_api_response(
            dict(service_name=service.name, new_heuristics=new_heuristics))
    except ValueError as e:  # Catch errors when building Service or Heuristic model(s)
        return make_api_response("", err=str(e), status_code=400)