예제 #1
0
    def validate_query(self, beats_version: str, ecs_versions: List[str]):
        """Static method to validate the query, called from the parent which contains [metadata] information."""
        indexes = self.index or []
        parsed = self.parsed_query

        beat_types = [
            index.split("-")[0] for index in indexes if "beat-*" in index
        ]
        beat_schema = beats.get_schema_from_kql(
            parsed, beat_types, version=beats_version) if beat_types else None

        if not ecs_versions:
            kql.parse(self.query,
                      schema=ecs.get_kql_schema(indexes=indexes,
                                                beat_schema=beat_schema))
        else:
            for version in ecs_versions:
                schema = ecs.get_kql_schema(version=version,
                                            indexes=indexes,
                                            beat_schema=beat_schema)

                try:
                    kql.parse(self.query, schema=schema)
                except kql.KqlParseError as exc:
                    message = exc.error_msg
                    trailer = None
                    if "Unknown field" in message and beat_types:
                        trailer = "\nTry adding event.module or event.dataset to specify beats module"

                    raise kql.KqlParseError(exc.error_msg,
                                            exc.line,
                                            exc.column,
                                            exc.source,
                                            len(exc.caret.lstrip()),
                                            trailer=trailer) from None
예제 #2
0
    def test_upper_tokens(self):
        queries = [
            "a:b AND c:d",
            "a:b OR c:d",
            "NOT a:b",
            "a:(b OR c)",
            "a:(b AND c)",
            "a:(NOT b)",
        ]

        for q in queries:
            with self.assertRaises(kql.KqlParseError):
                kql.parse(q)
예제 #3
0
    def validate(self, as_rule=False, versioned=False):
        """Validate against a rule schema, query schema, and linting."""
        self.normalize()

        if as_rule:
            schema_validate(self.rule_format(), as_rule=True)
        else:
            schema_validate(self.contents, versioned=versioned)

        if self.query and self.contents['language'] == 'kuery':
            # validate against all specified schemas or the latest if none specified
            ecs_versions = self.metadata.get('ecs_version')

            indexes = self.contents.get("index", [])
            beat_types = [
                index.split("-")[0] for index in indexes if "beat-*" in index
            ]
            beat_schema = beats.get_schema_for_query(
                self.parsed_kql, beat_types) if beat_types else None

            if not ecs_versions:
                kql.parse(self.query,
                          schema=ecs.get_kql_schema(indexes=indexes,
                                                    beat_schema=beat_schema))
            else:
                for version in ecs_versions:
                    try:
                        schema = ecs.get_kql_schema(version=version,
                                                    indexes=indexes,
                                                    beat_schema=beat_schema)
                    except KeyError:
                        raise KeyError(
                            'Unknown ecs schema version: {} in rule {}.\n'
                            'Do you need to update schemas?'.format(
                                version, self.name))

                    try:
                        kql.parse(self.query, schema=schema)
                    except kql.KqlParseError as exc:
                        message = exc.error_msg
                        trailer = None
                        if "Unknown field" in message and beat_types:
                            trailer = "\nTry adding event.module and event.dataset to specify beats module"

                        raise kql.KqlParseError(exc.error_msg,
                                                exc.line,
                                                exc.column,
                                                exc.source,
                                                len(exc.caret.lstrip()),
                                                trailer=trailer)
예제 #4
0
 def test_all_rule_queries_optimized(self):
     """Ensure that every rule query is in optimized form."""
     for rule in self.production_rules:
         if rule.contents.data.get("language") == "kql":
             source = rule.contents.data.query
             tree = kql.parse(source, optimize=False)
             optimized = tree.optimize(recursive=True)
             err_message = f'\n{self.rule_str(rule)} Query not optimized for rule\n' \
                           f'Expected: {optimized}\nActual: {source}'
             self.assertEqual(tree, optimized, err_message)
예제 #5
0
    def validate(self, data: QueryRuleData, meta: RuleMeta) -> None:
        """Static method to validate the query, called from the parent which contains [metadata] information."""
        ast = self.ast

        if meta.query_schema_validation is False or meta.maturity == "deprecated":
            # syntax only, which is done via self.ast
            return

        indexes = data.index or []
        beats_version = meta.beats_version or beats.get_max_version()
        ecs_versions = meta.ecs_versions or [ecs.get_max_version()]

        beat_types = [
            index.split("-")[0] for index in indexes if "beat-*" in index
        ]
        beat_schema = beats.get_schema_from_kql(
            ast, beat_types, version=beats_version) if beat_types else None

        if not ecs_versions:
            kql.parse(self.query,
                      schema=ecs.get_kql_schema(indexes=indexes,
                                                beat_schema=beat_schema))
        else:
            for version in ecs_versions:
                schema = ecs.get_kql_schema(version=version,
                                            indexes=indexes,
                                            beat_schema=beat_schema)

                try:
                    kql.parse(self.query, schema=schema)
                except kql.KqlParseError as exc:
                    message = exc.error_msg
                    trailer = None
                    if "Unknown field" in message and beat_types:
                        trailer = "\nTry adding event.module or event.dataset to specify beats module"

                    raise kql.KqlParseError(exc.error_msg,
                                            exc.line,
                                            exc.column,
                                            exc.source,
                                            len(exc.caret.lstrip()),
                                            trailer=trailer) from None
예제 #6
0
 def get_unique_query_fields(cls, rule_contents):
     """Get a list of unique fields used in a rule query from rule contents."""
     query = rule_contents.get('query')
     language = rule_contents.get('language')
     if language in ('kuery', 'eql'):
         parsed = kql.parse(
             query) if language == 'kuery' else eql.parse_query(query)
         return sorted(
             set(
                 str(f) for f in parsed
                 if isinstance(f, (eql.ast.Field, kql.ast.Field))))
예제 #7
0
    def test_all_rule_queries_optimized(self):
        """Ensure that every rule query is in optimized form."""
        for file_name, contents in self.rule_files.items():
            rule = TOMLRule(file_name, contents)

            if contents["rule"].get("langauge") == "kql":
                source = contents["rule"]["query"]
                tree = kql.parse(source, optimize=False)
                optimized = tree.optimize(recursive=True)
                err_message = f'\n{self.rule_str(rule)} Query not optimized for rule\n' \
                              f'Expected: {optimized}\nActual: {source}'
                self.assertEqual(tree, optimized, err_message)
예제 #8
0
    def validate(self, data: QueryRuleData, meta: RuleMeta) -> None:
        """Static method to validate the query, called from the parent which contains [metadata] information."""
        ast = self.ast

        if meta.query_schema_validation is False or meta.maturity == "deprecated":
            # syntax only, which is done via self.ast
            return

        for stack_version, mapping in meta.get_validation_stack_versions(
        ).items():
            beats_version = mapping['beats']
            ecs_version = mapping['ecs']
            err_trailer = f'stack: {stack_version}, beats: {beats_version}, ecs: {ecs_version}'

            beat_types = beats.parse_beats_from_index(data.index)
            beat_schema = beats.get_schema_from_kql(
                ast, beat_types, version=beats_version) if beat_types else None
            schema = ecs.get_kql_schema(version=ecs_version,
                                        indexes=data.index or [],
                                        beat_schema=beat_schema)

            try:
                kql.parse(self.query, schema=schema)
            except kql.KqlParseError as exc:
                message = exc.error_msg
                trailer = err_trailer
                if "Unknown field" in message and beat_types:
                    trailer = f"\nTry adding event.module or event.dataset to specify beats module\n\n{trailer}"

                raise kql.KqlParseError(exc.error_msg,
                                        exc.line,
                                        exc.column,
                                        exc.source,
                                        len(exc.caret.lstrip()),
                                        trailer=trailer) from None
            except Exception:
                print(err_trailer)
                raise
예제 #9
0
 def parsed_query(self):
     if self.query:
         if self.contents['language'] == 'kuery':
             return kql.parse(self.query)
         elif self.contents['language'] == 'eql':
             return eql.parse_query(self.query)
예제 #10
0
 def parsed_query(self) -> kql.ast.Expression:
     return kql.parse(self.query)